release: promote testnet-canary to main for 10.0.10 - #1983
Merged
Conversation
…tract Closed deterministic fixture harness under devnet/rfc64-gate2-multi-asset-completeness. Raw@1 and verdict@1 schemas, a canonical encoder (sorted keys, integers only, lowercase hex, single trailing LF), a deterministic generator (pure function of asset count), an independent inventory set root (domain-tagged, full-row leaves, sorted, recomputed from the received rows and never trusted from the declared field), and a fail-closed verifier that always returns a verdict and rejects missing, extra, duplicate, misordered, count-mismatched, digest/length-mismatched, and schema-malformed input. Fixture harness only: productBoundary is not-connected and gateEvaluation is not-evaluated on every artifact and verdict; output never asserts a real Gate 2 pass, and fixtureComplete is kept distinct from any gate disposition. Evidence at source commit 19892c1: tsc --noEmit clean; node --test 15 pass / 0 fail (one per material invariant, each mutated artifact still parses); two-run byte determinism sha256 b5bb0ad3c1b78b67dd19fa785ac7d59bf2f9d123975a8f065d0f8ba3d64b2892. No product, Gate 0/Gate 1, integration/rfc64-devnet, or existing-worktree edits; not pushed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ss-policy feat(agent): authorize RFC-64 catalog policy cells
Replace the ad-hoc open-policy check on the RFC-64 catalog transports with the registry-backed access-policy authorizer from #1835: - exactly one authorizer must be configured (current or legacy compatibility), - authorization is re-checked after each await on the inbound and outbound paths, so a policy revoked mid-operation cannot be raced, - head requests remain bound to the authorized scope. The two native CONTENT protocols stay restricted to publicly-readable cells (accessPolicy === 0). They resolve objects and bundles purely by the digest carried in the request, out of a store shared by every context graph on the node, and nothing on the serve path binds what is served back to the graph that authorized the request. Admitting a private cell here would therefore let any peer authorized under any public cell the node happens to hold read another graph's control objects and KA bundles by digest alone, and would leave a removed member's remembered digests a permanent read capability -- inverting the fail-closed requirement this commit exists to enforce. Serving private content needs the serve path scope-bound first (heads carry networkId/contextGraphId, buckets carry catalogScopeDigest, bundles bind only via row membership in the announced head); that is a design slice for the private-CG milestone, not a widening of this gate. Tests pin the boundary rather than the hole: the live two-node fetch runs across both public cells (publishPolicy governs finalized-VM admission only and must not affect read authorization), and a private-policy provider is asserted to deny before provider lookup on BOTH content protocols. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Compose the service/API generic-authoring slice onto the authorized transport stack. Adds the policy-neutral public authoring surface used by both publicly readable cells: - acceptRfc64CatalogAccessSnapshotV1 (accepts exact public/open and public/curated policy snapshots with no roster; private requires a current member roster and fails closed without one), - publishAuthorCatalogGenesisV1 / publishAuthorCatalogExactSetSuccessorV1 (generic author/subgraph/catalog-era scope; root + named subgraphs; not routed through the legacy open/root-only assertions), - rfc64CatalogAccessPolicyAuthority create-option, package-root + type exports. Two test-coverage gaps found while reviewing this slice are closed here, because both were mutation-dead -- they passed whether or not the product behaved: - The private branch of isSwmAuthorAuthorized was unfalsifiable: the suite's own memberRoster() helper enrols every wallet the tests author with, so deleting the roster clause at either call site left the whole suite green. Adds an off-roster private-cell author negative covering both the author side (assertAcceptedPolicyMatchesCatalogScope) and the receive side (announceCatalogHead -> resolveTrustedCatalogScope), with a positive control on an enrolled roster so the denial is attributable to the roster check. - The new public-API typecheck introduced each params value as `declare const x: T` and passed it to a method taking exactly T -- a T-assignable-to-T no-op that proves only the export name exists. A rename or a newly required field on the published signatures compiled clean. Rebuilt around real object literals (matching the sibling applied-inventory-digest typecheck), so field names are load-bearing. Validation on this exact tree: agent build (tsc + test:types + test:package-root) green; full RFC-64 unit regression green. Land as small reviewable PRs on integration/rfc64-devnet; do NOT merge to main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(sync): retry foreground catchup under backpressure
…e queues (#1837) (#1883) * feat(publisher): job-scoped terminal cleanup for publisher + SWM share queues (#1837) Adds two atomic, idempotent, by-exact-jobId TERMINAL-clear operations (epic PR4). Each uses the node's native terminal state as the sole authority, rejects nonterminal/unknown/ malformed without mutation, never broadens to other jobs, and returns a bounded TerminalJobClearOutcome { cleared | already_absent | rejected(nonterminal|unknown|malformed) }. Publisher (lift): - clearTerminalJob(jobId) on a segregated VmPublishTerminalJobClearer interface. Runs INSIDE withClaimLock and — the anti-sweep fix — retry() is now ALSO wrapped in withClaimLock (retry() is the only lock-free terminal→active transition; without this a concurrent by-id clear could sweep a just-reaccepted active job). Terminal authority = new shared isClearableTerminalLiftJob (finalized|failed, excluding retry_recovery-failed which may still land an on-chain tx); bulk clear() refactored to reuse it (behavior identical). Ordered defensive read splits already_absent/malformed/unknown/nonterminal without throwing. Preserves the #1829 journal by construction (deleteJob is subject-scoped to the control-plane graph; a regression test asserts the lineage survives a clear). Promote (SWM share): - clearTerminalJob(jobId) on a segregated PromoteTerminalJobClearer, inside the existing withMutationLock (already serializes every transition, incl. recover()), so it is race-safe with no new locking. New subject-scoped deleteJob primitive. Terminal = {succeeded, failed} with NO carve-out (nothing background re-drives a terminal promote row). Bare-state probe splits already_absent/unknown/malformed. Surface: POST /api/publisher/clear-job and POST /api/knowledge-assets/swm/share-jobs/:id/clear (both DISTINCT from cancel; already_absent→200 not 404); agent.assertion.clearPromoteAsync pass-through; api-client publisherClearJob + knowledgeAssetClearShareJob. Base contracts stay frozen; factory/getter intersections widened. Tests: 19 unit (lift 11: all outcomes, retry_recovery injection, no-sweep vs concurrent retry(), journal preservation, concurrent determinism; promote 8) + 3 route (outcome→HTTP) + existing lift/promote regression 67 unchanged. Publisher/agent/cli tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(publisher): address #1883 local-review findings (clear-job route + promote parse + shared responder) - 🔴 POST /api/publisher/clear-job: `const { jobId } = JSON.parse(body)` threw on a literal `null` body (JSON.parse succeeds, destructure of null → TypeError → 500). Optional-chain the parsed body so a null/primitive falls through to the bounded 400 malformed guard. Regression: a `null` body returns 400 rejected(malformed), not 500. - 🟡 promote clearTerminalJob: the state-triple parse (`parseLiteral` = JSON.parse) was unguarded and could throw out of the method, contradicting its "never throws" contract and diverging from the lift sibling. Wrap it in try/catch → rejected(unknown). Regression: a subject with an unrecognized state literal returns rejected(unknown) without throwing. - 💡 extract respondTerminalClearOutcome(res, outcome, jobId) into http-utils, used by BOTH the publisher and SWM share-job clear handlers, so the TerminalJobClearOutcome→HTTP contract is single-sourced and can't drift. Publisher/agent/cli tsc clean; clear tests 20 (lift 11 + promote 9) + route 4 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(publisher): address #1883 review round 2 — validate clear jobId + neutral shared type (#1837) Combined remote (otReviewAgent) + local multi-angle review findings: - 🔴 (remote): clearTerminalJob interpolated an unvalidated jobId into the control-plane SPARQL IRI (jobSubject(jobId) in `<…>`); the new clear-job route accepts arbitrary jobId from a JSON body, so a value with a space / '>' / '{' could break the query out of the IRI → 500 / injection instead of the bounded outcome. Add isSafeClearJobId (the producer grammar /^[A-Za-z0-9][A-Za-z0-9._:-]*$/, ≤256 — IRI-safe) and reject an empty OR unsafe jobId as `malformed` BEFORE building the query, in BOTH clearers (lift + promote). - 🟡 (remote) + 🔵 (local, corroborated): TerminalJobClearOutcome was parked in async-lift-publisher-types.ts and imported by the promote queue (cross-family coupling). Move it to a neutral `terminal-job-clear.ts` (which also owns isSafeClearJobId); both queue type modules + impls import from there; barrels re-export from the neutral owner. - 🟡 (remote): the SWM share-job clear endpoint had no route-level test. Add route tests in promote-async-routes.test.ts (terminal→200 cleared, repeat/absent→200 already_absent, nonterminal→409, unsafe path→400 before queue lookup) + the clearPromoteAsync mock facade. Also add unsafe-jobId cases to the lift/promote clear unit tests and the publisher clear-job route test (bad id / bad>id → 400 malformed, no 500/query error). Deferred (reasoned replies on-thread): the capability-intersection→named-contract refactor and the per-route JSON-parsing-island helper — both follow the established #1828/#1829 convention and are cross-cutting cleanups best done on their own, not correctness gaps. Publisher/agent/cli tsc clean; publisher clear unit 20 + route 41 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(publisher): address bot round-3 maintainability findings on #1837 terminal clear Follow-up to the round-2 fixes on PR #1883; resolves the four maintainability comments the reviewer raised against those fixes. No behaviour change. - Type the promote-queue capability at the ownership boundary instead of casting it at the getter: `DKGAgentBase._promoteQueue` is now `AsyncPromoteQueue & PromoteTerminalJobClearer`, so a substituted queue must satisfy the clearer at compile time (closes the runtime type hole). - Move `respondTerminalClearOutcome` out of the generic `http-utils.ts` into a route-owned `daemon/routes/terminal-clear-response.ts`; both clear routes import it there. `http-utils.ts` stays protocol-level. - Make the safe job-id grammar a single authoritative contract: the publisher exports `SAFE_CLEAR_JOB_ID_PATTERN`/`SAFE_CLEAR_JOB_ID_MAX_LENGTH`, and the CLI `validatePromoteJobId` imports them instead of re-declaring the regex, so route-level and control-plane job-id acceptance cannot drift. - Add a production DKGAgent facade regression (`clear-promote-async-facade.test.ts`) that drives a REAL promote queue through `agent.assertion.clearPromoteAsync`, asserting exact-row removal, idempotent repeat, and nonterminal rejection — a mis-delegation to `cancel()` (which retains the row) would fail it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(cli): cover the #1837 clear-job ApiClient wrappers Follow-up to the round-4 review on PR #1883. The new thin client wrappers `knowledgeAssetClearShareJob` and `publisherClearJob` were exercised only indirectly; a wrong URL/method/encoding/body would break callers while the route + queue tests stayed green. Adds a focused block in api-client.test.ts (beside the existing share-job helper assertions) asserting: - knowledgeAssetClearShareJob('share/job 1') → POST /api/knowledge-assets/swm/share-jobs/share%2Fjob%201/clear with {} (method, percent-encoding, empty body). - publisherClearJob('lift job 7') → POST /api/publisher/clear-job with { jobId: 'lift job 7' } (jobId travels in the body, not the path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(publisher): round-5 review touch-ups on #1883 (no behaviour change) Small follow-ups to the round-4 review on PR #1883: - Make TERMINAL_PROMOTE_JOB_STATES the single source of truth: isTerminalPromoteJobState now returns TERMINAL_PROMOTE_JOB_STATES.includes(state) instead of duplicating ['succeeded','failed'] inline (removes the dead constant). - Wire the #1837 DKGAgent facade regression into the curated agent unit suite: add clear-promote-async-facade.test.ts to vitest.unit.config.ts, beside the sibling promote-async-default-agent.test.ts. (It already ran in CI, which globs the full test/ dir via ci-shard-agent.mjs; this is a test:unit consistency add.) - Lock the shared terminal-clear HTTP contract: add terminal-clear-response.test.ts asserting respondTerminalClearOutcome for every branch, incl. the previously untested rejected(unknown) -> 409 mapping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e-deadlines fix(agent): complete large durable recovery assets
This was referenced Jul 28, 2026
Jurij89
added a commit
that referenced
this pull request
Jul 29, 2026
The 10.0.10 sync (#1986) was squash-merged, which flattened it into a single commit parented on 8334c0a — a pre-release commit on main. The content came across correctly (main and testnet-canary trees were already byte-identical), but git no longer saw main's release commits as ancestors of testnet-canary, so `git rev-list testnet-canary..main` reported four phantom commits: f61f335 Merge pull request #1984 (release/10.0.10-bump) f516586 Merge pull request #1983 (release/10.0.10) e89f8f6 fix(epcis): fail fast on invalid payloads 0112e78 Fix release CI test expectations This is a real merge commit, so the histories join and those stop being reported as missing. It changes no files. Squash is right for feature PRs; for a branch-sync PR it defeats the purpose, because the ancestry is the payload. Future main -> testnet-canary syncs should use 'Create a merge commit'. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Promotes the
testnet-canarypayload tomainas the 10.0.10 release candidate. Frozen at canary tip8334c0adf.497 commits · 153 merges · 488 files · +116,949 / −2,350 (~50k product src, ~53k tests).
mainis fully contained intestnet-canary(merge-base ==maintip), so this is a clean promotion with no conflict resolution.No Solidity, ABI, or deployment-registry changes — no on-chain deployment required for this release.
Why this branch and not #1931
#1931 must not be used as the release PR. Its head
65a320667is 58 commits behind the canary tip. Merging it would silently drop five PRs from the release — #1932 (SWM 4 MiB limit), #1937, #1942, #1959, #1967. It is green, and it looks like the release PR, which is exactly what makes it dangerous.This PR uses a frozen
release/10.0.10branch rather thantestnet-canarydirectly so the payload cannot shift while CI runs, and so canary stays free for ongoing work.Why this PR exists: CI coverage
ci.yml:14(push) and:16(pull_request) both filter tobranches: [main, v10-rc, release/rc.12, rc17-vm-wip].testnet-canaryis in neither, so the vitest lane has never run on a push to canary. This PR is the first time full CI sees this payload.Coverage is better than the raw commit count suggests:
29970311736(CI, success, 47 jobs, full lane) at head65a3206, whose tree hash is byte-identical to canary@6c284d903.packages/agentfinalization-recovery (finalization-recovery.ts+1,682 new,finalization-handler.ts+658/−325,finalization-recovery-sqlite-store.ts+616 new,dkg-agent-lifecycle.ts+442/−164).CI should auto-plan full here (488 files > 100, 7 workspaces >= 4, plus a rename triggering
riskyChange).What's in it
packages/agent/src/rfc64/**(+21,249), 5 new libp2p protocol IDs (/dkg/catalog/1/*), new SQLite inventory subsystem./api/status.CREATE TABLE IF NOT EXISTS snapshot_page_indexes(db.ts:1196-1213). No ALTER/DROP/backfill.DKG_GOSSIP_MAX_MESSAGE_BYTESnow resolves toSTORAGE_ACK_MAX_STAGING_BYTES(4 MiB) —packages/core/src/constants.ts. The 4–10 MB band genuinely worked on 10.0.9. A 10.0.9-era importer usingROOT_CHUNK = 1000will start erroring;SKILL.mdbatch guidance is already updated 1000 -> 400. Inbound, a 10.0.9 peer emitting >4 MiB is now dropped atmaxInboundDataLength(packages/core/src/node.ts:1080-1081).dkg-agent-lifecycle.ts:1194callsawait this.prepareRfc64PersistenceV1()outside the followingtry, gated only onconfig.dataDir— whichpackages/cli/src/daemon/lifecycle.tspasses on every daemon start. A failure there abortsDKGAgent.start(). Ordering appears deliberate (it must precedenode.start()and protocol registration); flagging so it is a conscious decision. Every upgraded node will createDKG_HOME/rfc64-sync/on first boot and serve four new inbound protocols parsing untrusted wire data.@origintrail-official/dkg-agentgains anexportsmap wheremainhad none (packages/agent/package.json:7-56), including"./dist/rfc64/*": nulland ~30 explicit nulls. External deep importers will hitERR_PACKAGE_PATH_NOT_EXPORTED.SNAPSHOT_INTERVAL_MS30s -> 24h (packages/node-ui/src/metrics-collector.ts:53, fix(node-ui): collect metrics daily by default #1981) — confirmed intentional ("collect metrics daily by default"), noted because it is a 2880x granularity drop in dashboard relay stats.Test Plan
dkg-core,dkg-storage,dkg-chain,dkg-publisher,dkg-agent,dkg(cli) — the package spread of the 5 uncovered PRspnpm install --frozen-lockfile && pnpm build(exercises the newtest:package-rootgate inpackages/agent/package.json:60)DKG_HOMEon Windows and Linux — confirmrfc64-sync/is created and startup completesnode-ui.dbtouser_version = 31, boot 10.0.9, confirm clean startmaincommit (RELEASE_PROCESS.md §4) — after mergeFollow-up (separate PR, before tagging)
Version bump to 10.0.10 across all 21 manifests, the
## [10.0.10]CHANGELOG section, anddocs/UPGRADE_10.0.9_TO_10.0.10.md(required by §10 — the SWM ceiling and theexportsnarrowing are both builder-impacting).🤖 Generated with Claude Code