fix(@data): round-trip the seed/read path against the real broker

Multiple compounding defects kept the connected @data read at 0 entities:
- writeEntity/updateEntityField and registration helpers wrote into an explicit
  GRAPH <plainNuri> named graph, invisible to the anchored default-graph read
  (read-model.readDoc) after the read switched to per-doc anchored. Drop the
  wrapper so writes land in the repo's default graph (matches the read).
- Seed entities are now owned by the CURRENT account, so protected seed docs
  (user profiles) pass the per-document ReadCap gate and round-trip.
- Suppress the double seed (explicit loadTestData + 3s dev auto-seed) and add a
  re-list signal so freshly-seeded protected docs enter the read set.
- @data step awaits the seed result and waits for events AND users > 0.

Documents the anchored-default-graph write pitfall in rule_document-per-entity.

Validated: connexion-nextgraph.feature @data = 4 scenarios / 13 steps green.
NB: the shared test wallet's private store bloats across runs and makes anchored
queries hang (>15s); a fresh .playwright-profile restores ~1.5s — durable wallet
hygiene is a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain Duchesne
2026-07-06 12:46:39 +02:00
parent 02cda056b8
commit 0911b1f9de
8 changed files with 163 additions and 84 deletions
+13 -6
View File
@@ -310,14 +310,16 @@ function ConnectedHarness() {
const protectedDocs = await reg.listEntityDocs('protected');
let total = 0;
for (const g of protectedDocs) {
// Anchored default-graph (no `GRAPH` clause): participations are
// written by writeEntity into each doc's DEFAULT graph, so the
// authoritative count must read that same graph (matches the app's
// registration.ts countParticipations after the graph-mismatch fix).
const query = `
SELECT (COUNT(DISTINCT ?s) AS ?n) WHERE {
GRAPH <${g}> {
?s a <http://festipod.org/Participation> ;
<http://festipod.org/event> ?event ;
<http://festipod.org/user> ?user .
FILTER( STR(?event) = "${esc(eventId)}" && STR(?user) = "${esc(userId)}" )
}
?s a <http://festipod.org/Participation> ;
<http://festipod.org/event> ?event ;
<http://festipod.org/user> ?user .
FILTER( STR(?event) = "${esc(eventId)}" && STR(?user) = "${esc(userId)}" )
}`;
const result: any = await docs.sparqlQuery(session.session_id, query, undefined, g);
const rows = Array.isArray(result) ? result : result?.results?.bindings ?? [];
@@ -569,15 +571,20 @@ function ConnectedHarness() {
const reg = await import('../utils/storeRegistry');
const disc = await import('../data/discovery');
reg.resetRegistryCache();
console.error('[PROBE] publishPublicEventAs: ensureAccount(publisher)…');
await reg.ensureAccount(publisher);
console.error('[PROBE] publishPublicEventAs: createEntityDoc(publisher,public)…');
const doc = await reg.createEntityDoc(publisher, 'public');
console.error('[PROBE] publishPublicEventAs: publisher doc=' + doc);
// Deposit AS the current identity: the inbox guard binds `from` to the
// CURRENT user and rejects a spoofed `from`. So make the publisher the
// current identity (its normalized-username key = the cap-owner key),
// then submit WITHOUT a spoofed explicit `from` — the SDK stamps the
// current identity itself (anonymous submission also allowed).
setCurrentUser(normalizeUsername(publisher));
console.error('[PROBE] publishPublicEventAs: submitEventToIndex…');
await disc.submitEventToIndex({ doc, id: doc, title }, getCurrentUser());
console.error('[PROBE] publishPublicEventAs: submitted OK');
return { doc };
},
async discoverPublicEventsAs(discoverer: string) {