Repository navigation
feat(blob_swarm): serve gate on persist's chunk→manifest link; withdraw-and-evict witnesses (#771) - #812
Conversation
…hunk→manifest link (#771) Persist v53.1 (#979) writes a chunk→manifest relation (V176) at seal and promote. PersistBlobChunkSource now asks it instead of keeping its own: - The #766 per-file membership cache is deleted, and so is edge's stream walk for every DAG persist has linked. `Engine::dag_contains_chunk` answers membership. A linked DAG that does not hold the chunk answers ChunkNotInNamedDag, whatever any row's pointer names. Before this a row referencing X whose pointer named Y's stream made the gate serve Y's chunk under a request naming X (persist I485's shape, on edge). - The named DAG's own fold (persist `binding_state`) is read first. A withdrawn DAG's chunk answers Withdrawn on every door, armed or bare, warm or cold, even when the chunk is shared with a live DAG. Before this an unarmed door served the shared chunk under the withdrawn name. - A plaintext DAG, which persist does not link, is still judged by its clear manifest. - A sealed DAG with no link (sealed or pulled before persist v53.1.0, never re-promoted) keeps the pre-#771 stream reading, uncached, as a legacy fallback until persist backfills its link. - The revocation register stays armed and is documented at the serve site. It is the only refusal for a withdrawal persist's fold cannot see (a reference by BlobPointer only). Witnesses in self_dag_field_path_717: the forged pointer (fails on main: "bytes" where "ChunkNotInNamedDag" is expected); A withdraws X and B's door refuses every chunk Withdrawn on the armed, warm and cold doors and over the wire, then B's eviction removes the manifest and every chunk X holds alone, and keeps the chunk shared with a live DAG plus all of that DAG (fails on main: ["Withdrawn", "bytes", "bytes"] for the shared chunk); and a link-less withdrawn DAG, where persist's gap is measured and the register is the only refusal (fails with the register check disarmed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ksniaWCvWoUwFQHRG3HQL
…ore holds no family bytes (CIRISConstitution#141) At the non-member forwarder F, both the inline file and the DAG file are checked. F has no blob head, F's open is refused, persist relates no chunk to the manifest, and no chunk sha that P1 sealed is held. A mutant that copies the family blob rows into F's store fails here, on F's head for the inline file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ksniaWCvWoUwFQHRG3HQL
…ag", and count it (#771) The lead's conditions and persist's contract (CIRISPersist#994): - The legacy stream walk is reached only when persist holds NO relation for the named manifest (`chunks_of_manifest` empty) AND the manifest's head is a `chunk_dag`. A linked DAG that lacks the chunk refuses it, so "not a member" never falls through to the walk. A whole blob also answers empty, and it never reaches the walk. - Every use of the walk is counted in `EdgeMetrics::blob_serve_legacy_dag_walks`. It is projected in the pyo3 snapshot as `blob_serve_legacy_dag_walks` and in the uniffi snapshot as `blob.serve_legacy_dag_walks_total`. Each manifest is named once at INFO, bounded by LEGACY_LOGGED_CAP. The sunset comment cites CIRISPersist#994, and notes that manifests this node cannot open stay link-less after that backfill. - `BlobChunkSource::attach_metrics` is a default no-op, the same shape as the transports' hook in CIRISEdge#809. `EdgeBuilder::build` hands it the edge's own bag. `PersistBlobChunkSource` keeps the bag in a OnceLock, and `with_metrics` sets it for a source built outside the builder. Every wrapper forwards the hook: edge_node's RowScopedChunkSource and the test fixtures. Witnesses in self_dag_field_path_717: - A linked DAG refuses a non-member chunk with zero walks, through a direct door and through the bag the builder attached. With the linked-non-member return removed, it fails: walks left 1, right 0. - A link-less sealed DAG serves every chunk through the walk, and each serve is counted. - A whole blob named as the DAG is refused with zero walks. With the chunk_dag head check removed, it fails: left 1, right 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ksniaWCvWoUwFQHRG3HQL
… shrinks with #994 (#771 review) The named DAG's binding_state is read before its link. If that read errored, the gate used to warn and go on to the link, so a DAG that might be withdrawn could be judged a Member on the link alone. It now answers NotMember (ChunkNotInNamedDag), as every other unreadable read in the gate does. Witness in self_dag_field_path_717: B's federation_attestations table is renamed away for one request, through B's SQLite writer, and then put back. A live linked DAG's chunk is refused ChunkNotInNamedDag, and it is served before and after. The warn-and-continue mutant fails with left "PolicyDenied", right "ChunkNotInNamedDag". The old path did not serve here only because persist's own chunk door failed on the same read. The sunset comments now say the legacy walk shrinks with CIRISPersist#994. They no longer say it goes when #994 lands: manifests this node cannot open stay link-less after the backfill. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ksniaWCvWoUwFQHRG3HQL
…metrics() of an Edge built normally (#771 on #810) A fetches the link-less W's chunk from B over the wire. B's door serves it through the legacy walk, and the walk is counted in b.edge.metrics(), the one bag EdgeBuilder::build settled on. B's Edge is built normally, and its wired source is a wrapper that forwards attach_metrics. The rebase onto #810 had auto-merged a second EdgeMetrics::new() for the chunk source ahead of #810's metrics block. The source now attaches the bag #810 settles on. Reintroducing the second bag fails this assertion with left 0, right 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ksniaWCvWoUwFQHRG3HQL
7e6db82 to
8889169
Compare
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Adopts persist #979's chunk→manifest link at edge's serve gate (CIRISEdge#771). This is not released: no version bump and no tag.
What replaced what
Engine::dag_contains_chunkanswers membership. A linked DAG that does not hold the chunk answersChunkNotInNamedDag, whatever any row's pointer names.binding_state. A withdrawn DAG's chunk answersWithdrawnon every door, armed or bare, warm or cold, including a chunk shared with a live DAG. If that read fails, the answer isChunkNotInNamedDag(fail-closed).put_blob_chunkswrites no link.chunk_dag. A linked DAG that lacks the chunk refuses it, so "not a member" never falls through to the walk. A whole blob also answers empty, and it never reaches the walk. Each use is counted inEdgeMetrics::blob_serve_legacy_dag_walks, which appears in both snapshots, and each manifest is named once at INFO. The walk shrinks with CIRISPersist#994's backfill but does not end with it, because manifests this node cannot open stay link-less.BlobChunkSource::attach_metricsis a default no-op, the same shape as the transports' hook in Pin load-bearing destinations in leviculum: persist is_load_bearing → retain_destination_data on peer install / announce admit, Unknown counts as retained — item 2 of #809 #809.EdgeBuilder::buildcalls it with the edge's own bag, and every wrapper forwards it (edge_node and the test fixtures). A downstream wrapper, such as CIRISServer's, must forward it as well. This touches the same builder lines as release(40.1.0): leviculum v0.29 — known-destinations backpressure, off-lock resource assembly, and transport counters that finally reach production #810, so expect a small conflict there when merging.BlobPointeronly.blob_swarm::persist_source::MEMBERSHIP_CACHE_FILESand the doc-hiddenholds_membership_ofare removed. Neither appears in any CIRISServer or CIRISAgent checkout on this host.ServerBlobChunkSourceinsrc/backend.rswrapsPersistBlobChunkSource. It compiles unchanged, but until it forwardsattach_metrics, its legacy-walk counter reads zero.Witnesses (each one fails on main or on a named mutant)
left: "bytes", right: "ChunkNotInNamedDag"["Withdrawn", "bytes", "bytes"]for the chunk shared with a live DAGleft: "bytes", right: "Withdrawn"left: 1, right: 0chunk_daghead check removed:left: 1, right: 0left: "PolicyDenied", right: "ChunkNotInNamedDag"The two-node witness runs on the real A-to-B pull. After the withdraw, it asserts on stored state. The manifest and every chunk X holds alone leave B. The shared chunk stays, and so does all of the live DAG. The relation rows stay too.
Gates
--skip swarm --skip replication,swarmandreplication: ok (1211, 240, 465).-D warnings: clean.🤖 Generated with Claude Code
https://claude.ai/code/session_011ksniaWCvWoUwFQHRG3HQL