Skip to content

fix(rs): unblock random-sampling on integration — T2 + T6 + T8 devnet-sweep triage#647

Merged
branarakic merged 4 commits into
integration/rfc38-mainnet-readyfrom
fix/rfc38-mainnet-ready-t2-trust-level-extractor
May 25, 2026
Merged

fix(rs): unblock random-sampling on integration — T2 + T6 + T8 devnet-sweep triage#647
branarakic merged 4 commits into
integration/rfc38-mainnet-readyfrom
fix/rfc38-mainnet-ready-t2-trust-level-extractor

Conversation

@branarakic
Copy link
Copy Markdown
Contributor

Summary

Closes the three concrete devnet-sweep regressions surfaced during the
integration/rfc38-mainnet-ready readiness pass. The remaining sweep
failures (T1/T3 sender-key reliability, T4/T5/T7-a catchup-runner deny
hang, T7-b WM-isolation suspicion) are pre-existing reliability gaps,
not regressions from this integration cut — they ship as documented
followups in TESTNET_RESET.md rather than gate the testnet release.

ID Where it broke Fix in this PR
T2 (curated) PR #630 wired RandomSampling.sol to draw against getCiphertextChunkCount for curated CGs, but the off-chain prover still extracts plaintext leaves via getMerkleLeafCountV10ProofLeafCountMismatchError every period Defer curated CG random sampling to RFC-39 Phase B by filtering curated CGs at _isCGEligible. Single-line revert when the prover's ciphertext path lands
T2 (public) dkg-agent.stampTrustLevel writes dkg:trustLevel = "N" to the dataGraph AFTER the publisher records merkleLeafCount on-chain. Extractor then sees N+1 leaves vs chain's N Skip both canonical and legacy dkg:trustLevel predicates during KC leaf extraction (publisher already refuses user-authored trustLevel triples, so excluding them on read is safe by construction)
T6 devnet-test-cli-invite.sh hardcoded Anvil default addresses + omitted the second step of the API join flow Dynamic agent discovery via /api/agent/identity, case-insensitive address match, timestamped slugs, correct /sign-join/request-join two-step
T8 devnet-test-publish.sh minted the on-chain CG directly from a hardhat signer the daemon didn't own, so the daemon's later register call created an orphan CG and publish failed with "not registered on-chain" Switch to the natural daemon-side flow (context-graph createcontext-graph registerpublish) so the CG is owned by the daemon's wallet from the start

Commits (split for review)

  1. 4967a16f fix(evm-module): defer curated CG random sampling to RFC-39 Phase BRandomSampling.sol._isCGEligible skip + two test updates (.skip the curated suite, flip the only-curated-CG edge case to expect NoEligibleContextGraph)
  2. 4a6e0370 fix(random-sampling): skip post-publish dkg:trustLevel stamps in KC leaf extractionkc-extractor.ts allow-list + regression test
  3. 3655c68e chore(prover): expand rs.tick.data-corrupted diagnosticsprover.ts log fields (this is the diagnostic that pinned down the trustLevel bug; preserved for testnet debugging)
  4. e5ae3395 test(devnet): fix publish + cli-invite scripts (T6 + T8) and add full-sweep harnessscripts/devnet-test-{publish,cli-invite}.sh + new scripts/_devnet-full-sweep.sh

Devnet smoke (single-node publish + random-sampling)

[v10-publish-test] Published KC id=1 tx=0xeaf4857fdfcd55f9...
                   verified readable via KCS
[rs.tick.submitted]    {"kcId":"1","cgId":"7","chunkId":"0",
                        "txHash":"0x6081..."}
[rs.tick.already-solved]  {"epoch":"1","periodStart":"600"}
[rs.tick.submitted]    {"kcId":"1","cgId":"7","chunkId":"0",
                        "txHash":"0xcf5a..."}

Proofs land on-chain every period for the publisher; non-hosting nodes
correctly emit [rs.tick.kc-not-synced] instead of the previous
data-corrupted.

