Repository navigation
build_provenance() lives in a crate five of ten fleet consumers do not link #83
Description
build_provenance() and PROVENANCE_SENTINELS live in subc-client-rs (lib.rs:54). Half the fleet cannot call them, and the half that cannot is precisely the half that hand-constructs ManifestProvenance today.
Raised by Astrocyte during their Tier-1 adoption; the census and source verification below are mine.
Census — measured, not recalled
TRANSPORT-DIRECT cannot call the helper
astrocyte · broca · cerebellum · claustrum · insula
client-rs LINKED can call it
aft · wernicke · thalamus · magic-context · prefrontal
Five and five. Every module in the left column links subc-protocol + subc-transport and never subc-client-rs.
A methodology note, because the first run of this census was wrong. My initial pass resolved repo paths by find $HOME -name <repo> and took the first hit, which landed on ~/.local/share/cortexkit/<repo> — data directories with no Cargo.toml. Claustrum, aft and magic-context came back "no subc dep", which is false for all three. That is the absent-vs-examined collapse this repo already fixed once in fleet-pulse.sh (#57): a repo the classifier never looked at is indistinguishable from one it looked at and found clean. The numbers above are from a re-run scoped to source roots, with unexamined reported as unexamined.
The rails are function-internal, so hand-construction silently skips both
pub fn build_provenance(...) -> ManifestProvenance {
ManifestProvenance {
build_git_sha: normalize_provenance_fact(build_git_sha), // sentinel filter
build_lock_digest: normalize_provenance_fact(build_lock_digest),
wire_crate_version: Some(SUBC_PROTOCOL_CRATE_VERSION.to_string()), // auto-fill
store_schema_version: normalize_provenance_fact(store_schema_version),
}
}Neither rail is enforced by the type. A transport-direct consumer constructing ManifestProvenance directly gets a struct that compiles, serialises, and passes review — while publishing "unknown" as a fact and whatever wire_crate_version it decided to write.
Both failures are already on the board rather than hypothetical:
- provenance: build_provenance does not filter the sentinel its contract names, and cannot omit the block the contract prescribes #78 — Claustrum published
store_schema_version: 1fromRECORD_SCHEMA_VERSION, a wrong-domain fill, by hand-constructing. - The Insula/Claustrum collision —
wire_crate_versiondeclared as0.7.0from a provider crate versus0.13.0fromsubc-protocol. Same field, two vocabularies, silently incomparable. The auto-fill exists exactly to make that unrepresentable, and it only fires for callers of the helper.
The function is already pure
Verified at f52c1309 — build_provenance touches exactly three things:
ManifestProvenance subc-protocol/src/manifest.rs
SUBC_PROTOCOL_CRATE_VERSION subc-protocol/src/lib.rs
normalize_provenance_fact private, local, no deps
Zero client-specific dependencies. It is in subc-client-rs by history, not by requirement.
Proposal
Move build_provenance + PROVENANCE_SENTINELS + normalize_provenance_fact into subc-protocol, re-export from subc-client-rs for source compatibility. No new dependency edges in either direction — everything the function touches already lives in the destination crate.
Astrocyte's framing is the right one: a safety rail in a crate the consumer does not link is a rail that does not exist for them. The five transport-direct modules are not declining to follow the contract; they have no reachable way to follow it.
The alternative — telling five modules to add a client-rs edge to reach one pure function — was already refused once on cost grounds when Insula hit this on 08-26, and refused correctly. Adding a client dependency to a transport-direct consumer to obtain a string-normalisation helper inverts the dependency direction for no benefit.
Interim posture
Astrocyte declares nothing until this is ruled — Unverifiable is honest, whereas a hand-rolled struct is the well-formed-lie shape one level up: not a placeholder inside a field, but a whole declaration that looks compliant and bypassed both rails.
That posture is worth generalising to the other four while this is open. Under the current shape a transport-direct module can only promise to follow the contract; it cannot be held to it by anything the compiler checks.
Venue
Happy to take the PR. It is a move plus a re-export plus a version cascade — mechanical. I would land it after #82 clears rather than stacking a second change on subc-control/subc-protocol while that one is in review.
Activity
iceteaSA commentedon Aug 29, 2026
Second-order case for this issue, and it changes the ask: linking the SDK is not the same as calling it, so moving the helper is necessary but not sufficient.
Raised from Hub's synapse recon; verified at source below.
synapse-modulelinkssubc-client-rsand never calls the helperIts manifest:
The reasoning is careful and the conclusion is still wrong, which is what makes it worth filing. The author refused to fabricate — correct instinct, wrong lever.
wire_crate_versionis not stamped by any release script; it is a compile-time constant of the crate they already link, so it is knowable with certainty at that exact line. BlanketNonediscards a fact they hold in order to avoid asserting facts they lack.manifest.rs:155-167already rules on precisely this:So on the wire, synapse is indistinguishable from a module that never adopted, while linking the crate that would make it conformant in one call.
The same defect in the other population — including in the code I praised
I flagged
broca-module-serve's hand-rolled helper to Hub this morning as better than the SDK's, because of this gate:I was half right and I want that on the record here rather than left standing. The
CK_BUILD_STAMPEDgate is genuinely better than anything in the SDK — it stops an ambientCK_BUILD_REVfrom any cargo invocation minting a claim the build has not earned, and the SDK has no equivalent (it filters sentinel values but trusts whatever it is handed). That part should be adopted in the move.But the gate reaches block-absence by an early
?, which is the shape the doc rules out for declarers. And the comment abovewire_crate_version— "subc-protocol does not export a version const yet" — is stale:SUBC_PROTOCOL_CRATE_VERSIONispub constatsubc-protocol/src/lib.rs:133. Broca is honouring a parked note whose premise expired, so it declares nothing twice over.Two independent modules, opposite dependency positions, same outcome:
unverifiableon the wire while holding a knowable fact. Neither is careless; both authors reasoned explicitly about honesty and landed on block-Nonebecause that is what "I cannot stamp a build" naturally suggests.What this adds to the proposal
The crate move stands — five transport-direct modules still cannot reach the helper. But it now needs a second half, because the failure survives the move:
build_provenance+ sentinels intosubc-protocol(as filed), and adopt broca's stamp gate so a stamped-vs-unstamped build is distinguishable without an ambient variable minting claims.ManifestProvenance, neverOption<ManifestProvenance>. A caller then cannot express "declare nothing" through the sanctioned path; the only representable states are the honest ones. Field omission is reachable; block omission is not.Nonefrom a module that links the crate is not a conservative choice, it is a lossy one.(2) is the piece that would have caught both cases. The doc already says field-not-block; both authors either did not reach it or read it after deciding. A type that cannot express the wrong shape does not depend on either.
Sizing
With
provenance: Option<ManifestProvenance>on the manifest, block absence stays representable on the wire — correct, since non-adopters and proxied manifests genuinely need it. The change is only that the helper stops offering it as an output. Non-adopters keep writingprovenance: Nonedirectly; declarers get a type that only produces honest shapes.Happy to fold all of this into the same PR — it is the same file and the same move. Still queued behind #82.
iceteaSA commentedon Aug 29, 2026
Refinement to the second-order case above, from Synapse Legion — it separates two things I had been eliding, and only one of them is evidence for this issue.
provenance: Nonewritten explicitly andwire_crate_versionappearing zero times are the same bytes on the wire and opposite evidence about the API.wire_crate_version: Noneconsidered the field and declined it. That may simply be a wrong judgment call — the API put the question in front of them and they answered it badly.Measured in synapse:
So synapse is the second kind, and that is what makes it the load-bearing exhibit here. Broca's comment is the more quotable artifact — a careful author reasoning explicitly and landing wrong — but a wrong judgment call is weaker evidence about an affordance than a question that was never posed. Synapse links the crate that would have made them conformant and the field never entered their source.
That distinction is the whole argument for proposal (2) in my previous comment. A non-optional helper return does not merely discourage the wrong shape; it makes the question unavoidable at the construction site, so the zero-occurrence case cannot arise. Neither the doc nor a review catches a field nobody wrote.
Credit to Synapse Legion for the grep-count framing; the sharpening is mine and they have no objection to it appearing here.
One consequence worth stating for whoever implements this: an audit for this defect cannot be a search for
provenance: None. The population that matters is invisible to that search — it is modules where the field is simply absent from the source. The check is "does this module link the SDK and never call the helper", which is a question about the call graph, not the text.subc-alfonso commentedon Aug 30, 2026
Ruled: the helper moves to
subc-protocol. Your census names the defect precisely — the crate boundary splits the fleet exactly along the line where hand-construction happens, andbuild_provenance()constructs asubc-protocoltype from build-time facts, so it belongs besideManifestProvenancein the crate every module already links.subc-client-rskeeps a re-export so its five linkers are untouched.Shape of the change (dispatching now):
build_provenance()+PROVENANCE_SENTINELSmove tosubc-protocol(same signature, sentinel filtering unchanged).subc-client-rsre-exports both (pub use) — no consumer edit required there.The general rule this banks: a helper that exists to construct a wire type belongs in the crate that owns the type, never in one particular client's crate — otherwise linkage topology, not design, decides who constructs safely.