Skip to content

feat(agent): public/meta context-graph projection + catalog open-serve#1172

Closed
branarakic wants to merge 1 commit into
feat/public-catalog-modelfrom
feat/public-context-graph-projection
Closed

feat(agent): public/meta context-graph projection + catalog open-serve#1172
branarakic wants to merge 1 commit into
feat/public-catalog-modelfrom
feat/public-context-graph-projection

Conversation

@branarakic

@branarakic branarakic commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

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 via markDirtyFromQuads, reading the <cg>/_catalog graph as a source (DCAT type / access-rights, disclosure-floor predicates only — never authz-bearing fields from an untrusted peer's catalog).
  • Backs 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.

const result = await this.fetchSyncPages(
ctx, responderPeerId, contextGraphId, false, 'catalog', catalogGraph, Date.now() + deadlineMs,
);
return result.quads;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

Comment thread packages/agent/src/dkg-agent-publish.ts Outdated
// `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); },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

@branarakic branarakic force-pushed the feat/public-context-graph-projection branch from 49e0c6e to 3423171 Compare June 15, 2026 01:02
@branarakic branarakic force-pushed the feat/public-catalog-model branch from 8a8681a to 1b40501 Compare June 15, 2026 01:02
// 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().

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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 ;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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>
@branarakic branarakic force-pushed the feat/public-catalog-model branch from 1b40501 to 394a029 Compare June 15, 2026 08:02
@branarakic branarakic force-pushed the feat/public-context-graph-projection branch from 3423171 to 5ea5967 Compare June 15, 2026 08:02
/** 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>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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().

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

@branarakic

Copy link
Copy Markdown
Contributor Author

Closing as redundant — superseded by #1203 (integration/rfc49-full) + #1201, which landed the RFC-49 SWM/agent work on main.

Verified against current main: the stack tip #1193 has 0 residual (every file it touches is byte-identical in main), and this branch adds nothing not already in main. Any per-file delta is a superseded intermediate — e.g. a relocated contextGraphCatalogUri, or a pre-curator-ack dkg-publisher that main has since advanced past (main carries the confirmBeforeCommit curator-ack gate this branch lacks).

No unique unmerged work. Reopen if I've missed something.

@branarakic branarakic closed this Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant