From cd68fa68998676ed5a8945ee94717bae967fc7f1 Mon Sep 17 00:00:00 2001 From: branarakic Date: Wed, 27 May 2026 16:36:03 +0200 Subject: [PATCH] fix(agent): always plumb on-chain CG id into finalization gossip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `publishFromSWM` emitted `targetContextGraphId: undefined` whenever the caller didn't explicitly set `options.subContextGraphId` / `options.contextGraphId` (i.e. every non-REMAP publish). Receiving cores then promoted the SWM snapshot into the legacy `/_meta` graph instead of the per-cgId `/context//_meta` graph that the RS prover's `extractV10KCFromStore` reads from — so every freshly published KC failed sampling with `KCNotFoundError` even though SWM had been replicated correctly. Reproduced end-to-end via `scripts/devnet-test-rfc39-comprehensive.sh` Scenario A; with the fix all four scenarios (public, curated 1-chunk, curated multi-chunk, late-join auto-backfill) land on-chain `submitChallengeProof`. The publisher already resolves `onChainId` (explicit REMAP target OR `getContextGraphOnChainId` lookup) — thread that into the gossip's `targetContextGraphId` while keeping `ctxGraphIdStr` REMAP-only so we don't trip the publisher's REMAP-delete branch for regular publishes. Also expose `DEVNET_CORE_ASK_TRAC` so the devnet bootstrap can use a realistic ask (e.g. 0.5 TRAC/KiB) without editing the script in place. Co-authored-by: Cursor --- packages/agent/src/dkg-agent.ts | 18 ++++++++++++++++-- scripts/devnet.sh | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/agent/src/dkg-agent.ts b/packages/agent/src/dkg-agent.ts index 4dc7c2955a..f5c4901168 100644 --- a/packages/agent/src/dkg-agent.ts +++ b/packages/agent/src/dkg-agent.ts @@ -8865,6 +8865,10 @@ export class DKGAgent { ): Promise { const ctx = options?.operationCtx ?? createOperationContext('publishFromSWM'); const effectiveSubCG = options?.subContextGraphId ?? options?.contextGraphId; + // `ctxGraphIdStr` doubles as `publishContextGraphId` for REMAP-flow + // publishes — the publisher uses its presence as a signal to DELETE the + // original copy from the default data graph. Keep it empty for non-REMAP + // publishes so we don't accidentally trigger the delete. const ctxGraphIdStr = effectiveSubCG != null ? String(effectiveSubCG) : undefined; const onChainId = ctxGraphIdStr ?? (await this.getContextGraphOnChainId(contextGraphId)) ?? undefined; @@ -8963,6 +8967,16 @@ export class DKGAgent { if (result.status === 'confirmed' && result.onChainResult) { const rootEntities = result.kaManifest.map(ka => ka.rootEntity); + // Always carry the resolved on-chain CG id in the finalization gossip + // so receiving cores promote SWM into the per-cgId `_meta` namespace + // (`/context//_meta`) that the RS prover reads from. + // Without this the prover 404'd with `KCNotFoundError` on every + // freshly-published KC even though the SWM payload had been + // replicated — see scripts/devnet-test-rfc39-comprehensive.sh + // Scenario A. We pass the *publisher-resolved* `onChainId` (which + // includes both explicit REMAP targets and the auto-lookup fallback) + // rather than the REMAP-only `ctxGraphIdStr`. + const broadcastCgId = onChainId != null ? String(onChainId) : undefined; const msg: FinalizationMessageMsg = { ual: result.ual, contextGraphId: contextGraphId, @@ -8976,14 +8990,14 @@ export class DKGAgent { rootEntities, timestampMs: Date.now(), operationId: ctx.operationId, - targetContextGraphId: result.contextGraphError ? undefined : ctxGraphIdStr, + targetContextGraphId: result.contextGraphError ? undefined : broadcastCgId, subGraphName: options?.subGraphName, }; const topic = contextGraphFinalizationTopic(contextGraphId); try { await this.gossip.publish(topic, encodeFinalizationMessage(msg)); - this.log.info(ctx, `Broadcast finalization for ${result.ual} to ${topic}${ctxGraphIdStr ? ` (contextGraph=${ctxGraphIdStr})` : ''}${result.contextGraphError ? ' (ctx-graph registration failed, omitting targetContextGraphId)' : ''}`); + this.log.info(ctx, `Broadcast finalization for ${result.ual} to ${topic}${broadcastCgId ? ` (contextGraph=${broadcastCgId})` : ''}${result.contextGraphError ? ' (ctx-graph registration failed, omitting targetContextGraphId)' : ''}`); } catch { this.log.warn(ctx, `No peers subscribed to ${topic} yet`); } diff --git a/scripts/devnet.sh b/scripts/devnet.sh index e10adcfec9..16a6690a5e 100755 --- a/scripts/devnet.sh +++ b/scripts/devnet.sh @@ -927,7 +927,7 @@ cmd_start() { if (idId === 0n) { console.log('Node ' + (i+1) + ' (core): no identity after 60s, skipping'); continue; } const stakeAmount = ethers.parseEther('50000'); - const askAmount = ethers.parseEther('1'); + const askAmount = ethers.parseEther('${DEVNET_CORE_ASK_TRAC:-1}'); // Lock tier 1 (1-month). Cheapest tier with non-zero multiplier; sufficient // for devnet random-sampling soak tests where we need nodeStakeV10 > 0. const lockTier = 1;