US-45.6 (v2): a thread merges only on trusted CI for the checkpoint's exact commit - #910
Merged
Merged
Conversation
… exact commit Rewrite of #909, whose third review round still found material defects in one design element: which CI results the gate trusts and against which policy. - intake_sources.required_checks (migration 20260927100000), read LIVE by the gate, so a renamed job can be corrected for stories already in flight. A thread source must name at least one; local-gate and non-string names are refused. - Only a check run created by GitHub Actions satisfies a required name. Commit statuses are read and recorded, never trusted: the implementer's runner can post them. - A checkpoint changing .github/workflows/ or .github/actions/ (renames included) is refused ci_definition_changed for a human: Actions runs the workflow files of the commit under test. - Per name, the latest run of each check suite counts and every suite must pass. - A check still running or not reported is a CI wait: unevaluated, Retry-After 300, never counted toward the unevaluated bound (and it clears the count), refused required_check_timed_out 24 hours after the story ENTERED ci (Stages.entered_at/3). A wait holds back only an allow; any other refusal is decided at once. - Evidence (required runs and local-gate only) is copied onto the checkpoint's gate_evidence["ci"] under a row lock; a record read no later than the stored one is :superseded (the allow path waits and re-evaluates), an identical judgement is not rewritten. An allow whose copy met contention is unevaluated, never an escalation. - Required checks with nothing read fail closed (ci_evidence_not_read). - MCP 2.108.0: intake_source_enroll/update take required_checks; merge_precondition names the reasons. Mutations A01-A47 (A31 re-run as A31b after strengthening its test), all exit 0.
1+2. Only a JOB of a GitHub Actions workflow run that a PUSH of the thread branch at the
checkpoint's exact commit triggered satisfies a required check (Actions runs + jobs
APIs, filtered by the API and again locally), and only by concluding success: a job
skipped because a needs: failed, or neutral, fails. A check run created by any other
workflow, or a status, is never trusted. check_evidence/3 now takes the branch.
3. Jobs are de-duplicated by id and a short list is refused as truncated; more than 10
workflow runs per push is refused rather than read.
4. Commit statuses are read best effort ({:unread, reason}), feeding only local_gate.
5. Stages.entered_at/3 runs under answering_busy with a bounded lock wait; contention is
the ci_entry_unreadable fact, a retry.
6. Scope stated in the CiEvidence moduledoc: scripts the jobs run are code under review,
judged by the thread review (US-45.3), not by this gate.
7. A diff that could not be listed refuses ci_definition_unknown (fail closed).
8. A story with no recorded ci entry measures its wait from the checkpoint's recording.
9. A required check name with surrounding whitespace is refused.
10. OpenAPI interpolates ci_wait_retry_after/0 and ci_wait_limit_seconds/0; MCP, verdict
and CHANGELOG no longer restate the numbers. Token scope is now actions: read.
Per-workflow judgement uses the highest job id (ids only grow); the separate newest-run
filter was redundant and removed (mutation B02 showed it could not fail).
Mutations A01-A47 (A14-A18, A20-A22, A25 superseded by the B set; A19 by B01; A30
retargeted) and B01, B03-B15, all exit 0.
1. Per workflow, only its newest run counts, and in it EVERY job carrying a required name must pass: matrix legs sharing a name are separate jobs (round 1 wrongly removed the newest-run step and judged the highest job id alone). 2. The ci entry time is read on exactly the path that reads CI, so a lock wait on it can no longer mask a merged or moved head's decision. 3. An identical evidence read that is later advances the stored read_at, so a slower read from in between is :superseded; an identical earlier read is :ok. 4. OpenAPI ci_evidence, the delivery-loop doc and the Source field comment describe the jobs design, not v1's check runs and statuses. 5. A composite action's action.yml anywhere in the diff is a CI definition change. 6. The jobs list's shape is judged before de-duplicating, so a malformed entry is unreadable_jobs, never a crash. 7. Per-run jobs reads run concurrently (4 at a time); the moduledoc restates the ceiling. 8. The migration names the manual step for thread sources enrolled before it; CHANGELOG gives the real column type. 9. ci_result/1 uses Map.fetch!: CI is judged once, in judge/1. Mutations re-run: A01-A47 and B01-B15 as retargeted, plus C01, C02, C04-C10 (C10 and A40 re-run as C10b/A40b after a test for the identical-earlier case). All exit 0. Finding 2's gating has no falsifiable test: which path reads the entry time is not observable from a test.
…nce on decisions Fixed in place (no round 4; every fix carries mutation proof), following #902's round 3: none of the findings touched the trust model the rewrite settled. - Runs (findings 2, 3, 4, 7): the adapter reduces the push runs to each workflow's NEWEST run before bounding and before any jobs read, and returns those runs. CiEvidence takes the newest run per workflow from the runs, once per judgement: a newest run with no jobs yet holds a name pending; one that ended with no jobs (startup_failure) fails a name no job carries (run_<conclusion>). - Evidence (findings 1, 5): read_at is stamped when the read STARTS; evidence is copied onto the checkpoint only for a decision (allow, refuse), so CI-wait polls write nothing. - Latency (finding 6): the gate's three forge reads (two trees, CI evidence) run concurrently. - MCP (finding 9): required_checks refuses surrounding whitespace and duplicates locally, matching the server. - Counter (finding 8): kept clearing on a pure CI wait (round 2's decision); documented why the wait is still bounded — every answered poll is judged against the CI wait limit. Mutations: the cited set re-run (A06b, A40b, C10b retargeted) and D01-D05, D07-D09 (D03 re-proved as D03b after a complexity split), all 60 exit 0.
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.
US-45.6 (Epic 45, change threads), v2: CI evidence by exact SHA. Rewrites #909, whose third review round still found material defects in its trust model. The review gate is still running.
What changed from #909, and why
.github/workflows/or.github/actions/is refusedci_definition_changed, because Actions runs the workflow files of the commit under test.ci(GitHub's self-hosted queue ceiling), not from the checkpoint's recording.Operator notes
Required checks are GitHub Actions JOB names, and each must run on every push to the thread branches (no path filter or job-level if:); a check that never appears is refused after the wait. A CI that reports only commit statuses cannot satisfy thread mode.
GITHUB_TOKENneeds checks: read and commit statuses: read. Migration20260927100000(additive).Mutations
A01-A47 cover every assertion added (failed/pending/missing/timeout/unset/not-read reasons, the trusted-app and per-suite rules, the CI-definition refusal including renames, live policy, ci-entry origin, evidence write ordering, superseded/busy handling, counter clearing, adapter paging and parsing, intake validation, controller and MCP wiring). All exit 0; A31 (entered_at's stage filter) survived its first run, its test was strengthened, and A31b exits 0. Invocations are in the branch's scratch log.
Review rounds
successpasses (a skipped job fails); paged reads deduped and bounded; statuses best effort; contention on the ci-entry read is a retry; unreadable diff fails closed (ci_definition_unknown); a story with no ci entry falls back to its checkpoint time; padded names refused; docs reference the constants. Scope stated: scripts the jobs run are code under review (US-45.3).read_at; compositeaction.ymlanywhere is a CI definition; shape before dedupe; concurrent jobs reads; migration names its manual step.read_atstamped at read start; evidence copied only on allow/refuse; the gate's forge reads run concurrently; MCP validation matches the server; the counter-clearing decision kept and its bound documented.Every assertion added in each round has a
bin/mutate.shproof (exit 0); the last round's set (60 mutations, cited ones retargeted) all exit 0. One fix has no falsifiable test and is stated as such: reading the ci-entry time only on the judged path (which path reads it is not observable from a test).