Skip to content

OT-RFC-38 LU-5 — edge curator can publish curated CGs to Verified Memory#608

Open
branarakic wants to merge 1 commit into
feat/cg-memory-modelfrom
feat/ot-rfc-38-lu5
Open

OT-RFC-38 LU-5 — edge curator can publish curated CGs to Verified Memory#608
branarakic wants to merge 1 commit into
feat/cg-memory-modelfrom
feat/ot-rfc-38-lu5

Conversation

@branarakic
Copy link
Copy Markdown
Contributor

Summary

Closes the §1.1 bug in OT-RFC-38 / SPEC_CG_HOSTING_MEMBERSHIP.md: an edge agent with no on-chain Profile can now create a curated CG and publish it to Verified Memory in no-attribution mode, with cores attesting to opaque ciphertext they cannot decrypt.

Stacked on top of #595 (SPEC_CG_MEMORY_MODEL LU-1..LU-4). #595 was a necessary precondition because the chain-side participantIdentityIds argument was incompatible with edge agents' identityId=0n.

What this commit does

Three concerns combined because they share a single API surface and only make sense together:

  1. Inline encrypted publish payload (rescoped minimal LU-5). Adds isEncryptedPayload field (proto field 14) to PublishIntent; cores branch on opaque ciphertext, verifying chunkDigest + byteSize against persisted bytes without ever N-Quad-parsing. AES-256-GCM chain-key AEAD helper in dkg-core (v10-publish-payload.ts), publisher-side encryptInlinePayload hook, agent-side _resolveEncryptInlinePayload wired into both publish and publishFromSharedMemory. Byte-size accounting uses ciphertext length (effectiveByteSize) when the payload is encrypted so ACK signing and on-chain pricing agree.

  2. Drop the identity=0 → skip on-chain gate in DKGPublisher. Edge agents that won't (and shouldn't) register a Profile must still publish in attributionId=0 mode, which KnowledgeAssetsV10 already accepts as no-attribution. The early willAttemptOnChainPublish check and the late identity gate both collapse to "do we have a CG id + a V10-ready adapter + a signer". Pre-fix, even after Context Graph memory model — edge agents can create curated CGs #595's LU-2 dropped the register-side gate, an edge agent's publish silently fell back to tentative and the UI optimistically reported success; now it goes on-chain or hard-fails with a clear error.

  3. UI honesty about VM publish status. The SWM→VM panel and per-entity publish card now require status=confirmed AND a real txHash to render success; anything else is a red "NOT published to Verified Memory" card with the actual status and a diagnostic. The full TX hash is shown (was truncated to ~16 chars before — too short to paste into a block explorer). Vite proxy honours DEVNET_UI_NODE so the UI can target an edge node (DEVNET_UI_NODE=5 ./scripts/devnet.sh ui start) — required to test the §1.1 flow at all.

Test plan

Unit:

  • v10-publish-payload.test.ts (new, 8): round-trip, wire layout, domain separation, error handling.
  • storage-ack-handler.test.ts (+5): encrypted-payload branch — byte-size verification, missing-claim error, empty stagingQuads error.
  • publisher-no-random-wallet.test.ts + phase-sequences.test.ts: two tests updated whose premise was the old short-circuit; new behaviour pinned with explanatory comments. Total publisher: 965/965 pass.

Devnet (scripts/devnet-test-rfc38-lu5.sh + lu5-public.sh):

  • Curated CG from edge node 5 (identity=0, no Profile) → KC confirmed on-chain via attributionId=0 publish, core ACKs collected, KCS read-back: merkleRoots=1, byteSize=N [ciphertext].
  • Public CG regression on the same node → KC confirmed on-chain, AEAD wrap correctly skipped, byteSize=N [plaintext].

Specs

  • docs/specs/SPEC_CG_HOSTING_MEMBERSHIP.md (new) — OT-RFC-38 full text.
  • docs/specs/SPEC_CG_MEMORY_MODEL.md — cross-reference to the new doc.

Follow-ups

LU-7/8/9/10 + integration harness land in the stacked follow-up PR. LU-6 (substrate hosting on cores) is deferred — gap documented in SPEC_CG_HOSTING_MEMBERSHIP.md §7.1.1.

Made with Cursor

…lish to VM

Implements the minimum unblocker for OT-RFC-38 §1.1: an edge agent that
has no on-chain Profile can now create a curated CG and publish it to
Verified Memory in no-attribution mode, with cores attesting to opaque
ciphertext they cannot decrypt.

