feat(data): union read model — list via anchorless sparql_query, hang eliminated
Replace the reactive-ORM per-entity fan-out read (which HUNG 75s: orm_start_graph opened every scope graph and RepoNotFound on any fresh/unsynced doc aborted the subscription) with the read model: - readEntities.ts → lib readUnion: resolve the by-need doc set (my own scope docs via listMyEntityDocs + public events via the discovery index — NOT all-accounts fan-out), then ONE anchorless union sparql_query (GRAPH ?g, VALUES-pinned). Map to app types. Re-query on a change signal (no reactive union query). - countUserParticipations no longer fans out over all accounts (own docs only). - await loadTestData in the seed step; deleted orphaned useShapeWithDefaults; removed the old multistore-stopgap fan-out scenarios; added the read-model-probe. - Doctrine: rule_document-per-entity read half + _overview rewritten to the union model (write half unchanged). Result: the 75s ORM hang is ELIMINATED (0 hangs; build/tsc/lib-93-tests green; boundary clean). @data is NOT yet fully green: remaining failures are 90s step timeouts in the test-harness broker data ops (clearWallet / runUnionProbe / seed) this run — a harness/broker-op issue, not the read path. To finish separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -126,14 +126,14 @@ function ConnectedHarness() {
|
||||
// to the raw ORM set only if the app hasn't hydrated a user yet.
|
||||
const currentUserId = appData.currentUserId || [...users][0]?.['@id'] || '';
|
||||
|
||||
// T03.i round-trip fix. The app now writes ONE DOCUMENT PER ENTITY (events →
|
||||
// public per-entity docs, participations/users → protected per-entity docs)
|
||||
// via `createEntityDoc`, and reads a scope by subscribing to the SET of its
|
||||
// per-entity documents (`listEntityDocs` + registerDoc). The old bridge read
|
||||
// the STORE-ROOT NURI directly (`useShape(protectedNuri)`), which never sees
|
||||
// the per-entity docs — so seed/creation didn't round-trip. The step-facing
|
||||
// `events/users/participations` + mutations/queries now delegate to the APP
|
||||
// data context (`appData`), i.e. the exact per-entity path the screens use.
|
||||
// The app writes ONE DOCUMENT PER ENTITY (events → public per-entity docs,
|
||||
// participations/users → protected per-entity docs) via `createEntityDoc`,
|
||||
// and READS by the union model (T03.k): resolve the by-need doc NURIs (my own
|
||||
// scope docs + the discovery index) then run ONE anchorless union
|
||||
// `sparql_query` (`readEntities` → `readModel.readUnion`), re-querying on a
|
||||
// change signal — never the reactive per-entity ORM fan-out (that HANGS). The
|
||||
// step-facing `events/users/participations` + mutations/queries delegate to the
|
||||
// APP data context (`appData`), i.e. the exact union-read path the screens use.
|
||||
// The step contract (`[...td.events]` with `@id`/`title`/`participantCount`,
|
||||
// `.size`, `p.user`/`p.event`) is preserved by mapping the app types to that
|
||||
// shape in a Set-like adapter.
|
||||
@@ -510,6 +510,47 @@ function ConnectedHarness() {
|
||||
return nuri;
|
||||
},
|
||||
|
||||
/**
|
||||
* T03.k PROBE — pins down the read-model union premise against the REAL
|
||||
* broker (docs/read-model.md § Minimal broker probe). Creates two graph
|
||||
* docs A and B, writes a DISTINCT triple into each (anchored per-doc),
|
||||
* then queries GRAPH ?g { ?s ?p ?o } twice: once with NO anchor (expect
|
||||
* BOTH A and B — the LOCAL UNION) and once anchored to A (expect ONLY A).
|
||||
* Returns the graphs seen in each mode so the step can assert the model.
|
||||
*/
|
||||
async runUnionProbe() {
|
||||
const sid = session.session_id;
|
||||
const docA = await docs.docCreate(sid, 'Graph', 'data:graph', 'store', undefined);
|
||||
const docB = await docs.docCreate(sid, 'Graph', 'data:graph', 'store', undefined);
|
||||
const sA = `urn:probe:s:${Date.now().toString(36)}:a`;
|
||||
const sB = `urn:probe:s:${Date.now().toString(36)}:b`;
|
||||
await docs.sparqlUpdate(sid, `INSERT DATA { GRAPH <${docA}> { <${sA}> <urn:probe:p> "A" } }`, docA);
|
||||
await docs.sparqlUpdate(sid, `INSERT DATA { GRAPH <${docB}> { <${sB}> <urn:probe:p> "B" } }`, docB);
|
||||
// Query our OWN probe subjects (sA/sB) so the assertion is by triple,
|
||||
// not by the repo_graph_name (which carries an overlay suffix and won't
|
||||
// string-equal the doc NURI). ?g is still selected for observability.
|
||||
const q = `SELECT ?g ?s ?o WHERE { GRAPH ?g { ?s <urn:probe:p> ?o . FILTER(?s IN (<${sA}>, <${sB}>)) } }`;
|
||||
const readObjs = (res: any): string[] => {
|
||||
const rows = Array.isArray(res) ? res : res?.results?.bindings ?? [];
|
||||
return rows.map((r: any) => r?.o?.value).filter(Boolean);
|
||||
};
|
||||
// NO anchor → local union across all opened graphs.
|
||||
const unionRes = await docs.sparqlQuery(sid, q, undefined, undefined);
|
||||
const unionObjs = readObjs(unionRes);
|
||||
// Anchor = A → one repo only.
|
||||
const anchorRes = await docs.sparqlQuery(sid, q, undefined, docA);
|
||||
const anchorObjs = readObjs(anchorRes);
|
||||
return {
|
||||
docA, docB,
|
||||
unionObjs,
|
||||
anchorObjs,
|
||||
unionHasA: unionObjs.includes('A'),
|
||||
unionHasB: unionObjs.includes('B'),
|
||||
anchorHasA: anchorObjs.includes('A'),
|
||||
anchorHasB: anchorObjs.includes('B'),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Round-trip the sharedWalletShim through the wallet: create an account
|
||||
* (3 docs + SPARQL INSERT), drop the cache, reload from the wallet via
|
||||
|
||||
Reference in New Issue
Block a user