Test plan

  • pnpm --filter @origintrail-official/dkg-random-sampling test --run → 48/48 pass (includes new regression test for the trustLevel skip)
  • ./scripts/devnet-test-publish.sh → green
  • ./scripts/devnet-test-cli-invite.sh → 21/21 assertions pass
  • ./scripts/devnet-test-random-sampling.sh → proofs landing on-chain
  • Full ./scripts/_devnet-full-sweep.sh — in progress at PR creation; remaining sweep failures expected to map only to documented pre-existing reliability gaps

Followups (NOT in this PR)

Tracked in TESTNET_RESET.md:

  • T1/T3 sender-key setup-send reliability (revocation + unclean-restart timeouts) — pre-existing
  • T4/T5 + T7-a catchup-runner doesn't terminate when all peers deny a sync request — pre-existing
  • T7-b possible WM-isolation breach in sharing — needs investigation
  • T2 curated: re-enable when PR LU-11: Chunked Ciphertext Commitment for Curated VM Publish (design + skeleton) #617 (LU-11 ciphertext commitment) + ciphertext-aware prover lands — single-line revert + test unskip noted in code comments
  • trustLevel post-publish injection (architectural): move the stamp to a separate _trust graph so the extractor allow-list is no longer needed

Made with Cursor

Branimir Rakic and others added 4 commits May 25, 2026 16:03
The PR #630 picker change wired the contract to draw against
`getCiphertextChunkCount` for curated CGs, but the off-chain prover at
`packages/random-sampling/src/prover.ts` still extracts plaintext leaves
via `getMerkleLeafCount`. Devnet sweep confirmed every curated draw
produced `V10ProofLeafCountMismatchError` and the core nodes published
zero proofs.

Filter curated CGs at `_isCGEligible` instead of unwinding the picker —
the curated branches in `_pickWeightedChallenge` steps 2/3 are retained
verbatim so the re-enable is a one-line revert here plus the test
unskip, contingent on the prover ciphertext path landing.

Test fallout:
- `RandomSampling-curated.test.ts` → `.skip` with a pointer to where the
  unskip will happen.
- `RandomSampling.test.ts` Phase-A.5 edge case "only curated CGs hold
  value" → expect `NoEligibleContextGraph` (pre-RFC-39 behaviour, since
  the curated CG never reaches the KC-level slot) instead of
  `NoEligibleKnowledgeCollection`.

Part of T2 in the devnet-sweep triage (curated branch). The public-CG
half of T2 is addressed by the kc-extractor trustLevel skip in a
follow-up commit.

Co-authored-by: Cursor <cursoragent@cursor.com>
…eaf extraction

T2 had two root causes. The curated-CG half was addressed by deferring
the contract picker to RFC-39 Phase B. The public-CG half is here: the
daemon's verify-quorum path stamps `dkg:trustLevel = "N"` onto the
dataGraph AFTER the publisher has computed `merkleLeafCount` and
registered it on-chain (see `dkg-agent.stampTrustLevel` /
`stampBatchTrustLevel`).

For single-node publishes — and every devnet test runs against a single
publisher because no remote ACK arrives in the test window —
`stampBatchTrustLevel(..., SelfAttested)` fires within seconds of
publish, polluting the dataGraph with one extra triple per root
subject. The extractor's CONSTRUCT then returns 2 leaves where the
on-chain commitment is 1, and the prover dies with
`V10ProofLeafCountMismatchError` every period.

Devnet diagnostic:
  computedLeafCount=2  expectedLeafCount=1
  extractedLeafCount=2 chainExpectedLeafCount=1

Filter both the canonical (`http://dkg.io/ontology/trustLevel`) and
legacy (`https://dkg.network/ontology#trustLevel`) predicates from the
extractor's per-root CONSTRUCT. The publisher already refuses
user-authored trustLevel triples via
`assertNoUserAuthoredTrustLevelQuads`, so excluding them on read is
safe by construction.

Regression test added to `kc-extractor.test.ts` — seeds a KC fixture
plus both trustLevel variants in the same dataGraph, asserts the
extractor returns only the original triples and the V10 root matches a
manual rebuild of the pre-stamp leaf set.

