Repository navigation
ci(release): read-only ruleset probe for the release gate (CR-245 F1) - #488
Merged
Merged
Conversation
…p pushed it (CR-245, round 1) The release workflow pushes a version-bump commit to main; no PR head has that tree, so the next release run refused at phase 1. The gate now also accepts main's head when it is a bump commit that (a) every push-event run of the sha on main records as pushed by the release App (actor and triggering_actor both pineforge-app[bot], at least one run, all pages read, a run by any other actor refuses), (b) has a single parent that itself passes phase 1 (a bump on a bump or a merge commit refuses), and (c) touches exactly the files the workflow writes and stages, VERSION and docker/run_failure_codes_diff.json, as modifications with unchanged mode 100644 and nothing else. Red-first tests through gate() with the injected API for the accepted path and each refusal, a mutation control per condition, preflight pins and docs. Commit metadata is not used for identity: it is forgeable. Round 2 adds the branch-ruleset check that makes the App the only possible pusher. Lane: ci-fu-cr245b (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…pp can push to main (CR-245, round 2) The actor check of round 1 is only as strong as the guarantee that nobody else can produce a push run as the release App. The gate now reads the repository's branch rulesets at gate time and requires that the cumulative bypass of every ACTIVE ruleset that applies to main and carries a pull_request or required_status_checks rule is exactly the release App (Integration 4105147, bypass mode always). It refuses when no push-blocking ruleset applies to main, when one is disabled or evaluate-only, when another actor or a pull-request-only bypass appears, when bypass_actors is not returned, when an inventory or detail response is malformed or incomplete (every page is read and linked), when an applicable ref pattern is not understood, and on any API error. No ruleset id or name is hard-coded. The gate relies on the App installation permission `administration: read`; release.yml, docs/ci.md and the preflight pins say so, so removing the permission is a visible breaking change. Nineteen ruleset tests and thirteen source mutation controls; preflight workflow mutations for the gate token wiring. Rounds 1 and 2 land together. Lane: ci-fu-cr245c (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… restricted to the release App; Actions pagination honours Link (CR-245, round 3) Replaces the round-2 barrier of a8b1276 ("the bump exemption also requires that only the release App can push to main"): it removes the round-2 bypass-intersection sentences and the status-only acceptance that a8b1276 added to docs/ci.md, and rewrites its tests. Fresh sol read of the round-2 head: intersecting bypass descriptors across rulesets does not prove an App-only barrier (a person who holds a role and a team bypasses two rulesets whose lists share only the App entry), and a required_status_checks rule alone is not a push barrier. The condition is now: at least one ACTIVE branch ruleset applies to main, carries a pull_request rule, and its OWN bypass list is exactly the release App (Integration 4105147, bypass mode always); other rulesets, such as the broad default one, no longer matter. required_status_checks no longer counts. The Actions workflow-runs reader also proves completeness with the Link header (an advertised unread next page refuses), as the rulesets reader already did. The round-2 tests that encoded the intersection and the status-only acceptance are rewritten (status-only now refuses) and new red rows cover overlapping role/team principals, a hybrid PR/status pair, a valid barrier beside broader rulesets, a second bypass actor, a non-always mode and the unread-page input; docs, preflight pins and the release.yml comment follow the new wording. Two refusal-text expectations in test_ci_release_gate.py were wrong, not the gate: the pagination row lacked "returned" and the empty-payload row doubled it. Both now assert the text the gate emits. A second fresh sol read found that the HTTP page check and the JSON page check counted different populations: one HTTP body could carry several JSON documents, so a valid page followed by a headerless document still returned the green bump verdict (for the runs reader and the rulesets reader alike). Each framed HTTP body must now be exactly one JSON document, checked before the pages are joined and before the Link relations are trusted; extra or trailing data, an empty body and a partially headerless response refuse with the endpoint named. New red rows cover the runs example, the rulesets analogue, an extra document on page 2, trailing garbage, partially missing headers, an empty body with a next Link and the PR/status bodies; two new full-source mutants remove the check and must go green on the mutant only. Lane: ci-fu-cr245d, ci-fu-cr245e and ci-fu-cr245f (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…e with the release App token (CR-245 F1) REV finding F1 on the CR-245 release-bump exemption (#486): the gate validates bypass_actors and ref patterns of EVERY branch ruleset in the includes_parents inventory before it decides which one is the barrier. GitHub returns bypass_actors only to a caller with write access to the ruleset, so if the release App token cannot see them on a ruleset (for example the organization ruleset), every release bump is refused. A dry_run of release.yml cannot test this: the ruleset reads happen only for a sha with no merged pull request. New subcommand `ci_release_gate.py rulesets-probe --repo OWNER/REPO` runs ONLY the ruleset reader the bump uses (no sha, ref, git, commit or run reads; GET requests for the repository, the ruleset inventory and the ruleset details), prints the barrier ruleset and one line per branch ruleset saying whether its bypass_actors were visible, and refuses with the same error text as `gate`. The gate's verdicts are unchanged: the reader now also returns the evidence it already validated. New workflow release-ruleset-probe.yml (workflow_dispatch only, contents: read) mints the App token with the same action, secrets and scope as release.yml, checks out without persisting credentials, and runs only the probe with that token. docs/ci.md says how to read its output. Tests: a token that cannot see bypass_actors is refused with the ruleset named; a visible green barrier is accepted with the exact two lines and only ruleset reads; the probe equals the real reader on every existing ruleset fixture; a source mutant that skips the reader goes green only on the mutant; workflow and preflight pins, each with mutation controls. The preflight and its tests pin the new workflow and runner label. Lane: ci-fu-cr486p (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
One conflict, scripts/test_ci_preflight.py (add/add in the same place, after the run-json-harness test): main added test_duplicate_test_name_stages_are_binding_and_pin_the_source_root (#407); this branch added test_release_bump_contract_and_gate_tests_remain_binding (CR-245). Both are kept, unchanged and unrelated, one blank line between them. Auto-merged without conflict: .github/workflows/ci.yml, AGENTS.md, scripts/ci_preflight.py (main added the duplicate-test-names stages; this branch's release-gate pins are untouched), scripts/test_ci_verify.py. New from main: scripts/check_duplicate_test_names.py, scripts/test_check_duplicate_test_names.py. scripts/ci_release_gate.py, scripts/test_ci_release_gate.py, .github/workflows/release.yml and docs/ci.md did not change on main and are as in the branch. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…g which App permission exposes bypass_actors (CR-245 F1, round 2) Fresh sol + argon reads of the probe commit 6adae66: - P1 (both): test_gcp_sites_reject_cross_class_routes asserted 29 routed GCP sites; the probe workflow adds one, and the authz job it now carries adds another, so the derived count is 31 (ci 5, ci-post 8, native 3, corpus 3, subset 2, docs 2, docs-post 2, release 3 + prebuilt matrix site, promotion 1, probe 2). The PF_CI_LABELS subset stays 15. - P2 (sol): release-ruleset-probe.yml had no authz job. It now has the canonical authz job, the probe job needs it and carries the canonical step guards; the preflight pin and its mutation controls refuse a probe workflow without authz or without the dependency. The App-token mint step is unchanged and still byte-identical to release.yml's (the probe must use the same token path as the bump). - P2 (sol): docs/ci.md, the release.yml comment (comment lines only) and the binding release-gate test stated as fact that the App permission administration: read exposes bypass_actors. That is not verified: GitHub's documentation says bypass_actors is returned only to a caller with write access to the ruleset, and what an installation token sees on an inherited organization ruleset is exactly what the probe measures. All three now say what is true (the gate needs bypass_actors visible on every branch ruleset in the inventory; if the release App token cannot see them the bump is refused; the probe reports whether it can), and the test pins that wording, so it fails if the unsupported claim comes back. - P3 (sol): docs/ci.md said every probe refusal names an endpoint; only API/response failures do. Narrowed. Names the docs/ci.md sentences this commit removes or rewrites, all introduced by a8b1276 ("ci(release): the bump exemption also requires that only the release App can push to main"), as a deliberate revert of their unverified claim: "The gate relies on the release App permission `administration: read` to read `bypass_actors`." "The App installation has that permission and `contents: write`; the workflow-level `contents: write` setting scopes GITHUB_TOKEN and does not grant App permissions." "Tokens without administration access may receive a ruleset response with `bypass_actors` omitted; a missing field refuses, and the guard never assumes an unreadable list is empty or App-only." "Removing this App permission makes every release-bump exemption refuse." They are replaced by the verified wording above; the guard behaviour (a missing bypass_actors field refuses) is unchanged. Open decision, not changed here: the probe and release.yml use the tag references actions/create-github-app-token@v1 and actions/checkout@v4 where the CI canon wants full-SHA pins; the probe keeps the same mint as the bump on purpose. Lane: ci-fu-cr486q (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…eset probe branch One conflict, scripts/test_ci_preflight.py (add/add after the release-bump tests): the probe branch's release-ruleset-probe tests and main's (#407) test_duplicate_test_name_stages_are_binding_and_pin_the_source_root, which #486 carries from main. Both kept unchanged, one blank line between them, markers removed. Auto-merged: scripts/ci_preflight.py, .github/workflows/ci.yml, AGENTS.md and the #407 files; the probe files are unchanged by the merge. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
luisleo526
changed the base branch from
fu/cr245-release-bump-exemption-v2
to
main
October 24, 2026 04:06
#486 landed on main as ONE squash commit, while this branch carries the same CR-245 gate as the three separate #486 commits plus the probe. Main's tree is byte-identical to the #486 head tree this branch was built on (git diff 773e811 72d9fd5 is empty), so the merge result is exactly this branch's tree: no hunk was resolved by hand and no probe hunk changed. Git sees conflicts only because the same changes arrive as three commits on one side and one squash on the other; the result is recorded as a merge with the squash as second parent. Against main the diff is only the probe: A .github/workflows/release-ruleset-probe.yml, M .github/workflows/release.yml (3 comment lines), M docs/ci.md, M scripts/ci_preflight.py, M scripts/ci_release_gate.py, M scripts/test_ci_preflight.py, M scripts/test_ci_release_gate.py. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… observed, not a permission; dispatch from main (CR-245 F1, round 3) REV (Opus + sol, static) on the probe: NO-GO, small. P1: the refusal text printed exactly when a ruleset's bypass_actors is hidden, which is the probe's decisive output, ended "(App requires administration: read)". That is the permission claim round 2 removed from the docs, the release.yml comment and the binding test; it was introduced by a8b1276 ("the bump exemption also requires that only the release App can push to main") as a fifth place and it is unverified (GitHub's documentation says bypass_actors is returned only to a caller with write access to the ruleset). The text is now "(token cannot see this ruleset's bypass_actors)", the two assertions that pinned the old text pin the new exact text, and the absence guard now scans the WHOLE source of ci_release_gate.py (strings and code, not only comment lines) for the forbidden phrases in every spelling, with restoration controls that reinsert the old refusal text and seven spelling variants. F4: docs/ci.md says the one-time dispatch must run from main with main's copy of the workflow, lists the probe on the isolated pf-gcp-main fleet, and scopes the FLEET-POST=A routing sentence (authz on pf-gcp-pr, the probe job on pf-gcp-main). F2: comments in ci_preflight.py, its tests and release-ruleset-probe.yml claimed a pin binds the authz header byte for byte; for this workflow the pin binds the normalised executable lines (comments and blank lines removed), and the comments now say so. F3: the display branch for a hidden ruleset is unreachable (the reader refuses first); kept, and docs/ci.md says two hidden rulesets need two dispatches because the first refusal names only the first. Rewrites that replace earlier sentences (named for the doc-reverts check): docs/ci.md "FLEET-POST=A splits routing at the file boundary: PR jobs use" (introduced by 3a0d150); docs/ci.md "Release and promotion use the isolated release fleet: `release`, `notify-hub`, Linux x64" (opening from 833f7ac, second line from 3a0d150); docs/ci.md "Dispatch **Release ruleset probe** (`release-ruleset-probe.yml`) once to check" (6adae66); ci_preflight.py "The canon header is text, not a decoded array" (a8089b2) and "Canon authz keeps its space after the comma" (1d5dfd6); the "GCP fleet (owner #1050): non-release jobs on pf-gcp-pr; release-class jobs only on pf-gcp-main" copies in the probe workflow, ci_preflight.py and test_ci_preflight.py (1d5dfd6). Not changed here: the tag-pinned actions (repo-wide card) and the dispatch exposure of the App secrets (separate L6 card). Lane: ci-fu-cr486r (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…d string literals, quoted and line-wrapped claims (CR-245 F1, round 3b) Fresh sol and argon reads of the round-3 commit 5ce34fc: the whole-source guard assert_ruleset_visibility_documentation only removed backticks and folded case before matching the forbidden phrases, so a claim that Python joins from adjacent string literals ("App " "requires administration:" " read"), a claim in single or double quotes ('administration': 'read'), or a claim wrapped over two comment lines still passed. The guard now also scans the string constants of the Python source (folding adjacent literals, constant additions and the literal parts of f-strings through ast) and a normalised view of every text with comment markers, quotes, backticks, line breaks and runs of whitespace collapsed, in addition to the existing contiguous scan. The forbidden patterns are unchanged and the three real texts (the gate source, docs/ci.md, release.yml) still pass. Seventeen restoration controls (concatenated literals, f-string and + variants, quoted and wrapped-comment variants) were added: each fails against the old guard and is rejected by the new one; every existing control is kept. Test-only change: scripts/test_ci_release_gate.py. Lane: ci-fu-cr486s (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
No conflict. Main moved by #483 only (the promote-baseline workflow-level defaults/env pins in scripts/ci_preflight.py and its tests); the probe files are untouched by it, and the merged scripts/ci_preflight.py and scripts/test_ci_preflight.py auto-merged. Against main the diff is only the probe. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…dmits only allowlisted workflows (CR-245 F1)
The org runner group pf-gcp-main (id 9) is restricted to workflows on an allow-list that does not name
release-ruleset-probe.yml, so a dispatch of the probe on ["self-hosted","pf-gcp-main"] would queue forever.
The allow-list is the owner's org security setting and is not widened; the one-time probe uses the
unrestricted pf-gcp-post group (the canon fleet for engine post-merge and manual jobs) instead. Everything
else is unchanged: workflow_dispatch from main, the App-token mint byte-identical to release.yml's,
contents: read, persist-credentials: false, the authz job on pf-gcp-pr, the probe command, release.yml.
Changes: the probe job's runs-on (workflow file and the whole-file template RELEASE_RULESETS_PROBE_WORKFLOW),
RELEASE_JOB_RUNNERS['release-ruleset-probe.yml'] = {'authz': PR_RUNNER, 'probe': POST_RUNNER} (the per-job
map already accepts a non-release label), the fleet tests (the helper now recognises a post override; the
probe on pf-gcp-main or pf-gcp-pr is refused with the specific finding; the post label is accepted; the
site counts stay 31 and 15, derived from the workflow files), and docs/ci.md with one added sentence saying
why. Red-first rows and deletion controls for the new doc sentences.
Rewrites that replace earlier text (named for the doc-reverts check), all introduced by 5ce34fc
("the probe's hidden-bypass_actors refusal states what was observed...", round 3 of the probe):
docs/ci.md "The manual `release-ruleset-probe.yml` workflow runs `authz` on" ... "and `probe` on
`["self-hosted","pf-gcp-main"]`." (only the probe's label changes);
docs/ci.md "Release, promotion and the release ruleset probe use the isolated release fleet:"
(the probe leaves the pf-gcp-main list; release, notify-hub, Linux x64 prebuilt and promote stay);
the comment "# authz uses pf-gcp-pr; the App-token probe uses pf-gcp-main." in the workflow, in
ci_preflight.py and in test_ci_preflight.py (now pf-gcp-post), which also touches the comment lines of
6adae66 and 1d5dfd6 that 5ce34fc had rewritten.
Also named for the doc-reverts check: the docs/ci.md sentence "`release`, `notify-hub`, Linux x64 `prebuilt`,
baseline `promote` and `release-ruleset-probe.yml`'s `probe` job use `["self-hosted","pf-gcp-main"]`." (the
second half of the sentence above) was introduced by 49a55b5 ("Merge main (a2bb907, the squash of #483)
into the ruleset probe branch", which carried the round-3 probe text) and is rewritten here on purpose: the
probe job leaves the pf-gcp-main list because that group admits only allowlisted workflows.
Lane: ci-fu-cr486t (Codex gpt-6.1-sol).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…in before it mints the App token; the runner refusal no longer calls the post fleet isolated (CR-245 F1) Fresh sol read of the pf-gcp-post move (065826e): moving the probe job to the unrestricted pf-gcp-post group removed the only thing that kept it on main, the restricted pf-gcp-main group's workflow and ref allow-list. A workflow_dispatch with ref=refs/heads/<topic> would run that branch's copy of the workflow, pass authz (which accepts any workflow_dispatch), mint the release App token and run the branch's script; neither the workflow nor the probe checked GITHUB_REF. The CI canon requires manual dispatch to satisfy the same ref restriction and to refuse an unsupported ref before checkout or repository-code execution. The probe job now starts with a step that has no secrets and no token: [ "$REF" = refs/heads/main ] || fail, modelled on promote-baseline.yml's own ref refusal, before the App-token mint and before checkout. The whole-file template and release_rulesets_probe_workflow_findings require that step first and refuse a probe workflow that lacks it, has it after the mint or checkout, in the authz job only, compares another ref, or neutralises its exit (|| true, continue-on-error, if:); 13 mutation rows plus an ordering control and a behavioural dispatch control (a topic ref exits 1 before the mint boundary). The App-token mint step is still byte-identical to release.yml's. docs/ci.md says the dispatch must run from main and the workflow itself refuses any other ref. The guard lives in the workflow file, so it stops mistakes, not a writer who edits the file on their own branch; the lock against that is the environment work (CR-304) that keeps the App secrets away from non-main refs. The per-job runner checker's refusal printed " (isolated release)" for a probe pinned to pf-gcp-post; the post fleet is shared. The suffix is now derived from the pinned label (none for the PR fleet, " (post fleet)" for the post fleet, " (isolated release)" only for the release label) and the pins that expected the old suffix (test_gcp_sites_pin_probe_to_the_dispatch_fleet, test_release_ruleset_probe_refuses_release_and_pr_fleets) expect the new one. Rewrites named for the doc-reverts check: the suffix expression and its test expectations came from 1d5dfd6 and 065826e; the pf-gcp-post routing and allow-list sentences of 065826e and the main-copy dispatch wording of 5ce34fc are kept; the generic probe refusal of 6adae66 is kept verbatim. No prose sentence or comment is deleted. Lane: ci-fu-cr486u (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
luisleo526
added a commit
that referenced
this pull request
Oct 24, 2026
No conflict. Main moved by #407 (duplicate-test-names stages), #466, #483 (promote-baseline pins), #486 (release-bump exemption in the release gate) and #488 (release ruleset probe); this PR touches only tests/native_live_startup_e2e.py, which none of them changed. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
luisleo526
added a commit
that referenced
this pull request
Oct 25, 2026
…y, gate unchanged) [replaces #489] (#505) * ci(release): the ruleset probe lists every branch ruleset as visible or hidden, even when the gate verdict is a refusal (CR-245 F1 measurement) The one dispatch of release-ruleset-probe.yml from main (run 40083495967) refused on the first branch ruleset of the inventory, the organization ruleset `default` (13094758): "returned missing or invalid bypass_actors (token cannot see this ruleset's bypass_actors)". The verdict reader stops at the first failure, so the run did not say whether the release App token can see bypass_actors on the repository ruleset that is the real barrier (21476386), nor on any other. The probe now first prints one line per branch ruleset of the includes_parents inventory (id, name, source_type, source, enforcement, updated_at, bypass_actors visible with the actor count or hidden, applies to main, has a pull_request rule), then runs the unchanged verdict: success prints the existing success lines; a refusal prints the same ::error:: text on stderr and exits 1, but stdout now carries the listing. A detail read that fails is reported on its own row and the listing continues. The listing is a measurement: `gate`, `_release_bump` and `_require_release_rulesets` are untouched, every gate test and the existing source mutants are kept, and the probe's verdict replay over every existing ruleset fixture still compares verdict and refusal text byte for byte. The workflow release-ruleset-probe.yml is not changed. The listing reads each branch ruleset detail independently of the verdict read (two reads per detail in the probe), which docs/ci.md now says. Red-first rows: the real situation (org ruleset hidden, repository ruleset visible: both listed, the org one hidden, refusal unchanged), both hidden, one detail read failing, all visible with a valid barrier, the read-only call list, and a source mutant that drops the listing. Rewrites named for the doc-reverts check, all introduced by 9c18d41 (#488, "ci(release): read-only ruleset probe for the release gate (CR-245 F1)"): docs/ci.md "Success prints the barrier id/name and one compact inventory line with enforcement, source and bypass visibility."; docs/ci.md "Refusal exits 1 with empty stdout and the reader's `::error::` diagnostic; API/response failures name the endpoint."; docs/ci.md "Two hidden rulesets need two dispatches: the first refusal names only the first hidden ruleset; make its `bypass_actors` visible before dispatching again to identify the second." (replaced: all hidden rulesets are listed in one dispatch); the probe docstring "Read exactly the bump's ruleset evidence, with the same refusal policy."; two test comments ("The reader report's complete [] inventory fails on main without an endpoint." and "The runtime assertions already hold there; the prose does not."). The module docstring sentence "Each endpoint/page is read at most once; failures refuse without retry or sleep." (ad1ebac, #479) is scoped to the gate's reads. Lane: ci-fu-cr488m (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe listing bounds its inventory=ERROR row to 240 characters like the detail-error rows (CR-245 F1 measurement, sol P2) The new stdout listing printed the whole exception text on an `inventory=ERROR` row, while the per-ruleset `detail=ERROR` rows truncate it to 240 characters; a `gh` transport error with 300 characters of stderr was echoed in full. The row now applies the same bound ([:240], still !r-escaped). Four rows assert the exact bounded line (an inventory transport error, a repository transport error, a default-branch rejection with a long repository argument, an invalid summary with a long field) together with the unchanged stderr, exit code 1 and API call list of the verdict; each has a mutation control that removes only the bound and must fail. The verdict functions are untouched and the verdict's own stderr is not bounded. No documentation sentence is changed (none described a length limit). Lane: ci-fu-cr488n (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]>
luisleo526
added a commit
that referenced
this pull request
Oct 25, 2026
…ncestry check and the doc-reverts default base read (ORG-ACTIONPINS-CARD) [replaces #494] (#507) * ci: fully qualify the two remote-tracking refs the promote-baseline ancestry check and the doc-reverts default base read (ORG-ACTIONPINS-CARD, LB-464 P3) promote-baseline.yml tested `git merge-base --is-ancestor "$MERGE_SHA" "origin/$BASE_REF"`, and check_doc_reverts.py default_base() resolved the bare names `origin/main` then `main`. A short name resolves through git's ref-lookup order, so a tag called `origin/main` (or `main`) would win over the remote-tracking branch or the local branch. Both now use the fully qualified names (`refs/remotes/origin/$BASE_REF`; `refs/remotes/origin/main`, then `refs/heads/main`, same order and same fallback to the merged-pull-request walk). The exact-text pin in scripts/ci_preflight.py is updated to the new command and stays as strict; the old short form is now a finding. Red-first tests: the pin refuses the short command and accepts the qualified one; a repository where a TAG named `origin/main` points at an unrelated commit and the real remote-tracking ref points elsewhere, and the same for a tag named `main` with no remote-tracking ref, each returns the merge base of the real ref and not the tag's. Five controls keep the order, the environment override, the local fallback, the merged-pull-request fallback and the no-base result. The first-parent walk is untouched (the lab-host defect is a separate card). The default_base docstring in scripts/check_doc_reverts.py is re-wrapped around the two renamed refs; the sentences it carries come from 1e360bd and d093560 and keep their wording, only the ref names change. Lane: ci-fu-shortrefs (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci: release ruleset probe lists every branch ruleset (measurement only, gate unchanged) [replaces #489] (#505) * ci(release): the ruleset probe lists every branch ruleset as visible or hidden, even when the gate verdict is a refusal (CR-245 F1 measurement) The one dispatch of release-ruleset-probe.yml from main (run 40083495967) refused on the first branch ruleset of the inventory, the organization ruleset `default` (13094758): "returned missing or invalid bypass_actors (token cannot see this ruleset's bypass_actors)". The verdict reader stops at the first failure, so the run did not say whether the release App token can see bypass_actors on the repository ruleset that is the real barrier (21476386), nor on any other. The probe now first prints one line per branch ruleset of the includes_parents inventory (id, name, source_type, source, enforcement, updated_at, bypass_actors visible with the actor count or hidden, applies to main, has a pull_request rule), then runs the unchanged verdict: success prints the existing success lines; a refusal prints the same ::error:: text on stderr and exits 1, but stdout now carries the listing. A detail read that fails is reported on its own row and the listing continues. The listing is a measurement: `gate`, `_release_bump` and `_require_release_rulesets` are untouched, every gate test and the existing source mutants are kept, and the probe's verdict replay over every existing ruleset fixture still compares verdict and refusal text byte for byte. The workflow release-ruleset-probe.yml is not changed. The listing reads each branch ruleset detail independently of the verdict read (two reads per detail in the probe), which docs/ci.md now says. Red-first rows: the real situation (org ruleset hidden, repository ruleset visible: both listed, the org one hidden, refusal unchanged), both hidden, one detail read failing, all visible with a valid barrier, the read-only call list, and a source mutant that drops the listing. Rewrites named for the doc-reverts check, all introduced by 9c18d41 (#488, "ci(release): read-only ruleset probe for the release gate (CR-245 F1)"): docs/ci.md "Success prints the barrier id/name and one compact inventory line with enforcement, source and bypass visibility."; docs/ci.md "Refusal exits 1 with empty stdout and the reader's `::error::` diagnostic; API/response failures name the endpoint."; docs/ci.md "Two hidden rulesets need two dispatches: the first refusal names only the first hidden ruleset; make its `bypass_actors` visible before dispatching again to identify the second." (replaced: all hidden rulesets are listed in one dispatch); the probe docstring "Read exactly the bump's ruleset evidence, with the same refusal policy."; two test comments ("The reader report's complete [] inventory fails on main without an endpoint." and "The runtime assertions already hold there; the prose does not."). The module docstring sentence "Each endpoint/page is read at most once; failures refuse without retry or sleep." (ad1ebac, #479) is scoped to the gate's reads. Lane: ci-fu-cr488m (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe listing bounds its inventory=ERROR row to 240 characters like the detail-error rows (CR-245 F1 measurement, sol P2) The new stdout listing printed the whole exception text on an `inventory=ERROR` row, while the per-ruleset `detail=ERROR` rows truncate it to 240 characters; a `gh` transport error with 300 characters of stderr was echoed in full. The row now applies the same bound ([:240], still !r-escaped). Four rows assert the exact bounded line (an inventory transport error, a repository transport error, a default-branch rejection with a long repository argument, an invalid summary with a long field) together with the unchanged stderr, exit code 1 and API call list of the verdict; each has a mutation control that removes only the bound and must fail. The verdict functions are untouched and the verdict's own stderr is not bounded. No documentation sentence is changed (none described a length limit). Lane: ci-fu-cr488n (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: ci-cx <ci-cx@invalid> Co-authored-by: Claude Sonnet 5.5 <[email protected]>
luisleo526
added a commit
that referenced
this pull request
Oct 25, 2026
…rapped, bytes-decoded and fullwidth respellings (PROBE-FU) [replaces #493] (#506) * ci(release): the permission-claim text guard also catches backslash-wrapped, bytes-decoded and fullwidth respellings (PROBE-FU, sol P2 1/2/4) The guard in WorkflowPinTests.assert_ruleset_visibility_documentation fails when the gate source, docs/ci.md or release.yml state that the App needs "administration: read". A read of the probe found three respellings it accepted: a Markdown hard break written as a backslash before the newline, a bytes constant (b"administr\x61tion: re\x61d", with or without .decode()), and fullwidth compatibility letters. The collapsed view now treats backslash-newline like whitespace, bytes constants are decoded into the scanned strings, and every view is NFKC-normalised before case folding. Three new tests (12 rows) each read the real artifacts, assert the unmodified baseline passes, mutate a copy and require the specific permission-claim AssertionError; removing only the matching handling turns its rows red. Not closed, by design: YAML double-quoted scalars with escapes in release.yml (sol P2 3) and runtime string construction (sol P3); a static text guard cannot decide them, and the exact-output pins on the refusal text remain the decisive protection. No existing assertion, control or forbidden pattern is changed. The rewritten `view` tuple is code, not a documentation or comment sentence. Lane: ci-fu-pfuguard (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the permission-claim guard scans the casefold-only views as well as the NFKC views (PROBE-FU round 2, sol R237 P2) The first PROBE-FU commit normalised every scanned view with NFKC and replaced the casefold-only views. For an input such as `Needs administration: read` followed by the trade mark sign, the old guard matched (the sign is a non-word character after `read`), but after NFKC the text is `readtm` and the word boundary no longer matches, so the claim was accepted: the change made the guard weaker for that input. Each text is now checked in four views (the two original views casefolded, and both NFKC+casefolded), so normalisation can only add matches. A restoration control appends the trade mark case to the gate source, docs/ci.md and the workflow text (direct and line-wrapped, six rows), and a removal mutant drops the casefold-only scan and must break all six rows. No existing assertion or control changes; no documentation or comment sentence is deleted or rewritten. Lane: ci-fu-pfuguard2 (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci: release ruleset probe lists every branch ruleset (measurement only, gate unchanged) [replaces #489] (#505) * ci(release): the ruleset probe lists every branch ruleset as visible or hidden, even when the gate verdict is a refusal (CR-245 F1 measurement) The one dispatch of release-ruleset-probe.yml from main (run 40083495967) refused on the first branch ruleset of the inventory, the organization ruleset `default` (13094758): "returned missing or invalid bypass_actors (token cannot see this ruleset's bypass_actors)". The verdict reader stops at the first failure, so the run did not say whether the release App token can see bypass_actors on the repository ruleset that is the real barrier (21476386), nor on any other. The probe now first prints one line per branch ruleset of the includes_parents inventory (id, name, source_type, source, enforcement, updated_at, bypass_actors visible with the actor count or hidden, applies to main, has a pull_request rule), then runs the unchanged verdict: success prints the existing success lines; a refusal prints the same ::error:: text on stderr and exits 1, but stdout now carries the listing. A detail read that fails is reported on its own row and the listing continues. The listing is a measurement: `gate`, `_release_bump` and `_require_release_rulesets` are untouched, every gate test and the existing source mutants are kept, and the probe's verdict replay over every existing ruleset fixture still compares verdict and refusal text byte for byte. The workflow release-ruleset-probe.yml is not changed. The listing reads each branch ruleset detail independently of the verdict read (two reads per detail in the probe), which docs/ci.md now says. Red-first rows: the real situation (org ruleset hidden, repository ruleset visible: both listed, the org one hidden, refusal unchanged), both hidden, one detail read failing, all visible with a valid barrier, the read-only call list, and a source mutant that drops the listing. Rewrites named for the doc-reverts check, all introduced by 9c18d41 (#488, "ci(release): read-only ruleset probe for the release gate (CR-245 F1)"): docs/ci.md "Success prints the barrier id/name and one compact inventory line with enforcement, source and bypass visibility."; docs/ci.md "Refusal exits 1 with empty stdout and the reader's `::error::` diagnostic; API/response failures name the endpoint."; docs/ci.md "Two hidden rulesets need two dispatches: the first refusal names only the first hidden ruleset; make its `bypass_actors` visible before dispatching again to identify the second." (replaced: all hidden rulesets are listed in one dispatch); the probe docstring "Read exactly the bump's ruleset evidence, with the same refusal policy."; two test comments ("The reader report's complete [] inventory fails on main without an endpoint." and "The runtime assertions already hold there; the prose does not."). The module docstring sentence "Each endpoint/page is read at most once; failures refuse without retry or sleep." (ad1ebac, #479) is scoped to the gate's reads. Lane: ci-fu-cr488m (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe listing bounds its inventory=ERROR row to 240 characters like the detail-error rows (CR-245 F1 measurement, sol P2) The new stdout listing printed the whole exception text on an `inventory=ERROR` row, while the per-ruleset `detail=ERROR` rows truncate it to 240 characters; a `gh` transport error with 300 characters of stderr was echoed in full. The row now applies the same bound ([:240], still !r-escaped). Four rows assert the exact bounded line (an inventory transport error, a repository transport error, a default-branch rejection with a long repository argument, an invalid summary with a long field) together with the unchanged stderr, exit code 1 and API call list of the verdict; each has a mutation control that removes only the bound and must fail. The verdict functions are untouched and the verdict's own stderr is not bounded. No documentation sentence is changed (none described a length limit). Lane: ci-fu-cr488n (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: ci-cx <ci-cx@invalid> Co-authored-by: Claude Sonnet 5.5 <[email protected]>
luisleo526
added a commit
that referenced
this pull request
Oct 25, 2026
…e, P2-CM-F4) (#504) * tests: drop the TIMEOUT 180 property from the six ownership manifest tests (P2-CM-F4) One set_tests_properties call carried TIMEOUT 180 for test_selected_window_ownership_manifest, the four test_selected_window_ownership_manifest_controls_shard_{1..4}of4 and test_selected_window_ownership_d4. The owner's no-time-limit rule forbids a ctest ceiling: under pool contention shard 1of4 (119 s normally) hit the 180 s ceiling on the b250ceb merge-commit ci-post (#283). WORKING_DIRECTORY is kept; no test, label or line count changes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci: release ruleset probe lists every branch ruleset (measurement only, gate unchanged) [replaces #489] (#505) * ci(release): the ruleset probe lists every branch ruleset as visible or hidden, even when the gate verdict is a refusal (CR-245 F1 measurement) The one dispatch of release-ruleset-probe.yml from main (run 40083495967) refused on the first branch ruleset of the inventory, the organization ruleset `default` (13094758): "returned missing or invalid bypass_actors (token cannot see this ruleset's bypass_actors)". The verdict reader stops at the first failure, so the run did not say whether the release App token can see bypass_actors on the repository ruleset that is the real barrier (21476386), nor on any other. The probe now first prints one line per branch ruleset of the includes_parents inventory (id, name, source_type, source, enforcement, updated_at, bypass_actors visible with the actor count or hidden, applies to main, has a pull_request rule), then runs the unchanged verdict: success prints the existing success lines; a refusal prints the same ::error:: text on stderr and exits 1, but stdout now carries the listing. A detail read that fails is reported on its own row and the listing continues. The listing is a measurement: `gate`, `_release_bump` and `_require_release_rulesets` are untouched, every gate test and the existing source mutants are kept, and the probe's verdict replay over every existing ruleset fixture still compares verdict and refusal text byte for byte. The workflow release-ruleset-probe.yml is not changed. The listing reads each branch ruleset detail independently of the verdict read (two reads per detail in the probe), which docs/ci.md now says. Red-first rows: the real situation (org ruleset hidden, repository ruleset visible: both listed, the org one hidden, refusal unchanged), both hidden, one detail read failing, all visible with a valid barrier, the read-only call list, and a source mutant that drops the listing. Rewrites named for the doc-reverts check, all introduced by 9c18d41 (#488, "ci(release): read-only ruleset probe for the release gate (CR-245 F1)"): docs/ci.md "Success prints the barrier id/name and one compact inventory line with enforcement, source and bypass visibility."; docs/ci.md "Refusal exits 1 with empty stdout and the reader's `::error::` diagnostic; API/response failures name the endpoint."; docs/ci.md "Two hidden rulesets need two dispatches: the first refusal names only the first hidden ruleset; make its `bypass_actors` visible before dispatching again to identify the second." (replaced: all hidden rulesets are listed in one dispatch); the probe docstring "Read exactly the bump's ruleset evidence, with the same refusal policy."; two test comments ("The reader report's complete [] inventory fails on main without an endpoint." and "The runtime assertions already hold there; the prose does not."). The module docstring sentence "Each endpoint/page is read at most once; failures refuse without retry or sleep." (ad1ebac, #479) is scoped to the gate's reads. Lane: ci-fu-cr488m (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe listing bounds its inventory=ERROR row to 240 characters like the detail-error rows (CR-245 F1 measurement, sol P2) The new stdout listing printed the whole exception text on an `inventory=ERROR` row, while the per-ruleset `detail=ERROR` rows truncate it to 240 characters; a `gh` transport error with 300 characters of stderr was echoed in full. The row now applies the same bound ([:240], still !r-escaped). Four rows assert the exact bounded line (an inventory transport error, a repository transport error, a default-branch rejection with a long repository argument, an invalid summary with a long field) together with the unchanged stderr, exit code 1 and API call list of the verdict; each has a mutation control that removes only the bound and must fail. The verdict functions are untouched and the verdict's own stderr is not bounded. No documentation sentence is changed (none described a length limit). Lane: ci-fu-cr488n (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]>
luisleo526
added a commit
that referenced
this pull request
Oct 25, 2026
…ted C++ (scoped site identities, fail-closed bounded admission) (#469) * ci: CR-150/C4 source allocation-bound gate over the engine and generated C++ The gate reads ci/cr150-source-bounds.yaml, resolves each entry against the tree, and fails closed when it cannot judge. Exit 0 = no new unbounded site (PASS(bounded) count + OPEN listing); --strict exits 1 while any OPEN entry remains. Two ci_preflight stages run it in warnings and strict mode. * ci: the CR-150 source-bounds manifest (seed data and ceilings) 1287 lines emitted by --emit-seed: every scanned site with its state, each OPEN entry (pending-flat-memory, unreviewed) listed and capped by ceilings so a new OPEN site needs a reviewed ceiling increase. * ci: pin scoped CR-150 site identities and prove the bound gate both ways The four docs/ci.md sentences that 48fb19d introduced (the index comparison, the exit-1 site conditions, the exit-2 manifest conditions, and how to add a bound) are replaced here by the scoped-site-identity text: the same rules now speak per (file, symbol) site identity rather than per manifest group. Naming them here is what check_doc_reverts.py requires of a commit that drops them. * ci: close the CR-150 source-gate review findings (P0-P3) P0: the site inventory's file and symbol fields accept placeholder-like identifiers again (two real rows carry the symbol `none`); the spelling field still refuses empty and placeholder values. P1: bounded admission decides locality from a recognized function body, crossing only statement blocks. Decorated classes, templates, attributed structs, nested types after an access label, unknown scopes and linkage blocks are barriers, and `extern` in the declaration's statement is refused. P2: `%:define` (the C++17 digraph for `#`) is read as a directive, so a macro cannot bypass the macro refusal. P3: a macro definition of a name guarded bounded admission uses (`push_back`, `emplace_back`, `size`, the entry symbol, the bound) is refused in every scanned file, not only the entry's own file. Tests cover each finding both ways (admit and refuse), including the inventory `none` row and a negative control that proves the gate still stays green on the unmodified tree. * ci: close the delta read's N1-N4 on the CR-150 source gate - _directives joins translation-phase splices before lexing, so `%\` + `:define` and every `\`-continued spelling are read as the directive they are (N1, N2). - bounded_issues refuses a trailing backslash or a <% / %> brace digraph in the entry and the fixed input file: alternate spellings are outside guarded bounded admission rather than silently unscoped (N1, N4). - macro_names now covers every identifier lexed from the entry and fixed input, not five names, and the define regex admits a comment swallowed without a separating space, so `#define/**/push_back`, `#define if(c)` and `#define static extern` are all refused (N2, N3). - _text refuses an empty file/symbol even where a placeholder is allowed, so a blank inventory field exits 2 (malformed) instead of 1 (stale site) (N6). Tests: spliced digraph, brace digraph in entry and fixed input, commented and spliced defines, keyword/type macro rewrites, and blank inventory fields; each pins the refusal and that no PASS(bounded) survives. Co-Authored-By: Claude Code <[email protected]> * ci: close the delta read 2's fail-open inputs on the CR-150 source gate Guarded bounded admission now refuses, on raw text rather than a lexed view, every spelling where a lexer and a compiler can disagree: line splices (including whitespace, FF and VT before the newline) inside comments and literals, raw string literals and their prefixed forms, NUL, BOM, non-ASCII text and brace digraphs. A define naming a proof-file identifier is refused in every scanned file after splice joining, regardless of lexical context, and blank inventory scope members are now malformed (exit 2) instead of an ordinary finding. Header entries under include/ are refused bounded admission: a consumer's prior includes and build flags are not proven, so the form cannot be enforced from the proof files alone. Rewords the one admission sentence added by c989d59dab in docs/ci.md (replacing its "in the participating source and fixed-input files" wording) to state the same rule the code now enforces. Co-Authored-By: Claude Code <[email protected]> * ci: skip suite jobs on draft PRs; the gate fails on drafts (LB-387) (#466) * ci: skip the PR suite on draft PRs (LB-387) Skip preflight, build, sanitizers, kernel-only, native-live, and corpus-parity-subset for drafts; start full runs on ready_for_review. Update ci_preflight.py trigger/condition pins and matching test_ci_preflight.py pin references, preserving check logic. Keep authz, dependencies, runners, time limits, and build-gate unchanged. * ci: re-run on converted_to_draft and split the CI group by draft state (LB-387) Round B of the final read of #466: add converted_to_draft to the pull_request types so a ready run is followed by a failing draft aggregate when the PR is converted, and key the concurrency group on the draft flag so a stale draft re-run cannot cancel the full ready run. Exact pins plus one red control per change in test_ci_preflight.py. Lane: ci-draft-fix1 (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci: run both ctest label-exclusion tests; refuse duplicate test names (CR-146) (#407) * ci: run both ctest label-exclusion tests; refuse duplicate test names (CR-146) * ci: close the evasions of the duplicate-test-name check (CR-146 review round) Both reads of #407 found the check fail-open: a method redefined inside an if/try/with block of the class body, a repeated class name, suites that CMake registers outside scripts/ and docker/ (tests/, tests/ownership/, benchmarks/tests/, *_self_test.py), and an empty inspection population (missing root, empty directory, zero test names) all returned success. The check now traverses scope-preserving blocks, refuses duplicate class bindings and duplicate module-level tests, scans the registered Python test inventory, and refuses an empty population; preflight pins the --root; the diagnostic tests assert the complete location instead of a line prefix. Regression tests with the readers' counterexamples; the repository tree itself must still pass (see the lane report for how that was read from the tree). Lane: ci-m407 fix (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci: the duplicate-name check ignores warnings from the files it parses (CR-146 round 3) The widened scan parses scripts/check_native_c_api_surface.py, whose docstring raises a SyntaxWarning (invalid escape sequence); the warning leaked into the checker's output and the repository test `test_repository_inventory_has_no_duplicate_bindings` (empty output expected) failed in preflight. The parse now suppresses warnings from the scanned source while a real SyntaxError still fails the check. Red tests: a clean file and a file with a real duplicate, each with an invalid-escape docstring, under warnings set to `always` and to `error`; the repository assertion is unchanged. Lane: ci-fu-m407r2 (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: HaoLiangWen <[email protected]> Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci(release): narrow release-bump exemption in the release gate (CR-245, G13, v2) (#486) * ci(release): accept a release-bump commit on main when the release App pushed it (CR-245, round 1) The release workflow pushes a version-bump commit to main; no PR head has that tree, so the next release run refused at phase 1. The gate now also accepts main's head when it is a bump commit that (a) every push-event run of the sha on main records as pushed by the release App (actor and triggering_actor both pineforge-app[bot], at least one run, all pages read, a run by any other actor refuses), (b) has a single parent that itself passes phase 1 (a bump on a bump or a merge commit refuses), and (c) touches exactly the files the workflow writes and stages, VERSION and docker/run_failure_codes_diff.json, as modifications with unchanged mode 100644 and nothing else. Red-first tests through gate() with the injected API for the accepted path and each refusal, a mutation control per condition, preflight pins and docs. Commit metadata is not used for identity: it is forgeable. Round 2 adds the branch-ruleset check that makes the App the only possible pusher. Lane: ci-fu-cr245b (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the bump exemption also requires that only the release App can push to main (CR-245, round 2) The actor check of round 1 is only as strong as the guarantee that nobody else can produce a push run as the release App. The gate now reads the repository's branch rulesets at gate time and requires that the cumulative bypass of every ACTIVE ruleset that applies to main and carries a pull_request or required_status_checks rule is exactly the release App (Integration 4105147, bypass mode always). It refuses when no push-blocking ruleset applies to main, when one is disabled or evaluate-only, when another actor or a pull-request-only bypass appears, when bypass_actors is not returned, when an inventory or detail response is malformed or incomplete (every page is read and linked), when an applicable ref pattern is not understood, and on any API error. No ruleset id or name is hard-coded. The gate relies on the App installation permission `administration: read`; release.yml, docs/ci.md and the preflight pins say so, so removing the permission is a visible breaking change. Nineteen ruleset tests and thirteen source mutation controls; preflight workflow mutations for the gate token wiring. Rounds 1 and 2 land together. Lane: ci-fu-cr245c (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the bump exemption needs one active pull_request ruleset restricted to the release App; Actions pagination honours Link (CR-245, round 3) Replaces the round-2 barrier of a8b1276 ("the bump exemption also requires that only the release App can push to main"): it removes the round-2 bypass-intersection sentences and the status-only acceptance that a8b1276 added to docs/ci.md, and rewrites its tests. Fresh sol read of the round-2 head: intersecting bypass descriptors across rulesets does not prove an App-only barrier (a person who holds a role and a team bypasses two rulesets whose lists share only the App entry), and a required_status_checks rule alone is not a push barrier. The condition is now: at least one ACTIVE branch ruleset applies to main, carries a pull_request rule, and its OWN bypass list is exactly the release App (Integration 4105147, bypass mode always); other rulesets, such as the broad default one, no longer matter. required_status_checks no longer counts. The Actions workflow-runs reader also proves completeness with the Link header (an advertised unread next page refuses), as the rulesets reader already did. The round-2 tests that encoded the intersection and the status-only acceptance are rewritten (status-only now refuses) and new red rows cover overlapping role/team principals, a hybrid PR/status pair, a valid barrier beside broader rulesets, a second bypass actor, a non-always mode and the unread-page input; docs, preflight pins and the release.yml comment follow the new wording. Two refusal-text expectations in test_ci_release_gate.py were wrong, not the gate: the pagination row lacked "returned" and the empty-payload row doubled it. Both now assert the text the gate emits. A second fresh sol read found that the HTTP page check and the JSON page check counted different populations: one HTTP body could carry several JSON documents, so a valid page followed by a headerless document still returned the green bump verdict (for the runs reader and the rulesets reader alike). Each framed HTTP body must now be exactly one JSON document, checked before the pages are joined and before the Link relations are trusted; extra or trailing data, an empty body and a partially headerless response refuse with the endpoint named. New red rows cover the runs example, the rulesets analogue, an extra document on page 2, trailing garbage, partially missing headers, an empty body with a next Link and the PR/status bodies; two new full-source mutants remove the check and must go green on the mutant only. Lane: ci-fu-cr245d, ci-fu-cr245e and ci-fu-cr245f (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci: promote-baseline preflight rejects workflow-level defaults/env (#482 follow-up) (#483) * ci: reject workflow-level defaults and env in the promote-baseline pin (SEC846 follow-up) Post-merge read of #482: the preflight pinned only the promote-guard job block, so a workflow-level defaults.run.shell or an env carrying a secret passed the checker while changing how the guard runs. ci_workflow_findings now rejects both, and the guard's own defaults, with specific findings; tests cover each reported input, the quoted and empty variants, and the unmodified workflow. Lane: ci-fu-eng482 (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * test: expect the pinned-guard finding in main's current wording (engine #483 after the main merge) After main (#470) landed, the checker's finding for a changed promote-guard block reads "must start with the pinned read-only promote-guard"; this PR's new test_promotion_guard_rejects_job_defaults still expected the earlier "credential-free" wording in its three subtests, so preflight failed. The expected list is now the exact list the checker emits (the job-level defaults finding, then the pinned-guard finding); the assertion stays an exact ordered equality. Checker unchanged. Lane: ci-fu-eng483b (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci(release): read-only ruleset probe for the release gate (CR-245 F1) (#488) * ci(release): accept a release-bump commit on main when the release App pushed it (CR-245, round 1) The release workflow pushes a version-bump commit to main; no PR head has that tree, so the next release run refused at phase 1. The gate now also accepts main's head when it is a bump commit that (a) every push-event run of the sha on main records as pushed by the release App (actor and triggering_actor both pineforge-app[bot], at least one run, all pages read, a run by any other actor refuses), (b) has a single parent that itself passes phase 1 (a bump on a bump or a merge commit refuses), and (c) touches exactly the files the workflow writes and stages, VERSION and docker/run_failure_codes_diff.json, as modifications with unchanged mode 100644 and nothing else. Red-first tests through gate() with the injected API for the accepted path and each refusal, a mutation control per condition, preflight pins and docs. Commit metadata is not used for identity: it is forgeable. Round 2 adds the branch-ruleset check that makes the App the only possible pusher. Lane: ci-fu-cr245b (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the bump exemption also requires that only the release App can push to main (CR-245, round 2) The actor check of round 1 is only as strong as the guarantee that nobody else can produce a push run as the release App. The gate now reads the repository's branch rulesets at gate time and requires that the cumulative bypass of every ACTIVE ruleset that applies to main and carries a pull_request or required_status_checks rule is exactly the release App (Integration 4105147, bypass mode always). It refuses when no push-blocking ruleset applies to main, when one is disabled or evaluate-only, when another actor or a pull-request-only bypass appears, when bypass_actors is not returned, when an inventory or detail response is malformed or incomplete (every page is read and linked), when an applicable ref pattern is not understood, and on any API error. No ruleset id or name is hard-coded. The gate relies on the App installation permission `administration: read`; release.yml, docs/ci.md and the preflight pins say so, so removing the permission is a visible breaking change. Nineteen ruleset tests and thirteen source mutation controls; preflight workflow mutations for the gate token wiring. Rounds 1 and 2 land together. Lane: ci-fu-cr245c (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the bump exemption needs one active pull_request ruleset restricted to the release App; Actions pagination honours Link (CR-245, round 3) Replaces the round-2 barrier of a8b1276 ("the bump exemption also requires that only the release App can push to main"): it removes the round-2 bypass-intersection sentences and the status-only acceptance that a8b1276 added to docs/ci.md, and rewrites its tests. Fresh sol read of the round-2 head: intersecting bypass descriptors across rulesets does not prove an App-only barrier (a person who holds a role and a team bypasses two rulesets whose lists share only the App entry), and a required_status_checks rule alone is not a push barrier. The condition is now: at least one ACTIVE branch ruleset applies to main, carries a pull_request rule, and its OWN bypass list is exactly the release App (Integration 4105147, bypass mode always); other rulesets, such as the broad default one, no longer matter. required_status_checks no longer counts. The Actions workflow-runs reader also proves completeness with the Link header (an advertised unread next page refuses), as the rulesets reader already did. The round-2 tests that encoded the intersection and the status-only acceptance are rewritten (status-only now refuses) and new red rows cover overlapping role/team principals, a hybrid PR/status pair, a valid barrier beside broader rulesets, a second bypass actor, a non-always mode and the unread-page input; docs, preflight pins and the release.yml comment follow the new wording. Two refusal-text expectations in test_ci_release_gate.py were wrong, not the gate: the pagination row lacked "returned" and the empty-payload row doubled it. Both now assert the text the gate emits. A second fresh sol read found that the HTTP page check and the JSON page check counted different populations: one HTTP body could carry several JSON documents, so a valid page followed by a headerless document still returned the green bump verdict (for the runs reader and the rulesets reader alike). Each framed HTTP body must now be exactly one JSON document, checked before the pages are joined and before the Link relations are trusted; extra or trailing data, an empty body and a partially headerless response refuse with the endpoint named. New red rows cover the runs example, the rulesets analogue, an extra document on page 2, trailing garbage, partially missing headers, an empty body with a next Link and the PR/status bodies; two new full-source mutants remove the check and must go green on the mutant only. Lane: ci-fu-cr245d, ci-fu-cr245e and ci-fu-cr245f (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): a read-only probe of the bump's ruleset barrier, run once with the release App token (CR-245 F1) REV finding F1 on the CR-245 release-bump exemption (#486): the gate validates bypass_actors and ref patterns of EVERY branch ruleset in the includes_parents inventory before it decides which one is the barrier. GitHub returns bypass_actors only to a caller with write access to the ruleset, so if the release App token cannot see them on a ruleset (for example the organization ruleset), every release bump is refused. A dry_run of release.yml cannot test this: the ruleset reads happen only for a sha with no merged pull request. New subcommand `ci_release_gate.py rulesets-probe --repo OWNER/REPO` runs ONLY the ruleset reader the bump uses (no sha, ref, git, commit or run reads; GET requests for the repository, the ruleset inventory and the ruleset details), prints the barrier ruleset and one line per branch ruleset saying whether its bypass_actors were visible, and refuses with the same error text as `gate`. The gate's verdicts are unchanged: the reader now also returns the evidence it already validated. New workflow release-ruleset-probe.yml (workflow_dispatch only, contents: read) mints the App token with the same action, secrets and scope as release.yml, checks out without persisting credentials, and runs only the probe with that token. docs/ci.md says how to read its output. Tests: a token that cannot see bypass_actors is refused with the ruleset named; a visible green barrier is accepted with the exact two lines and only ruleset reads; the probe equals the real reader on every existing ruleset fixture; a source mutant that skips the reader goes green only on the mutant; workflow and preflight pins, each with mutation controls. The preflight and its tests pin the new workflow and runner label. Lane: ci-fu-cr486p (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the ruleset probe gets the canon authz job; stop claiming which App permission exposes bypass_actors (CR-245 F1, round 2) Fresh sol + argon reads of the probe commit 6adae66f2: - P1 (both): test_gcp_sites_reject_cross_class_routes asserted 29 routed GCP sites; the probe workflow adds one, and the authz job it now carries adds another, so the derived count is 31 (ci 5, ci-post 8, native 3, corpus 3, subset 2, docs 2, docs-post 2, release 3 + prebuilt matrix site, promotion 1, probe 2). The PF_CI_LABELS subset stays 15. - P2 (sol): release-ruleset-probe.yml had no authz job. It now has the canonical authz job, the probe job needs it and carries the canonical step guards; the preflight pin and its mutation controls refuse a probe workflow without authz or without the dependency. The App-token mint step is unchanged and still byte-identical to release.yml's (the probe must use the same token path as the bump). - P2 (sol): docs/ci.md, the release.yml comment (comment lines only) and the binding release-gate test stated as fact that the App permission administration: read exposes bypass_actors. That is not verified: GitHub's documentation says bypass_actors is returned only to a caller with write access to the ruleset, and what an installation token sees on an inherited organization ruleset is exactly what the probe measures. All three now say what is true (the gate needs bypass_actors visible on every branch ruleset in the inventory; if the release App token cannot see them the bump is refused; the probe reports whether it can), and the test pins that wording, so it fails if the unsupported claim comes back. - P3 (sol): docs/ci.md said every probe refusal names an endpoint; only API/response failures do. Narrowed. Names the docs/ci.md sentences this commit removes or rewrites, all introduced by a8b1276 ("ci(release): the bump exemption also requires that only the release App can push to main"), as a deliberate revert of their unverified claim: "The gate relies on the release App permission `administration: read` to read `bypass_actors`." "The App installation has that permission and `contents: write`; the workflow-level `contents: write` setting scopes GITHUB_TOKEN and does not grant App permissions." "Tokens without administration access may receive a ruleset response with `bypass_actors` omitted; a missing field refuses, and the guard never assumes an unreadable list is empty or App-only." "Removing this App permission makes every release-bump exemption refuse." They are replaced by the verified wording above; the guard behaviour (a missing bypass_actors field refuses) is unchanged. Open decision, not changed here: the probe and release.yml use the tag references actions/create-github-app-token@v1 and actions/checkout@v4 where the CI canon wants full-SHA pins; the probe keeps the same mint as the bump on purpose. Lane: ci-fu-cr486q (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe's hidden-bypass_actors refusal states what was observed, not a permission; dispatch from main (CR-245 F1, round 3) REV (Opus + sol, static) on the probe: NO-GO, small. P1: the refusal text printed exactly when a ruleset's bypass_actors is hidden, which is the probe's decisive output, ended "(App requires administration: read)". That is the permission claim round 2 removed from the docs, the release.yml comment and the binding test; it was introduced by a8b12768f ("the bump exemption also requires that only the release App can push to main") as a fifth place and it is unverified (GitHub's documentation says bypass_actors is returned only to a caller with write access to the ruleset). The text is now "(token cannot see this ruleset's bypass_actors)", the two assertions that pinned the old text pin the new exact text, and the absence guard now scans the WHOLE source of ci_release_gate.py (strings and code, not only comment lines) for the forbidden phrases in every spelling, with restoration controls that reinsert the old refusal text and seven spelling variants. F4: docs/ci.md says the one-time dispatch must run from main with main's copy of the workflow, lists the probe on the isolated pf-gcp-main fleet, and scopes the FLEET-POST=A routing sentence (authz on pf-gcp-pr, the probe job on pf-gcp-main). F2: comments in ci_preflight.py, its tests and release-ruleset-probe.yml claimed a pin binds the authz header byte for byte; for this workflow the pin binds the normalised executable lines (comments and blank lines removed), and the comments now say so. F3: the display branch for a hidden ruleset is unreachable (the reader refuses first); kept, and docs/ci.md says two hidden rulesets need two dispatches because the first refusal names only the first. Rewrites that replace earlier sentences (named for the doc-reverts check): docs/ci.md "FLEET-POST=A splits routing at the file boundary: PR jobs use" (introduced by 3a0d150); docs/ci.md "Release and promotion use the isolated release fleet: `release`, `notify-hub`, Linux x64" (opening from 833f7ac, second line from 3a0d150); docs/ci.md "Dispatch **Release ruleset probe** (`release-ruleset-probe.yml`) once to check" (6adae66); ci_preflight.py "The canon header is text, not a decoded array" (a8089b2) and "Canon authz keeps its space after the comma" (1d5dfd6); the "GCP fleet (owner #1050): non-release jobs on pf-gcp-pr; release-class jobs only on pf-gcp-main" copies in the probe workflow, ci_preflight.py and test_ci_preflight.py (1d5dfd6). Not changed here: the tag-pinned actions (repo-wide card) and the dispatch exposure of the App secrets (separate L6 card). Lane: ci-fu-cr486r (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the permission-claim absence guard also sees concatenated string literals, quoted and line-wrapped claims (CR-245 F1, round 3b) Fresh sol and argon reads of the round-3 commit 5ce34fc50: the whole-source guard assert_ruleset_visibility_documentation only removed backticks and folded case before matching the forbidden phrases, so a claim that Python joins from adjacent string literals ("App " "requires administration:" " read"), a claim in single or double quotes ('administration': 'read'), or a claim wrapped over two comment lines still passed. The guard now also scans the string constants of the Python source (folding adjacent literals, constant additions and the literal parts of f-strings through ast) and a normalised view of every text with comment markers, quotes, backticks, line breaks and runs of whitespace collapsed, in addition to the existing contiguous scan. The forbidden patterns are unchanged and the three real texts (the gate source, docs/ci.md, release.yml) still pass. Seventeen restoration controls (concatenated literals, f-string and + variants, quoted and wrapped-comment variants) were added: each fails against the old guard and is rejected by the new one; every existing control is kept. Test-only change: scripts/test_ci_release_gate.py. Lane: ci-fu-cr486s (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the ruleset probe job runs on pf-gcp-post; pf-gcp-main admits only allowlisted workflows (CR-245 F1) The org runner group pf-gcp-main (id 9) is restricted to workflows on an allow-list that does not name release-ruleset-probe.yml, so a dispatch of the probe on ["self-hosted","pf-gcp-main"] would queue forever. The allow-list is the owner's org security setting and is not widened; the one-time probe uses the unrestricted pf-gcp-post group (the canon fleet for engine post-merge and manual jobs) instead. Everything else is unchanged: workflow_dispatch from main, the App-token mint byte-identical to release.yml's, contents: read, persist-credentials: false, the authz job on pf-gcp-pr, the probe command, release.yml. Changes: the probe job's runs-on (workflow file and the whole-file template RELEASE_RULESETS_PROBE_WORKFLOW), RELEASE_JOB_RUNNERS['release-ruleset-probe.yml'] = {'authz': PR_RUNNER, 'probe': POST_RUNNER} (the per-job map already accepts a non-release label), the fleet tests (the helper now recognises a post override; the probe on pf-gcp-main or pf-gcp-pr is refused with the specific finding; the post label is accepted; the site counts stay 31 and 15, derived from the workflow files), and docs/ci.md with one added sentence saying why. Red-first rows and deletion controls for the new doc sentences. Rewrites that replace earlier text (named for the doc-reverts check), all introduced by 5ce34fc50 ("the probe's hidden-bypass_actors refusal states what was observed...", round 3 of the probe): docs/ci.md "The manual `release-ruleset-probe.yml` workflow runs `authz` on" ... "and `probe` on `["self-hosted","pf-gcp-main"]`." (only the probe's label changes); docs/ci.md "Release, promotion and the release ruleset probe use the isolated release fleet:" (the probe leaves the pf-gcp-main list; release, notify-hub, Linux x64 prebuilt and promote stay); the comment "# authz uses pf-gcp-pr; the App-token probe uses pf-gcp-main." in the workflow, in ci_preflight.py and in test_ci_preflight.py (now pf-gcp-post), which also touches the comment lines of 6adae66f and 1d5dfd66 that 5ce34fc50 had rewritten. Also named for the doc-reverts check: the docs/ci.md sentence "`release`, `notify-hub`, Linux x64 `prebuilt`, baseline `promote` and `release-ruleset-probe.yml`'s `probe` job use `["self-hosted","pf-gcp-main"]`." (the second half of the sentence above) was introduced by 49a55b5 ("Merge main (a2bb907b0485, the squash of #483) into the ruleset probe branch", which carried the round-3 probe text) and is rewritten here on purpose: the probe job leaves the pf-gcp-main list because that group admits only allowlisted workflows. Lane: ci-fu-cr486t (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the ruleset probe refuses any dispatch ref other than main before it mints the App token; the runner refusal no longer calls the post fleet isolated (CR-245 F1) Fresh sol read of the pf-gcp-post move (065826e98): moving the probe job to the unrestricted pf-gcp-post group removed the only thing that kept it on main, the restricted pf-gcp-main group's workflow and ref allow-list. A workflow_dispatch with ref=refs/heads/<topic> would run that branch's copy of the workflow, pass authz (which accepts any workflow_dispatch), mint the release App token and run the branch's script; neither the workflow nor the probe checked GITHUB_REF. The CI canon requires manual dispatch to satisfy the same ref restriction and to refuse an unsupported ref before checkout or repository-code execution. The probe job now starts with a step that has no secrets and no token: [ "$REF" = refs/heads/main ] || fail, modelled on promote-baseline.yml's own ref refusal, before the App-token mint and before checkout. The whole-file template and release_rulesets_probe_workflow_findings require that step first and refuse a probe workflow that lacks it, has it after the mint or checkout, in the authz job only, compares another ref, or neutralises its exit (|| true, continue-on-error, if:); 13 mutation rows plus an ordering control and a behavioural dispatch control (a topic ref exits 1 before the mint boundary). The App-token mint step is still byte-identical to release.yml's. docs/ci.md says the dispatch must run from main and the workflow itself refuses any other ref. The guard lives in the workflow file, so it stops mistakes, not a writer who edits the file on their own branch; the lock against that is the environment work (CR-304) that keeps the App secrets away from non-main refs. The per-job runner checker's refusal printed " (isolated release)" for a probe pinned to pf-gcp-post; the post fleet is shared. The suffix is now derived from the pinned label (none for the PR fleet, " (post fleet)" for the post fleet, " (isolated release)" only for the release label) and the pins that expected the old suffix (test_gcp_sites_pin_probe_to_the_dispatch_fleet, test_release_ruleset_probe_refuses_release_and_pr_fleets) expect the new one. Rewrites named for the doc-reverts check: the suffix expression and its test expectations came from 1d5dfd6 and 065826e98; the pf-gcp-post routing and allow-list sentences of 065826e98 and the main-copy dispatch wording of 5ce34fc50 are kept; the generic probe refusal of 6adae66f is kept verbatim. No prose sentence or comment is deleted. Lane: ci-fu-cr486u (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * test(live): serialize startup replay webhook deliveries (#463) * test(live): serialize startup replay webhook deliveries * test(live): assert the delivery order of the startup replay webhooks Both reads of #463 found that nothing asserted the order the serialisation exists to preserve: receipts_by_sequence() returns a dict whose equality ignores order. Adds an ordered raw-byte comparison of the received payloads against the ledger ordinals for the initial delivery and the redelivery, with a reversal control that calls the same check, and a comment that --max-attempts is ignored on the routed path. Lane: ci-m463 fix (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * CR-150 O(1) report integration: the engine 1.7.0 line as one commit over main 9eff925de1 One squash-equivalent commit: parent = engine main 9eff925de1 (#463), tree = the CR-150 picks line merged with that main (docs/ci.md the only conflict: the line's clarification paragraph and main's two baseline-promotion paragraphs are adjacent additions, both kept). The squash body at the merge carries this message verbatim. The 40 documentation sentences below were deleted by commits of the line and are intended: they are the superseded wording of the pages the final 1.7.0 text replaces. scripts/check_doc_reverts.py reads this message for them. Format per line: file | introducer short sha | start of the deleted sentence. Introducers: dbf56ed (docs/design/arena-bound.md 22), 03b2406 (docs/ci.md 7), c7e6bcb (docs/pages/scratch-tables.md 5), 1fa26e7 (scratch-tables.md 3), b4a3f76 (scratch-tables.md 1), 00dec17 (docs/scratch-store.md 1), 2688775 (docs/pages/abi-stability.md 1). docs/pages/abi-stability.md | 2688775 | "The engine validates/attaches the inherited fixed-fd-5 arena or returns the handle's typed `report_sink_failed`/`arena_backing`." docs/pages/scratch-tables.md | 1fa26e7 | 'A jailed arena must be a sealed memfd with at least `F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL`, and the jailed admission (`strategy_require_report_sc' docs/pages/scratch-tables.md | 1fa26e7 | 'Where sealed memfds do not exist (not Linux) the admission accepts the arena the host supplies.' docs/pages/scratch-tables.md | 1fa26e7 | 'With `PF_SCRATCH_ARENA_FD` set, only exactly `"1"` enables it; unset or exactly `"0"` preserves the unjailed backing rules, and every other value (inc' docs/pages/scratch-tables.md | b4a3f76 | 'A jailed arena must be a sealed memfd with at least `F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL`; ordinary files, sealing-disabled memfds and unsupport' docs/pages/scratch-tables.md | c7e6bcb | 'The engine never changes process signal disposition.' docs/scratch-store.md | 00dec17 | 'Below it the drain is held to 4,096 B for every population up to 8,191, where the resident journal events own the definitions the spilled book rows ne' docs/design/arena-bound.md | dbf56ed | 'L18 sibling footer evidence described below; absent from this base' docs/design/arena-bound.md | dbf56ed | '`erased_minima_` `include/pineforge/source/pine_adapter.hpp:N`;' docs/design/arena-bound.md | dbf56ed | '`remember_erased` `src/source/pine_adapter.cpp:N`' docs/design/arena-bound.md | dbf56ed | '`consumed_partial_exit_cycles_` `include/pineforge/source/pine_adapter.hpp:N`;' docs/design/arena-bound.md | dbf56ed | '`src/source/pine_adapter.cpp:N`' docs/design/arena-bound.md | dbf56ed | 'The exact external file:line evidence is recorded in the lane report; these sources must be merged and their local anchors refreshed before claiming c' docs/design/arena-bound.md | dbf56ed | 'Summing all eleven peaks gives:' docs/design/arena-bound.md | dbf56ed | 'There are eleven persistent regions and up to three extra replacement indexes.' docs/design/arena-bound.md | dbf56ed | 'Each independently rounded region wastes less than C.' docs/design/arena-bound.md | dbf56ed | 'Summing independent peaks and adding fourteen complete chunks is conservative even when their real peak times differ.' docs/design/arena-bound.md | dbf56ed | 'This covers chunk fragmentation, not unlisted stores or a missing population proof.' docs/design/arena-bound.md | dbf56ed | 'At L=64 the position term is **94*T**; each placement data term is **104*D+168*Q**; consumed-cycle data is **169*Q**; the root term is **16*R**.' docs/design/arena-bound.md | dbf56ed | "The contract's eleven coefficients agree with the inspected code and L19l-A/C reports." docs/design/arena-bound.md | dbf56ed | 'No numeric correction to those coefficients was needed. “72..104 bytes/key” in the L19l-C steady-state explanation does not include the rehash overlap' docs/design/arena-bound.md | dbf56ed | 'Each of two placement data files' docs/design/arena-bound.md | dbf56ed | 'Each of two placement index peaks' docs/design/arena-bound.md | dbf56ed | 'Eleven contributions in the table above, including both two-file rows.' docs/design/arena-bound.md | dbf56ed | 'D=32,Q=1,L=1,E=1; both 128-slot rehash peaks are 3072;' docs/design/arena-bound.md | dbf56ed | 'No production header declaration is changed.' docs/design/arena-bound.md | dbf56ed | 'Linux linker wrappers reject write/pwrite/ftruncate on unlinked regular scratch files, while allowing the named report sink.' docs/design/arena-bound.md | dbf56ed | 'The current base still has those owning-port dependencies.' docs/design/arena-bound.md | dbf56ed | 'It must not be reported as corpus proof until they land.' docs/pages/scratch-tables.md | c7e6bcb | 'The report sink stays **OFF**.' docs/pages/scratch-tables.md | c7e6bcb | 'The host neither constructs a temporary sink nor uses the sink verification/transposition/staging files; it uses ordinary report arrays and the in-mem' docs/pages/scratch-tables.md | c7e6bcb | 'Optional recorded outputs use the ordinary retained-output reader.' docs/pages/scratch-tables.md | c7e6bcb | 'Keep the report sink OFF and use the ordinary report route.' docs/ci.md | 03b2406 | 'Push this prepared migration only after both CORE-TOP gates: laneboss posts **"pf-ci-release live"** and the owner answers **REL-HOSTED**.' docs/ci.md | 03b2406 | 'Until then a job on that label with no runners only waits, and nothing is pushed.' docs/ci.md | 03b2406 | 'Each job using these two CI fragments uses the expression verbatim and sets its matching array as a job-level **string** `PF_CI_LABELS`, merged into a' docs/ci.md | 03b2406 | 'The pre-job hook for those CI fragments also requires the matching `PF_CI_LABELS` string.' docs/ci.md | 03b2406 | 'It pins the hosted PR deployment job in `docs.yml`, the isolated jobs in `release.yml` and `promote-baseline.yml`, and the complete release matrix wit' docs/ci.md | 03b2406 | 'Both workflows require exact runner values without a fallback and carry no `PF_CI_LABELS`; unlisted credential-bearing jobs retain the existing hosted' docs/ci.md | 03b2406 | 'A credential-bearing job on a PR pool, a restored hosted label on a moved release job, a PR trigger on `release.yml`, a `pull_request` trigger or chan' Second list, 14 more deletions: the change is judged against main 9eff925de1 as one commit, so old main sentences that the 1.7.0 pages replace are deletions too. Introducers 4c78f39, d3cca9e, d093560, 47184e5, 41ca2ee, f48b7c8, a8f84b9, 65c69b0; same format. (Read by ORG-CX from the preflight of the previous single commit.) README.md | 4c78f39 | 'The counts below include recorded outputs and describe the released 1.5.0 API.' docs/design/native-feature-parity.md | d3cca9e | '`sha256:H` c_abi.cpp:N, `sha256:H` check_c_abi_runtime.py:N, c_abi.cpp:N, `sha256:H` c_abi.cpp:N, `sha256:H` c_abi.cpp:N, `sha256:H` c_abi.cpp:N, `sha' docs/native-refactor-progress.md | d093560 | '`WorkingRequestCore::history` (`include/pineforge/native_order.hpp:N`) hands out the whole vector and the core holds `history_` (`include/pineforge/na' docs/native-refactor-progress.md | d093560 | "`decompose_ms_local` (`src/session_time.cpp:N`) reads two thread-locals per call on a zoned clock, and the prepared order storage's `free_blocks` (`sr" docs/pages/index.md | 4c78f39 | 'The counts below include recorded outputs and describe the released 1.5.0 API.' docs/pages/index.md | 47184e5 | '- `<pineforge/execution_observer.h>` and `<pineforge/selected_window.h>` — three each: the six of the 80 runtime implementations that `pineforge.h` do' docs/pages/native-engine.md | 41ca2ee | '`closed_trade(i)` is the row itself, by reference — the object `get_trade(i)` and `get_report_trade(i)` read at the same index and the row `fill_repor' docs/pages/native-engine.md | f48b7c8 | "A run that began owns its rows whatever became of it: a `Completed` report and a started-then-failed run's partial rows are published unchanged." docs/pages/native-engine.md | d093560 | 'A C++ host reads a closed row whole, at any time, every `Trade` field included.' docs/pages/report-schema.md | a8f84b9 | '`pf_report_t` is the single output of every backtest.' docs/pages/report-schema.md | a8f84b9 | 'This page maps each field to its meaning and units.' docs/pages/report-schema.md | a8f84b9 | 'Heap array, ordered by exit time ascending.' docs/pages/run-failure-codes.md | 47184e5 | 'The other 38 codes retain their released 1.4.0 status.' docs/pages/run-failure-codes.md | 65c69b0 | 'Machine memory; retryable, capped.' Also in this commit: S4E's tag-time follow-up 6dfe9daa78 (tests and two doc paragraphs; no src/ or include/ change). Its own message follows verbatim so scripts/check_doc_reverts.py reads the sentences it rewrites or qualifies in docs/design/o1-report-contract.md and docs/pages/run-failure-codes.md. tests, docs: REV's follow-ups on the scratch series: the sink_fd rows name their real subject and cause, the capability row's cells are meaningful and disclosed, the predicate scan covers the signed spellings, the docs state the ENOTSUP exception tests/test_run_json_sink_fd.py: the comment of the unjailed TMPDIR row now says the pre-series failure came from the native writer opening work/bad.sink in the mode-0555 work directory (sink_io), not from TMPDIR; new row test_native_fd_fsize_zero_path_sink_refused_in_read_only_work restores that subject (work 0555, TMPDIR 0755, report_sink_failed sink_io, nothing created); the TMPDIR-cause row runs without an arena, without PINEFORGE_COMMAND_SCRATCH_DIR and PINEFORGE_RUN_JSON_PROFILE, and proves DAC enforcement with an os.open probe first (visible pytest.skip with its reason otherwise). tests/test_default_scratch_effective_credentials.cpp nonroot_row() only (row() and main() unchanged): the setreuid outcome check can fail (EPERM or EINVAL accepted, credentials verified on both branches), the disclosure text is a pure fixture function pinned in test_scratch_capability_parse (every omitted cell "<cell> omitted (<reason>)", "missing, unreadable or malformed"), four more parser cases (0x80, -80, +80, space after the colon). tests/test_scratch_filesystem_predicate.py: the scan and its self-test also cover -2054924042(L) and 0xffffffff858458f6. docs/design/o1-report-contract.md and docs/pages/run-failure-codes.md: one paragraph each states the ENOTSUP exception (no probe, or a real errno 95 from stat/faccessat/statfs, keeps the default candidate as in 1.6.3 with no tmpfs claim and no note; /var/tmp selected without the rejected-candidate note when its probe answers ENOTSUP) and qualifies, with "except as described below", the sentences "A failed filesystem query stops default resolution with its syscall errno and names the failed candidate at every create wrapper; it never permits fallback." and "If creation in the selected `/var/tmp` fails, the detail names that directory, the rejected first candidate and `set TMPDIR to a disk-backed directory`." (o1-report-contract.md) and "A failed query stops resolution with its syscall errno and names the failed candidate." and "failures and all-memory refusals" (run-failure-codes.md); the other sentences keep their words and only their line wrapping changed (same number of lines in each block). No production code changed. Mutations m15 (setreuid EFAULT) and m16 (signed magic). Also in this commit, frozen 2026-10-24 for the 1.7.0 tag (three additions to the tree the CR-150 legs of 6f8f40eca615 measured; src/ and include/ are unchanged): 1. CHANGELOG.md, section "Costs that remain": three new bullets (ORG-CX), 49 lines added and none removed, so no sentence is deleted. They state the costs that remain with the symbols they name: the I/O paths that can terminate or fail silently, the reads of closed trades and cohorts that now cost I/O, and the report-sink cost. 2. tests/test_scratch_arena.cpp (LAB-CX, commit 280c472249 on parent 0f5072d2c5, whose tree equals the one measured; test-only, +25/-4): on the CLOUD Batch VM the container filesystem answers open(O_TMPFILE) with errno 95 (EOPNOTSUPP), measured on lab job rj-20261024t121649-2fe44d, so the row failed there with the bare message "create backing descriptor" while it passes on NTU2 (rj-20261024t121045-c354ce). anonymous_regular_backing now falls back to mkstemp + unlink ONLY when the O_TMPFILE open fails with EOPNOTSUPP, so the anonymous regular-file arena shape is still exercised; it prints a NOTE with the errno when it does. Any other errno still fails, now with "errno N (text)" in the message. Nothing is skipped and no assertion is weakened: after the descriptor exists the row runs the same preallocation, attachment, ensure_size and zero-read checks as before. The engine never opens an O_TMPFILE. Docs addition 3 (ORG-CX R9-F1/F2, docs only, line counts unchanged): three public pages stop promising an envelope run_json never emits and a NULL sink removal that can be refused. Its own message follows verbatim so scripts/check_doc_reverts.py reads the nine sentences it replaces. docs: three public pages stop promising an envelope run_json never emits and a NULL sink removal that can be refused REV's read of the frozen tree (R9-F1, R9-F2): the code and the CHANGELOG are right, three pages are not. report-schema.md (lines 253-267) said explicit-mode JSON carries `report_contract`/`report_capture` and that explicit modes add policy keys and move the fingerprint to version 3. On the `run_json.py` transport an explicit `--report-mode FULL` is the legacy route (docker/run_json.py: "--report-mode FULL is identical to omitting the option"; only SUMMARY_ONLY and STREAMED are refused as `report_mode_unsupported`), so it adds none of those keys; they appear on a host that fills the v1 envelope (C++ `fill_report_envelope_v1`). o1-report-contract.md (lines 47-55) made the same claim for every explicit request. abi-stability.md (lines 165-172) listed -1, -2 and -3 for the sink setters but not that NULL returns -3 while an idle handle still holds a completed run's scratch curve or sealed rows (src/engine_report.cpp set_report_sink_v1 and set_report_sink_fd_v1), that an invalid scratch-arena environment returns -1, or that a completed fd generation detaches and keeps its receipt and summary (include/pineforge/report_sink.h documents all three). Line counts of all three pages are unchanged (818, 584, 672), so no citation moves. Sentences replaced (named for the doc-reverts check): - "Explicit-mode JSON includes `report_contract: "pineforge-report/v1"` and `report_capture`, containing `version`, the mode name, `complete`, and all seven sequence objects above." - "Explicit modes also add `report_contract`, `report_mode`, and `sequence_capture` to `applied_runtime` and fingerprinted `provenance.runtime`." - "Either sink setter replaces the one installed sink, and NULL through either setter removes it." - "Both return 0 on acceptance, -1 for a bad handle or descriptor prefix (or a negative fd), -2 during a stream and -3 during a batch run or report capture." - "Run-begin validation and write errors use the existing typed `report_sink_failed` path." - "The caller keeps the fd open through execution and report capture, then closes it to signal EOF." - "The engine never duplicates or closes a borrowed fd, including on failure, replacement or handle destruction." - "No option keeps the legacy route." - "An explicit request must either return the requested versioned envelope or fail." Frozen2 additions (2026-10-24, after REV's P1 on the adapter retirement sweep, R6a-F1): the tree of the first frozen commit cad8b4bfbc plus exactly four changes, nothing else. 1. src/source/pine_adapter.cpp (CR150-CX, commit f5deaf806a, parent cad8b4bfbc, 6 insertions and 6 deletions): erase_retired_rows keeps a dormant askable-origin exit leg pinned for its cycle, as v1.6.3's K1 pinned it. The retirement sweep released a dormant askable-origin exit leg A when a later same-family, same-source, same-from_entry exit with different stop bits existed (release_superseded), dropped it from the K2/K3 candidates, and let K3 keep it anyway as the first witness of a live exit Y. A's own supersessor B (released by a later C, nobody's witness, no projection_predecessor link) was then erased. revive_brackets_after_margin decides "superseded" only over retained rows, so with B gone and C's stop bits equal to A's, a margin call restored A as a marketable flatten at the margin price under A's exit id: an extra trade that v1.6.3 never books (v1.6.3 pinned every unwithdrawn lifecycle row for the cycle). held_by_cycle and the matching collection of held legs in the unpinned pre-pass now require the leg's origin NOT to be askable, dormant or not: a dormant askable-origin leg is pinned by pinned_by_itself exactly as in v1.6.3 and is never released. Withdrawn legs, non-askable origins and everything else are unchanged. The cost: a script that keeps re-issuing strategy.exit(qty=) over a dormant askable leg without cancelling retains its rows while the origin stays open, as v1.6.3 did. 2. tests/test_k1_askable_origin_supersessor_erasure.cpp and tests/cr150.d/p2cx-k1.cmake (P2-CX, commit b4c5a42dbe, parent cad8b4bfbc; the registration is the tree's own tests/cr150.d/*.cmake glob, so a clean checkout builds it): the K1 reproduction of REV's R6a-F1 tape runs on two fresh hosts, default ERASE and RETAIN-every-row. Per-bar leg, dormancy and position state is printed for both, controls assert that each tape condition held, and the only oracle is erase trades == retain trades (no literal trade list). A red here (erase books an extra margin-price flatten under "X") proves REV's P1. Green on NTU2 with the fix: job rj-20261024t140803-0bf6e2, 8/8, ERASE == RETAIN. 3. CHANGELOG.md (ORG-CX, patch CHANGELOG-171-DORMANT, sha256 1407fc7f980338d4b11ef31ab25aac5b7df3a9a299e8db09757307b708b0b610): a new last bullet of "Costs that remain", "Re-issued exits on a dormant leg keep one placement row per re-issue" (card ORG-171-DORMANT), and one exception clause in the "placement retirement rules (CR-150)" entry. The sentence it changes (named for scripts/check_doc_reverts.py): "for the dormant exit legs of one origin, exit id, family and entry only the oldest is kept as the representative, with superseded dormant legs released; the retired rows leave the hashed placement sequence, so hash values change wherever such a placement retires;" now reads "..., with superseded dormant legs released (except a dormant leg that was not withdrawn while its origin is still open: it is kept until its position cycle ends, see Costs that remain); the retired rows leave the hashed placement sequence, so hash values change wherever such a placement retires;". 4. The message of the first frozen commit is carried verbatim above, with ONE number corrected: "Line counts of all three pages are unchanged (818, 584, 672), so no citation moves." (the first message said 817; docs/pages/report-schema.md has 818 lines). Frozen3-MIN addition (2026-10-25, CORE-TOP/L6 rulings of 01:51 and 02:14): the tree of the second frozen commit c82fd00e8d plus exactly the inputs below, nothing else. No source or header file other than the three named in paragraph 5 changes relative to c82fd00e8d. 5. include/pineforge/engine.hpp, include/pineforge/trade_ledger.hpp, src/engine_orders.cpp, tests/cr150.d/p2cx-tledger.cmake, tests/p2cx_tledger_fixture.hpp, tests/test_p2cx_tledger_budget.cpp, tests/test_p2cx_tledger_io.cpp (P2-CX, the cumulative patch of commits 65487753da and d21ea8d416 on c82fd00e8d, sha256 05b77f191b974e09f028a6b0d9942798ec1a44e20c9de4410866e7e01cd5d3e6, 1181 insertions and 46 deletions; the tests register through the tree's own tests/cr150.d/*.cmake glob): TradeLedger gets a budgeted read cache of decoded closed rows and per-field accessors. A script that reads strategy.closedtrades every bar decoded each historical row per field call (an index pread below the 64-row descriptor window and four string preads outside the 10752-byte payload tail) and every Pine accessor built a whole Trade by value to read one field. Now TradeLedger::<field>_at(i) returns one field straight from the cached record (numeric fields build no Trade and no string; string fields read only the requested packed bytes), with the same checked-row contract as at(); the closed_trade_* accessors in engine.hpp use them, with values, error behaviour and percent formulas unchanged. The cache is direct-mapped by global index; the default budget is 65,536 rows (maximum 1,048,576); one entry is a 168-byte record plus 16 bytes of key and generation plus 128 inline string bytes, 312 bytes (20,447,232 bytes at the default); a row whose four strings exceed 128 bytes keeps its numeric fields cached and reads strings from scratch; the array grows lazily to min(budget, next power of two >= max(64, rows)), so memory is bounded by the budget independent of bars. seal() no longer invalidates the cache (publication changes no row content and the checked accessors refuse sealed rows before reaching the cache); replace, rewind (resize), move and clear invalidate it; replace() reuses an unchanged string extent instead of appending the same bytes again. kResidentRows (64), kRecordBytes, resident_size() and resident_base() are unchanged. No environment variable or run-spec field controls the budget: the default is what runs (C++ API only). The budget setter's refusal still uses setting_rejected until the shared resident-budget code lands. Tests: test_trade_ledger, test_p2cx_tledger_io (syscall counter, zero reads and writes in a hungpixi-shaped 24,238-row loop after warm-up, oracle-compared) and test_p2cx_tledger_budget (budget formula, lazy growth boundaries, wide-string fallback, replace, rewind and move invalidation, sealed-row refusal for every accessor); P2-CX reports them Passed on NTU2 (job rj-20261024t175023-a20f66) with mutation rows red on the intended assertions. SM-CX measured this patch on frozen2: hungpixi 106.14 to 107.58 s over four repetitions against 72.84 to 73.55 s on 1.6.3, trades identical in every cell; the engine still makes scratch reads and writes per bar on other paths (id tables, exit scratch, journal), which is the cost named in the CHANGELOG. 6. CHANGELOG.md and documentation pages (ORG-CX; patches CHANGELOG-171-DORMANT-V2, OVERCLAIM-170-v4, REPIN-FROZEN3MIN-NOPAGE, CHANGELOG-170-SLOWER-FILLED-v3 and CHANGELOG-LEDGERCACHE-v2, applied in that order after paragraph 5; CHANGELOG-LEDGERCACHE-v2 rewrites the opening of the "Reads of closed trades and cohorts now cost I/O" bullet because the closed-trade read cache of paragraph 5 makes its statement about scratch reads on every call false: it now states the cache, its default budget of 65,536 rows of 312 bytes (20,447,232 bytes at most per engine), its lazy allocation at the first closed trade of every Pine run, that above 65,536 closed trades it is direct-mapped so a loop over the whole history re-reads colliding rows from scratch files on every pass, and that no user control sets it in this release): the dormant-leg bullet of "Costs that remain" is rewritten (V2), nine over-broad memory sentences are corrected (v4), the engine.hpp citation numbers in docs/adr/0001-kernel-adapter-boundary.md, docs/design/native-feature-parity.md, docs/pages/native-engine.md and docs/pages/pine-to-native.md are re-pinned by the anchor tool because paragraph 5 moves engine.hpp lines by one or two (the box anchor check reports every documentation anchor resolved), and a new first bullet of "Costs that remain" states that 1.7.0 is slower than 1.6.3 on some scripts with the figures SM-CX measured (no existing sentence is replaced by that bullet). No memory-management page is added. The sentences replaced or deleted by this paragraph, named for scripts/check_doc_reverts.py: - "The bounded paths of this release are the trade ledger, the report sink and the command journals described above." (CHANGELOG.md, Costs that remain) - "The complexity in the number of rows is unchanged; the cost per row is not." (CHANGELOG.md, Costs that remain) - "Origin/cohort receipts, bracket rosters, historical IDs, successor roots and position tails use retirement, bounded resident windows or scratch instead of retaining all history in RAM." (CHANGELOG.md, Adapter history retirement) - "the production Pine host uses KernelRecordedAtHostMarks (src/source/pine_adapter.cpp:2870), whose recorded curve is held in O(1) and streamed to the scratch file." (docs/design/cr150-contracts.md) - "Complete enabled sequences, in existing report order, through a bounded sink" (docs/pages/report-schema.md, STREAMED row) - "Fixed slab for historical table bytes, prepared before the run, with count/byte admission and no growth or disk fallback on exhaustion." (docs/pages/scratch-tables.md, bounded_memory row) - "For the short waiting-child parent-terminal fixture, the book's 4,096-row cap and the journal's 2,048-event cap give a per-engine definition plateau of 1,890,980 bytes (about 1.9 MB) on GNU 13.4 x86-64, independent of symbols, window and trades." (docs/pages/scratch-tables.md) - "caps (book 4,096 + journal 2,048 events), constant, independent of symbols, window and trades." (docs/scratch-store.md, resident bound of the parent-terminal drain) - "for the dormant exit legs of one origin, exit id, family and entry only the oldest is kept as the representative, with superseded dormant legs released; the retired rows leave the hashed placement sequence, so hash values change wherever such a placement retires;" (CHANGELOG.md) - "**Reads of closed trades and cohorts now cost I/O.** The prefix sums described above apply to sealed rows only. Unless history is streamed, the trade ledger keeps 64 rows resident and reads older closed rows, text included, back from scratch files on every call, where the previous release walked an in-memory vector (`std::vector<Trade>`)." (CHANGELOG.md, Costs that remain, closed-trade reads bullet; rewritten by CHANGELOG-LEDGERCACHE-v2) - "`BacktestEngine::avg_trade_percent`, `avg_winning_trade_percent`, `avg_losing_trade_percent` and the `closed_trade_*` accessors therefore read every older row per call, so a script that calls them each bar pays one scratch read per row per call." (CHANGELOG.md, same bullet; rewritten by CHANGELOG-LEDGERCACHE-v2) Every citation line changed by the anchor re-pin differs from main only in a line number inside the same sentence; the doc-reverts checker normalises numbers, so none of them is named above. The message of the second frozen commit is carried verbatim above (frozen2 paragraphs 1 to 4, which in turn carry the first frozen commit's message). 7. tests/test_p2cx_tledger_io.cpp (P2-CX, commit ef4c628470, a test-only fix of +13 lines, merged with the rest as one squash): the test replaced the throwing global operator new and operator delete but not the std::nothrow_t forms, which CommandScratchStore uses (include/pineforge/detail/scratch_store_impl.hpp:1755), so AddressSanitizer reported alloc-dealloc-mismatch and the sanitizers job rj-20261024t184800-7cdb3a on the earlier head failed on that one test; the fix replaces the nothrow new, new[], delete and delete[] forms with the same allocation counter. No src or include file changes. 8. include/pineforge/trade_ledger.hpp, src/native_execution_consumer.cpp, tests/test_p2cx_tledger_budget.cpp (P2-CX, commit 38e00f30e8, +18 lines, merged with the rest as one squash): the closed-trade read cache of paragraph 5 is bounded when a report sink is installed. NativeExecutionConsumer::begin_ready sets the ledger read-cache budget at every run begin: TradeLedger::kSinkCacheRows = 128 rows when a report sink is installed (report_sink_state_.enabled), otherwise TradeLedger::kDefaultCacheRows = 65,536 rows as before; a cache entry is 312 bytes, so the cache is at most 128 x 312 = 39,936 bytes with a sink, independent of the trade count, and at most 20,447,232 bytes without one. Two static_asserts in tests/test_p2cx_tledger_budget.cpp pin 128 x 312 under one 64 KiB scratch window. Why: test_selected_metrics_l17b, the CR-150 witness that report-sink trade capture and reads stay inside 2 x report_sink::kScratchWindowBytes at 1,000 and at 100,000 closed trades, failed on the earlier head in the kernel leg (rj-20261024t184803-97a657), the plain release leg (rj-20261024t184754-79978c) and the private release leg (rj-20261024t184821-bbd361), each with exactly that one test failing and a printed 100k largest allocation of 20,447,232 bytes (319,488 at 1k): the cache was allocated at the first closed trade of every Pine run and grew with the trade count up to its cap, in sink runs too. With the 128-row bound the sink-on largest allocation and peak stay under the witness budget and equal at 1k and 100k; the witness itself is not changed. P2-CX's lab proof of the fix: NTU2 job rj-20261024t193800-40c004 (21 of 21 Passed, including test_selected_metrics_l17b), and the red reproduction on the parent commit (rj-20261024t193803-675fc0). A sink-on loop over more than 128 closed trades re-reads colliding rows from scratch files, which the CHANGELOG states. 9. CHANGELOG.md and seven documentation files, as two commits applied after paragraph 8 (ORG-CX): (a) patch CHANGELOG-LEDGERCACHE-v3-DELTA-on-69467f7cc2 (sha256 5841f8c3bd9e3993e04cea7038de38eaefd632c7d358276da3046babefc04ea9) rewrites the closed-trade cache bullet of "Costs that remain" for the sink-on bound: sink off at most 65,536 rows of 312 bytes (20,447,232 bytes), sink on a fixed 128 rows (39,936 bytes), the direct-mapped miss cost for both budgets, and that begin_ready sets the budget with no user control. It replaces these sentences of the earlier text of this change (they never existed on main): "The cache holds at most `TradeLedger::kDefaultCacheRows` = 65,536 rows of `TradeLedger::kLedgerEntryBytes` = 312 bytes, 20,447,232 bytes, and is allocated at the smaller of that budget and the next power of two at or above the number of closed rows (at least 64): 19,968 bytes up to 64 closed trades, 10,223,616 bytes at 24,238, 20,447,232 bytes above 32,768." and "It stays until the next run begins or the engine is destroyed, and each engine holds its own, so K engines alive together hold K times that." and "Above 65,536 closed tr…
luisleo526
added a commit
that referenced
this pull request
Oct 25, 2026
…#481) * test(ci): pin equivalence CTest row and prerequisite contract (CR-223-ROW) * test(ci): require equivalence execution and corpus LFS provisioning * test(ci): extend verifier partition and native row accounting * ci(live): run corpus equivalence as a required CTest row (CR-223-ROW) The corpus equivalence driver becomes a permanent runner CTest row, and the runner-e2e equivalence exclusion leaves `ci_verify.py`. Two sentences of `docs/ci.md` that dbd17b3 ("Live runner: normalized feed input and per-action webhook routing (#320)") introduced are retired here because this change makes them false, and the message names them so the doc-revert gate can read the retirement: - "The standalone corpus equivalence driver is not a runner CTest row; its harness unit tests remain in the engine-wide inventory." The driver is now the `native_live_equivalence_e2e` row in `runner/CMakeLists.txt`, and the same sentence's second clause (the harness unit tests staying in the engine-wide inventory) is kept verbatim in the replacement text. - "It refuses label exclusions, missing runner compile commands, missing ASan/UBSan or frame-pointer flags, skipped/disabled tests, and, while `runner/transport.cpp` exists, a missing WebSocket row or transport skip even without `--require-websocket`." Its "skipped/disabled tests" clause is rewritten because a required runner e2e skip now fails outright; every other clause is kept in the replacement paragraph. Restored on purpose: nothing. The rest of the diff only adds text. * ci: release ruleset probe lists every branch ruleset (measurement only, gate unchanged) [replaces #489] (#505) * ci(release): the ruleset probe lists every branch ruleset as visible or hidden, even when the gate verdict is a refusal (CR-245 F1 measurement) The one dispatch of release-ruleset-probe.yml from main (run 40083495967) refused on the first branch ruleset of the inventory, the organization ruleset `default` (13094758): "returned missing or invalid bypass_actors (token cannot see this ruleset's bypass_actors)". The verdict reader stops at the first failure, so the run did not say whether the release App token can see bypass_actors on the repository ruleset that is the real barrier (21476386), nor on any other. The probe now first prints one line per branch ruleset of the includes_parents inventory (id, name, source_type, source, enforcement, updated_at, bypass_actors visible with the actor count or hidden, applies to main, has a pull_request rule), then runs the unchanged verdict: success prints the existing success lines; a refusal prints the same ::error:: text on stderr and exits 1, but stdout now carries the listing. A detail read that fails is reported on its own row and the listing continues. The listing is a measurement: `gate`, `_release_bump` and `_require_release_rulesets` are untouched, every gate test and the existing source mutants are kept, and the probe's verdict replay over every existing ruleset fixture still compares verdict and refusal text byte for byte. The workflow release-ruleset-probe.yml is not changed. The listing reads each branch ruleset detail independently of the verdict read (two reads per detail in the probe), which docs/ci.md now says. Red-first rows: the real situation (org ruleset hidden, repository ruleset visible: both listed, the org one hidden, refusal unchanged), both hidden, one detail read failing, all visible with a valid barrier, the read-only call list, and a source mutant that drops the listing. Rewrites named for the doc-reverts check, all introduced by 9c18d41 (#488, "ci(release): read-only ruleset probe for the release gate (CR-245 F1)"): docs/ci.md "Success prints the barrier id/name and one compact inventory line with enforcement, source and bypass visibility."; docs/ci.md "Refusal exits 1 with empty stdout and the reader's `::error::` diagnostic; API/response failures name the endpoint."; docs/ci.md "Two hidden rulesets need two dispatches: the first refusal names only the first hidden ruleset; make its `bypass_actors` visible before dispatching again to identify the second." (replaced: all hidden rulesets are listed in one dispatch); the probe docstring "Read exactly the bump's ruleset evidence, with the same refusal policy."; two test comments ("The reader report's complete [] inventory fails on main without an endpoint." and "The runtime assertions already hold there; the prose does not."). The module docstring sentence "Each endpoint/page is read at most once; failures refuse without retry or sleep." (ad1ebac, #479) is scoped to the gate's reads. Lane: ci-fu-cr488m (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe listing bounds its inventory=ERROR row to 240 characters like the detail-error rows (CR-245 F1 measurement, sol P2) The new stdout listing printed the whole exception text on an `inventory=ERROR` row, while the per-ruleset `detail=ERROR` rows truncate it to 240 characters; a `gh` transport error with 300 characters of stderr was echoed in full. The row now applies the same bound ([:240], still !r-escaped). Four rows assert the exact bounded line (an inventory transport error, a repository transport error, a default-branch rejection with a long repository argument, an invalid summary with a long field) together with the unchanged stderr, exit code 1 and API call list of the verdict; each has a mutation control that removes only the bound and must fail. The verdict functions are untouched and the verdict's own stderr is not bounded. No documentation sentence is changed (none described a length limit). Lane: ci-fu-cr488n (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci: fully qualify the two remote-tracking refs the promote-baseline ancestry check and the doc-reverts default base read (ORG-ACTIONPINS-CARD) [replaces #494] (#507) * ci: fully qualify the two remote-tracking refs the promote-baseline ancestry check and the doc-reverts default base read (ORG-ACTIONPINS-CARD, LB-464 P3) promote-baseline.yml tested `git merge-base --is-ancestor "$MERGE_SHA" "origin/$BASE_REF"`, and check_doc_reverts.py default_base() resolved the bare names `origin/main` then `main`. A short name resolves through git's ref-lookup order, so a tag called `origin/main` (or `main`) would win over the remote-tracking branch or the local branch. Both now use the fully qualified names (`refs/remotes/origin/$BASE_REF`; `refs/remotes/origin/main`, then `refs/heads/main`, same order and same fallback to the merged-pull-request walk). The exact-text pin in scripts/ci_preflight.py is updated to the new command and stays as strict; the old short form is now a finding. Red-first tests: the pin refuses the short command and accepts the qualified one; a repository where a TAG named `origin/main` points at an unrelated commit and the real remote-tracking ref points elsewhere, and the same for a tag named `main` with no remote-tracking ref, each returns the merge base of the real ref and not the tag's. Five controls keep the order, the environment override, the local fallback, the merged-pull-request fallback and the no-base result. The first-parent walk is untouched (the lab-host defect is a separate card). The default_base docstring in scripts/check_doc_reverts.py is re-wrapped around the two renamed refs; the sentences it carries come from 1e360bd and d093560 and keep their wording, only the ref names change. Lane: ci-fu-shortrefs (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci: release ruleset probe lists every branch ruleset (measurement only, gate unchanged) [replaces #489] (#505) * ci(release): the ruleset probe lists every branch ruleset as visible or hidden, even when the gate verdict is a refusal (CR-245 F1 measurement) The one dispatch of release-ruleset-probe.yml from main (run 40083495967) refused on the first branch ruleset of the inventory, the organization ruleset `default` (13094758): "returned missing or invalid bypass_actors (token cannot see this ruleset's bypass_actors)". The verdict reader stops at the first failure, so the run did not say whether the release App token can see bypass_actors on the repository ruleset that is the real barrier (21476386), nor on any other. The probe now first prints one line per branch ruleset of the includes_parents inventory (id, name, source_type, source, enforcement, updated_at, bypass_actors visible with the actor count or hidden, applies to main, has a pull_request rule), then runs the unchanged verdict: success prints the existing success lines; a refusal prints the same ::error:: text on stderr and exits 1, but stdout now carries the listing. A detail read that fails is reported on its own row and the listing continues. The listing is a measurement: `gate`, `_release_bump` and `_require_release_rulesets` are untouched, every gate test and the existing source mutants are kept, and the probe's verdict replay over every existing ruleset fixture still compares verdict and refusal text byte for byte. The workflow release-ruleset-probe.yml is not changed. The listing reads each branch ruleset detail independently of the verdict read (two reads per detail in the probe), which docs/ci.md now says. Red-first rows: the real situation (org ruleset hidden, repository ruleset visible: both listed, the org one hidden, refusal unchanged), both hidden, one detail read failing, all visible with a valid barrier, the read-only call list, and a source mutant that drops the listing. Rewrites named for the doc-reverts check, all introduced by 9c18d41 (#488, "ci(release): read-only ruleset probe for the release gate (CR-245 F1)"): docs/ci.md "Success prints the barrier id/name and one compact inventory line with enforcement, source and bypass visibility."; docs/ci.md "Refusal exits 1 with empty stdout and the reader's `::error::` diagnostic; API/response failures name the endpoint."; docs/ci.md "Two hidden rulesets need two dispatches: the first refusal names only the first hidden ruleset; make its `bypass_actors` visible before dispatching again to identify the second." (replaced: all hidden rulesets are listed in one dispatch); the probe docstring "Read exactly the bump's ruleset evidence, with the same refusal policy."; two test comments ("The reader report's complete [] inventory fails on main without an endpoint." and "The runtime assertions already hold there; the prose does not."). The module docstring sentence "Each endpoint/page is read at most once; failures refuse without retry or sleep." (ad1ebac, #479) is scoped to the gate's reads. Lane: ci-fu-cr488m (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe listing bounds its inventory=ERROR row to 240 characters like the detail-error rows (CR-245 F1 measurement, sol P2) The new stdout listing printed the whole exception text on an `inventory=ERROR` row, while the per-ruleset `detail=ERROR` rows truncate it to 240 characters; a `gh` transport error with 300 characters of stderr was echoed in full. The row now applies the same bound ([:240], still !r-escaped). Four rows assert the exact bounded line (an inventory transport error, a repository transport error, a default-branch rejection with a long repository argument, an invalid summary with a long field) together with the unchanged stderr, exit code 1 and API call list of the verdict; each has a mutation control that removes only the bound and must fail. The verdict functions are untouched and the verdict's own stderr is not bounded. No documentation sentence is changed (none described a length limit). Lane: ci-fu-cr488n (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: ci-cx <ci-cx@invalid> Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci(release): the permission-claim text guard also catches backslash-wrapped, bytes-decoded and fullwidth respellings (PROBE-FU) [replaces #493] (#506) * ci(release): the permission-claim text guard also catches backslash-wrapped, bytes-decoded and fullwidth respellings (PROBE-FU, sol P2 1/2/4) The guard in WorkflowPinTests.assert_ruleset_visibility_documentation fails when the gate source, docs/ci.md or release.yml state that the App needs "administration: read". A read of the probe found three respellings it accepted: a Markdown hard break written as a backslash before the newline, a bytes constant (b"administr\x61tion: re\x61d", with or without .decode()), and fullwidth compatibility letters. The collapsed view now treats backslash-newline like whitespace, bytes constants are decoded into the scanned strings, and every view is NFKC-normalised before case folding. Three new tests (12 rows) each read the real artifacts, assert the unmodified baseline passes, mutate a copy and require the specific permission-claim AssertionError; removing only the matching handling turns its rows red. Not closed, by design: YAML double-quoted scalars with escapes in release.yml (sol P2 3) and runtime string construction (sol P3); a static text guard cannot decide them, and the exact-output pins on the refusal text remain the decisive protection. No existing assertion, control or forbidden pattern is changed. The rewritten `view` tuple is code, not a documentation or comment sentence. Lane: ci-fu-pfuguard (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the permission-claim guard scans the casefold-only views as well as the NFKC views (PROBE-FU round 2, sol R237 P2) The first PROBE-FU commit normalised every scanned view with NFKC and replaced the casefold-only views. For an input such as `Needs administration: read` followed by the trade mark sign, the old guard matched (the sign is a non-word character after `read`), but after NFKC the text is `readtm` and the word boundary no longer matches, so the claim was accepted: the change made the guard weaker for that input. Each text is now checked in four views (the two original views casefolded, and both NFKC+casefolded), so normalisation can only add matches. A restoration control appends the trade mark case to the gate source, docs/ci.md and the workflow text (direct and line-wrapped, six rows), and a removal mutant drops the casefold-only scan and must break all six rows. No existing assertion or control changes; no documentation or comment sentence is deleted or rewritten. Lane: ci-fu-pfuguard2 (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci: release ruleset probe lists every branch ruleset (measurement only, gate unchanged) [replaces #489] (#505) * ci(release): the ruleset probe lists every branch ruleset as visible or hidden, even when the gate verdict is a refusal (CR-245 F1 measurement) The one dispatch of release-ruleset-probe.yml from main (run 40083495967) refused on the first branch ruleset of the inventory, the organization ruleset `default` (13094758): "returned missing or invalid bypass_actors (token cannot see this ruleset's bypass_actors)". The verdict reader stops at the first failure, so the run did not say whether the release App token can see bypass_actors on the repository ruleset that is the real barrier (21476386), nor on any other. The probe now first prints one line per branch ruleset of the includes_parents inventory (id, name, source_type, source, enforcement, updated_at, bypass_actors visible with the actor count or hidden, applies to main, has a pull_request rule), then runs the unchanged verdict: success prints the existing success lines; a refusal prints the same ::error:: text on stderr and exits 1, but stdout now carries the listing. A detail read that fails is reported on its own row and the listing continues. The listing is a measurement: `gate`, `_release_bump` and `_require_release_rulesets` are untouched, every gate test and the existing source mutants are kept, and the probe's verdict replay over every existing ruleset fixture still compares verdict and refusal text byte for byte. The workflow release-ruleset-probe.yml is not changed. The listing reads each branch ruleset detail independently of the verdict read (two reads per detail in the probe), which docs/ci.md now says. Red-first rows: the real situation (org ruleset hidden, repository ruleset visible: both listed, the org one hidden, refusal unchanged), both hidden, one detail read failing, all visible with a valid barrier, the read-only call list, and a source mutant that drops the listing. Rewrites named for the doc-reverts check, all introduced by 9c18d41 (#488, "ci(release): read-only ruleset probe for the release gate (CR-245 F1)"): docs/ci.md "Success prints the barrier id/name and one compact inventory line with enforcement, source and bypass visibility."; docs/ci.md "Refusal exits 1 with empty stdout and the reader's `::error::` diagnostic; API/response failures name the endpoint."; docs/ci.md "Two hidden rulesets need two dispatches: the first refusal names only the first hidden ruleset; make its `bypass_actors` visible before dispatching again to identify the second." (replaced: all hidden rulesets are listed in one dispatch); the probe docstring "Read exactly the bump's ruleset evidence, with the same refusal policy."; two test comments ("The reader report's complete [] inventory fails on main without an endpoint." and "The runtime assertions already hold there; the prose does not."). The module docstring sentence "Each endpoint/page is read at most once; failures refuse without retry or sleep." (ad1ebac, #479) is scoped to the gate's reads. Lane: ci-fu-cr488m (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe listing bounds its inventory=ERROR row to 240 characters like the detail-error rows (CR-245 F1 measurement, sol P2) The new stdout listing printed the whole exception text on an `inventory=ERROR` row, while the per-ruleset `detail=ERROR` rows truncate it to 240 characters; a `gh` transport error with 300 characters of stderr was echoed in full. The row now applies the same bound ([:240], still !r-escaped). Four rows assert the exact bounded line (an inventory transport error, a repository transport error, a default-branch rejection with a long repository argument, an invalid summary with a long field) together with the unchanged stderr, exit code 1 and API call list of the verdict; each has a mutation control that removes only the bound and must fail. The verdict functions are untouched and the verdict's own stderr is not bounded. No documentation sentence is changed (none described a length limit). Lane: ci-fu-cr488n (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: ci-cx <ci-cx@invalid> Co-authored-by: Claude Sonnet 5.5 <[email protected]> * tests: drop the TIMEOUT 180 ctest property (owner's no-time-limit rule, P2-CM-F4) (#504) * tests: drop the TIMEOUT 180 property from the six ownership manifest tests (P2-CM-F4) One set_tests_properties call carried TIMEOUT 180 for test_selected_window_ownership_manifest, the four test_selected_window_ownership_manifest_controls_shard_{1..4}of4 and test_selected_window_ownership_d4. The owner's no-time-limit rule forbids a ctest ceiling: under pool contention shard 1of4 (119 s normally) hit the 180 s ceiling on the b250ceb merge-commit ci-post (#283). WORKING_DIRECTORY is kept; no test, label or line count changes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci: release ruleset probe lists every branch ruleset (measurement only, gate unchanged) [replaces #489] (#505) * ci(release): the ruleset probe lists every branch ruleset as visible or hidden, even when the gate verdict is a refusal (CR-245 F1 measurement) The one dispatch of release-ruleset-probe.yml from main (run 40083495967) refused on the first branch ruleset of the inventory, the organization ruleset `default` (13094758): "returned missing or invalid bypass_actors (token cannot see this ruleset's bypass_actors)". The verdict reader stops at the first failure, so the run did not say whether the release App token can see bypass_actors on the repository ruleset that is the real barrier (21476386), nor on any other. The probe now first prints one line per branch ruleset of the includes_parents inventory (id, name, source_type, source, enforcement, updated_at, bypass_actors visible with the actor count or hidden, applies to main, has a pull_request rule), then runs the unchanged verdict: success prints the existing success lines; a refusal prints the same ::error:: text on stderr and exits 1, but stdout now carries the listing. A detail read that fails is reported on its own row and the listing continues. The listing is a measurement: `gate`, `_release_bump` and `_require_release_rulesets` are untouched, every gate test and the existing source mutants are kept, and the probe's verdict replay over every existing ruleset fixture still compares verdict and refusal text byte for byte. The workflow release-ruleset-probe.yml is not changed. The listing reads each branch ruleset detail independently of the verdict read (two reads per detail in the probe), which docs/ci.md now says. Red-first rows: the real situation (org ruleset hidden, repository ruleset visible: both listed, the org one hidden, refusal unchanged), both hidden, one detail read failing, all visible with a valid barrier, the read-only call list, and a source mutant that drops the listing. Rewrites named for the doc-reverts check, all introduced by 9c18d41 (#488, "ci(release): read-only ruleset probe for the release gate (CR-245 F1)"): docs/ci.md "Success prints the barrier id/name and one compact inventory line with enforcement, source and bypass visibility."; docs/ci.md "Refusal exits 1 with empty stdout and the reader's `::error::` diagnostic; API/response failures name the endpoint."; docs/ci.md "Two hidden rulesets need two dispatches: the first refusal names only the first hidden ruleset; make its `bypass_actors` visible before dispatching again to identify the second." (replaced: all hidden rulesets are listed in one dispatch); the probe docstring "Read exactly the bump's ruleset evidence, with the same refusal policy."; two test comments ("The reader report's complete [] inventory fails on main without an endpoint." and "The runtime assertions already hold there; the prose does not."). The module docstring sentence "Each endpoint/page is read at most once; failures refuse without retry or sleep." (ad1ebac, #479) is scoped to the gate's reads. Lane: ci-fu-cr488m (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe listing bounds its inventory=ERROR row to 240 characters like the detail-error rows (CR-245 F1 measurement, sol P2) The new stdout listing printed the whole exception text on an `inventory=ERROR` row, while the per-ruleset `detail=ERROR` rows truncate it to 240 characters; a `gh` transport error with 300 characters of stderr was echoed in full. The row now applies the same bound ([:240], still !r-escaped). Four rows assert the exact bounded line (an inventory transport error, a repository transport error, a default-branch rejection with a long repository argument, an invalid summary with a long field) together with the unchanged stderr, exit code 1 and API call list of the verdict; each has a mutation control that removes only the bound and must fail. The verdict functions are untouched and the verdict's own stderr is not bounded. No documentation sentence is changed (none described a length limit). Lane: ci-fu-cr488n (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci: pin every third-party action to a full commit SHA with a version comment, and lint it in preflight (ORG-ACTIONPINS) [replaces #495] (#508) * ci: pin every third-party action in the engine workflows to a full commit SHA with a version comment (ORG-ACTIONPINS) 66 `uses:` lines changed in .github/workflows/*.yml and .github/actions/setup-live-curl/action.yml: 65 mutable tags rewritten to `@<40-hex sha> # v<version>` and one bare SHA that lacked its comment. Each pin is the same major the file used before; no step, job, permission, environment, secret or token mint changes (a before/after comparison of the diff with the refs stripped is identical line for line). Resolved on 2026-10-24 22:48 with `git ls-remote --tags` on a spot box; every tag is lightweight, so the tag's sha is the commit: actions/checkout v4 11d5960a326750d5838078e36cf38b85af677262 v4.4.0 actions/upload-artifact v4 ea165f8d65b6e75b540449e92b4886f43607fa02 v4.6.2 actions/setup-python v5 a26af69be951a213d495a4c3e4e4022e16d87065 v5.6.0 actions/cache, cache/save, cache/restore v4 0057852bfaa89a56745cba8c7296529d2fc39830 v4.3.0 actions/create-github-app-token v1 d72941d797fd3113feb6b93fd0dec494b13a2547 v1.12.0 cloudflare/wrangler-action v3 9acf94ace14e7dc412b076f2c5c20b8ce93c79cd v3.15.0 The byte-pinned copies of the workflow text that scripts/ci_preflight.py, scripts/test_ci_preflight.py and scripts/test_ci_release_gate.py compare against (the embedded probe workflow, the release and cache expectations, the Wrangler deployment, test mutations and fixtures) are moved to the same pinned text so each still equals its workflow. Two tests read the real workflows and composite action and require a 40-hex sha and a version comment on every third-party reference. No documentation or comment sentence is deleted or rewritten. Lane: ci-fu-pins (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(preflight): refuse any third-party `uses:` that is not a 40-hex commit SHA with a same-line version comment; the lint accepts plain block-style YAML only (ORG-ACTIONPINS) scripts/ci_preflight.py gains `action_pin_findings`: every `uses:` in .github/workflows/*.yml and every composite .github/actions/**/action.yml (or .yaml) that is not a local `./` path or a `docker://` image must be `owner/action[/path]@<40 lowercase hex> # v<version>` on the same line; quoted values, step and job-level `uses:`, remote reusable workflows and a ref-less `uses:` are judged; the finding names file, line and the offending reference. The lint is a regression guard for our own reviewed workflow files, so it does not parse arbitrary YAML: it accepts plain block-style YAML and refuses, with a finding naming file, line, construct and source text, what it cannot follow: anchors, aliases, merge keys, explicit keys, tags, non-empty flow mappings, quoted keys, backslash escapes in quoted values, quoted values that do not end on their line, tab indentation, document markers, `uses` without a same-line scalar, unrecognised block lines, non-ASCII whitespace in structural positions, and any non-LF line break (bare CR, NEL, LS, PS, VT, FF) or C0 control character in the whole file. Allowed (surveyed on the real tree, twelve YAML files): plain block mappings and sequences, plain and quoted scalar values, empty `{}` and `[]` as a whole plain value, non-empty flow sequences, block-scalar bodies including `- |`. Quotes inside flow sequences count only at element start; delimiters of a refused flow collection are counted without quote masking and the refusal region ends at the first outdent. The `${{ ... }}` scan is linear. The loaders read the bytes and decode strict UTF-8 (a file that is not UTF-8 is a finding), so a bare CR is no longer erased before the refusal runs. The documented limit for exotic YAML is in docs/ci.md. The lint was read by sol and argon over five rounds (every finding closed or classified); the loader and the three promotion-guard tests that now also see the lint's quoted-key finding were fixed from the lab legs. Tests: 15 first-round and the later red-first tests (one per refused construct, each break character in a block-scalar body and in a comment, the C0 controls, flow-quote variants, outdent resumption, loader tests on real files in a temp layout, a real-tree zero-findings test, mutation controls for the refusal branch and the integration call). docs/ci.md describes the rule. This commit is the squash of the lint rounds of the series; the pins commit before it rewrites the workflow files. Lanes: ci-fu-pinlint, ci-fu-pins2/3/4/5 (Codex gpt-6.1-sol), ci-fu-pins6 and pins7 (Claude Sonnet 5.5 via claudock). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: ci-cx <ci-cx@invalid> Co-authored-by: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> Co-authored-by: ci-cx <ci-cx@invalid>
luisleo526
added a commit
that referenced
this pull request
Oct 25, 2026
* docs: release-notes backfill for 1.6.2, 1.6.3 and 1.7.0 (README Releases list, CHANGELOG sections) (CR-315) The README Releases list stopped at v1.6.1 and the CHANGELOG had no 1.6.2 or 1.6.3 section (1.7.0 sat directly above 1.6.1). This adds, from the tags and the GitHub release bodies only: - README.md Releases: v1.7.0 (2026-10-25), v1.6.3 (2026-10-23), v1.6.2 (2026-10-21), each with its release link, the codegen it pairs with and the CHANGELOG anchor. - CHANGELOG.md: "1.6.3 - 2026-10-23" (CI, release-gate and test-only; `git diff v1.6.2 v1.6.3` under src/ include/ cmake/ docker/ runner/ shows only docker/run_failure_codes_diff.json) and "1.6.2 - 2026-10-21" (selected-window contract revision 1.3 with the EN-115 completeness proof, proportional annualisation for runs shorter than a day, CI changes; the maintainer-built linux-x86_64 archive and its SHA-256 as the release body states them). Additions only (0 lines removed), so no sentence is replaced and no citation moves; the engine README and CHANGELOG carry no file:line citations. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci: fully qualify the two remote-tracking refs the promote-baseline ancestry check and the doc-reverts default base read (ORG-ACTIONPINS-CARD) [replaces #494] (#507) * ci: fully qualify the two remote-tracking refs the promote-baseline ancestry check and the doc-reverts default base read (ORG-ACTIONPINS-CARD, LB-464 P3) promote-baseline.yml tested `git merge-base --is-ancestor "$MERGE_SHA" "origin/$BASE_REF"`, and check_doc_reverts.py default_base() resolved the bare names `origin/main` then `main`. A short name resolves through git's ref-lookup order, so a tag called `origin/main` (or `main`) would win over the remote-tracking branch or the local branch. Both now use the fully qualified names (`refs/remotes/origin/$BASE_REF`; `refs/remotes/origin/main`, then `refs/heads/main`, same order and same fallback to the merged-pull-request walk). The exact-text pin in scripts/ci_preflight.py is updated to the new command and stays as strict; the old short form is now a finding. Red-first tests: the pin refuses the short command and accepts the qualified one; a repository where a TAG named `origin/main` points at an unrelated commit and the real remote-tracking ref points elsewhere, and the same for a tag named `main` with no remote-tracking ref, each returns the merge base of the real ref and not the tag's. Five controls keep the order, the environment override, the local fallback, the merged-pull-request fallback and the no-base result. The first-parent walk is untouched (the lab-host defect is a separate card). The default_base docstring in scripts/check_doc_reverts.py is re-wrapped around the two renamed refs; the sentences it carries come from 1e360bd31 and d0935605c and keep their wording, only the ref names change. Lane: ci-fu-shortrefs (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci: release ruleset probe lists every branch ruleset (measurement only, gate unchanged) [replaces #489] (#505) * ci(release): the ruleset probe lists every branch ruleset as visible or hidden, even when the gate verdict is a refusal (CR-245 F1 measurement) The one dispatch of release-ruleset-probe.yml from main (run 40083495967) refused on the first branch ruleset of the inventory, the organization ruleset `default` (13094758): "returned missing or invalid bypass_actors (token cannot see this ruleset's bypass_actors)". The verdict reader stops at the first failure, so the run did not say whether the release App token can see bypass_actors on the repository ruleset that is the real barrier (21476386), nor on any other. The probe now first prints one line per branch ruleset of the includes_parents inventory (id, name, source_type, source, enforcement, updated_at, bypass_actors visible with the actor count or hidden, applies to main, has a pull_request rule), then runs the unchanged verdict: success prints the existing success lines; a refusal prints the same ::error:: text on stderr and exits 1, but stdout now carries the listing. A detail read that fails is reported on its own row and the listing continues. The listing is a measurement: `gate`, `_release_bump` and `_require_release_rulesets` are untouched, every gate test and the existing source mutants are kept, and the probe's verdict replay over every existing ruleset fixture still compares verdict and refusal text byte for byte. The workflow release-ruleset-probe.yml is not changed. The listing reads each branch ruleset detail independently of the verdict read (two reads per detail in the probe), which docs/ci.md now says. Red-first rows: the real situation (org ruleset hidden, repository ruleset visible: both listed, the org one hidden, refusal unchanged), both hidden, one detail read failing, all visible with a valid barrier, the read-only call list, and a source mutant that drops the listing. Rewrites named for the doc-reverts check, all introduced by 9c18d41 (#488, "ci(release): read-only ruleset probe for the release gate (CR-245 F1)"): docs/ci.md "Success prints the barrier id/name and one compact inventory line with enforcement, source and bypass visibility."; docs/ci.md "Refusal exits 1 with empty stdout and the reader's `::error::` diagnostic; API/response failures name the endpoint."; docs/ci.md "Two hidden rulesets need two dispatches: the first refusal names only the first hidden ruleset; make its `bypass_actors` visible before dispatching again to identify the second." (replaced: all hidden rulesets are listed in one dispatch); the probe docstring "Read exactly the bump's ruleset evidence, with the same refusal policy."; two test comments ("The reader report's complete [] inventory fails on main without an endpoint." and "The runtime assertions already hold there; the prose does not."). The module docstring sentence "Each endpoint/page is read at most once; failures refuse without retry or sleep." (ad1ebac, #479) is scoped to the gate's reads. Lane: ci-fu-cr488m (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe listing bounds its inventory=ERROR row to 240 characters like the detail-error rows (CR-245 F1 measurement, sol P2) The new stdout listing printed the whole exception text on an `inventory=ERROR` row, while the per-ruleset `detail=ERROR` rows truncate it to 240 characters; a `gh` transport error with 300 characters of stderr was echoed in full. The row now applies the same bound ([:240], still !r-escaped). Four rows assert the exact bounded line (an inventory transport error, a repository transport error, a default-branch rejection with a long repository argument, an invalid summary with a long field) together with the unchanged stderr, exit code 1 and API call list of the verdict; each has a mutation control that removes only the bound and must fail. The verdict functions are untouched and the verdict's own stderr is not bounded. No documentation sentence is changed (none described a length limit). Lane: ci-fu-cr488n (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: ci-cx <ci-cx@invalid> Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci(release): the permission-claim text guard also catches backslash-wrapped, bytes-decoded and fullwidth respellings (PROBE-FU) [replaces #493] (#506) * ci(release): the permission-claim text guard also catches backslash-wrapped, bytes-decoded and fullwidth respellings (PROBE-FU, sol P2 1/2/4) The guard in WorkflowPinTests.assert_ruleset_visibility_documentation fails when the gate source, docs/ci.md or release.yml state that the App needs "administration: read". A read of the probe found three respellings it accepted: a Markdown hard break written as a backslash before the newline, a bytes constant (b"administr\x61tion: re\x61d", with or without .decode()), and fullwidth compatibility letters. The collapsed view now treats backslash-newline like whitespace, bytes constants are decoded into the scanned strings, and every view is NFKC-normalised before case folding. Three new tests (12 rows) each read the real artifacts, assert the unmodified baseline passes, mutate a copy and require the specific permission-claim AssertionError; removing only the matching handling turns its rows red. Not closed, by design: YAML double-quoted scalars with escapes in release.yml (sol P2 3) and runtime string construction (sol P3); a static text guard cannot decide them, and the exact-output pins on the refusal text remain the decisive protection. No existing assertion, control or forbidden pattern is changed. The rewritten `view` tuple is code, not a documentation or comment sentence. Lane: ci-fu-pfuguard (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the permission-claim guard scans the casefold-only views as well as the NFKC views (PROBE-FU round 2, sol R237 P2) The first PROBE-FU commit normalised every scanned view with NFKC and replaced the casefold-only views. For an input such as `Needs administration: read` followed by the trade mark sign, the old guard matched (the sign is a non-word character after `read`), but after NFKC the text is `readtm` and the word boundary no longer matches, so the claim was accepted: the change made the guard weaker for that input. Each text is now checked in four views (the two original views casefolded, and both NFKC+casefolded), so normalisation can only add matches. A restoration control appends the trade mark case to the gate source, docs/ci.md and the workflow text (direct and line-wrapped, six rows), and a removal mutant drops the casefold-only scan and must break all six rows. No existing assertion or control changes; no documentation or comment sentence is deleted or rewritten. Lane: ci-fu-pfuguard2 (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci: release ruleset probe lists every branch ruleset (measurement only, gate unchanged) [replaces #489] (#505) * ci(release): the ruleset probe lists every branch ruleset as visible or hidden, even when the gate verdict is a refusal (CR-245 F1 measurement) The one dispatch of release-ruleset-probe.yml from main (run 40083495967) refused on the first branch ruleset of the inventory, the organization ruleset `default` (13094758): "returned missing or invalid bypass_actors (token cannot see this ruleset's bypass_actors)". The verdict reader stops at the first failure, so the run did not say whether the release App token can see bypass_actors on the repository ruleset that is the real barrier (21476386), nor on any other. The probe now first prints one line per branch ruleset of the includes_parents inventory (id, name, source_type, source, enforcement, updated_at, bypass_actors visible with the actor count or hidden, applies to main, has a pull_request rule), then runs the unchanged verdict: success prints the existing success lines; a refusal prints the same ::error:: text on stderr and exits 1, but stdout now carries the listing. A detail read that fails is reported on its own row and the listing continues. The listing is a measurement: `gate`, `_release_bump` and `_require_release_rulesets` are untouched, every gate test and the existing source mutants are kept, and the probe's verdict replay over every existing ruleset fixture still compares verdict and refusal text byte for byte. The workflow release-ruleset-probe.yml is not changed. The listing reads each branch ruleset detail independently of the verdict read (two reads per detail in the probe), which docs/ci.md now says. Red-first rows: the real situation (org ruleset hidden, repository ruleset visible: both listed, the org one hidden, refusal unchanged), both hidden, one detail read failing, all visible with a valid barrier, the read-only call list, and a source mutant that drops the listing. Rewrites named for the doc-reverts check, all introduced by 9c18d41 (#488, "ci(release): read-only ruleset probe for the release gate (CR-245 F1)"): docs/ci.md "Success prints the barrier id/name and one compact inventory line with enforcement, source and bypass visibility."; docs/ci.md "Refusal exits 1 with empty stdout and the reader's `::error::` diagnostic; API/response failures name the endpoint."; docs/ci.md "Two hidden rulesets need two dispatches: the first refusal names only the first hidden ruleset; make its `bypass_actors` visible before dispatching again to identify the second." (replaced: all hidden rulesets are listed in one dispatch); the probe docstring "Read exactly the bump's ruleset evidence, with the same refusal policy."; two test comments ("The reader report's complete [] inventory fails on main without an endpoint." and "The runtime assertions already hold there; the prose does not."). The module docstring sentence "Each endpoint/page is read at most once; failures refuse without retry or sleep." (ad1ebac, #479) is scoped to the gate's reads. Lane: ci-fu-cr488m (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe listing bounds its inventory=ERROR row to 240 characters like the detail-error rows (CR-245 F1 measurement, sol P2) The new stdout listing printed the whole exception text on an `inventory=ERROR` row, while the per-ruleset `detail=ERROR` rows truncate it to 240 characters; a `gh` transport error with 300 characters of stderr was echoed in full. The row now applies the same bound ([:240], still !r-escaped). Four rows assert the exact bounded line (an inventory transport error, a repository transport error, a default-branch rejection with a long repository argument, an invalid summary with a long field) together with the unchanged stderr, exit code 1 and API call list of the verdict; each has a mutation control that removes only the bound and must fail. The verdict functions are untouched and the verdict's own stderr is not bounded. No documentation sentence is changed (none described a length limit). Lane: ci-fu-cr488n (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: ci-cx <ci-cx@invalid> Co-authored-by: Claude Sonnet 5.5 <[email protected]> * tests: drop the TIMEOUT 180 ctest property (owner's no-time-limit rule, P2-CM-F4) (#504) * tests: drop the TIMEOUT 180 property from the six ownership manifest tests (P2-CM-F4) One set_tests_properties call carried TIMEOUT 180 for test_selected_window_ownership_manifest, the four test_selected_window_ownership_manifest_controls_shard_{1..4}of4 and test_selected_window_ownership_d4. The owner's no-time-limit rule forbids a ctest ceiling: under pool contention shard 1of4 (119 s normally) hit the 180 s ceiling on the b250ceba merge-commit ci-post (#283). WORKING_DIRECTORY is kept; no test, label or line count changes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci: release ruleset probe lists every branch ruleset (measurement only, gate unchanged) [replaces #489] (#505) * ci(release): the ruleset probe lists every branch ruleset as visible or hidden, even when the gate verdict is a refusal (CR-245 F1 measurement) The one dispatch of release-ruleset-probe.yml from main (run 40083495967) refused on the first branch ruleset of the inventory, the organization ruleset `default` (13094758): "returned missing or invalid bypass_actors (token cannot see this ruleset's bypass_actors)". The verdict reader stops at the first failure, so the run did not say whether the release App token can see bypass_actors on the repository ruleset that is the real barrier (21476386), nor on any other. The probe now first prints one line per branch ruleset of the includes_parents inventory (id, name, source_type, source, enforcement, updated_at, bypass_actors visible with the actor count or hidden, applies to main, has a pull_request rule), then runs the unchanged verdict: success prints the existing success lines; a refusal prints the same ::error:: text on stderr and exits 1, but stdout now carries the listing. A detail read that fails is reported on its own row and the listing continues. The listing is a measurement: `gate`, `_release_bump` and `_require_release_rulesets` are untouched, every gate test and the existing source mutants are kept, and the probe's verdict replay over every existing ruleset fixture still compares verdict and refusal text byte for byte. The workflow release-ruleset-probe.yml is not changed. The listing reads each branch ruleset detail independently of the verdict read (two reads per detail in the probe), which docs/ci.md now says. Red-first rows: the real situation (org ruleset hidden, repository ruleset visible: both listed, the org one hidden, refusal unchanged), both hidden, one detail read failing, all visible with a valid barrier, the read-only call list, and a source mutant that drops the listing. Rewrites named for the doc-reverts check, all introduced by 9c18d41 (#488, "ci(release): read-only ruleset probe for the release gate (CR-245 F1)"): docs/ci.md "Success prints the barrier id/name and one compact inventory line with enforcement, source and bypass visibility."; docs/ci.md "Refusal exits 1 with empty stdout and the reader's `::error::` diagnostic; API/response failures name the endpoint."; docs/ci.md "Two hidden rulesets need two dispatches: the first refusal names only the first hidden ruleset; make its `bypass_actors` visible before dispatching again to identify the second." (replaced: all hidden rulesets are listed in one dispatch); the probe docstring "Read exactly the bump's ruleset evidence, with the same refusal policy."; two test comments ("The reader report's complete [] inventory fails on main without an endpoint." and "The runtime assertions already hold there; the prose does not."). The module docstring sentence "Each endpoint/page is read at most once; failures refuse without retry or sleep." (ad1ebac, #479) is scoped to the gate's reads. Lane: ci-fu-cr488m (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe listing bounds its inventory=ERROR row to 240 characters like the detail-error rows (CR-245 F1 measurement, sol P2) The new stdout listing printed the whole exception text on an `inventory=ERROR` row, while the per-ruleset `detail=ERROR` rows truncate it to 240 characters; a `gh` transport error with 300 characters of stderr was echoed in full. The row now applies the same bound ([:240], still !r-escaped). Four rows assert the exact bounded line (an inventory transport error, a repository transport error, a default-branch rejection with a long repository argument, an invalid summary with a long field) together with the unchanged stderr, exit code 1 and API call list of the verdict; each has a mutation control that removes only the bound and must fail. The verdict functions are untouched and the verdict's own stderr is not bounded. No documentation sentence is changed (none described a length limit). Lane: ci-fu-cr488n (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci: pin every third-party action to a full commit SHA with a version comment, and lint it in preflight (ORG-ACTIONPINS) [replaces #495] (#508) * ci: pin every third-party action in the engine workflows to a full commit SHA with a version comment (ORG-ACTIONPINS) 66 `uses:` lines changed in .github/workflows/*.yml and .github/actions/setup-live-curl/action.yml: 65 mutable tags rewritten to `@<40-hex sha> # v<version>` and one bare SHA that lacked its comment. Each pin is the same major the file used before; no step, job, permission, environment, secret or token mint changes (a before/after comparison of the diff with the refs stripped is identical line for line). Resolved on 2026-10-24 22:48 with `git ls-remote --tags` on a spot box; every tag is lightweight, so the tag's sha is the commit: actions/checkout v4 11d5960a326750d5838078e36cf38b85af677262 v4.4.0 actions/upload-artifact v4 ea165f8d65b6e75b540449e92b4886f43607fa02 v4.6.2 actions/setup-python v5 a26af69be951a213d495a4c3e4e4022e16d87065 v5.6.0 actions/cache, cache/save, cache/restore v4 0057852bfaa89a56745cba8c7296529d2fc39830 v4.3.0 actions/create-github-app-token v1 d72941d797fd3113feb6b93fd0dec494b13a2547 v1.12.0 cloudflare/wrangler-action v3 9acf94ace14e7dc412b076f2c5c20b8ce93c79cd v3.15.0 The byte-pinned copies of the workflow text that scripts/ci_preflight.py, scripts/test_ci_preflight.py and scripts/test_ci_release_gate.py compare against (the embedded probe workflow, the release and cache expectations, the Wrangler deployment, test mutations and fixtures) are moved to the same pinned text so each still equals its workflow. Two tests read the real workflows and composite action and require a 40-hex sha and a version comment on every third-party reference. No documentation or comment sentence is deleted or rewritten. Lane: ci-fu-pins (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(preflight): refuse any third-party `uses:` that is not a 40-hex commit SHA with a same-line version comment; the lint accepts plain block-style YAML only (ORG-ACTIONPINS) scripts/ci_preflight.py gains `action_pin_findings`: every `uses:` in .github/workflows/*.yml and every composite .github/actions/**/action.yml (or .yaml) that is not a local `./` path or a `docker://` image must be `owner/action[/path]@<40 lowercase hex> # v<version>` on the same line; quoted values, step and job-level `uses:`, remote reusable workflows and a ref-less `uses:` are judged; the finding names file, line and the offending reference. The lint is a regression guard for our own reviewed workflow files, so it does not parse arbitrary YAML: it accepts plain block-style YAML and refuses, with a finding naming file, line, construct and source text, what it cannot follow: anchors, aliases, merge keys, explicit keys, tags, non-empty flow mappings, quoted keys, backslash escapes in quoted values, quoted values that do not end on their line, tab indentation, document markers, `uses` without a same-line scalar, unrecognised block lines, non-ASCII whitespace in structural positions, and any non-LF line break (bare CR, NEL, LS, PS, VT, FF) or C0 control character in the whole file. Allowed (surveyed on the real tree, twelve YAML files): plain block mappings and sequences, plain and quoted scalar values, empty `{}` and `[]` as a whole plain value, non-empty flow sequences, block-scalar bodies including `- |`. Quotes inside flow sequences count only at element start; delimiters of a refused flow collection are counted without quote masking and the refusal region ends at the first outdent. The `${{ ... }}` scan is linear. The loaders read the bytes and decode strict UTF-8 (a file that is not UTF-8 is a finding), so a bare CR is no longer erased before the refusal runs. The documented limit for exotic YAML is in docs/ci.md. The lint was read by sol and argon over five rounds (every finding closed or classified); the loader and the three promotion-guard tests that now also see the lint's quoted-key finding were fixed from the lab legs. Tests: 15 first-round and the later red-first tests (one per refused construct, each break character in a block-scalar body and in a comment, the C0 controls, flow-quote variants, outdent resumption, loader tests on real files in a temp layout, a real-tree zero-findings test, mutation controls for the refusal branch and the integration call). docs/ci.md describes the rule. This commit is the squash of the lint rounds of the series; the pins commit before it rewrites the workflow files. Lanes: ci-fu-pinlint, ci-fu-pins2/3/4/5 (Codex gpt-6.1-sol), ci-fu-pins6 and pins7 (Claude Sonnet 5.5 via claudock). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: ci-cx <ci-cx@invalid> Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci: CR-150/C4 source allocation-bound gate over the engine and generated C++ (scoped site identities, fail-closed bounded admission) (#469) * ci: CR-150/C4 source allocation-bound gate over the engine and generated C++ The gate reads ci/cr150-source-bounds.yaml, resolves each entry against the tree, and fails closed when it cannot judge. Exit 0 = no new unbounded site (PASS(bounded) count + OPEN listing); --strict exits 1 while any OPEN entry remains. Two ci_preflight stages run it in warnings and strict mode. * ci: the CR-150 source-bounds manifest (seed data and ceilings) 1287 lines emitted by --emit-seed: every scanned site with its state, each OPEN entry (pending-flat-memory, unreviewed) listed and capped by ceilings so a new OPEN site needs a reviewed ceiling increase. * ci: pin scoped CR-150 site identities and prove the bound gate both ways The four docs/ci.md sentences that 48fb19d introduced (the index comparison, the exit-1 site conditions, the exit-2 manifest conditions, and how to add a bound) are replaced here by the scoped-site-identity text: the same rules now speak per (file, symbol) site identity rather than per manifest group. Naming them here is what check_doc_reverts.py requires of a commit that drops them. * ci: close the CR-150 source-gate review findings (P0-P3) P0: the site inventory's file and symbol fields accept placeholder-like identifiers again (two real rows carry the symbol `none`); the spelling field still refuses empty and placeholder values. P1: bounded admission decides locality from a recognized function body, crossing only statement blocks. Decorated classes, templates, attributed structs, nested types after an access label, unknown scopes and linkage blocks are barriers, and `extern` in the declaration's statement is refused. P2: `%:define` (the C++17 digraph for `#`) is read as a directive, so a macro cannot bypass the macro refusal. P3: a macro definition of a name guarded bounded admission uses (`push_back`, `emplace_back`, `size`, the entry symbol, the bound) is refused in every scanned file, not only the entry's own file. Tests cover each finding both ways (admit and refuse), including the inventory `none` row and a negative control that proves the gate still stays green on the unmodified tree. * ci: close the delta read's N1-N4 on the CR-150 source gate - _directives joins translation-phase splices before lexing, so `%\` + `:define` and every `\`-continued spelling are read as the directive they are (N1, N2). - bounded_issues refuses a trailing backslash or a <% / %> brace digraph in the entry and the fixed input file: alternate spellings are outside guarded bounded admission rather than silently unscoped (N1, N4). - macro_names now covers every identifier lexed from the entry and fixed input, not five names, and the define regex admits a comment swallowed without a separating space, so `#define/**/push_back`, `#define if(c)` and `#define static extern` are all refused (N2, N3). - _text refuses an empty file/symbol even where a placeholder is allowed, so a blank inventory field exits 2 (malformed) instead of 1 (stale site) (N6). Tests: spliced digraph, brace digraph in entry and fixed input, commented and spliced defines, keyword/type macro rewrites, and blank inventory fields; each pins the refusal and that no PASS(bounded) survives. Co-Authored-By: Claude Code <[email protected]> * ci: close the delta read 2's fail-open inputs on the CR-150 source gate Guarded bounded admission now refuses, on raw text rather than a lexed view, every spelling where a lexer and a compiler can disagree: line splices (including whitespace, FF and VT before the newline) inside comments and literals, raw string literals and their prefixed forms, NUL, BOM, non-ASCII text and brace digraphs. A define naming a proof-file identifier is refused in every scanned file after splice joining, regardless of lexical context, and blank inventory scope members are now malformed (exit 2) instead of an ordinary finding. Header entries under include/ are refused bounded admission: a consumer's prior includes and build flags are not proven, so the form cannot be enforced from the proof files alone. Rewords the one admission sentence added by c989d59dab in docs/ci.md (replacing its "in the participating source and fixed-input files" wording) to state the same rule the code now enforces. Co-Authored-By: Claude Code <[email protected]> * ci: skip suite jobs on draft PRs; the gate fails on drafts (LB-387) (#466) * ci: skip the PR suite on draft PRs (LB-387) Skip preflight, build, sanitizers, kernel-only, native-live, and corpus-parity-subset for drafts; start full runs on ready_for_review. Update ci_preflight.py trigger/condition pins and matching test_ci_preflight.py pin references, preserving check logic. Keep authz, dependencies, runners, time limits, and build-gate unchanged. * ci: re-run on converted_to_draft and split the CI group by draft state (LB-387) Round B of the final read of #466: add converted_to_draft to the pull_request types so a ready run is followed by a failing draft aggregate when the PR is converted, and key the concurrency group on the draft flag so a stale draft re-run cannot cancel the full ready run. Exact pins plus one red control per change in test_ci_preflight.py. Lane: ci-draft-fix1 (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci: run both ctest label-exclusion tests; refuse duplicate test names (CR-146) (#407) * ci: run both ctest label-exclusion tests; refuse duplicate test names (CR-146) * ci: close the evasions of the duplicate-test-name check (CR-146 review round) Both reads of #407 found the check fail-open: a method redefined inside an if/try/with block of the class body, a repeated class name, suites that CMake registers outside scripts/ and docker/ (tests/, tests/ownership/, benchmarks/tests/, *_self_test.py), and an empty inspection population (missing root, empty directory, zero test names) all returned success. The check now traverses scope-preserving blocks, refuses duplicate class bindings and duplicate module-level tests, scans the registered Python test inventory, and refuses an empty population; preflight pins the --root; the diagnostic tests assert the complete location instead of a line prefix. Regression tests with the readers' counterexamples; the repository tree itself must still pass (see the lane report for how that was read from the tree). Lane: ci-m407 fix (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci: the duplicate-name check ignores warnings from the files it parses (CR-146 round 3) The widened scan parses scripts/check_native_c_api_surface.py, whose docstring raises a SyntaxWarning (invalid escape sequence); the warning leaked into the checker's output and the repository test `test_repository_inventory_has_no_duplicate_bindings` (empty output expected) failed in preflight. The parse now suppresses warnings from the scanned source while a real SyntaxError still fails the check. Red tests: a clean file and a file with a real duplicate, each with an invalid-escape docstring, under warnings set to `always` and to `error`; the repository assertion is unchanged. Lane: ci-fu-m407r2 (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: HaoLiangWen <[email protected]> Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci(release): narrow release-bump exemption in the release gate (CR-245, G13, v2) (#486) * ci(release): accept a release-bump commit on main when the release App pushed it (CR-245, round 1) The release workflow pushes a version-bump commit to main; no PR head has that tree, so the next release run refused at phase 1. The gate now also accepts main's head when it is a bump commit that (a) every push-event run of the sha on main records as pushed by the release App (actor and triggering_actor both pineforge-app[bot], at least one run, all pages read, a run by any other actor refuses), (b) has a single parent that itself passes phase 1 (a bump on a bump or a merge commit refuses), and (c) touches exactly the files the workflow writes and stages, VERSION and docker/run_failure_codes_diff.json, as modifications with unchanged mode 100644 and nothing else. Red-first tests through gate() with the injected API for the accepted path and each refusal, a mutation control per condition, preflight pins and docs. Commit metadata is not used for identity: it is forgeable. Round 2 adds the branch-ruleset check that makes the App the only possible pusher. Lane: ci-fu-cr245b (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the bump exemption also requires that only the release App can push to main (CR-245, round 2) The actor check of round 1 is only as strong as the guarantee that nobody else can produce a push run as the release App. The gate now reads the repository's branch rulesets at gate time and requires that the cumulative bypass of every ACTIVE ruleset that applies to main and carries a pull_request or required_status_checks rule is exactly the release App (Integration 4105147, bypass mode always). It refuses when no push-blocking ruleset applies to main, when one is disabled or evaluate-only, when another actor or a pull-request-only bypass appears, when bypass_actors is not returned, when an inventory or detail response is malformed or incomplete (every page is read and linked), when an applicable ref pattern is not understood, and on any API error. No ruleset id or name is hard-coded. The gate relies on the App installation permission `administration: read`; release.yml, docs/ci.md and the preflight pins say so, so removing the permission is a visible breaking change. Nineteen ruleset tests and thirteen source mutation controls; preflight workflow mutations for the gate token wiring. Rounds 1 and 2 land together. Lane: ci-fu-cr245c (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the bump exemption needs one active pull_request ruleset restricted to the release App; Actions pagination honours Link (CR-245, round 3) Replaces the round-2 barrier of a8b1276 ("the bump exemption also requires that only the release App can push to main"): it removes the round-2 bypass-intersection sentences and the status-only acceptance that a8b1276 added to docs/ci.md, and rewrites its tests. Fresh sol read of the round-2 head: intersecting bypass descriptors across rulesets does not prove an App-only barrier (a person who holds a role and a team bypasses two rulesets whose lists share only the App entry), and a required_status_checks rule alone is not a push barrier. The condition is now: at least one ACTIVE branch ruleset applies to main, carries a pull_request rule, and its OWN bypass list is exactly the release App (Integration 4105147, bypass mode always); other rulesets, such as the broad default one, no longer matter. required_status_checks no longer counts. The Actions workflow-runs reader also proves completeness with the Link header (an advertised unread next page refuses), as the rulesets reader already did. The round-2 tests that encoded the intersection and the status-only acceptance are rewritten (status-only now refuses) and new red rows cover overlapping role/team principals, a hybrid PR/status pair, a valid barrier beside broader rulesets, a second bypass actor, a non-always mode and the unread-page input; docs, preflight pins and the release.yml comment follow the new wording. Two refusal-text expectations in test_ci_release_gate.py were wrong, not the gate: the pagination row lacked "returned" and the empty-payload row doubled it. Both now assert the text the gate emits. A second fresh sol read found that the HTTP page check and the JSON page check counted different populations: one HTTP body could carry several JSON documents, so a valid page followed by a headerless document still returned the green bump verdict (for the runs reader and the rulesets reader alike). Each framed HTTP body must now be exactly one JSON document, checked before the pages are joined and before the Link relations are trusted; extra or trailing data, an empty body and a partially headerless response refuse with the endpoint named. New red rows cover the runs example, the rulesets analogue, an extra document on page 2, trailing garbage, partially missing headers, an empty body with a next Link and the PR/status bodies; two new full-source mutants remove the check and must go green on the mutant only. Lane: ci-fu-cr245d, ci-fu-cr245e and ci-fu-cr245f (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci: promote-baseline preflight rejects workflow-level defaults/env (#482 follow-up) (#483) * ci: reject workflow-level defaults and env in the promote-baseline pin (SEC846 follow-up) Post-merge read of #482: the preflight pinned only the promote-guard job block, so a workflow-level defaults.run.shell or an env carrying a secret passed the checker while changing how the guard runs. ci_workflow_findings now rejects both, and the guard's own defaults, with specific findings; tests cover each reported input, the quoted and empty variants, and the unmodified workflow. Lane: ci-fu-eng482 (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * test: expect the pinned-guard finding in main's current wording (engine #483 after the main merge) After main (#470) landed, the checker's finding for a changed promote-guard block reads "must start with the pinned read-only promote-guard"; this PR's new test_promotion_guard_rejects_job_defaults still expected the earlier "credential-free" wording in its three subtests, so preflight failed. The expected list is now the exact list the checker emits (the job-level defaults finding, then the pinned-guard finding); the assertion stays an exact ordered equality. Checker unchanged. Lane: ci-fu-eng483b (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * ci(release): read-only ruleset probe for the release gate (CR-245 F1) (#488) * ci(release): accept a release-bump commit on main when the release App pushed it (CR-245, round 1) The release workflow pushes a version-bump commit to main; no PR head has that tree, so the next release run refused at phase 1. The gate now also accepts main's head when it is a bump commit that (a) every push-event run of the sha on main records as pushed by the release App (actor and triggering_actor both pineforge-app[bot], at least one run, all pages read, a run by any other actor refuses), (b) has a single parent that itself passes phase 1 (a bump on a bump or a merge commit refuses), and (c) touches exactly the files the workflow writes and stages, VERSION and docker/run_failure_codes_diff.json, as modifications with unchanged mode 100644 and nothing else. Red-first tests through gate() with the injected API for the accepted path and each refusal, a mutation control per condition, preflight pins and docs. Commit metadata is not used for identity: it is forgeable. Round 2 adds the branch-ruleset check that makes the App the only possible pusher. Lane: ci-fu-cr245b (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the bump exemption also requires that only the release App can push to main (CR-245, round 2) The actor check of round 1 is only as strong as the guarantee that nobody else can produce a push run as the release App. The gate now reads the repository's branch rulesets at gate time and requires that the cumulative bypass of every ACTIVE ruleset that applies to main and carries a pull_request or required_status_checks rule is exactly the release App (Integration 4105147, bypass mode always). It refuses when no push-blocking ruleset applies to main, when one is disabled or evaluate-only, when another actor or a pull-request-only bypass appears, when bypass_actors is not returned, when an inventory or detail response is malformed or incomplete (every page is read and linked), when an applicable ref pattern is not understood, and on any API error. No ruleset id or name is hard-coded. The gate relies on the App installation permission `administration: read`; release.yml, docs/ci.md and the preflight pins say so, so removing the permission is a visible breaking change. Nineteen ruleset tests and thirteen source mutation controls; preflight workflow mutations for the gate token wiring. Rounds 1 and 2 land together. Lane: ci-fu-cr245c (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the bump exemption needs one active pull_request ruleset restricted to the release App; Actions pagination honours Link (CR-245, round 3) Replaces the round-2 barrier of a8b1276 ("the bump exemption also requires that only the release App can push to main"): it removes the round-2 bypass-intersection sentences and the status-only acceptance that a8b1276 added to docs/ci.md, and rewrites its tests. Fresh sol read of the round-2 head: intersecting bypass descriptors across rulesets does not prove an App-only barrier (a person who holds a role and a team bypasses two rulesets whose lists share only the App entry), and a required_status_checks rule alone is not a push barrier. The condition is now: at least one ACTIVE branch ruleset applies to main, carries a pull_request rule, and its OWN bypass list is exactly the release App (Integration 4105147, bypass mode always); other rulesets, such as the broad default one, no longer matter. required_status_checks no longer counts. The Actions workflow-runs reader also proves completeness with the Link header (an advertised unread next page refuses), as the rulesets reader already did. The round-2 tests that encoded the intersection and the status-only acceptance are rewritten (status-only now refuses) and new red rows cover overlapping role/team principals, a hybrid PR/status pair, a valid barrier beside broader rulesets, a second bypass actor, a non-always mode and the unread-page input; docs, preflight pins and the release.yml comment follow the new wording. Two refusal-text expectations in test_ci_release_gate.py were wrong, not the gate: the pagination row lacked "returned" and the empty-payload row doubled it. Both now assert the text the gate emits. A second fresh sol read found that the HTTP page check and the JSON page check counted different populations: one HTTP body could carry several JSON documents, so a valid page followed by a headerless document still returned the green bump verdict (for the runs reader and the rulesets reader alike). Each framed HTTP body must now be exactly one JSON document, checked before the pages are joined and before the Link relations are trusted; extra or trailing data, an empty body and a partially headerless response refuse with the endpoint named. New red rows cover the runs example, the rulesets analogue, an extra document on page 2, trailing garbage, partially missing headers, an empty body with a next Link and the PR/status bodies; two new full-source mutants remove the check and must go green on the mutant only. Lane: ci-fu-cr245d, ci-fu-cr245e and ci-fu-cr245f (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): a read-only probe of the bump's ruleset barrier, run once with the release App token (CR-245 F1) REV finding F1 on the CR-245 release-bump exemption (#486): the gate validates bypass_actors and ref patterns of EVERY branch ruleset in the includes_parents inventory before it decides which one is the barrier. GitHub returns bypass_actors only to a caller with write access to the ruleset, so if the release App token cannot see them on a ruleset (for example the organization ruleset), every release bump is refused. A dry_run of release.yml cannot test this: the ruleset reads happen only for a sha with no merged pull request. New subcommand `ci_release_gate.py rulesets-probe --repo OWNER/REPO` runs ONLY the ruleset reader the bump uses (no sha, ref, git, commit or run reads; GET requests for the repository, the ruleset inventory and the ruleset details), prints the barrier ruleset and one line per branch ruleset saying whether its bypass_actors were visible, and refuses with the same error text as `gate`. The gate's verdicts are unchanged: the reader now also returns the evidence it already validated. New workflow release-ruleset-probe.yml (workflow_dispatch only, contents: read) mints the App token with the same action, secrets and scope as release.yml, checks out without persisting credentials, and runs only the probe with that token. docs/ci.md says how to read its output. Tests: a token that cannot see bypass_actors is refused with the ruleset named; a visible green barrier is accepted with the exact two lines and only ruleset reads; the probe equals the real reader on every existing ruleset fixture; a source mutant that skips the reader goes green only on the mutant; workflow and preflight pins, each with mutation controls. The preflight and its tests pin the new workflow and runner label. Lane: ci-fu-cr486p (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the ruleset probe gets the canon authz job; stop claiming which App permission exposes bypass_actors (CR-245 F1, round 2) Fresh sol + argon reads of the probe commit 6adae66f2: - P1 (both): test_gcp_sites_reject_cross_class_routes asserted 29 routed GCP sites; the probe workflow adds one, and the authz job it now carries adds another, so the derived count is 31 (ci 5, ci-post 8, native 3, corpus 3, subset 2, docs 2, docs-post 2, release 3 + prebuilt matrix site, promotion 1, probe 2). The PF_CI_LABELS subset stays 15. - P2 (sol): release-ruleset-probe.yml had no authz job. It now has the canonical authz job, the probe job needs it and carries the canonical step guards; the preflight pin and its mutation controls refuse a probe workflow without authz or without the dependency. The App-token mint step is unchanged and still byte-identical to release.yml's (the probe must use the same token path as the bump). - P2 (sol): docs/ci.md, the release.yml comment (comment lines only) and the binding release-gate test stated as fact that the App permission administration: read exposes bypass_actors. That is not verified: GitHub's documentation says bypass_actors is returned only to a caller with write access to the ruleset, and what an installation token sees on an inherited organization ruleset is exactly what the probe measures. All three now say what is true (the gate needs bypass_actors visible on every branch ruleset in the inventory; if the release App token cannot see them the bump is refused; the probe reports whether it can), and the test pins that wording, so it fails if the unsupported claim comes back. - P3 (sol): docs/ci.md said every probe refusal names an endpoint; only API/response failures do. Narrowed. Names the docs/ci.md sentences this commit removes or rewrites, all introduced by a8b1276 ("ci(release): the bump exemption also requires that only the release App can push to main"), as a deliberate revert of their unverified claim: "The gate relies on the release App permission `administration: read` to read `bypass_actors`." "The App installation has that permission and `contents: write`; the workflow-level `contents: write` setting scopes GITHUB_TOKEN and does not grant App permissions." "Tokens without administration access may receive a ruleset response with `bypass_actors` omitted; a missing field refuses, and the guard never assumes an unreadable list is empty or App-only." "Removing this App permission makes every release-bump exemption refuse." They are replaced by the verified wording above; the guard behaviour (a missing bypass_actors field refuses) is unchanged. Open decision, not changed here: the probe and release.yml use the tag references actions/create-github-app-token@v1 and actions/checkout@v4 where the CI canon wants full-SHA pins; the probe keeps the same mint as the bump on purpose. Lane: ci-fu-cr486q (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the probe's hidden-bypass_actors refusal states what was observed, not a permission; dispatch from main (CR-245 F1, round 3) REV (Opus + sol, static) on the probe: NO-GO, small. P1: the refusal text printed exactly when a ruleset's bypass_actors is hidden, which is the probe's decisive output, ended "(App requires administration: read)". That is the permission claim round 2 removed from the docs, the release.yml comment and the binding test; it was introduced by a8b12768f ("the bump exemption also requires that only the release App can push to main") as a fifth place and it is unverified (GitHub's documentation says bypass_actors is returned only to a caller with write access to the ruleset). The text is now "(token cannot see this ruleset's bypass_actors)", the two assertions that pinned the old text pin the new exact text, and the absence guard now scans the WHOLE source of ci_release_gate.py (strings and code, not only comment lines) for the forbidden phrases in every spelling, with restoration controls that reinsert the old refusal text and seven spelling variants. F4: docs/ci.md says the one-time dispatch must run from main with main's copy of the workflow, lists the probe on the isolated pf-gcp-main fleet, and scopes the FLEET-POST=A routing sentence (authz on pf-gcp-pr, the probe job on pf-gcp-main). F2: comments in ci_preflight.py, its tests and release-ruleset-probe.yml claimed a pin binds the authz header byte for byte; for this workflow the pin binds the normalised executable lines (comments and blank lines removed), and the comments now say so. F3: the display branch for a hidden ruleset is unreachable (the reader refuses first); kept, and docs/ci.md says two hidden rulesets need two dispatches because the first refusal names only the first. Rewrites that replace earlier sentences (named for the doc-reverts check): docs/ci.md "FLEET-POST=A splits routing at the file boundary: PR jobs use" (introduced by 3a0d150); docs/ci.md "Release and promotion use the isolated release fleet: `release`, `notify-hub`, Linux x64" (opening from 833f7ac, second line from 3a0d150); docs/ci.md "Dispatch **Release ruleset probe** (`release-ruleset-probe.yml`) once to check" (6adae66); ci_preflight.py "The canon header is text, not a decoded array" (a8089b2) and "Canon authz keeps its space after the comma" (1d5dfd6); the "GCP fleet (owner #1050): non-release jobs on pf-gcp-pr; release-class jobs only on pf-gcp-main" copies in the probe workflow, ci_preflight.py and test_ci_preflight.py (1d5dfd6). Not changed here: the tag-pinned actions (repo-wide card) and the dispatch exposure of the App secrets (separate L6 card). Lane: ci-fu-cr486r (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the permission-claim absence guard also sees concatenated string literals, quoted and line-wrapped claims (CR-245 F1, round 3b) Fresh sol and argon reads of the round-3 commit 5ce34fc50: the whole-source guard assert_ruleset_visibility_documentation only removed backticks and folded case before matching the forbidden phrases, so a claim that Python joins from adjacent string literals ("App " "requires administration:" " read"), a claim in single or double quotes ('administration': 'read'), or a claim wrapped over two comment lines still passed. The guard now also scans the string constants of the Python source (folding adjacent literals, constant additions and the literal parts of f-strings through ast) and a normalised view of every text with comment markers, quotes, backticks, line breaks and runs of whitespace collapsed, in addition to the existing contiguous scan. The forbidden patterns are unchanged and the three real texts (the gate source, docs/ci.md, release.yml) still pass. Seventeen restoration controls (concatenated literals, f-string and + variants, quoted and wrapped-comment variants) were added: each fails against the old guard and is rejected by the new one; every existing control is kept. Test-only change: scripts/test_ci_release_gate.py. Lane: ci-fu-cr486s (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the ruleset probe job runs on pf-gcp-post; pf-gcp-main admits only allowlisted workflows (CR-245 F1) The org runner group pf-gcp-main (id 9) is restricted to workflows on an allow-list that does not name release-ruleset-probe.yml, so a dispatch of the probe on ["self-hosted","pf-gcp-main"] would queue forever. The allow-list is the owner's org security setting and is not widened; the one-time probe uses the unrestricted pf-gcp-post group (the canon fleet for engine post-merge and manual jobs) instead. Everything else is unchanged: workflow_dispatch from main, the App-token mint byte-identical to release.yml's, contents: read, persist-credentials: false, the authz job on pf-gcp-pr, the probe command, release.yml. Changes: the probe job's runs-on (workflow file and the whole-file template RELEASE_RULESETS_PROBE_WORKFLOW), RELEASE_JOB_RUNNERS['release-ruleset-probe.yml'] = {'authz': PR_RUNNER, 'probe': POST_RUNNER} (the per-job map already accepts a non-release label), the fleet tests (the helper now recognises a post override; the probe on pf-gcp-main or pf-gcp-pr is refused with the specific finding; the post label is accepted; the site counts stay 31 and 15, derived from the workflow files), and docs/ci.md with one added sentence saying why. Red-first rows and deletion controls for the new doc sentences. Rewrites that replace earlier text (named for the doc-reverts check), all introduced by 5ce34fc50 ("the probe's hidden-bypass_actors refusal states what was observed...", round 3 of the probe): docs/ci.md "The manual `release-ruleset-probe.yml` workflow runs `authz` on" ... "and `probe` on `["self-hosted","pf-gcp-main"]`." (only the probe's label changes); docs/ci.md "Release, promotion and the release ruleset probe use the isolated release fleet:" (the probe leaves the pf-gcp-main list; release, notify-hub, Linux x64 prebuilt and promote stay); the comment "# authz uses pf-gcp-pr; the App-token probe uses pf-gcp-main." in the workflow, in ci_preflight.py and in test_ci_preflight.py (now pf-gcp-post), which also touches the comment lines of 6adae66f and 1d5dfd66 that 5ce34fc50 had rewritten. Also named for the doc-reverts check: the docs/ci.md sentence "`release`, `notify-hub`, Linux x64 `prebuilt`, baseline `promote` and `release-ruleset-probe.yml`'s `probe` job use `["self-hosted","pf-gcp-main"]`." (the second half of the sentence above) was introduced by 49a55b5 ("Merge main (a2bb907b0485, the squash of #483) into the ruleset probe branch", which carried the round-3 probe text) and is rewritten here on purpose: the probe job leaves the pf-gcp-main list because that group admits only allowlisted workflows. Lane: ci-fu-cr486t (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * ci(release): the ruleset probe refuses any dispatch ref other than main before it mints the App token; the runner refusal no longer calls the post fleet isolated (CR-245 F1) Fresh sol read of the pf-gcp-post move (065826e98): moving the probe job to the unrestricted pf-gcp-post group removed the only thing that kept it on main, the restricted pf-gcp-main group's workflow and ref allow-list. A workflow_dispatch with ref=refs/heads/<topic> would run that branch's copy of the workflow, pass authz (which accepts any workflow_dispatch), mint the release App token and run the branch's script; neither the workflow nor the probe checked GITHUB_REF. The CI canon requires manual dispatch to satisfy the same ref restriction and to refuse an unsupported ref before checkout or repository-code execution. The probe job now starts with a step that has no secrets and no token: [ "$REF" = refs/heads/main ] || fail, modelled on promote-baseline.yml's own ref refusal, before the App-token mint and before checkout. The whole-file template and release_rulesets_probe_workflow_findings require that step first and refuse a probe workflow that lacks it, has it after the mint or checkout, in the authz job only, compares another ref, or neutralises its exit (|| true, continue-on-error, if:); 13 mutation rows plus an ordering control and a behavioural dispatch control (a topic ref exits 1 before the mint boundary). The App-token mint step is still byte-identical to release.yml's. docs/ci.md says the dispatch must run from main and the workflow itself refuses any other ref. The guard lives in the workflow file, so it stops mistakes, not a writer who edits the file on their own branch; the lock against that is the environment work (CR-304) that keeps the App secrets away from non-main refs. The per-job runner checker's refusal printed " (isolated release)" for a probe pinned to pf-gcp-post; the post fleet is shared. The suffix is now derived from the pinned label (none for the PR fleet, " (post fleet)" for the post fleet, " (isolated release)" only for the release label) and the pins that expected the old suffix (test_gcp_sites_pin_probe_to_the_dispatch_fleet, test_release_ruleset_probe_refuses_release_and_pr_fleets) expect the new one. Rewrites named for the doc-reverts check: the suffix expression and its test expectations came from 1d5dfd6 and 065826e98; the pf-gcp-post routing and allow-list sentences of 065826e98 and the main-copy dispatch wording of 5ce34fc50 are kept; the generic probe refusal of 6adae66f is kept verbatim. No prose sentence or comment is deleted. Lane: ci-fu-cr486u (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * test(live): serialize startup replay webhook deliveries (#463) * test(live): serialize startup replay webhook deliveries * test(live): assert the delivery order of the startup replay webhooks Both reads of #463 found that nothing asserted the order the serialisation exists to preserve: receipts_by_sequence() returns a dict whose equality ignores order. Adds an ordered raw-byte comparison of the received payloads against the ledger ordinals for the initial delivery and the redelivery, with a reversal control that calls the same check, and a comment that --max-attempts is ignored on the routed path. Lane: ci-m463 fix (Codex gpt-6.1-sol). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]> * CR-150 O(1) report integration: the engine 1.7.0 line as one commit over main 9eff925de1 One squash-equivalent commit: parent = engine main 9eff925de1 (#463), tree = the CR-150 picks line merged with that main (docs/ci.md the only conflict: the line's clarification paragraph and main's two baseline-promotion paragraphs are adjacent additions, both kept). The squash body at the merge carries this message verbatim. The 40 documentation sentences below were deleted by commits of the line and are intended: they are the superseded wording of the pages the final 1.7.0 text replaces. scripts/check_doc_reverts.py reads this message for them. Format per line: file | introducer short sha | start of the deleted sentence. Introducers: dbf56ed (docs/design/arena-bound.md 22), 03b2406 (docs/ci.md 7), c7e6bcb (docs/pages/scratch-tables.md 5), 1fa26e7 (scratch-tables.md 3), b4a3f76 (scratch-tables.md 1), 00dec17 (docs/scratch-store.md 1), 2688775 (docs/pages/abi-stability.md 1). docs/pages/abi-stability.md | 2688775 | "The engine validates/attaches the inherited fixed-fd-5 arena or returns the handle's typed `report_sink_failed`/`arena_backing`." docs/pages/scratch-tables.md | 1fa26e7 | 'A jailed arena must be a sealed memfd with at least `F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL`, and the jailed admission (`strategy_require_report_sc' docs/pages/scratch-tables.md | 1fa26e7 | 'Where sealed memfds do not exist (not Linux) the admission accepts the arena the host supplies.' docs/pages/scratch-tables.md | 1fa26e7 | 'With `PF_SCRATCH_ARENA_FD` set, only exactly `"1"` enables it; unset or exactly `"0"` preserves the unjailed backing rules, and every other value (inc' docs/pages/scratch-tables.md | b4a3f76 | 'A jailed arena must be a sealed memfd with at least `F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL`; ordinary files, sealing-disabled memfds and unsupport' docs/pages/scratch-tables.md | c7e6bcb | 'The engine never changes process signal disposition.' docs/scratch-store.md | 00dec17 | 'Below it the drain is held to 4,096 B for every population up to 8,191, where the resident journal events own the definitions the spilled book rows ne' docs/design/arena-bound.md | dbf56ed | 'L18 sibling footer evidence described below; absent from this base' docs/design/arena-bound.md | dbf56ed | '`erased_minima_` `include/pineforge/source/pine_adapter.hpp:N`;' docs/design/arena-bound.md | dbf56ed | '`remember_erased` `src/source/pine_adapter.cpp:N`' docs/design/arena-bound.md | dbf56ed | '`consumed_partial_exit_cycles_` `include/pineforge/source/pine_adapter.hpp:N`;' docs/design/arena-bound.md | dbf56ed | '`src/source/pine_adapter.cpp:N`' docs/design/arena-bound.md | dbf56ed | 'The exact external file:line evidence is recorded in the lane report; these sources must be merged and their local anchors refreshed before claiming c' docs/design/arena-bound.md | dbf56ed | 'Summing all eleven peaks gives:' docs/design/arena-bound.md | dbf56ed | 'There are eleven persistent regions and up to three extra replacement indexes.' docs/design/arena-bound.md | dbf56ed | 'Each independently rounded region wastes less than C.' docs/design/arena-bound.md | dbf56ed | 'Summing independent peaks and adding fourteen complete chunks is conservative even when their real peak times differ.' docs/design/arena-bound.md | dbf56ed | 'This covers chunk fragmentation, not unlisted stores or a missing population proof.' docs/design/arena-bound.md | dbf56ed | 'At L=64 the position term is **94*T**; each placement data term is **104*D+168*Q**; consumed-cycle data is **169*Q**; the root term is **16*R**.' docs/design/arena-bound.md | dbf56ed | "The contract's eleven coefficients agree with the inspected code and L19l-A/C reports." docs/design/arena-bound.md | dbf56ed | 'No numeric correction to those coefficients was needed. “72..104 bytes/key” in the L19l-C steady-state explanation does not include the rehash overlap' docs/design/arena-bound.md | dbf56ed | 'Each of two placement data files' docs/design/arena-bound.md | dbf56ed | 'Each of two placement index peaks' docs/design/arena-bound.md | dbf56ed | 'Eleven contributions in the table above, including both two-file rows.' docs/design/arena-bound.md | dbf56ed | 'D=32,Q=1,L=1,E=1; both 128-slot rehash peaks are 3072;' docs/design/arena-bound.md | dbf56ed | 'No production header declaration is changed.' docs/design/arena-bound.md | dbf56ed | 'Linux linker wrappers reject write/pwrite/ftruncate on unlinked regular scratch files, while allowing the named report sink.' docs/design/arena-bound.md | dbf56ed | 'The current base still has those owning-port dependencies.' docs/design/arena-bound.md | dbf56ed | 'It must not be reported as corpus proof until they land.' docs/pages/scratch-tables.md | c7e6bcb | 'The report sink stays **OFF**.' docs/pages/scratch-tables.md | c7e6bcb | 'The host neither constructs a temporary sink nor uses the sink verification/transposition/staging files; it uses ordinary report arrays and the in-mem' docs/pages/scratch-tables.md | c7e6bcb | 'Optional recorded outputs use the ordinary retained-output reader.' docs/pages/scratch-tables.md | c7e6bcb | 'Keep the report sink OFF and use the ordinary report route.' docs/ci.md | 03b2406 | 'Push this prepared migration only after both CORE-TOP gates: laneboss posts **"pf-ci-release live"** and the owner answers **REL-HOSTED**.' docs/ci.md | 03b2406 | 'Until then a job on that label with no runners only waits, and nothing is pushed.' docs/ci.md | 03b2406 | 'Each job using these two CI fragments uses the expression verbatim and sets its matching array as a job-level **string** `PF_CI_LABELS`, merged into a' docs/ci.md | 03b2406 | 'The pre-job hook for those CI fragments also requires the matching `PF_CI_LABELS` string.' docs/ci.md | 03b2406 | 'It pins the hosted PR deployment job in `docs.ym…
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.
Read-only release-ruleset probe for the CR-245 release gate (stacked on #486).
scripts/ci_release_gate.py rulesets-probe --repo OWNER/REPOruns only_require_release_rulesets(read-only API calls, gate verdicts unchanged) and prints the barrier ruleset plus one line per branch ruleset withbypass_actors=visible|hidden; refusal text = the gate's, exit 1..github/workflows/release-ruleset-probe.yml:workflow_dispatchonly,contents: read, canon authz job, mints the release App token exactly asrelease.ymldoes, runs only the probe withpersist-credentials: false.bypass_actorson the org-sourced ruleset before the 1.7.0 tag (the gate refuses a bump if it cannot). Red-first rows: hidden org bypass -> exit 1 naming the ruleset; visible barrier -> accepted.Base is #486's branch until #486 merges; then this retargets to main and its diff is only the probe.
Known residuals (CI-CX, 10-24)
This PR adds a read-only probe (
ci_release_gate.py rulesets-probe) and a dispatch-only workflow (release-ruleset-probe.yml) that runs it once with the release App token, to learn whether that token can seebypass_actorson every branch ruleset (the CR-245 gate refuses every release bump if it cannot). It changes no gate verdict.What is deliberately NOT closed here:
Text-guard respellings (sol P2 3 and P3). The guard
assert_ruleset_visibility_documentationkeeps the unverified App-permission claim ("administration: read" / "App requires") out of the gate source,docs/ci.mdandrelease.yml. It foldsaststring constants, adjacent literals and constant+, and collapses quotes, comment markers and line wraps. Two respellings stay uncaught and cannot be closed by a static text guard: a YAML double-quoted scalar with\x61escapes inrelease.yml(P2 3) and strings built at run time (prefix + "requires",%,.format,.join, f-string expressions; P3). What protects the probe's decisive output is the exact-output pins on the refusal text, not this guard.Cheap guard gaps (sol P2 1, 2, 4) (a markdown backslash-newline wrap, decoded bytes literals, NFKC compatibility characters) and REV's other P2s: one follow-up card, PROBE-FU, due 10-29 18:00 conditional.
Tag-pinned actions. The probe keeps the same mint step as
release.ymlon purpose (actions/create-github-app-token@v1,actions/checkout@v4), so both use tags where the CI canon wants full-SHA pins. A repo-wide card, includingrelease.yml.Dispatch exposure.
workflow_dispatchruns a branch's copy of a workflow with repository secrets and this repository has noenvironment:on the App-minting jobs (theauthzjob is the canon answer;release.ymlhas the same exposure). The real fix (environmentsrelease/release-bot,main-only branch policy, secrets moved into the environment) is CR-304, after the 1.7.0 tag.Runner and ref guard. The probe job runs on
["self-hosted","pf-gcp-post"]because the org runner grouppf-gcp-mainis restricted to allowlisted workflows and does not list this one (L6 ruling: the org allow-list is not widened). The unrestricted group no longer keeps the probe onmain, so the probe job's first step refuses any ref other thanrefs/heads/mainbefore the App token is minted or anything is checked out. That guard is in the workflow file, so it stops mistakes, not a writer who edits the file on their own branch: the lock against that is CR-304 (environment with amain-only branch policy).The one-time dispatch must run from
main(the workflow file must be on the default branch and the run must usemain's copy).Evidence: preflight rj-20261024t073410-7ebc2e passed on da6ab60 (tree 698bc79280; sol + argon APPROVE, REV GO). Earlier lab jobs on tree 7397d9927f: rj-20261024t050339-a90049 (preflight, debug, sanitizers, kernel, docs passed; release failed once on an unrelated copytree race in
test_pending_intent_view_mutations) and rj-20261024t054833-5ebb86 (release passed on a fresh run). Reads: sol, argon and REV (Opus) on the round-3 tree; REV GO 10-24 13:21.🤖 Generated with Claude Code