feat(agent): public/meta context-graph projection + catalog open-serve#1172
feat(agent): public/meta context-graph projection + catalog open-serve#1172branarakic wants to merge 1 commit into
Conversation
| const result = await this.fetchSyncPages( | ||
| ctx, responderPeerId, contextGraphId, false, 'catalog', catalogGraph, Date.now() + deadlineMs, | ||
| ); | ||
| return result.quads; |
There was a problem hiding this comment.
🔴 Bug: fetchPublicCatalog() only returns the fetched quads; it never writes them into the local _catalog graph or invalidates the meta projection. That means getCgMeta() / listContextGraphs() still cannot see a remotely discovered private CG after this call. Persist result.quads to catalogGraph here and mark the projection dirty.
| // `projectionGraph` (the source `_catalog`), so a plain store insert | ||
| // lands them in the right named graph — matching the canonical | ||
| // publisher catalog persist (dkg-publisher.ts store.insert). | ||
| publishProjection: async (_id, quads) => { await this.store.insert(quads); }, |
There was a problem hiding this comment.
🔴 Bug: this appends the refreshed catalog entry without clearing the previous one. Because committedRoot changes on every publish, the _catalog graph will accumulate multiple dkg:committedRoot triples for the same CG, and open-serve consumers cannot tell which root is current. Reuse the existing clear-and-replace catalog persist path (delete existing rows for the subject in _catalog before insert) and invalidate the projection cache.
49e0c6e to
3423171
Compare
8a8681a to
1b40501
Compare
| // OT-RFC-49 §5.9: a private CG's public face is its `_catalog` graph (DCAT | ||
| // dataset record, subject = the CG DID = `uri`). Load it so a CG known only | ||
| // through its catalog (e.g. fetched from a peer with no local `_meta`) is | ||
| // visible to getCgMeta()/listContextGraphsFromProjection(). |
There was a problem hiding this comment.
🔴 Bug: This read model is still unreachable from production code in this PR. listContextGraphs()/getCgMeta() never instantiate or consult ContextGraphMetaProjection, so AGENTS-only private rows still go through the old SPARQL path and the new projection-mode privacy cases remain broken. Please wire this projection into the flagged code path before depending on the new behavior/tests.
| break; | ||
| case DKG_ONTOLOGY.SCHEMA_NAME: | ||
| case `${LEGACY_SCHEMA_NS}name`: | ||
| record.name ??= object; |
There was a problem hiding this comment.
🔴 Bug: rebuild() loads ONTOLOGY before AGENTS and _meta, and these fields use ??=. That means stale ONTOLOGY values win forever for name/description/creator/curator/createdAt, even when a later AGENTS or _meta row is the authoritative one. Please define an explicit source precedence (for example _meta > AGENTS > ONTOLOGY > _catalog) and let higher-precedence facts overwrite lower-precedence ones.
| const result = await this.store.query( | ||
| `SELECT ?subGraph ?name ?createdBy ?createdAt ?description WHERE { | ||
| GRAPH <${metaGraph}> { | ||
| ?subGraph ?typePred ?subGraphType ; |
There was a problem hiding this comment.
🟡 Issue: This query treats any SubGraph-shaped subject in the _meta graph as belonging to the current CG, but it never checks the emitted parentContextGraph triple. A stray/future metadata subject in the same _meta graph will be surfaced as a local sub-graph. Add a parentContextGraph = <current CG DID> constraint (current + legacy namespace) so the projection is scoped correctly.
| `public catalog entry is a private-CG concept; refusing accessPolicy='${input.accessPolicy}'`, | ||
| ); | ||
| } | ||
| const ual = input.ual?.trim(); |
There was a problem hiding this comment.
🔴 Bug: The IRI-bearing inputs are only trimmed, not validated. ual, graph, publisher, accessService, and conformsTo are later serialized as bare IRIs, so a malformed value here can produce invalid RDF or inject extra triples once the publisher wraps it in <...>. Please reject/validate these fields with the same safe-IRI guard used elsewhere before building quads.
| const quads = buildPublicProjection({ | ||
| ual, | ||
| accessPolicy: 'private', | ||
| committedRoot, |
There was a problem hiding this comment.
🟡 Issue: emitPublicProjection() always passes committedRoot, so the emitted catalog can never use the documented combined-model shape where dkg:committedRoot is omitted. If this helper is used in the current combined publish flow, every refresh will publish an extra field that the builder comments say should not be there. Make the root optional here or gate it behind the separate-KA variant explicitly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1b40501 to
394a029
Compare
3423171 to
5ea5967
Compare
| /** True iff the CG is private. Only private CGs get a projection (§5.9). */ | ||
| isPrivateContextGraph(contextGraphId: string): Promise<boolean>; | ||
| /** Resolve the CG's UAL — the subject of the projection. */ | ||
| resolveUal(contextGraphId: string): Promise<string>; |
There was a problem hiding this comment.
🔴 Bug: this write-side API is keyed on an arbitrary ual/projectionGraph, but the read/serve side in this PR only recognizes catalog entries at subject contextGraphDataUri(contextGraphId) in graph contextGraphCatalogUri(contextGraphId). If a caller follows this contract literally (for example the did:dkg:otp:... + /_projection shape used in the tests), the emitted projection will never be discovered by ContextGraphMetaProjection or matched by partitionCatalogQuads(..., contextGraphDataUri(id)). Derive the subject/graph from contextGraphId here, or at least validate that callers pass the canonical context-graph DID and /_catalog graph.
| // OT-RFC-49 §5.9: a private CG's public face is its `_catalog` graph (DCAT | ||
| // dataset record, subject = the CG DID = `uri`). Load it so a CG known only | ||
| // through its catalog (e.g. fetched from a peer with no local `_meta`) is | ||
| // visible to getCgMeta()/listContextGraphsFromProjection(). |
There was a problem hiding this comment.
🔴 Bug: nothing in packages/agent/src actually consumes this projection yet. listContextGraphs() still runs the legacy SPARQL path, there is no DKG_LIST_CONTEXT_GRAPHS_PROJECTION gate in the runtime, and participant lookup still reads _meta directly, so the privacy/revocation behavior added here never reaches production code. Wire this projection into those call sites in the same PR, or keep it internal until the integration lands.
|
Closing as redundant — superseded by #1203 ( Verified against current No unique unmerged work. Reopen if I've missed something. |
Part 3/5 · base:
feat/public-catalog-model.In-memory projections of context-graph metadata, the read side of the public-projection work.
ContextGraphMetaProjection+ContextGraphPublicProjection— projections of CG metadata kept fresh viamarkDirtyFromQuads, reading the<cg>/_cataloggraph as a source (DCAT type / access-rights, disclosure-floor predicates only — never authz-bearing fields from an untrusted peer's catalog).getCgMeta()/listContextGraphsFromProjection()so a privately-discovered CG resolves locally.Self-contained (no facet/recovery imports). projection tests green. Please do not merge before review / out of order.
🤖 Generated with Claude Code
Depends on #1171 — please merge that first.