Devnet smoke (single node publish + random-sampling):
  [rs.tick.submitted] {"kcId":"1","cgId":"7","chunkId":"0",
                       "txHash":"0x6081..."}
  followed by steady `already-solved` — proofs now land every period.

Architectural followup (deferred): the deeper fix is to move the trust
stamp out of the dataGraph into a separate `_trust` graph so the
extractor's allow-list is unnecessary. Tracked in TESTNET_RESET.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
When the V10 proof builder rejects a recomputed leaf set, the
`rs.tick.data-corrupted` log line previously only carried the reason
name and the error class. That made the leaf-count regressions during
T2 triage near-undebuggable from logs alone — you needed to attach a
debugger to a long-running daemon to see what counts the builder was
comparing.

Surface every numeric field the V10 builder errors expose
(`computedLeafCount`, `expectedLeafCount`, `chunkId`, `leafCount`) plus
the two pieces of context the caller already has (extractor's
`leaves.length` and the chain-fetched `expectedLeafCount`). Fields are
spread defensively so future error shapes survive without log changes.

This is the diagnostic that pinned down the trustLevel-injection bug:
extractedLeafCount=2 vs chainExpectedLeafCount=1 made the off-by-one
obvious without needing a debugger session on the daemon.

No runtime behaviour change.

Co-authored-by: Cursor <cursoragent@cursor.com>
…-sweep harness

Three changes, all in `scripts/`. None of the daemon or contract code is
touched.

devnet-test-publish.sh (T8 — "Context graph N is not registered on-chain")
  Previous revision called `ContextGraphs.createContextGraph(...)` directly
  from a hardhat signer. That minted an on-chain CG owned by a wallet
  the daemon does not control, so the subsequent
  `dkg context-graph register <id>` call created a SECOND on-chain CG
  under the daemon's wallet rather than binding the existing one. The
  publish then targeted the orphan and failed.

  Replaced with the natural daemon-side CLI flow:
    dkg context-graph create <slug>     → local CG, no chain tx
    dkg context-graph register <slug>   → mints on-chain CG owned by
                                          the daemon + binds slug → id
    dkg publish <slug> --file <rdf>     → publishes against owned CG

  Slug is timestamped (`v10-publish-smoke-$(date +%s)`) so reruns within
  the same devnet session don't collide. The `create` output line "ID:
  …" is parsed because the CLI auto-namespaces bare slugs to
  `{agentAddress}/{slug}`; the fully-qualified id is what subsequent
  commands and the data-graph URI both consume.

devnet-test-cli-invite.sh (T6 — hardcoded anvil addresses + broken API
  join flow)
  - Replaced hardcoded `0xf39Fd6e5...` anvil defaults with dynamic
    discovery via `/api/agent/identity` per node.
  - Switched address `grep`s to `grep -qi` so checksummed (CLI) and
    lowercase (API) variants match.
  - Slugs now carry a `$(date +%s)` suffix for idempotency.
  - API-based join request rewritten as the correct two-step flow:
    `/sign-join` returns a delegation, `/request-join` forwards it to
    the curator's peer ID. Previous revision only called `/sign-join`
    and asserted the request would land.
  - Bumped gossip-poll wait from 2s → 15s to absorb cross-node sync
    skew; without it the curator-side assertion races.

  All 21 assertions now pass on a fresh devnet.

_devnet-full-sweep.sh (added)
  Internal harness used for the devnet-readiness triage. Runs every
  devnet-test script not already covered by devnet-test-rfc38-all.sh
  (rfc38-curator-offline-midbatch, rfc38-revocation,
  rfc38-prereg-bytecap-stress, rfc38-unclean-restart, publish, sharing,
  invite-flow, cli-invite, reject-flow, random-sampling), aggregates
  pass/fail and writes per-script logs to
  `.devnet/full-sweep/<ts>/<id>.log` for post-mortem. SOAK=1 adds the
  30-min soak-rs run.

  Test order is intentional: revocation / unclean-restart mutate the
  devnet in ways subsequent scripts have to tolerate; random-sampling
  and soak run last so a single chain advance doesn't poison cheaper
  scripts' in-flight challenges.

