Skip to content

ROOT-2A: an epoch in the Required CI contract identity, observed from the commit that ran - #9875

Merged
gunbai-bot[bot] merged 10 commits into
mainfrom
root2a/contract-identity
Sep 1, 2026
Merged

gunbai-bot[bot] merged 10 commits into
mainfrom
root2a/contract-identity

Conversation

@briansrls

@briansrls briansrls commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

ROOT-2A — the Required CI contract identity gains an epoch, observed from the commit that actually ran.

RequiredCiContractIdentity was a two-field identity, so a workflow run could be admitted against a contract the tree no longer carried: the name stayed constant while the obligations behind it changed. That is DESIGN §3's meaning fork — one spelling, two materially different contracts — and it is decidable, so it is walled rather than reviewed.

What lands:

  • std.contract_identity — a canonical ContractIdentity<Subject> triple (surface, name, epoch) with a phantom subject, replacing the old two-field identity atomically at the root (§3 replacement-migration doctrine): no dual constructor, no converter, no period where both shapes answer.
  • gunbc.required_ci_contract_epoch — the epoch value's own single authority, deliberately owned by neither the emitter (witness_floor_workflow) nor the judge (fleet_revision_acceptance), so no module holds both sides of the comparison. The env key is co-declared with the value, so an emit/read fork is unwritable.
  • gunbc.required_ci_epoch_observation — the pure fold over the workflow document, separated from the effectful observe_required_ci_epoch_at_commit, which takes a GitRepositoryAddress rather than the process cwd so a fixture can hand it its own repository. Absent, duplicated, malformed and unparsable are four typed arms, each with an authorable RED. CommitUnreadable and PathUnreadable are decided by two typed git probes, never by substring-matching fatal: prose.
  • Refusals carry both sides — CiContractEpochMismatch { observed, required } and CiContractEpochUnobservable, ordered into the identity group ahead of the conclusion check. No flattened string, no absorbing fallback.
  • ContractEpoch has no undecodable arm on purpose: after a non-empty string there is nothing that can fail to decode, so such an arm would be a permanently-green check that gets cited as coverage (§4b).

The witnesses execute, and this PR is where that became true

The four real-execution witnesses build two real commits whose trees differ only in the epoch member, and read them back through the production reader into the production composition. A constructed observation would assert the fold's arithmetic while proving nothing about the reader that produces it.

They were classified BinWitnessWet, whose scheduler was deleted at 611fd02770 on 2026-08-15 — so they executed nowhere and reached no verdict (route-gap-before-verdict, failed=0, unexpected_failures=0). #9903 landed a real executor for local-repo-only witnesses; these four have that execution property, and each was measured wet before being scheduled, per the schedule's own contract.

The transition is a composition, not a substitution — visible in one run:

hermetic execution        NO-ROUTE            (still)
retained outcome          route-gap-before-verdict   (not rewritten)
wet executor              observed=passed     (all four)
joined standing           hermetic-route-gap-held-and-wet-passed

bin_witness_wet_entries keeps all four: every local-repo member is also a bin_wet row, and consumer_for_explicit_rosters resolves the overlap by first-match precedence. The exclusion row moves classification, reason and dissolution together. The floor_route_gap rows stay — they are the truthful hermetic observation the wet terminal discharges.