Three concerns combined here because they share a single API surface and
their semantics only make sense together:

  1. Inline encrypted publish payload (the rescoped minimal LU-5 from
     the build plan). Adds `isEncryptedPayload` to PublishIntent so cores
     branch on opaque ciphertext, AES-256-GCM chain-key AEAD helper in
     dkg-core (`v10-publish-payload.ts`), publisher-side `encryptInlinePayload`
     hook, agent-side `_resolveEncryptInlinePayload` wired into both
     `publish` and `publishFromSharedMemory`. Byte-size accounting uses
     ciphertext length (`effectiveByteSize`) when the payload is encrypted
     so ACK signing and on-chain pricing agree.

  2. Drop the "identity=0 → skip on-chain" gate in DKGPublisher. Edge
     agents that won't (and shouldn't) register a Profile must still
     publish in attribution-id=0 mode, which `KnowledgeAssetsV10` already
     accepts as no-attribution. The early `willAttemptOnChainPublish`
     check and the late identity gate both collapse to "do we have a CG
     id + a V10-ready adapter + a signer". Pre-fix, even after LU-2
     dropped the register-side gate, an edge agent's publish silently
     fell back to tentative and the UI optimistically reported success;
     now it goes on-chain or hard-fails with a clear error.

  3. UI honesty about VM publish status. The SWM→VM panel and per-entity
     publish card now require status=confirmed AND a real txHash to
     render success; anything else is a red "NOT published to Verified
     Memory" card with the actual status and a diagnostic. The full TX
     hash is shown (was truncated to ~16 chars before — too short to
     paste into a block explorer). Vite proxy honours `DEVNET_NODE` so
     the UI can target an edge node (`UI_NODE_ID=5 ./scripts/devnet.sh
     ui start`) — required to test the §1.1 flow at all.

Tests:
- `v10-publish-payload.test.ts` (new, 8): round-trip, wire layout, domain
  separation, error handling.
- `storage-ack-handler.test.ts` (+5): encrypted-payload branch — byte-size
  verification, missing-claim error, empty stagingQuads error.
- `publisher-no-random-wallet.test.ts` + `phase-sequences.test.ts`: two
  tests updated whose premise was the old short-circuit; new behaviour
  pinned with explanatory comments. Total publisher: 965/965 pass.

Devnet validation (`scripts/devnet-test-rfc38-lu5.sh` and `-lu5-public.sh`):
- Curated CG from edge node 5 (identity=0, no Profile) → KC #3 confirmed
  on-chain (TX 0xa137cc7d…) via attributionId=0 publish, 1 core ACK
  collected, KCS read-back: merkleRoots=1, byteSize=512 [ciphertext].
- Public CG regression on the same node → KC #4 confirmed on-chain (TX
  0xa24f3bc1…), no chain-key AEAD wrap fires (correctly skipped for
  public CGs), byteSize=131 [plaintext].

Specs:
- `SPEC_CG_HOSTING_MEMBERSHIP.md` (new) — OT-RFC-38 full text.
- `SPEC_CG_MEMORY_MODEL.md` — cross-reference to the new doc.

Co-authored-by: Cursor <cursoragent@cursor.com>
// mismatches; outsider attestation tokens (LU-9) let third parties
// verify after the fact. Cores DO verify `stagingQuads.length` matches
// `publicByteSize` so a misreported size can't slip past pricing.
if (intent.isEncryptedPayload === true) {
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 trusts a sender-controlled isEncryptedPayload bit without checking that the target CG is actually curated. A malicious publisher can set the flag on a public CG and bypass every root/KA/merkle verification path, because the handler will sign whatever merkleRoot, kaCount, and merkleLeafCount it was told. Gate this branch on the graph's real access policy (or another trusted curated-only signal) before ACKing opaque payloads.

object: ciphertextLiteral,
graph: stagingGraphUri,
}]);
setTimeout(async () => {
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 core signs an availability ACK and then unconditionally deletes the ciphertext 10 minutes later. That breaks the new persist-before-sign contract under slow/failing chain submits and guarantees later catch-up requests cannot be served from an ACKed core. Cleanup needs to be tied to publish finalization/retention bookkeeping, not a fixed timer started before the publish outcome is known.

?? this.peerId;
const stateKey = swmSenderStateKey(contextGraphId, subGraphName, senderAddress);
const state = this.swmSenderKeySendStates.get(stateKey);
if (!state) {
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: On a fresh curated CG with no sender state yet, this silently returns undefined, which makes publish() fall back to the old plaintext-inline path. That leaks private data to cores exactly in the case this change is meant to protect. For curated graphs, fail closed here (or bootstrap the sender state first) instead of downgrading to plaintext.

// agent membership with hosting membership and broke verify
// entirely for curated CGs (cores aren't agents in the
// allowlist) — revert kept here as Codex round-5 follow-up.
getParticipantPeers: () => {
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: VerifyCollector's contract explicitly says the peer set must be filtered to recipients that are allowed to see the proposal payload, but this now broadcasts rootEntities for every verify request to all connected peers. On invite-only CGs that's a real privacy regression, not just extra traffic. Please resolve the sharding-table/core roster first, or strip sensitive fields from the proposal until that mapping exists.

// publish path requires an on-chain CG id). The modal already
// tells the user "Registering context graph on the network…",
// so the combined create+register flow matches the UX promise.
register: true,
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 removes the UI's ability to create a local-only context graph. The daemon route still supports create-now/register-later, but unfunded or offline users will now get a hard create failure from the primary UI path instead of a usable local project. Consider making registration opt-in or falling back to local creation when the register leg fails.

// CG that looks fine in the UI but can never publish to VM
// (publish-to-VM requires an on-chain CG id). Fail loud with
// an actionable hint pointing at the retry endpoint.
if (result.registered === false) {
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: Throwing here turns a partial success into an apparent create failure even though the CG was already created locally. The next retry will usually hit 409 already exists, and the user never gets routed to the existing project to retry registration from Settings. Surface this as a warning on the created project instead of aborting the flow.

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