Co-authored-by: Cursor <cursoragent@cursor.com>
// Re-enabling curated random sampling is a single-line revert here +
// the unskip in `RandomSampling-curated.test.ts`, contingent on the
// prover ciphertext path being green.
if (contextGraphStorage.getIsCurated(contextGraphId)) return 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: this blanket curated => false gate disables challenge generation for every curated context graph, including ones whose KCs already have a valid ciphertext commitment. That is an on-chain behavior break from RFC-39 Phase A.5: committed curated KCs can no longer be sampled at all. If the prover needs a temporary guard, keep it off-chain or behind an explicit feature flag/epoch gate so already-migrated curated graphs do not become permanently ineligible.

// `_pickWeightedChallenge` retains the curated branches so the unskip is a
// one-line revert in `RandomSampling._isCGEligible` + removing the
// `.skip` below.
describe.skip('@unit RandomSampling — RFC-39 curated picker [Phase B deferred]', () => {
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: describe.skip suppresses the whole curated suite, including the commitment getter/validation tests later in this file that do not depend on the prover mismatch. This leaves the ciphertext-commitment API effectively uncovered until Phase B lands. Split the picker-dependent cases into a nested skipped block and keep the storage/validation assertions active.

# the devnet in ways subsequent scripts have to tolerate. Random-sampling
# and soak run last so a single chain advance doesn't poison the in-flight
# challenges of the cheaper scripts.
SCRIPTS=(
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: the new 'full sweep' still omits scripts/devnet-test.sh, which is a standalone baseline harness and is not covered by devnet-test-rfc38-all.sh. A green run here can therefore miss regressions in the main devnet smoke suite. Either add it to SCRIPTS or narrow the script/header so it no longer claims to cover every remaining devnet test.

|| fail "context-graph register failed (CG=$CG_FQ_ID)"
CG_ONCHAIN_ID=$(printf '%s\n' "$REG_OUT" | sed -nE 's/.*On-chain:[[:space:]]+([0-9]+).*/\1/p' | head -n1)
[ -n "$CG_ONCHAIN_ID" ] || fail "could not parse on-chain id from register output:\n$REG_OUT"
# The slug is what `dkg publish` and the data-graph URI both consume;
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: after switching creation/registration to the daemon, this smoke test no longer proves that the published BATCH_ID actually belongs to CG_ONCHAIN_ID. The step-6 getKnowledgeCollectionMetadata(batchId) check only shows that some KC was created, so a stale slug→id mapping or fallback to another registered CG would still pass. Add an assertion on kcToContextGraph(batchId) / getKCContextGraphId(batchId) in the verification block.

@branarakic branarakic merged commit 051eae1 into integration/rfc38-mainnet-ready May 25, 2026
3 checks passed
branarakic pushed a commit that referenced this pull request May 25, 2026
Adds the rc.10 operator note for the Base Sepolia contract redeploy
(chainResetMarker → v10-rc10-rfc38-mainnet-ready-2026-05-25; Hub +
Token retained; ConvictionStakingStorage.v10LaunchEpoch sealed at
497 via DKGStakingConvictionNFT.finalizeMigrationBatch).

Existing [Unreleased] content (OT-RFC-38 Phase A LU-5/7/8/9 +
LU-6 deferred; CG memory model rewrite LU-1/2/3/4; private graph
SPARQL filterability #633) is promoted verbatim.

New entries cover rc.10-cycle fixes that landed via separate PRs
and weren't previously documented:
  - #574 Profile.recreateProfile for testnet recovery
  - #640 WM persistence durability across restarts
  - #647 T2/T6/T8 random-sampling devnet-sweep triage
    (defer curated CG sampling to RFC-39 Phase B; skip
    post-publish trustLevel stamps in KC leaf extraction;
    devnet publish + cli-invite scripts hardened)

Co-authored-by: Cursor <cursoragent@cursor.com>
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