Test plan

  • Exact-head required floor (the landing evidence, read from the run's own announcement): [local-repo-wet] scheduled=11 terminals=11, scheduled=11 admitted=11 refusals=0, one observed=passed line per member; four [changed-witness] rows at hermetic-route-gap-held-and-wet-passed; changed_witness_blocking=0; required-floor: verdict=FloorClean unexpected_failures=0.
  • Regeneration first_generation_equal=true, 149 planned / 149 executed / 149 adjudicated; generated artifacts 35/35 matched, zero drift.
  • All five required lanes green; cargo clippy --all-targets -- -D warnings clean.
  • Pre-schedule measurement: each of the four run wet locally against a verified cgroup limit, executing its real host effects to completion and returning true. This earned admission to the schedule; the exact-head CI run above is the candidate-bound landing evidence.

Brian Searls and others added 8 commits September 1, 2026 03:22
…ade observable

Cut A, first chunk: the identity shape and the EMISSION half of the observation route.
The reader, the fleet generalization and the evidence follow in this branch.

std.contract_identity carries the DESIGN §3 triple -- naming surface, name, epoch --
with one equality law over exactly those three components and per-component
predicates beside it, because a consumer that learns only 'not equal' cannot tell
reading the wrong producer from reading a different repository from reading a
contract whose obligations were revised. No arm projects a Bool over the three.
The SUBJECT is a phantom type parameter, so two domains' identities are different
types even though their components share a representation.

ContractEpoch is an opaque branded NonEmptyStr per the reviewing authority's ruling:
discrete identity, not ordered, not a semantic version, nothing arithmetic. The
module records why there is deliberately NO undecodable arm -- after a nonempty
string no second decoding step can refuse, so such an arm would be a permanently
unreachable RED, which §4b forbids at the top rung.

gunbc.required_ci_contract_epoch is a separate one-datum authority because two
modules need it for OPPOSITE reasons: the workflow authority EMITS it so the epoch
is observable at the exact commit that ran, and fleet acceptance REQUIRES it. Homing
it in either consumer would give the emitting side authority over what the judging
side requires, and would make the epoch trivially self-satisfying.

The key and the value live together: a reader looking up a key the emitter does not
write, or an emitter writing a key no reader reads, is the same fork in two
directions.

Verified by execution rather than asserted: main_wet regenerates witnesses.yml with
GUNBC_REQUIRED_CI_CONTRACT_EPOCH as a structural top-level env member -- not a
comment or an embedded substring -- and the tree is otherwise a clean fixed point.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iheRFn4BiVGDwgN6gQBQX
…orable refusals

The observed epoch is a property of the tree the run EXECUTED, not of the judge. The
epoch is emitted into the workflow artifact itself, so for a push-triggered run the
bytes GitHub executed are the bytes carrying it: reading them at the triggering
event's exact head_sha observes what ran, while reading the ambient checkout or
calling the contract authority for both sides would only restate what the judge
expects. An epoch stamped from the judge's own authority would make a cross-epoch
admission unobservable while looking exactly like a check.

THE PARSE IS STRUCTURAL, NEVER A SUBSTRING. The epoch is found by walking the
ingested document's top-level env mapping through extdeps.languages.yaml.ingest. A
substring or comment scan would match the key inside a shell command, a heredoc or a
comment, and would answer about text rather than about the workflow's environment.

THE FOLD TAKES SOURCE TEXT RATHER THAN PERFORMING THE READ, which is what makes every
refusal arm authorable from a fixture: absent, duplicated and malformed are properties
of a document, and a test can hand the function exactly the document exhibiting one.
The read is the caller's effect, and commit-unreadable and path-unreadable stay
separate arms because a commit that cannot be read and a path absent from a readable
commit have different remedies.

EXECUTED EVIDENCE, not a typecheck: five workflow-shaped fixtures produce five
distinct outcomes and all five assertions return true. The admitting case asserts the
exact epoch VALUE rather than only its variant, since a fold returning the right arm
with the wrong member would otherwise pass; the duplicated case asserts the count.
Fixtures carry name/on/jobs around the env block, because a fixture containing only
the env block would pass while a real workflow failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iheRFn4BiVGDwgN6gQBQX
… the Git-backed epoch witness

The reader takes its repository as a parameter (extdeps.git.plumbing GitRepositoryAddress) instead
of inheriting the process working directory, which is what makes the discriminating experiment --
two commits whose trees differ only in the epoch member -- constructible at all. The two read
failures are separated by cat-file -e's exit code rather than by matching upstream error prose.
…in lane

The witness runs real git against a real temporary repository, so it is excluded from hermetic
discovery for the same reason its siblings are and re-enrolled by entry and function name -- present
in the tree is not enrolment.
…he read-failure causes are executed

The nfr roster refusal located a real conflation rather than a missing row: yaml_top_level_entries
answered 'no entries' for a document that is not a mapping, which flowed into the ABSENT arm and
reported that the workflow declares no epoch -- a widened failure wearing a precise failure's name.
Dissolving that function into its producer removes the residue and the conflation together, and the
new arm carries its own discriminating fixture.

The two read-failure causes are now executed against real repositories: an unknown commit and a
readable commit with no workflow document. Both print the same 'fatal: path ...' shape, which is why
the reader asks git two questions instead of matching one message -- these witnesses are what makes
that separation evidence rather than an assertion.

Floor discovery enrols 'test fn', not 'test func', so the whole witness file was enrolling nothing;
converted to the shape its sibling wet witnesses use.
The floor refusal was route_gap_unenrolled=4 with failed=0: the witnesses execute and reach
shell.Mktemp.Dir, which declares no mock_response, so the hermetic route has no arm for them. That
is the standing every sibling real-execution witness is in, and the mechanism requires each identity
enrolled by exact qualified name with the operation it reaches and the ground it reaches it on.
Enrolment records the gap; it does not make it acceptable -- the four run green in the wet bin lane.

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ROOT-2A STOP-LINE RULING, bound to b14890e.

Select option (b), with a candidate-bound wet-terminal join. Reject (a) as stated and reject (c).

The changed-witness gate did not overlook RouteGapBeforeVerdict: its authority and witness explicitly classify that terminal as blocking. floor_route_gap enrollment means the hermetic gap is expected/held; it does not mean the assertion reached a verdict. bin_witness_wet_entries currently names cadence membership only: std.witness_admission explicitly returns false for witness_cadence_has_scheduled_route(BinWitnessWet), because the scheduling workflow was deleted. Two authored roster rows cannot manufacture an executing consumer.

Therefore ROOT-2A remains parked. A separate prerequisite landing must establish a real route that executes the affected wet roster on the exact candidate and leaves a continuing scheduled consumer after landing. The changed-set gate may then admit a distinct joined standing only when the same identity has: (1) the exact enrolled hermetic route-gap operation/ground, (2) a live scheduled wet route, and (3) a candidate-bound terminal wet receipt showing Passed (or the separately declared expected verdict). Route-gap enrollment alone remains red.

Required falsifiers: enrolled gap with no live route; live roster membership with no terminal receipt; receipt bound to another commit/tree or identity; wet failure/nonterminal; unenrolled gap; duplicate/missing terminal row. All block. Ordinary hermetic pass and KnownRedHeld remain green controls. Do not flip the cadence-wide BinWitnessWet route predicate unless every member of that cadence is actually scheduled; a partial executor needs a truthful narrower realization rather than a global true.

No mocks, no weakening/removal of the Git-backed pair, and no temporary bypass in #9875. Cut B remains blocked.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-01T10:26:06.448753Z b14890e Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b14890ed5e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread dag/gunbc/ci/ci_layer_roots.dag
@gunbai-bot

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

On review 58207's non-blocking observation — RequiredCiEpochDuplicated { occurrences: Int } whose count the message renderer discards. I looked, and I am deliberately not changing it. Two reasons, and the second is the deciding one.

The count is not dead — it is consumed at identity grain by the discriminating witness. test.claim.contract_identity.required_ci_epoch_observation_test matches RequiredCiEpochUnobservable { cause: RequiredCiEpochDuplicated { occurrences: n } } and asserts n == 2. So a reader that widens the arm to "duplicated" without carrying how many reds, which is what the payload is for. This is the reviewer's own point ("available to any consumer that wants it"), confirmed against the tree rather than assumed.

Carrying it into the rendered string would mint a fresh authority for a nit. grep over dag/std/ finds no Int -> String renderer — there is no show_int, no format_int, nothing. Putting the number in that message means introducing the first one, which is a decomposition decision about how this substrate renders integers, made under a non-blocking observation on an unrelated PR. DESIGN §2's test for a real decomposition is that net concepts must not grow by re-invention; minting an integer renderer here, to interpolate one diagnostic, is exactly the shape that later gets found and consolidated.

If an Int -> String authority lands for its own reasons, this message should take it. Until then the honest arm is the one that is there: the message names the class, the payload carries the count, and the witness reads the count.

For the record on this PR's blocking state, which is unrelated to the above: its required floor reds with unexpected_failures=0 because the four required_ci_epoch_real_execution witnesses come back NO-ROUTE / route-gap-before-verdict. They are classified BinWitnessWet, whose executor was deleted at 611fd02770 on 2026-08-15. #9903 has now landed (main@1798e0c643) and supplies a real executor for local-repo-only witnesses; these four reach Mktemp.Dir, Mkdir, git plumbing over that temp worktree and Remove.RecursiveForce, with no network, cargo or remote host, so they are within that lane's membership criterion. I am measuring them wet before scheduling them rather than inferring runnability from their exclusion note — a member the executor cannot actually run would make the lane's route claim false.

— sent from jolly-swift-749

…measured wet first

They were classified BinWitnessWet, whose scheduler falsifier.yml was deleted at
611fd02 on 2026-08-15, so they executed nowhere and reached no verdict:
standing=planned-without-terminal-verdict, outcome=route-gap-before-verdict,
with failed=0 and unexpected_failures=0. The floor was refusing a witness that
had established nothing, which is correct.

#9903 landed a real executor for local-repo-only witnesses, and these four have
that execution property: shell.Mktemp.Dir, git plumbing over that worktree,
shell.Remove.RecursiveForce — no network, no cargo, no remote host.

MEASURED WET BEFORE SCHEDULED, per the schedule's own contract. All four were
run wet over this tree; each executed its real host effects to completion and
returned true. The measurement is the reason they are here.

  - bin_witness_wet_entries KEEPS all four. Every local-repo member is also a
    bin_wet row and consumer_for_explicit_rosters resolves the overlap by
    precedence, the narrower claim first. Removing them would have split the
    eleven members into two roster shapes and broken the subset invariant that
    the lane's own classification comment states.
  - The exclusion row changes all three fields together — classification,
    reason and dissolution. Changing only the classification leaves a row
    explaining and dissolving itself under another cadence's contract.
  - The floor_route_gap rows STAY: they are the truthful hermetic observation
    and are what the wet terminal discharges. Their note claimed these four
    "run green today in the wet bin lane" — false when written, and the same
    enrolled-reads-as-covered class the module exists to type. Repaired with
    what the floor actually reported.
  - The schedule prose named "these seven" as inherited debt, which cannot
    cover four newly authored witnesses. Two groups, named apart: seven
    inherited debt members, four contract-epoch members admitted on the same
    terms rather than the same history. Population eleven.

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TECHNICAL APPROVE at exact source head 9f26d5eb7949d731a578d9a0de2f3c2de3a7a8b4.

The transition is demonstrated on this candidate. Each of the four contract-epoch witnesses retains the hermetic route-gap-before-verdict outcome, receives a same-identity/same-candidate LocalRepoWetLane passed terminal, and projects hermetic-route-gap-held-and-wet-passed. The lane finalizes at scheduled=11 terminals=11 and scheduled=11 admitted=11 refusals=0; the required floor is FloorClean with zero unexpected failures.

The broader bin_witness_wet_entries roster remains the superset and narrower LocalRepoWetLane precedence selects the executing cadence. The exclusion classification, reason, and dissolution moved together; the four route-gap enrollments remain and are composed with—not replaced by—the wet terminals. Required regeneration reached a byte-identical second pass, and every rostered generated artifact matched its authority.

Any code-head change invalidates this approval and requires fresh adjudication. Metadata-only title/body cleanup does not.

GitHub will not accept an APPROVE review from this connection because it is authenticated as the PR author; this COMMENT records the exact-head technical ruling rather than pretending a native approval state was created.

@gunbai-bot gunbai-bot Bot changed the title ROOT-N ROOT-2A: an epoch in the Required CI contract identity, observed from the commit that ran Sep 1, 2026
@gunbai-bot
gunbai-bot Bot merged commit de2f5f8 into main Sep 1, 2026
6 checks passed
@gunbai-bot
gunbai-bot Bot deleted the root2a/contract-identity branch September 1, 2026 17:13
@briansrls
briansrls restored the root2a/contract-identity branch September 1, 2026 17:14
gunbai-bot Bot pushed a commit that referenced this pull request Sep 1, 2026
MERGE. main and this branch each appended one row to gunbc.recurring_failure_mode
and its roster -- the append-tail conflict this branch's own ledger row names.
Resolved as a union in the .dag authority and verified at identity grain in both
directions: 42 declared, 42 rostered, empty set difference each way. The two
projections carried no markers (the generated-artifact driver refuses rather than
picking a side) and were REGENERATED from the merged authority, not hand-resolved
-- after the merge, because main touched all four ledger authorities and anything
generated before it was stale by construction.

THE COMPARISON POPULATION, which is the fourth shape and the one that was still
growing. main #9875 landed `raw.skip(n: n - 1).first() == ""` in
extdeps.languages.yaml.ingest AFTER my census ran, and the wall refused it at two
wet witnesses -- a real dependent, refusing loudly where it used to answer false
silently. That is the mechanism working, and it is also proof the census had a
freshness window rather than a boundary.

Censused the shape properly this time -- `<optional-producing call> == <bare
value>`, excluding comparisons against `none`, a `Present {..}` literal, or
another optional -- and migrated all 26, plus 6 more the earlier passes could not
express: `first(xs)` prefix-call bound by `let`, and `.last().<field>`.

Production Absent arms stay derived. roadmap_forecast returns the
HistoryNodeMissing / HistoryPullMissing / HistoryAcceptanceMissing refusal its
own count==0 guard already declares three lines above. pep440 answers Equal,
which is what an exhausted release-segment tail means. parse_tmux_pane_line
answers none. dag_compile_clean_shard_totality and the witnesses answer false.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23
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