Repository navigation
release(41.0.0): persist v55.0.0 + verify v20.1.0 — one holding claim, key grants as rows, typed refusals that park or end - #868
Merged
Merged
Conversation
…bundle field list, both bindings, round/permit histograms, PyO3 links (#848) The release line (v40.0.7 → v40.0.12) shipped the P0 telemetry; main had only #810's attach-at-build. This ports it onto main's bundle. - `EDGE_METRICS_BUNDLE_FIELDS` from `edge_metrics_bundle_fields!`, which also destructures `EdgeMetricsBundle` exhaustively, so a field missing from the list fails the build. `EdgeMetricsBundle::flatten()` projects every field (key `f` always present, detail `f.<label>`), and the UniFFI `metrics_snapshot()` is now that projection. The hand-written UniFFI projections it replaces are gone: `insert_park_ledger` (#858), the A/V ledger loop (#805), `durable.enqueued_total.*` (#845), `transport.packets_dropped_total` / `transport.known_destination_evictions` (#809) and `blob.serve_legacy_dag_walks_total` (#771). Those values are now under their bundle field names. The three legacy reachability and low-trust keys and the resident `durable.queue_depth` gauge stay. - PyO3 `metrics_snapshot()` projects the bundle through the free function `metrics_bundle_to_pydict`, which a parity test can drive. Main's PyO3 dict omitted 22 bundle fields: the #819 pool gauges, the #853 link gauges, links-closed-by-reason, the transport counters, the DAG phase clocks, and the new histograms and responder metrics. - `replication_round_duration_seconds` (by kind, recorded in `GatedRound::run` around `round_and_report`) and `sweep_permit_wait_seconds` (recorded inside `SweepGate::enter` for bounded acquires), with the OTel name constants. FixedHistogram was already on main (#859). - The bundle gains the #853 responder telemetry (`responder_rounds_total`, `responder_link_up_seconds`, `responder_link_up_total`), `recovered_links_total` and `unclaimed_ship_refused_total`. Main counted these but reported them only through `EdgeMetrics` getters. - PyO3 `link_count()` / `link_list()` (including #853's `direction`). - UniFFI `queue_depth()` was already the resident depth (#845); this adds its first test. Names follow main where both lines have a field. Exception: main's `durable_enqueued_total` (#845's deliberate rename) is the release line's `durable_queue_depth`. On main, `durable_queue_depth` means the resident depth. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C5FDihqVKTSXyNYs2CLnrK
Points both ciris-persist entries at persist's local v54.1-admission candidate (02c2b27d = v54.0.0 + #1042 (with the Edge follow-ups: SignerDoesNotActFor, SignatureInvalid terminal, typed licensure/nonce refusals, empty signer malformed) + #1044 + #1041 + #1047, Cargo version still 54.0.0) via git+file:// on its worktree. Never merge this commit as-is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C5FDihqVKTSXyNYs2CLnrK
…es (#858 fix 7) persist v54.1.0 (CIRISPersist#1042, #1044, #1047) types an unknown signer apart from a bad signature, repairs a stored Key row that fails verification, and moves accord nonces to the proposer. The mapping: - Error::AttesterKeyUnknown { attesting_key_id, .. } and the key plane's KeyRefusalReason::AttesterKeyUnknown: transient, parked on the NAMED signer (ApplyOutcome::Refused gains `awaits_signer`), released by that Key's admit. The directory-absence read stays beside it (FSD I5). An empty key id names nothing that can land, so it is not a park. - Error::FederationTierUnverified and the key plane's unverifiable_signature: terminal. Ends the self-park of an invalid self-scrubbed Key. - Bare SignatureInvalid stays transient at this pin: persist's check_signer_acts_for still uses it for the #776 ordering gap. Pinned by a witness so the later flip is visible. - ReplicatedKeyOutcome::InvalidReplaced: admitted; releases rows parked on that key. - licensure_delegator_not_authority: authority_acts_by_quorum and accord_proposal_nonce_reused: terminal (prefix-only verdicts on generic variants; persist exports no constant for either). - The key_grant door reads the signature axis too (its carrier rides the attestation plane). Witnesses: replication::typed_refusal_858_tests (5) and bridge::tests::a_quorum_authority_licence_and_a_reused_accord_nonce_are_terminal, each red with its mapping reverted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C5FDihqVKTSXyNYs2CLnrK
…gnatureInvalid terminal, typed licensure/nonce (#858 fix 7) persist's #1042 follow-ups (02c2b27d) type the remaining fused arms: - Error::SignerDoesNotActFor { attesting_key_id, .. }: retryable. The outcome carries AwaitedSigner::ActsFor(signer) and the row goes on the #858 held-signer index on any door that asks acts-for (occurrence, its revocation, route, touch claim). Released by the binding or occurrence that makes the signer act; from the second refusal on the terminal schedule, which bounds a signer that is never bound (2 offers ~20 s apart, then 30 min doubling to the 6 h cap). - Bare SignatureInvalid: terminal (persist: terminal on every door). - LicensureDelegatorNotAuthority { reason: authority_acts_by_quorum } and AccordProposalNonceReused: terminal, matched on the typed variants; the prose prefixes and their local constants are gone. - The empty-signer guard stays as defence; persist now refuses an empty id as InvalidArgument at the source. park_fresh_858 pins what O5 means now. - ApplyOutcome::Refused.awaits_signer is Option<AwaitedSigner> (Key | ActsFor); Session notes a missing Key only for Key. Witnesses (typed_refusal_858_tests): (5) SignatureInvalid terminal, (6) SignerDoesNotActFor indexed and released by the binding, (7) a never-bound signer on the route door settles on the terminal schedule. The park_fresh log-capture witnesses get a process-wide defer-to-scoped subscriber: the new witnesses emit the same park lines from other threads, and a callsite cached `never` there emptied the capture intermittently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C5FDihqVKTSXyNYs2CLnrK
… row repairs are terminal (#858 fix 7) Per persist release-54.1.0 (e165a2ac): named_edge_not_license_delegation, named_edge_not_onto_emitter, authority_acts_by_quorum and not_authority_at_receipt are terminal. named_edge_absent and not_on_live_chain_at_asserted_at turn on rows still replicating and keep the transient rule with its re-offer cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C5FDihqVKTSXyNYs2CLnrK
Points both ciris-persist entries at persist's v54.1.0 release candidate e1591219 (72f432ca + CHANGELOG and an evidence claim-ID rename, no code; release-54.1.0: #1042 + follow-ups, #1044, #1041, #1047, #1046 steward purge, #1052/#1054; Cargo version still 54.0.0, the tag commit 0ea1cf0f adds only the version, Cargo.lock and evidence) via git+file:// on /home/emoore/CIRISPersist-wt-541. Never merge this commit as-is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C5FDihqVKTSXyNYs2CLnrK
…l, purge_unauthorised excepted (#1046)
Error::PurgeRefused { token, .. } maps to a terminal refusal named by its
token: purge_tombstoned (a purged record, refused at every admission door
for good) and trace_run_kind_mock (a federation-tier trace signing
run_kind = mock) among them. purge_unauthorised stays on the transient
rule: a replicated bundle is re-verified against this node's state, so it
can mean the claimant root's community row or a cosigner's Key is not
held yet, until CIRISPersist#1055 types the two apart.
Witness: bridge::tests::unrepairable_licences_a_reused_accord_nonce_and_a_purge_are_terminal
gains the purge_tombstoned and trace_run_kind_mock rows (terminal) and the
purge_unauthorised row (transient). Reverting the arm turns the
tombstoned row red; narrowing it to TOMBSTONED turns the mock row red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C5FDihqVKTSXyNYs2CLnrK
Re-points both ciris-persist entries at persist's v54.1.0 RC 012aa94f (PR #1057 head; was e1591219 / 0ea1cf0f), on the release-54.1.0 line via git+file:///home/emoore/CIRISPersist-wt-541. Additive over e1591219: purge_put_execution replaces purge_put_tombstone on FederationDirectory, the purge_execution_incomplete token, PurgeState::Incomplete and the `voided` report fields, the trace-purge rollup recompute, accountability rows kept when tombstoned, and the manifest-form run_kind check. Edge implements no FederationDirectory and matches no PurgeState: no compile change. Cargo version is now 54.1.0 (edge's `version = "54"` still matches). Never merge this commit as-is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
persist raises Error::TraceDimensionInvalid from admission::check_trace_dimension_admission at the attestation put door the replicated apply goes through: the attester absent from subject_key_ids, a missing trace_id / agent_id_hash, not exactly one of the inline and manifest forms, or (v54.1.0, PR #1057) a run_kind outside the closed vocabulary in either form. Every arm judges the signed row, so re-offering the same bytes never admits. typed_terminal_verdict now maps it terminal, named by persist's own kind() (federation_trace_dimension_invalid); before, it fell to the unclassified transient rule and took about eight refusals and three capped windows to settle. Witness: a Terminal row in unrepairable_licences_a_reused_accord_nonce_and_a_purge_are_terminal with persist's manifest-form run_kind refusal. Removing the arm turns it RED (Transient != Terminal). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
Re-points both ciris-persist entries at persist's v54.1.0 RC ae1dadad (PR #1057 head after Codex round 2; was 012aa94f), via git+file:///home/emoore/CIRISPersist-wt-541. Seven commits over 012aa94f: now/now_iso dropped from the purge surfaces (edge calls none), a receiver's purge_human_subject_pending_notice_row for an unbound blob (terminal through edge's PurgeRefused arm, as before), a purged sealed manifest evicting its unshared DAG chunks, derivatives withheld with a withheld parent transitively, and debug_canonicalize hashing the real preimage. No edge compile or behaviour change. Never merge this commit as-is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
Re-points both ciris-persist entries at persist's v54.1.0 round-3 head
ea7f6e56 (6 commits on ae1dadad, Codex round 3 on PR #1057), via
git+file:///home/emoore/CIRISPersist-wt-541. Adopter surface:
BlobError::Purged { sha256_hex, notice_id } (blob_purged, mapped in the
next commit), get_detection_events excluding tombstoned rows and V189's
accord_proposal_nonce_claim (edge uses neither). Never merge this commit
as-is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
persist v54.1.0 (Codex round 3 on CIRISPersist#1057) adds
BlobError::Purged { sha256_hex, notice_id } (kind blob_purged): a steward
purge tombstoned the bytes, or every manifest a sealed-DAG chunk belongs
to. Persist's contract is Withdrawn's: never NotHeld, because every holder
rooted at the same trust root is bound by the same purge. It arrives from
the serve disposition, the viewer reads and the sealed-manifest open.
Before this, the serve door's wildcard answered PolicyDenied ("this node
holds the bytes and declines"), which demotes the peer and walks the
fetcher to the next holder, and the viewer reads mapped it to Substrate.
Now, everywhere Withdrawn is handled:
- blob_swarm::serve_result_to_chunk: Purged -> ChunkSourceRefusal::Withdrawn,
so the wire word is MissReason::Withdrawn and both fetchers abort
(SwarmScheduler -> GoneFederationWide -> PullOutcome::Refused; the DAG
SwarmFetch stops without trying another holder).
- blob_swarm::persist_source (the manifest serve and the stream listing):
Purged -> DagMembership::Withdrawn.
- group_content: a typed GroupContentError::Purged { sha256_hex, notice_id },
which chat::UnopenedReason::from_store_error (the one reader mapping, used
by chat and files) reads as Withdrawn.
Witnesses: blob_swarm::tests::a_purged_blob_from_the_persist_door_is_withdrawn_on_the_wire
(arm removed: PolicyDenied != Withdrawn), the Purged row in
every_persist_serve_refusal_is_mapped, and
group_content::persist_store::tests::a_purged_blob_is_a_typed_answer_and_reads_as_withdrawn
(arm removed: Substrate("... is withheld: steward purge ...")).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…minally
The whole-blob SwarmScheduler has always ended on GoneFederationWide as
PullOutcome::Refused. The DAG fetcher (SwarmFetch) stopped asking holders
on the same wire word (MissReason::Withdrawn / Revoked; persist v54.1.0's
BlobError::Purged reaches the wire as Withdrawn), but returned the stop as
a String that fetch_verified read as DagFetchStop::Transport, so the pull
ended FetchFailed and booked a retry: a retracted DAG was re-asked up the
retry ladder though every holder is bound by the same verdict.
- DagFetchError { Transport, Gone } (re-exported beside DagByteFetch), and
a DEFAULT DagByteFetch::fetch_typed that reads every fetch error as
Transport, so the required fetch and every existing impl are unchanged.
- SwarmFetch::fetch_typed returns Gone for a gone miss; fetch delegates.
- DagFetchStop::Gone, and dag_gone(): PullOutcome::Refused with no
book_retry, at all five DagFetchStop sites (pull_inline_with, the sealed
manifest and lane chunk, the plaintext manifest and chunk).
- edge.rs ContentResult::ContentMiss doc: edge does not retry; Withdrawn
and Revoked mean stop (the host decides), NotHeld / PolicyDenied /
DiskPressure may go elsewhere.
Witness: blob_swarm::dag_gone_terminal_tests (a gone manifest and a gone
chunk each end Refused with no retry booked; the control pull stores).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
clippy -D warnings (--lib --profile=test, all three feature sets) refused d899e70's `dag_gone(&self, ..)`: the helper reads nothing of the puller. Moved beside DagFetchStop as a free fn; the five call sites are unchanged otherwise. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
Re-points both ciris-persist entries at persist's v54.1.0 round-4 head 9daa25a2 (7 commits on ea7f6e56, Codex round 4 on PR #1057), via git+file:///home/emoore/CIRISPersist-wt-541. Adopter surface: a new `plane` value "purge_tombstone_subject" on the existing retryable AttesterKeyUnknown (edge keys on the variant: transient, parked on the named key), Purged byte doors unchanged since round 3 (edge keys on the variant, never notice_id), put_detection_event / `voided` / FederationDirectory::purge_tombstone_notices (edge uses none, implements no FederationDirectory). No edge code change. Gated together with the dag-gone-terminal commits (d899e70, 286137d) fast-forwarded onto this branch. Never merge this commit as-is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
Both ciris-persist entries move from the LOCAL TEST PIN (git+file on persist's worktree at 9daa25a2) to the released tag: git = "https://github.com/CIRISAI/CIRISPersist", tag = "v54.1.0", version = "54" (the main-line form). Cargo.lock resolves to c03aae67b4a017e9d6fcfbf1a434786274dcb556, the merge of CIRISPersist#1057; 9daa25a2 (the head gated on 78fb786) is its ancestor and the tree diff between them is empty, so the code is the code that was gated. pyproject's `ciris-persist>=54,<55` already admits 54.1.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
… tag v55.0.0 before merge Both ciris-persist entries point at persist's release-55.0.0 candidate 95e36ac6 (21f72044 + one test-only Python file) via git+file:// on /home/emoore/CIRISPersist-wt-550. Its Cargo version is still 54.1.1 until persist's bump commit, so `version = "54"` stays until the tag (then 55). Cargo.lock's persist line was hand-edited (no dependency changes between c03aae67 and 95e36ac6); `cargo update -p ciris-persist` re-confirms it at the first build. Never merge this commit as-is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…t rows - is_holds_bytes_type matches persist's whole retired stem (registry::RETIRED_HOLDING_CLAIM_STEM), and the possession fixture is rebuilt on holding_claim_row - store_gate asks cohort_scope::is_structurally_invisible - key_grant_emitter counts emitted ROWS (KeyGrantEmission.attestation_ids) - test carriers read key grants as rows: is_key_grant_row, SignedKeyGrantRow, KeyGrantRow::from_attestation(..).axis, and count an admission by its projection Still red on 95e36ac6 (a persist defect, reported): chunk_grants_779 799 and 797. persist's dirty_axes (key_grant.rs:1807) builds a self/family Content axis with the node key as its owner. The pending loop then withholds every recipient and clears the ledger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…fusal dispositions
Key grants:
- a row routes to the key-grant door when it is_key_grant_row, or names a
retired KeyGrant set type
- apply_key_grant pre-parses with KeyGrantRow::from_attestation (an Err is
terminal) and admits a SignedKeyGrantRow
- the subject-pull carve is unchanged: grants travel by may_receive only
typed_terminal_verdict:
- MalformedRow and TraceDimensionInvalid: terminal
- AttestationTypeUnregistered and PaymentProcessorIdentifier: terminal
- DimensionRejected: terminal only for registered_family_held_shut and
ledger_l1_binding_violation
- PurgeRefused: purge_unauthorised is terminal unless
unauthorised_reason::is_retryable; takedown_content_unbound is transient;
every other token is terminal
- KeyGrantRefused: terminal for malformed, set_retired,
wrap_algorithm_not_v2 and signer_not_{self,minter,author,stream_owner};
the audience, membership and attestation reasons stay transient. The
table points to CIRISPersist#1064 and gives way to persist's predicate
once it ships.
Witness v55_typed_refusals_follow_persists_dispositions walks
KeyGrantRefusalReason::ALL with an exhaustive match.
#776 and #858 re-shapes: persist v55 refuses a tampered occurrence (a
device_class, a diverging asserted_at, a row naming no signer) as
federation_malformed_row. Edge holds that terminal from the first refusal,
so none of those rows parks. occurrence_before_binding_776 asserts the
terminal verdict and no park; park_fresh_858 now expects three parks (O1
on P, O2 on A, O3 on U) where it expected four.
Fix 6's witness moves to a RefusalBackoff unit test
(a_dependency_less_transient_moves_to_the_terminal_schedule_at_refusal_8)
because no occurrence row reaches that path any more. O5 (no signer) was
the only dependency-less transient row in park_fresh_858, and it is now a
terminal malformed row. The schedule fix 6 bounds still exists for any
unclassified transient, so the bound is now witnessed at the backoff
itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
- The manifest is adopted LocalOnly. The gate's disposition is carried to promote_adopted_manifest_to_dag, and Stored reports DagPromotion.announced (CIRISPersist#951). - key_grant_admitted wakes a parked pull only for a projected, non-pending row. - R1: a v4 community DAG whose chunks carry an epoch is a stream-keyed room. Its chunks are adopted with no community epoch on the provenance, and it waits on chunk-key readiness as an invisible DAG does (is_stream_keyed_room, chunk_provenance_of, needs_chunk_key_readiness). The epoch clear is a workaround until persist ships BlobProvenance::for_stream_keyed_chunk. - DurabilitySweep::already_here: since v55 the write doors file the holding claim as they store. A pass that reaches a file it holds whole, with a live and fresh `here`, now says so; before, that file showed up nowhere. The durability_763 witnesses assert it, plus the `Here` verdict right after publish. A repair now reports the claim that exists (announced: true). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
PersistGroupContentStore::spawn_holding_claim_reack starts Engine::spawn_reack_sweeper and returns a HoldingClaimReack handle: is_running, async stop(), and a Drop that signals stop. edge_node keeps the handle in its Occurrence and stops it after the role returns. A custody:ack `here` lives 72 h, and the sweeper renews it about daily (CC 3.1.3.3 rule 5). Witness: the_hybrid_store_runs_the_reack_sweeper_and_stops_it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
Each self and family test asserts that: - a non-member never receives the custody:ack claim, and its list_holders and live_holders are empty; - a co-member sees the claim, and live_holders names the writer; - no retired holds_bytes row exists. Where a test had no non-member node, one is added (node_b in a_file_over_the_inline_bound). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
EdgeConfig::holds_bytes_ttl_seconds and DEFAULT_HOLDS_BYTES_TTL_SECONDS stay, documented as deprecated and INERT: a holding claim's freshness is its own row's expires_at. The release-notes draft for the MAJOR goes at the top of docs/RELEASE_NOTES.md. There is no version bump yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
… room DAGs are v4 The (pos) leg of f0de7ca was red. The serve helper built a bridge with no self provider, so the writer advertised none of its own SelfOwn rows, whatever their audience. The helper now wires the bridge as production does: the node's own key, plus the identity and engine for the family tier. A fresh bridge per call means no earlier round's per-peer watermark can hide a row from the listing. a_community_chunk_dag_is_pulled_holder_to_holder_under_the_rooms_dek: since persist #977 a room stream is stream-keyed. Its manifest names five data chunks and epoch 0's terminator, and C keeps the terminator beside the chunks it kept. This is R1's path end to end: B and C pull a stream-keyed room DAG through edge's pull, and D's pull of the old file still parks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…still files `here` already_here now reports real state that production uses. Each outcome of a pass is booked into a new EdgeMetrics family, durability_custody, and shows up in the snapshot, the UniFFI counters and the PyO3 dict: - filed_here - already_here - filed_none - report_refused - repair The four durability_763 rewrites follow v55's file-on-write, so a first pass finds the claim fresh. That leaves the pass's own filing job unwitnessed, so this adds the_pass_files_here_when_the_claim_is_retracted_stale_or_lapsed_763. A claim retracted by a `none`, one 25 h old (past the daily refresh) and one 73 h old (past the 72 h liveness) are each filed afresh. live_holders names A again, and each filing is booked. The pass reads its clock through durability_sweep_at(now), so a test can stand a day or three days on. Fail-before: with the filing arm removed, the retracted leg is red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…es as retired Docs only. Every mention that described `holds_bytes` as a live mechanism now describes the v55 holding claim instead: a `custody:ack:v1` `here` row at the content's own scope, filed by persist's write doors (and, for self/family, by edge's `report_here` after a pull), live for 72 h, renewed by the re-ack sweeper, and retracted by its signer. That covers src/, tests/, FSD/ and docs/. Historical mentions are kept and labelled as retired in v55: dated decision records, witnessed results from before v55, the legacy-row possession tests, and the CEG §10.1.2 TTL helper. docs/RELEASE_NOTES.md history is unchanged. FSD/CONTENT_TRANSFER.md §7 invariant 1 is restated for v55. A self/family claim is observable only inside its cohort. Its witness is assert_holding_claim_confined in three integration files, and its mutant is edge's audience_withholds opened. The section's duplicated heading is fixed. A few runtime strings say "holding claim" now where they said holds_bytes: two revocation log lines, MeaningRefusal::PossessionIsNotMeaning's Display, the edge_node key-mismatch error, and test assertion messages. No test matches on them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
Notes the new EdgeMetrics family and its five outcome tags, plus a merge note: when this branch meets #820 (local/820-p1c), the catalogue completeness gate needs a spec for it (a counter with a closed `outcome` label from an enum). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…m receipts; per-recipient key carriers
Since persist v55 (#989), `Audience::discoverable()` is true only for a
Commons placement: a room's holding claims stay in the room. Edge derived
`With::is_structurally_invisible` as `!discoverable()`, so a room started
reading as invisible. It now asks persist's own predicate,
`cohort_scope::is_structurally_invisible` (self and family only), which is
what chat_message_federates' `with_answers_both_questions_from_persist`
asserts. Before the fix, `only_self_and_family_are_structurally_invisible`
and `with_answers_both_questions_from_persist` were both red.
Tests:
- chat_message_federates
- A widened room row's `replicates.discoverable` is now false, because
its claims stay in the room.
- `carry_key_grants` hands each row only to its recipient's node, and
only rows that node does not already hold, as may_receive's refers-to
delivery and the want-diff do. Re-offering carol's own old-epoch row
after her removal is refused `recipient_not_in_audience`.
- delivery_receipts_738: a room stream is stream-keyed (#977), so its tree
includes epoch 0's terminator. K is therefore 6. The wrong-epoch and
past-the-tree legs take K from the published tree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…5 disposition witness - TraceDimensionInvalid and MalformedRow share one arm (`|`). Both are Some(err.kind()), so they had the same disposition anyway. - v55_typed_refusals_follow_persists_dispositions is split by family into three tests over one helper, assert_v55_dispositions: row shape, purge and takedown, and the KeyGrantRefusalReason walk. The split replaces an allow, and every row is unchanged. - The re-ack test's binding `none` is renamed `unsigned`. - late_device_768 passes is_key_grant_row by path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
Edge vendors the same Constitution as the persist it pins. persist v55
vendors CC 1.0-rc7, and edge was still on rc6 (651140a). The rc6
`holds_bytes:sha256:{prefix}` family, retired in rc7, made
cc_namespace_match_vectors_replay report 6 divergences against persist
v55's matcher.
Both files are copied byte-for-byte from the local CIRISConstitution tag:
`git show v1.0-rc7:manifests/namespace_registry.json` and
`git show v1.0-rc7:manifests/namespace_match_vectors.json`.
- tag v1.0-rc7 -> commit 536b8cbb931da1f4549425284af24cd227587c36
- namespace_registry.json sha256
6a14cb027ac65b8515f2004274278e73f4bcc4d3dfe233e9870c27c7d02d3c84
- namespace_match_vectors.json sha256
8fc35ba167f1ab0d2cb5b4ea742865dff3fd92d1bd751105ddbb7fc191a73331
Both equal persist v55's VENDORED_*_FILE_SHA256.
- _meta.registry_sha256
796f609981434b838edab1165462484f1be67889eed7f60c26b210ede5984464
- vectors: 968 -> 1103
cc_namespace consts moved with the bytes: VENDORED_CC_COMMIT,
VENDORED_REGISTRY_SHA256, VENDORED_N_VECTORS = 1103, and cc_version
1.0-rc7. The replay now runs 1103/1103 with 0 divergences.
vendored_grammar_hashes_to_its_pin and the relay-delivery leaf test pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…och), interim; replace with tag v55.0.0 before merge Both ciris-persist entries now point at persist branch v55-fix-provenance-epoch @ 8c2d88f7 (on c577accd, v55.0.0), via git+file:// on /home/emoore/CIRISPersist-wt-550. That commit carries BlobProvenance::for_stream_keyed_chunk and the StreamKeyedChunkCommunityEpoch refusal (CIRISPersist#977). The crate is 55.0.0, so the requirement moves to version = "55". This is an interim pin. The final head follows persist's key_grant owner fix (dirty_axes, key_grant.rs:1807). pyproject moves its floor with the MAJOR: ciris-persist>=55,<56. None of the four ABI versions moved (DIRECTORY 7, the other three 1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…edy a community-bound chunk at promote
persist 8c2d88f7 (CIRISPersist#977) fixes the provenance epoch on
stream-keyed room chunks. Edge's side:
- A stream-keyed room DAG is a community_dek DAG whose v4 manifest names
per-chunk epochs. Its chunks are adopted under
`row_provenance.for_stream_keyed_chunk()`, which drops the community
epoch and the minter. This replaces the clear-.epoch workaround.
- At the chunk adopt, persist's StreamKeyedChunkCommunityEpoch refuses the
whole batch. That is terminal, logged at ERROR as an edge bug, and
reported as DagPullRefusal::StreamKeyedChunkCommunityEpoch { stage:
"adopt" } (tag dag_stream_keyed_chunk_community_epoch).
- At the promote, the same refusal names a chunk bound before the stream's
key rows landed. The pull applies persist's remedy, ONCE per attempt:
every chunk the manifest names that a community epoch binds is evicted
(Engine::evict_blob), re-fetched, verified, re-adopted stream-keyed and
booked blob_dag_chunks.remediated. Then it promotes again. A second
refusal is terminal { stage: "promote" } and is an edge bug.
- GroupContentError gains the typed StreamKeyedChunkCommunityEpoch, mapped
from persist. Chat's read mapping treats it as a substrate fault.
Witnesses:
- pull.rs `a_stream_keyed_room_dag_waits_for_stream_keys_and_a_legacy_one_does_not`
(R1). The chunk provenance equals persist's constructor: no epoch, no
minter, the room kept. A non-room provenance is refused. A legacy room
DAG keeps the row's provenance.
- blob_federation_e2e
`a_room_chunk_bound_to_the_community_epoch_is_refused_at_adopt_and_remedied_at_promote_977`:
- the gap: a chunk under the row's provenance lands bound before the
stream's rows;
- the adopt refusal: after the rows, the same provenance is refused,
nothing written;
- the remedy: the pull is Stored, with exactly 1 chunk remedied, no
community binding left, and the file byte-identical.
Fail-befores, both observed:
- no remediation: the pull ends DagRefused { stage: "promote" };
- the row's provenance reused for chunks: DagRefused { stage: "adopt" },
and the room DAG holder-to-holder test goes red as well.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…wner fix); superseded by the final v55 head Both ciris-persist entries point at v55-fix-provenance-epoch @ ff72d7ce (8c2d88f7 + the key_grant dirty-loop fix: self/family content rows are emitted to the owner's devices; a mis-built axis stays dirty). For the targeted chunk_grants_779 check only; the full re-gate runs on the final head. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…t pending, and addressed to this node The key_grant_admitted guard was `projected && !pending`. It now also requires `recipient_key_id == local_key_id`: on a shared backend a sibling occurrence's wrap can be projected, and it must not re-pull this node's DAG. PullSink carries the puller's local_key_id, set in `start`. The chunk_grants_779 wake tests went red only with the wake removed entirely. Dropping either half of the guard kept them green, so the guard's precision was unwitnessed. A spurious wake costs one re-pull. Witness: a_parked_dag_wakes_only_on_a_projected_grant_to_this_node. A sha parked on its content key: - is NOT woken by a pending row (whether or not it is reported projected), by an unprojected row, or by a projected row for another node's key; - IS woken by the projected, non-pending row to this node. Fail-befores, all observed RED, each at its own leg: - `|| pending` dropped - `!projected` dropped - the recipient check dropped With the wake removed entirely, 797 and the two #779 ladder tests are red (checked on ff72d7ce). Why `recipient_key_id == local_key_id` cannot miss a wake: it is the ONE key the puller can open with. - Every persist unwrap looks the wrap up BY the viewer key: - self/family: `get_at_rest_grant(sha, viewer_key_id)`, in at_rest_cascade.rs `recover_blob_dek_for_viewer`; - stream epochs: `writers_own_epoch` / `stream_grant_sealer(.., viewer_key_id)` in chunk_dag_cascade.rs; - community epochs: `community_dek_member_grant_wrap(.., viewer_key_id)` in community_dek.rs. - The puller asks every one of those doors as `self.local_key_id`: open_sealed_manifest_as, sealed_dag_readiness and promote_adopted_manifest_to_dag in pull.rs. So a wrap to any other recipient changes nothing the parked pull can read, and a wake for it re-pulls only to park again. A persist wrap's recipient is the occurrence_key_id (resolve_recipients). Several occurrences can share one backend's content-KEM identity. That happens on a shared backend: one KEM row, `id = 0`. Each engine there still has one derived key. The puller is given that key: edge_node refuses to start unless engine.local_derived_key_id() == cfg.node_id, which is the key it passes to BlobPuller::spawn. #541's use_node_identity selects the TRANSPORT signer (pyo3.rs `node_transport_identity`), not the content engine's key, and the PyO3 host spawns no puller. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…6 (release-55.0.0, PR #1065); replace with tag v55.0.0 before merge Both ciris-persist entries point at 11e48c56 via git+file:// on /home/emoore/CIRISPersist-wt-550. It descends from c577accd, 8c2d88f7, ff72d7ce and a6c36c7d (all verified with merge-base --is-ancestor). Since a6c36c7d it adds: register_stream_epoch_dek keeps the chunk door's stream-name rule (a 64-hex name is refused at register), V192 re-dirties the content axes a v54 node cleared, and docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
… r2 fix head); replace with tag v55.0.0 before merge Both ciris-persist entries point at 9e1d6dda (descends from 11e48c56, verified). Lockfile-only (`cargo update -p ciris-persist`); not compiled yet: the delta re-gate runs after persist's certify. Docs: meaning.rs said `registry::RETIRED_CARRIER_STEMS` is "a prefix match". At 9e1d6dda each entry carries a `RetiredMatch`: the holds_bytes: entry is `Stem`, the retired key-grant SET entries are `Exact` (whole types), so `key_grant:*:v10` / `v1beta` / `v1x` are not retired and are served. Worded from persist's registry.rs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…e`, never a silent StoreFailed persist 9e1d6dda runs `would_hold` on the chunk doors too, so a pull of content held for others can be refused DiskPressureProxyRefused at the manifest adopt, the chunk adopt (and its #977 re-adopt), the plaintext DAG door and the whole-blob doors. Each such refusal ended as an unnamed StoreFailed — a swallowed refusal at a secondary door. Now every store door books persist's DiskPressureProxyRefused under the new `blob_pull_refusals` tag `disk_pressure` (`PULL_REFUSAL_DISK_PRESSURE`, `store_refusal_tag`) and logs it — ONCE per refused pull: the chunk walk keeps each refused item's tag beside its message and books only the refusal that becomes the pull's outcome (the first stop), never one per chunk or per adopt batch; single-call doors book their one refusal. The outcome is unchanged: StoreFailed, not retried (persist: it clears only when the host disk recovers). Witnesses (UNBUILT here — no cargo during persist's certify; they run in the 9e1d6dda delta gate): - lib pull::tests::a_disk_pressure_store_refusal_is_booked_by_name — the mapping, and the puller's hook books exactly one `disk_pressure`. - blob_federation_e2e::a_pull_under_disk_pressure_books_its_store_refusal_by_name — a real refusal: bob's puller over an engine at the Stop pressure tier, pulling alice's room file; the manifest adopt, then (manifest pre-held) the chunk adopt, each StoreFailed AND booked `disk_pressure`; the count is exactly 1 then exactly 2 (leg 2's walk refuses every in-flight chunk, one count for the pull). Fail-before (to run with the gate): the booking removed → both red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…unks to refuse So the once-per-pull count bites: the per-item-booking mutant (run with the delta gate) must book more than 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…booked `withheld`, nothing filed, no repair persist 65b1d081 (Codex r3 on #1065) refuses a `here` about bytes a takedown or steward purge withholds (`custody_ack_here_withheld`), and `emit_holding_claim` refuses them `Purged`. A counter-notice takedown keeps the bytes on disk through its window, and the file row stays in our self / family listing, so the durability pass met the blob every pass: a refused `here` booked `report_refused` + WARN each time, and when a chunk was missing it queued a Repair that pulled the taken-down content back. Now the pass asks persist's own predicate (`steward_purge::purge_withheld`, the one its doors ask) right after the audience check: a withheld blob is booked `withheld` (new DURABILITY_WITHHELD tag in `durability_custody`; `DurabilitySweep.withheld`) once per pass at DEBUG, nothing is filed and no repair is queued. Witness (UNBUILT — no cargo during persist's certify): durability_763 `a_durability_pass_leaves_withheld_bytes_alone` — a real counter-notice takedown (as persist's I778: the claimant is the signed subject of the content's establishing row, files a signed takedown_notice; the node records it), then a lost chunk: withheld == [sha], no repair, no report filed, `withheld` booked once. Fail-before (to run): the check removed → a repair is queued and a report filed. Release notes: the `withheld` tag, and the `disk_pressure` pull-refusal tag from adbd7a8 (it had no line). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
… r3 head); replace with tag v55.0.0 before merge Lockfile-only (descends from 9e1d6dda, verified); not compiled — the r3 delta gate runs after persist's certify. Edge names none of KeyGrantProjection (new exhaustive NotAuthor), ReackReport (new `withheld`) or emit_holding_claim (may return Purged) directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…many_lines); the witness doc says what its fail-before shows `withheld_in_pass` holds the purge_withheld check (durability_of_file was 116/100 lines under -D warnings, clippy ×3). The witness's fail-before (check removed) is red because nothing is booked `withheld`; without the check the pass ALSO files nothing and repairs nothing in this fixture, because persist's readiness door refuses a withheld manifest and the pass stops at "holding unreadable" — silently. The doc now says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…opt call `dag_adopt_batch_chunks: 1` makes the refused walk span several adopt calls (asserted: blob_dag_chunks.adopt_batches >= 2), so the exact count tells once-per-pull booking (2) from once-per-call (7) and once-per-chunk (7) booking. At the default batch size all six chunks fit one call and per-call booking read 2 too. Mutants on this fixture: per-chunk RED at 7, per-call RED at 7; the real code green at 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
… v55 candidate); replace with tag v55.0.0 before merge Lockfile-only (descends from 65b1d081, verified); not compiled — the gate runs after persist's certify. Edge constructs neither DurabilityDeficit nor BlobCustody by struct literal (grep src + tests), so the new `withheld` fields need no edit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…eld `here` books `withheld`; the guard reads persist's deficit
persist 5ee68307 refuses a `here` about bytes a takedown withholds as
`federation::Error::PurgeRefused { token: steward_purge::token::BLOB_PURGED,
body: {sha256_hex, notice_id, retryable: false} }`.
- `durability::file_custody` returns `CustodyFileError` (Retracted /
Unreadable / Persist(federation::Error)) instead of a String;
`withheld_notice()` matches PurgeRefused with persist's BLOB_PURGED const
(imported, no literal).
- The pass's report booking (`book_custody_report`) books that refusal
`withheld` at DEBUG and leaves the file (no repair) — a takedown landing
between the guard and the report — never `report_refused` + WARN. The
post-pull report_here logs it at DEBUG too.
- The guard reads `DurabilityDeficit.withheld`: edge's PassCustody already
computes each file's deficit through persist's `deficit_over`, which
fills `withheld` from `steward_purge::purge_withheld` — the same
predicate, the same read, so the separate purge_withheld walk is gone.
Witness: durability_763
`a_withheld_here_refused_by_persist_books_withheld_not_report_refused` —
the guard bypassed (file_custody called directly), a real counter-notice
takedown: persist refuses TYPED naming the notice, and the booking reads it
`withheld` (1), no `report_refused`. Fail-before (gate): the
`withheld_notice` match removed → `report_refused`.
UNBUILT: no cargo until persist's certify finishes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
… r4 head); replace with tag v55.0.0 before merge Lockfile-only (descends from 5ee68307, verified); not compiled — the r4 delta runs after persist's certify. r4: the re-ack sweeper's first pass runs after HOLDING_CLAIM_REACK_START_DELAY (90 s); room-stream retention keeps chunks a retained DAG manifest still links. Edge keeps Engine::spawn_reack_sweeper() (via PersistGroupContentStore:: spawn_holding_claim_reack) for production; it exposes no delay knob, and no edge test that counts claims spawns the sweeper (grep, src + tests + mesh + tools + python), so nothing is pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
Both ciris-persist entries pin tag v55.0.0 (commit ca6bdf60, tree b9f53dbd — identical to the a28aa640 candidate this branch was gated on). Cargo.lock resolves git+https://github.com/CIRISAI/CIRISPersist?tag=v55.0.0 #ca6bdf6004577b4738cf3fd82d196c040f4cf7c3. pyproject's floor is already ciris-persist>=55,<56. Supersedes this branch's interim LOCAL TEST PIN commits (95e36ac6 … a28aa640). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…n blob_federation_e2e `Option::is_none_or` is past the crate's MSRV (1.75; stable since 1.82): `map_or(true, ..)`. A redundant closure in the #977 community test: `flat_map(&recipients_of)`. No behaviour change; blob_federation_e2e 43/0. (The gate's clippy ran --lib --profile=test, which does not lint tests/*.rs.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
Tree-identical: #865 squashed local/main-next, which this branch already carries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
…, key grants as rows, typed refusals that park or end, the P0 telemetry on main MAJOR from v40.1.0. Pins persist v55.0.0 (through v54.0.0 / v54.1.0) and CIRISVerify v20.1.0; leviculum v0.30.0+ciris.1 unchanged. - Cargo.toml / Cargo.lock 40.1.0 -> 41.0.0; evidence/CIRISEdge.cc_impl.tsv regenerated (ciris-edge@v41.0.0; evidence_tsv_matches_emitted green). - docs/RELEASE_NOTES.md: the v55 draft becomes v41.0.0, with sections for persist v54/v54.1 (#862, #858 fix 7, purged-as-withdrawn, dag-gone terminal), the v55 adoption items edge checked, verify 20.1, the #848 P0 telemetry forward-port, everything else on main since v40.1.0, and the host-facing API changes (file_custody's CustodyFileError, durability_custody `withheld`, blob pull refusal `disk_pressure`, EdgeMetricsBundle.durable_queue_depth gone -> Edge::durable_queue_depth()). - bridge.rs (test only): v55_ledger_refusals_follow_persists_dispositions pins persist v55 #753's ledger refusals: a ledger-plane SignerDoesNotActFor (federation_signer_not_acting_for) parks on the held signer, transient; ledger_l1_binding_violation is terminal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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.
Release v41.0.0 (MAJOR): persist v55.0.0 + CIRISVerify v20.1.0
This branch is
local/persist-v55-adopt(55b73aa) withorigin/main(244baf6, #865) merged in. The merge is tree-identical, because #865 squashedlocal/main-next, which the adopt branch already carries. On top of that is one release commit:evidence/CIRISEdge.cc_impl.tsvis regenerated (ciris-edge@v41.0.0).docs/RELEASE_NOTES.md: the v55 draft becomes v41.0.0. It covers:file_custodyreturnsCustodyFileError;durability_custodygainswithheld; blob pull refusals gaindisk_pressure;EdgeMetricsBundle.durable_queue_depthis removed (readEdge::durable_queue_depth().awaitinstead; the cumulative count isdurable_enqueued_total).v55_ledger_refusals_follow_persists_dispositions(test only). It pins the two ledger refusals from persist v55 release(37.1.0): a relay serves a third party's announced owner-binding at first contact (CC 5.4.6 public roster) (#752) #753:SignerDoesNotActFor(federation_signer_not_acting_for) is transient and parks on the held signer;ledger_l1_binding_violationis terminal.Ladder: edge v41.0.0 · persist v55.0.0 · verify v20.1.0 (leviculum v0.30.0+ciris.1).
persist v55 adoption check (#753 and slices B–D)
registered_family_held_shutonfederation_dimension_rejectedtyped_terminal_verdict(persist's const)ledger:*:federation_signer_not_acting_for(retryable)refusesends it toSignatureVerdict::of, which parks it on the held acts-for signer; new witnessledger:*:ledger_l1_binding_violationsettlement:*:federation_payment_processor_identifiermatch_family/authority_fordirectly and keeps no table of its ownRC6_RETIRED_CARRIERS_ADMITTED_UNTIL_989. A retired carrier isattestation_type_unregistered(terminal), andis_holds_bytes_typematches the whole retired stemMalformedRow, thepurge_unauthorisedretryable reasons,takedown_content_unboundis_retryable, and transient, respectivelyciris.accord_invoke.v2accord:invokerow in edge is a test seedNo token edge maps by name was missing.
Local gate (head 0451d16; the tree was the same except the notes-only
durable_queue_depthline)Every step ran one at a time at
nice -n 19 ionice -c3withCARGO_BUILD_JOBS=2.cargo check --all-targets(transport-http transport-reticulum pyo3 test-anchor): passcargo fmt --all -- --check: passcargo clippy --lib --profile=test -D warnings, three feature sets (…lxmf mesh-harness test-anchor / …ffi-uniffi / …test-anchor): passcargo clippy --all-targets -D warnings, set 1: passtransport::: 501 passedreplication::: 500 passedevidence_tsv_matches_emittedand the new ledger witnessDo not merge or tag from this PR without the lead.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Bzxmyd64qo6upHJaSLKdX8