Fold prep: substrate co-bump, CI, and the constitution corpus re-home - #136
Conversation
…CIRISConstitution (#131) The CIRIS Constitution moved to its own repo and has evolved past the copies here: CIRISConstitution is at 1.0-RC3 while FSD/CIRIS_Constitution carried 0.7. manifests/WIRE_VOCABULARY.md had likewise diverged — its sha256 is c6bd6aa4… here against 80dfa166… upstream, so #131's "sha256 unchanged" note is itself out of date and the stale copy was the one in this repo. Removed: - FSD/CIRIS_Constitution/ (54 tracked files + the .aux/.log/.out build litter) - manifests/WIRE_VOCABULARY.md FSD/CEG/ is replaced by a redirect stub rather than deleted outright. Roughly 28 files across seven sibling repos (Server, Persist, Edge, Verify, Agent, Conformance, NodeCore) link into FSD/CEG/*.md paths; the stub gives those links a signpost and carries the section→part mapping, since the 20 CEG sections were reorganised into the constitution's 8 Parts and the §N numbering did not survive the move. Cross-repo repointing is follow-up work, not a prerequisite. Also corrects CEG_VERSION from "0.10" to "1.0-RC3". The registry has been emitting 0.10 on every response's CEG-Version header. Note the 1.0-RC29 line this directory used to declare is DISCONTINUED lineage from before the re-home — it is not a later revision than RC3, and the stub says so explicitly so nobody "restores" the higher number. No test or consumer pinned the old value. Intra-repo links in MISSION.md, docs/ and FSD-003 are repointed at the stub. Refs #131
…han prod The registry has had no `cargo test` in CI. `docker.yml` builds an image and gates :latest behind a smoke test, which catches a broken binary but not a broken behaviour. The last main-branch run was 2026-06-13. That is a tolerable gap for a repo nobody is changing. It is not tolerable for one about to take the persist/edge/verify co-bump (#76): the substrate delta is small (five struct fields) but the verification story is currently "deploy to three live nodes and watch", which is the wrong place to discover a mistake. Two jobs, split so a database outage cannot mask a compile or logic regression: test — build + 148 tests, no database. Registry uses runtime `sqlx::query(...)` and never the compile-time `query!` macros, so there is no live schema to verify against at build time and no .sqlx cache to keep in sync. db — `tests/db_integration.rs` against a postgres:16 service, since `sqlx::test` creates and migrates a database per test. Deliberately not gated, both for the same reason — they are red on arrival and fixing them inside a substrate bump would bury the diff that matters: - `cargo fmt --check`: ~200 standing rustfmt diffs. Land the reformat on its own commit, then add the gate. - clippy `-D warnings`: 200+ standing warnings, 0 errors. Clippy runs so regressions stay visible in the log, with continue-on-error. Verified locally against the exact commands the workflow runs: 103 lib + 19 crypto_properties + 26 capability_properties, all green, `--locked` clean.
persist v5.5.5 → v32.3.0
edge v2.2.2 → v17.4.1
verify v5.1.3 → v13.3.1 (ciris-crypto + ciris-keyring)
Pinned to the tags CIRISServer 0.5.177 pins, NOT to the newest tags (persist
v36.1.0 / edge v17.8.0 / verify v13.4.0 exist). Matching exactly is the point:
registry-core is being absorbed into that workspace, and CIRISServer#340
measured what divergence costs — persist and edge both pin verify by git `tag`,
and a cargo git tag is part of the source ID rather than a semver range, so
moving the verify pin alone forks verify instead of upgrading it. This old
floor is precisely why CIRISServer held registry-core out of its dependency
graph; it can come back in now.
The gap reads as 31 major versions of persist. The real cost was six changes,
all in test code — the library itself compiled against the new substrate
without a single edit:
KeyRecord.roles → capability_roles, plus the new consent_role and
additional_scrubs — 2 sites (edge_transport/content_miss, federation/mod)
Attestation.additional_scrubs (new)
— 2 sites (edge_transport/agent_files, content_miss)
Ed25519Signer::random() is now fallible
— 2 sites (api/http, build_manifest)
That last one is a safety improvement rather than churn: `random()` returns
Result so it can fail secure when ciris-crypto's RNG health check has marked
the source failed, instead of silently minting a key from a degraded RNG.
Revocation.revoked_after was also added upstream but this crate never
constructs a Revocation literal, so it needed no change.
The surface held because only 12 of this crate's files touch the substrate at
all, the `From<federation::Error>` match ends in a catch-all arm (new upstream
variants cannot break it), and this crate declares its OWN 8-method
FederationDirectory trait and delegates into persist's rather than implementing
it (so persist growing that trait is invisible here).
Also removed: the `pkcs8 = "=0.11.0-rc.11"` resolver pin. It existed because
ciris-crypto pulled `ml-dsa = "=0.1.0-rc.8"` and cargo's caret resolution for
pre-releases would otherwise select API-incompatible pkcs8 0.11.0 stable.
v13.3.1 pulls stable `ml-dsa = "=0.1.1"` which wants plain `pkcs8 ^0.11`, so
the pin now conflicts with the dependency it was added to satisfy and blocked
resolution outright. Registry never used pkcs8 directly.
MSRV 1.84 → 1.86, the highest of the three floors (verify 1.86; persist 1.83,
edge 1.75).
All feature names survived the gap unchanged — persist `postgres`/`sqlite`,
edge `transport-reticulum-tcp-{server,client}`, crypto `pqc-ml-dsa`, keyring
`software`/`pqc-ml-dsa`.
Green: 103 lib + 19 crypto_properties + 26 capability_properties, `--locked`
clean. db_integration still needs a live postgres (now wired in CI).
Closes #76
FSD-002 is cited 34 times across CIRISVerify as the normative attestation-
surface spec, so its drift propagates. Two defects, and the second is the
serious one.
1. Ladder-named dimensions contradicted the ratified set.
§3.2 carried `attestation:l1:self_verify` … `attestation:l5:agent_integrity`.
CC part_3 §3.1.2 and CIRISVerify's shipped constants
(ciris-verify-core/src/federation_provenance.rs::dim) both use mechanism
names with no ladder number. CC gives the reasoning: L-numbers name a
verdict-shape, not a mechanism, and the L1-L5 ladder lives as consumer-side
composition per CC 4.4.3.6 Policy I. Verify dropped the ladder from the wire
in v3.7.0 and CC subsequently ratified it — FSD-002 was the sole outlier.
Note the L2 row changed TWO tokens: the ladder number went, and the mechanism
token is `hardware_rooted`, not `hardware`. The rename carries through §4.3,
§4.8, §5.3 and §5.5.
One of those is not a rename. §948's enforcement rule was keyed on the PREFIX
`attestation:l1:*` — a pattern matching a prefix nothing emits. Under the
ratified set there is no `l1` subtree, so the rule now binds a single leaf
dimension, `attestation:self_verify`, and not a wildcard.
2. §3.2.1.x pinned a line-oriented signing preimage — an injection surface.
The v1 canonical bytes were a domain prefix followed by newline-delimited
`key=value` fields. A delimiter-based preimage is ambiguous whenever a field
carries attacker-influenceable free text, and `source = direct:{url}` is
exactly such a field: a crafted URL carrying a newline plus a `key=` sequence
can present a different field set that hashes identically.
CC supersedes it with a v2 JCS form — `sha256(JCS({…}))` over pinned `.v2`
domain literals — which closes the class by construction. Producers MUST emit
v2; CIRISVerify adopts it as a deliberate wire break (CIRISVerify#231). Only
the Merkle LEAF preimage changed; parent-node hashing, locale ordering,
padding and inclusion-proof shape are untouched.
The retired v1 blocks are preserved and marked MUST-NOT at §3.2.1.1-R and
§3.2.1.2-R rather than deleted, and the retired ladder names are kept in
correction notes, so a reader arriving from one of the ~34 inbound citations
finds what replaced them instead of a silent gap.
No FSD-002 version bump: the version line of record is CC's, not this
document's. This repo no longer carries the spec — normative pointers now go to
CIRISAI/CIRISConstitution.
Closes #132
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
persist v32.3.0 → v37.1.0
edge v17.4.1 → v18.2.0
verify v13.3.1 → v13.6.1
Supersedes the previous commit's target. That one matched CIRISServer 0.5.177's
then-current pins; edge 18.2 is the declared **Server 0.6 (registry fold)**
release, so the fold's floor moves with it. Server co-bumps to the same set.
## Why this set is takeable
The rule from the previous commit stands — never move the verify pin alone,
because persist and edge both pin verify by git `tag` and a tag is part of the
source ID, so an independent bump forks verify rather than upgrading it
(CIRISServer#340). What makes this bump safe is that the set is **internally
coherent**:
edge v18.2.0 pins persist v37.1.0 and verify v13.6.1
persist v37.1.0 pins verify v13.6.1
One verify in the graph, not two. Confirmed in the lockfile: ciris-crypto and
ciris-keyring both resolve to exactly 13.6.1.
## Cost
Nothing. Zero source changes — not even in tests this time.
KeyRecord, Attestation and Revocation are **field-identical** between v32.3.0
and v37.1.0, so the construction sites the previous commit fixed needed no
further work. All 20 substrate symbols this crate imports survive at the new
tags. MSRV stays 1.86 (verify's floor; persist 1.83, edge 1.75).
Leviculum rode along 0.16.0 → 0.20.0 as edge's transitive Reticulum stack.
Re-verified the two pins that would bite in production rather than CI:
libsqlite3-sys still resolves to a single 0.28, so the `bundled` union holds and
the libsqlite3.so.0 crash-loop stays closed; and no persist federation type is
serialized into an HTTP response, so nothing here is wire-visible to consumers.
Green: 103 lib + 19 crypto_properties + 26 capability_properties.
Refs #76, #62
persist v37.1.0 → v40.0.0
edge v18.2.0 → v20.1.1
verify v13.6.1 → v14.1.0
## verify 14.2.0 exists and is deliberately NOT taken
Both edge v20.1.1 and persist v40.0.0 pin the verify family at v14.1.0. Taking
14.2.0 would not upgrade verify — it would FORK it, because a cargo git `tag` is
part of the source id, not a semver range (CIRISServer#340). One verify in the
graph is the whole constraint. 14.2.0 becomes available when persist and edge
repin, not before.
## One real change this time
`FederationDirectory::put_attestation` now returns `AttestationOutcome`
(Inserted | AlreadyHeld) where it returned `()`. We discard it, and the comment
at the call site says why that is safe rather than leaving a reader to guess:
persist documents BOTH variants as idempotent success — "I already hold this" is
the anti-entropy protocol working, deliberately not an error variant. A genuine
disagreement (same attestation_id, different bytes) never reaches the Ok path;
it is a typed `Error::Conflict` and still propagates.
What is lost is the Inserted-vs-AlreadyHeld progress signal. Registry's own
FederationDirectory trait returns `Result<()>`, nothing acts on the distinction
today, and widening a trait on a crate being dissolved into CIRISServer to carry
a signal no caller reads would be speculative. Widen it when something needs it.
KeyRecord / Attestation / Revocation remain field-identical across the gap, so
no construction site moved. MSRV stays 1.86. Leviculum rode 0.20 → 0.24.
Re-verified: single libsqlite3-sys 0.28 (the `bundled` union holds, so the
libsqlite3.so.0 crash-loop stays closed), and no persist federation type is
serialized into an HTTP response, so nothing here is wire-visible.
Green: 103 lib + 19 crypto_properties + 26 capability_properties.
Refs #76, #62
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iEaqmXHrbpy7P1rvXR6BS
…ds the directory (#133, #137, #138) Two changes that let the registry navigate the CEG-native transition honestly, on the nodes that are live today. ## /v1/steward-key now serves the baked GenesisBundle The old body published registry's own key as a trust root — unsigned on the wire while declaring `signature_mode: "HYBRID_REQUIRED"`, asserting `hardware_class: HSM_PROD` under `self_attested: true`, and in a shape none of verify's three client schemas could parse. There was no working contract to preserve, so it is replaced rather than repaired (#133). What is served is the GenesisBundle persist bakes: the humanity-accord charter, the A1/B1/C1 roster, the infra:* scopes the charter confers, and the serve-node grants. It is self-authenticating — its `authorizations` are hybrid signatures from accord holders over the charter, re-derived by the consumer from its OWN records. Everything outside `bundle` is unsigned convenience metadata, and there is deliberately no `response_signature`: signing the wrapper would prove only that the relay said so, which is exactly what the old endpoint proved. The schema is field-for-field CIRISServer's `/v1/trust-root/bundle`, which is now also served here under that path, so the fold changes nothing a consumer sees. ## /v1/builds provenance stops asserting what it never verified `compose_federation_provenance` synthesized `provenance:build_manifest:{target}` at `score: 1.0, confidence: 1.0` from the Postgres row itself, attributed to whoever inserted it — an unsigned self-assertion in the position of an attestation, and one that OVER-reports: a consumer was told a manifest was attested when a row had merely been inserted (#138). Its own comment conceded the intent: "#17 will read from federation_attestations once attestations land there." It now reads the directory. The manifest entry is emitted only when a pipeline-signed Contribution binding this exact (target, binary_version, manifest_hash) exists, signed by a `node` key carrying the accord-conferred infra:attest role, whose bound-hybrid signature re-verifies here against the pipeline's directory-pinned pubkeys — the same threshold-1 check ciris_verify_core::manifest_contribution performs. Absent that, the dimension is ABSENT and `note` says so. Walk order is forced by what the directory indexes: no dimension index exists, so it starts from WHO may attest and lists what each blessed pipeline signed. The bless predicate requires the role AND an accord scrub (primary or additional); a role a key declares about itself confers nothing. Nothing emits these Contributions into the directory yet — CIRISServer#25 is the consumer half and the producers are not wired (#138 has the order). So today this returns no manifest entry everywhere. That is the correct answer today, and the code lights up unchanged when the pipelines start signing. Manifest BYTES continue to be served from the registry's build table through the transition; only the provenance claim changed. ## -stable is folded on read, and nothing else Publishers strip the channel suffix before registering, so registry holds `2.9.48` while the agent reports `2.9.48-stable` and nothing on the consumer side re-strips it — a guaranteed 404 on data that exists (#137). `-stable` is the publisher's own documented equivalence and is folded on version lookup. `-rc*`/`-beta*` are NOT: an rc and a stable of the same number are different artifacts, and folding them would serve the stable manifest for an rc query — a false pass. A test pins the asymmetry. ## Plumbing - ciris-verify-core added as a direct dep (default-features = false, "pqc"). The note refusing it over a libsqlite3-sys conflict was stale: persist has pulled it transitively at every tag since #76, and the lock resolves one libsqlite3-sys 0.28. Declaring it adds nothing to the graph. - registry's FederationDirectory trait gains list_keys_by_identity_type (NoOp + persist delegation) — the entry point of the walk. - VerificationPolicy is retained: it belongs to /v1/accord-holders, not steward-key. Five tests pin the invariants that would regress into over-reporting: the outer envelope claims no authority; the baked bundle names its charter and roster; the version fold strips -stable only; a pipeline is blessed only by an accord scrub carrying the role; an empty directory yields no manifest provenance and a note that says ABSENT. Green: 108 lib + 19 crypto_properties + 26 capability_properties. Refs #133, #137, #138, #62, CIRISServer#25 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iEaqmXHrbpy7P1rvXR6BS
3b95184 to
8da9ae8
Compare
…substrate floor) MAJOR per RELEASE.md: adopting a new spec series bumps MAJOR. 2.x was CEG 0.2; this branch moves CEG_VERSION to the constitution's current cut and reconciles FSD-002 to it. #41 also pre-declared the cutover as v3.0.0. CC 1.0-rc4 shipped while this was in flight (2026-09-04). Nothing rc4 changed touches what FSD-002 was reconciled against — part_3 §3.1.2's attestation table and §3.1.2.1's canonical bytes are untouched — and rc4's 3.4.7.3 ruling (`node` exclusive of `agent`/`user`; infrastructure holding no agency is what can be trusted to refuse it) strengthens the model the manifest consumer is built on: a CI pipeline blessed with infra:attest stays pure `node`. The CEG-Version header now matches the constitution's VERSION file byte-for-byte (`1.0-rc4`), since that file is the artifact the header claims conformance to. Cut directly rather than through an rc: RELEASE.md prefers vX.Y.Z-rc.N first for breaking changes, but rc tags do not promote :latest (docker.yml promotes only on refs/heads/main), so an rc would not reach the three nodes. The wire changes are the point of this release. Deviates from RELEASE.md step 3 in one respect: `cargo update -p` on the two member crates rather than `cargo update --workspace`. The workspace form would also bump every transitive crates.io dependency in a release commit, which is not what a version bump is for; the lock changes here are exactly the two package versions. Rollback: no migrations. Config unchanged. The old /v1/steward-key shape and the synthesized provenance entry are gone — both by design, see CHANGELOG. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iEaqmXHrbpy7P1rvXR6BS
8da9ae8 to
48fb171
Compare
CC 1.0-rc4 landed while the 3.0.0 cut was in flight. One of its rulings
touches FSD-002 directly: CC 2.1 no longer names an
`accountability:mode_shift:{from}:{to}` dimension. Per rc4, a mode shift is a
stewardship act on the delegation plane — a superseding `delegates_to` carrying
the new scope set — and there is no `accountability:*` family at all:
accountability is structural (the audit chain), never a named axis, which is
why the earlier draft's dimension could never have admitted under CC 3.1.7 R2.
FSD-002 v1.4.1 announced that dimension in two places (the §2.1 changelog
bullet and the `oversight_mode` envelope-field row) as "attestable
Contributions". Both now state the withdrawal and quote the rc4 ruling
verbatim, extracted programmatically from CC part_2's own `oversight_mode`
row rather than paraphrased, so the two documents cannot drift in wording.
The `oversight_mode` FIELD itself is unchanged and remains in the envelope —
rc4 withdrew the attestable-dimension claim, not the human-control gradient.
Confirmed before removing it: no shipped code emits or parses
`accountability:mode_shift` — zero occurrences across CIRISVerify, CIRISAgent,
CIRISPersist, CIRISServer and CIRISNodeCore outside tests. Removing it from
the spec opens no doc/code split; it closes one the spec had with the
constitution.
Same class as #132 (FSD-002 is cited ~34x by CIRISVerify as normative);
found by the rc4 adoption pass rather than filed separately.
Refs #132
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iEaqmXHrbpy7P1rvXR6BS
The FSD-002 correction in 3e3c3b6 ships in this release; the notes should say so rather than leave "reconciled with CC" to imply it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iEaqmXHrbpy7P1rvXR6BS
Prepares CIRISRegistry for the fold into CIRISServer. Four commits, each independently reviewable.
What lands
fa6dc30c77efd6aacab5d72bc0a7The co-bump was far cheaper than the version numbers suggest
persist
v5.5.5 → v32.3.0, edgev2.2.2 → v17.4.1, verifyv5.1.3 → v13.3.1.That reads as 31 major versions of persist drift. The library compiled against it without a single edit. All six changes were in test code:
KeyRecord.roles→capability_roles, plus newconsent_role/additional_scrubs— 2 sitesAttestation.additional_scrubs— 2 sitesEd25519Signer::random()is now fallible — 2 sitesThe surface held because only 12 of this crate's files touch the substrate at all, the
From<federation::Error>match ends in a catch-all arm, and this crate declares its own 8-methodFederationDirectorytrait and delegates into persist's rather than implementing it — so persist growing that trait is invisible here.That last change is a safety improvement rather than churn:
random()returnsResultso it fails secure when ciris-crypto's RNG health check has marked the source degraded, instead of silently minting a key from it.Pinned to CIRISServer's tags, deliberately not to latest
persist v36.1.0 / edge v17.8.0 / verify v13.4.0 exist. We are not taking them. CIRISServer#340 measured why: persist and edge both pin verify by git
tag, and a cargo git tag is part of the source ID rather than a semver range — so moving the verify pin alone forks verify instead of upgrading it, yielding two incompatibleciris_keyring+ciris_verify_corecrates. The verify pin moves only as part of an upstream persist+edge co-bump.Two checks that would have bitten in production, not CI
federation/andedge_transport/— so therolesrename is invisible to the three live nodes.libsqlite3.so.0crash-loop. The graph still resolves a singlelibsqlite3-sys 0.28, so thebundledfeature union still propagates to every consumer.Also removed the
pkcs8 = "=0.11.0-rc.11"resolver pin: it existed to stop cargo preferring API-incompatible stable pkcs8 over the pre-releaseml-dsarc.8 wanted, and v13.3.1 ships stableml-dsa 0.1.1, so the pin now conflicted with the dependency it was added to satisfy and blocked resolution outright. MSRV 1.84 → 1.86 (verify's floor; Dockerfile is on 1.93 and unaffected).CI
There was none —
docker.ymlbuilt an image and nothing rancargo test; last main run was 2026-06-13. Two jobs now: build + 148 tests with no database (this crate uses runtimesqlx::query(...), never the compile-timequery!macros), anddb_integrationagainst a postgres:16 service.Gated on build and tests only.
cargo fmt --checkcarries ~200 standing diffs and clippy 200+ warnings against 0 errors — gating either would make the workflow red on arrival, and reformatting inside a substrate bump would bury the six-line diff. Both are noted in the workflow header as follow-ups.Doc corpus
The constitution moved to its own repo and evolved past the copies here (1.0-RC3 vs the 0.7 carried here).
manifests/WIRE_VOCABULARY.mdhad diverged too — sha256c6bd6aa4…here against80dfa166…upstream, so #131's "sha256 unchanged" note was itself out of date and this repo held the stale copy.FSD/CEG/is replaced by a redirect stub rather than deleted: ~28 files across seven sibling repos link intoFSD/CEG/*.md, and the stub carries the 20-section → 8-Part map since the§Nnumbering did not survive the re-home. Cross-repo repointing is follow-up, not a prerequisite.CEG_VERSIONcorrected from0.10to1.0-RC3— note the1.0-RC29line this directory declared is discontinued lineage, not a later revision than RC3.FSD-002 (#132)
Two defects. The ladder-named dimensions (
attestation:l1:self_verify) contradicted the ratified mechanism-named set — CIRISVerify's shipped crate already carried an authority note flagging this exact drift and citing #132. One occurrence was not a rename: an enforcement rule keyed on the prefixattestation:l1:*, which under the ratified set has no subtree, so it now binds a single leaf dimension.The second is a signing-preimage injection surface. §3.2.1.x pinned a line-oriented preimage — newline-delimited
key=value— andsource = direct:{url}carries attacker-influenceable text, so a crafted URL with a newline plus akey=sequence can present a different field set that hashes identically. CC supersedes it with a v2 JCS form that closes the class by construction; CIRISVerify adopts it as a deliberate wire break (CIRISVerify#231). Retired blocks are preserved and marked MUST-NOT rather than deleted, for the ~34 inbound citations.Not in this PR
All 32 open issues are triaged and labelled (
fold:move-to-constitution16 /fold:closes-via-fold8 /fold:real8), each with a comment stating its disposition. None were closed — that is a maintainer call.Closes #76
Closes #131
Closes #132