Skip to content

Make the Rust observation a call, type its identities, re-home seed growth - #7607

Merged
briansrls merged 2 commits into
mainfrom
session/clever-crab-111-observation-contract
Aug 1, 2026
Merged

briansrls merged 2 commits into
mainfrom
session/clever-crab-111-observation-contract

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #7562, which merged before these three modeling corrections landed. Each was raised against #7562 and each is verified by execution here on current main.

Blocker 1 — an import could perform Git I/O

The observation was data rust_source_host_observation = observe_stage0_rust_sources(...). A top-level data declaration is evaluated by importing it, so any module naming the symbol did Git work as a consequence of module loading.

That is not hypothetical. A dead import — in a file whose test bodies no longer referenced the observation at all — still reached git.Inspect.HeadCommit under the hermetic envelope, refused, and failed the entire 105-row discovery batch. Deleting that import fixed the incident but not the class: the next module to import the symbol would have done the same thing, and nothing stopped it.

It is now fn current_stage0_rust_source_observation(). The effect is reachable only by calling it, so no import can trigger it. Modules may import the RustSourceHostObservation coproduct, the path normalizers and the verdict fold without observing the host. The injection boundary is deliberately kept — rust_source_manifest_state_verdict(observation) still takes the observation as a parameter, so every consumer stays pure and testable against constructed observations.

This is the construction-over-validation move (DESIGN §5): documenting that imports evaluate live data does not stop the next import from doing so.

Blocker 2 — the identities were flattened to String, and the SHA-256 claim was false

repository_revision is now CommitSha; manifest_digest is now manifest_content_identity: ContentHash. The as String casts are removed — content_hash_atom already returned ContentHash and the cast was discarding it.

On the naming: content_hash_atom delegates to atom_identity_hash, which is FNV-1a-64, not SHA-256. #7562’s body called this "SHA-256-identified"; that claim is withdrawn rather than obtained by renaming FNV output. This is a structural content hash. When the hash-family grounding of #7480 lands, this should become the exact family (very likely Fnv1a64Structural) — and if a cryptographic requirement is ever genuinely established here, it needs a real digest, not a relabel.

A guarantee climbed as a side effect, so the dead check was dissolved rather than left beside it. ContentHash is a branded NonEmptyStr, so an empty manifest identity is now unwritable — the verdict’s manifest_content_identity == "" disjunct became unreachable and is deleted (§4b dissolution-on-climb). The RED control was not deleted with it: it keeps testing the axis that is still writable — an empty repository_revision, since CommitSha is a plain alias — so the control still discriminates instead of quietly becoming decorative.

Concern 3 — the seed-growth carrier was in the wrong authority

SeedGrowthJustification moves from gunbc.roadmap_authority to a new gunbc.seed_growth. A host-observation carrier was importing the entire roadmap authority for one type; the roadmap consumes seed-debt facts, so the dependency now runs that way (roadmap_authority imports seed_growth, not the reverse). RoadmapNodeId stays as the owning-lane reference because naming the lane is the point of the row, and it comes from roadmap_model — the identity vocabulary, not the graph.

The new module also states what the roster does not guarantee, so it is not misread as closed: hand_authored_declarations is an authored obligation roster, not a derived denominator. It beats a line-count literal — each row names a declaration that either resolves or does not — but nothing structurally prevents a later helper in the same Rust file from escaping it. Closing that needs a census comparing every newly authored Rust declaration against these rows and refusing on the difference. That join belongs to the honest-frontier lane and is deliberately not attempted here.

Verification

Green by execution on current main: 28/28 lifecycle scaffold witnesses and 38/38 roadmap authority witnesses.

Scope

No acceptance receipt is claimed. v1-rust-source-observation remains unaccepted — the three live witnesses are still excluded because the wet cadence is dark, which the carrier records rather than papering over with fabricated Git mocks. This PR is the modeling correction only; the exact-head live observation contract is separate work.

🤖 Generated with Claude Code

Three modeling corrections on top of the CI fix.

BLOCKER 1 -- an import could perform Git I/O. The observation was
`data rust_source_host_observation = observe_stage0_rust_sources(...)`, and a
top-level data decl is evaluated by IMPORTING it, so any module naming the
symbol did Git work as a consequence of loading. That is not hypothetical: a
dead import in a witness file whose test bodies no longer touched the
observation still failed the entire 105-row discovery batch. Deleting that
import fixed the incident, not the class.

It is now `fn current_stage0_rust_source_observation()`, so the effect is
reachable only by calling it and no import can trigger it. Modules may import
the coproduct, the normalizers and the verdict fold without observing the
host. The injection boundary is kept: rust_source_manifest_state_verdict still
takes the observation as a parameter.

BLOCKER 2 -- the identities were flattened to String. repository_revision is
now CommitSha and manifest_digest is now manifest_content_identity: ContentHash,
with the `as String` casts removed. content_hash_atom already returned
ContentHash; the cast was discarding it. No SHA-256 claim is made anywhere in
the carrier -- content_hash_atom delegates to atom_identity_hash, which is
FNV-1a-64, so this is a structural content hash and the PR body is corrected
to say so rather than renaming FNV output into a cryptographic claim.

A guarantee climbed as a side effect, so the dead check was dissolved rather
than left beside it: an empty manifest identity is now UNWRITABLE (ContentHash
is a branded NonEmptyStr), so the verdict's `manifest_content_identity == ""`
disjunct was unreachable and is deleted. The RED control keeps testing the axis
that is still writable -- an empty repository_revision, since CommitSha is a
plain alias -- rather than being deleted along with the arm.

CONCERN 3 -- SeedGrowthJustification moved from gunbc.roadmap_authority to a
new gunbc.seed_growth. A host-observation carrier was importing the entire
roadmap authority for one type; the roadmap consumes seed-debt facts, so the
dependency now runs that way. The new module also states what the roster does
NOT guarantee: it is an authored obligation roster, not a derived denominator,
and a later helper in the same Rust file can still escape it until a
source-census join lands in the honest-frontier lane.

Verified by execution: 28/28 lifecycle witnesses and 38/38 roadmap authority
witnesses green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Resolving the conditional in review 46558, since it is the asymmetry this change turns on and it should not stay ambiguous in the record.

std.types declares:

type CommitSha    = String
type ContentHash  = NonEmptyStr where brand("ContentHash")

So CommitSha is a plain alias, not a branded NonEmptyStr — the reviewer's second case. The remaining repository_revision == "" check is therefore live, not dead: an empty revision is still writable and still has to be refused at runtime.

That asymmetry is exactly why the two fields were treated differently rather than uniformly, and it is worth being explicit that this was the reasoning and not an oversight:

  • manifest_content_identity: ContentHash — empty became unwritable, so the check was unreachable and was deleted (§4b dissolution-on-climb: the proof replaces the validator rather than accumulating beside it).
  • repository_revision: CommitSha — empty stays writable, so the check stays and is load-bearing.

And the RED control moved with it rather than being deleted alongside the arm: scaffold_observed_refuses_missing_host_evidence_fields now exercises the empty revision — the axis that can still fail — instead of the empty identity, which no longer has a constructor. Deleting the control along with the dead arm would have left the surviving refusal unguarded, which is the failure §4b names when it says the evidence stays enrolled even as the machinery dissolves.

No change needed; recording the fact so the conditional is settled.

— sent from clever-crab-111

@briansrls
briansrls merged commit def7ac0 into main Aug 1, 2026
5 checks passed
@briansrls
briansrls deleted the session/clever-crab-111-observation-contract branch August 1, 2026 19:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant