Skip to content

feat(agent): catalog open-serve + members-only sync auth#1175

Closed
branarakic wants to merge 1 commit into
feat/public-context-graph-projectionfrom
feat/facet-open-serve
Closed

feat(agent): catalog open-serve + members-only sync auth#1175
branarakic wants to merge 1 commit into
feat/public-context-graph-projectionfrom
feat/facet-open-serve

Conversation

@branarakic

@branarakic branarakic commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Part 4/5 · base: feat/public-context-graph-projection.

The serve/auth side: cores serve a private CG's public catalog to outsiders, and member-only operations are authorized strictly.

  • Catalog open-serve — cores serve the bounded <cg>/_catalog subgraph to outsiders with no allowlist auth (readCatalogPage in graph-plan, the catalog branch in sync-handler, catalog admission in cg-resolve), keyed on the context-graph DID; all gated data stays members-only.
  • Projection-routed meta reads — access policy / participants / agent-gate resolve through the projection, preserving revoked-agent filtering (a store-only read would re-authorize revoked agents); partial projection records fall back per-field to the store.
  • Members-only recovery auth — a recovery flag on the sync request makes the responder enforce a hard-deny members-only gate (isMemberRecoveryAuthorized) on the cryptographically-recovered signer against a fresh _meta gate, instead of the participant/peer fallback.
  • Catalog persisted/cleared consistently (peer-fetched catalog persisted + projection invalidated; post-publish emit clear-replaces).

catalog serve/read + auth tests green. Please do not merge before review / out of order.

🤖 Generated with Claude Code

Depends on #1172 — please merge that first.


if (!needsAuth) {
const prefix = includeSharedMemory ? `workspace:${contextGraphId}` : contextGraphId;
const phaseSuffix = phase === 'meta'

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: unauthenticated phase: 'catalog' requests are still encoded as the plain cg|offset|limit text form, so the responder parses them as the default data phase instead of the new catalog phase. fetchPublicCatalog() will therefore hit the wrong handler (or an auth denial) instead of the open _catalog path. Add a |catalog suffix here, the same way meta/snapshot are encoded.

// catalog quads cannot poison the authz-bearing creator/curator/allowlist
// fields. Mirrors refreshMetaFromCurator's persist+invalidate path.
if (result.quads.length > 0) {
await this.store.insert(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: this persists every quad returned by fetchSyncPages(), but the generic requester-side filter accepts any graph under did:dkg:context-graph:${contextGraphId}/…, not just <cg>/_catalog. A buggy or malicious peer can use the unauthenticated catalog path to inject _meta, _private, or VM quads into the local store. Before inserting, enforce q.graph === catalogGraph (and ideally the expected subject) for catalog fetches.

// dct:accessRights — CATALOG_META_PREDICATES), so persisting peer-fetched
// catalog quads cannot poison the authz-bearing creator/curator/allowlist
// fields. Mirrors refreshMetaFromCurator's persist+invalidate path.
if (result.quads.length > 0) {

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: partial catalog fetches are treated as success. fetchSyncPages() can return completed: false with a partial page set on timeout, but this branch still inserts the truncated catalog and deletes the checkpoint, so the caller keeps a corrupted local _catalog graph and loses the resume cursor. Only persist/delete the checkpoint after result.completed === true, or surface the timeout as a failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@branarakic branarakic force-pushed the feat/facet-open-serve branch from 76fcd31 to ccd5c2f 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
// (`<source-cg>/_catalog`), the exact graph open-serve reads via
// `contextGraphCatalogUri`. The previous TARGET-CG data graph left
// outsiders with an empty `<source-cg>/_catalog`.
projectionGraph: (id) => contextGraphCatalogUri(id),

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: publicProjectionContextGraphId is only acting as an on/off switch here. The projection still gets written to contextGraphCatalogUri(id), and there are no other runtime references to that config, so configuring a separate public projection CG never actually publishes anything into that CG. Either route the write through target or drop the config until the destination behavior exists.

* forwarded to `buildSyncRequest` so the responder gates it via the strict
* members-only `isMemberRecoveryAuthorized`). Default false ⇒ normal sync.
*/
recovery?: boolean;

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 new recovery plumbing is still dead in production. fetchSyncPages accepts the flag, but DKGAgent.fetchSyncPages() still does not expose/forward it and there are no runtime call sites passing true, so member recovery continues to use the normal auth flow. Please wire the flag through the public fetch helper and the actual recovery caller, or add an end-to-end test that proves the recovery branch is exercised.

// creator/curator/allowlist fields. Mirrors refreshMetaFromCurator's
// persist+invalidate path.
if (catalogQuads.length > 0) {
await this.store.insert(catalogQuads);

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 refresh path only appends to the local <cg>/_catalog graph. If the remote catalog entry changes, stale triples like the previous dkg:committedRoot remain alongside the new ones, so local readers can observe multiple conflicting versions. Mirror persistCatalogEntry/emitPublicProjectionAfterPublish and replace the subject before inserting.

@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