Skip to content

Build and test compatible release archives - #733

Closed
luisleo526 wants to merge 18 commits into
mainfrom
ci/prebuilt-bookworm-public
Closed

luisleo526 wants to merge 18 commits into
mainfrom
ci/prebuilt-bookworm-public

Conversation

@luisleo526

@luisleo526 luisleo526 commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Build and check Linux release archives with a digest-pinned Bookworm toolchain. Before upload, verify and unpack the packed archive, then link and run the native kernel host against its full shipped library.

Apply strict secret-expression checks only to prebuilt; retain main's credential behavior for every other job. Keep prebuilt token-only with its existing matrix and runner pins, and pin the delegated archive-consumer source. Correct the environment inventory and isolated workflow fixtures.

Validation on this head: whole preflight504tests and whole release-gate289tests passed across eight shards each. Updated-head required CI, final review and the first real archive-consumer dry run remain pending.

🤖 Generated with Claude Code

luisleo526 and others added 11 commits November 4, 2026 14:23
Exercise the shipped artifact workflow and preserve strict rejection of
production secrets, token minting and broader runner or permission scopes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Bind artifact builds to the existing release environment. Admit that metadata
only for the exact artifact matrix while retaining production-secret, token
minting and OIDC rejection.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Inspect every staged shared library and static archive member with GNU ELF
metadata tools. Reject newer glibc versions, incompatible symbol references
and incomplete inspection, with real ELF fixture and mutation controls.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Build with a preloaded Debian 12 toolchain selected by a repository variable
and pinned by content identity. Export credential-free source and check every
staged library before packaging, preserving the ARM and release contracts.

Fixes #730

Co-Authored-By: Codex <[email protected]>
Exercise raw script comments, bracket references and trailing workflow inputs
without relaxing the artifact environment or token admission rules.

Co-Authored-By: Codex <[email protected]>
Preserve script comment content and trailing workflow environment bindings
when checking artifact credential scope. Keep unrelated jobs separate and
retain exact token admission.

Co-Authored-By: Codex <[email protected]>
Require complete findings for token-only jobs and exercise the registered
credential-free route with a real classifier mutation. Register the complete
GNU ELF artifact suite in Linux preflight and verify collection and assertion
failure propagation through the registered stage.

Co-Authored-By: Codex <[email protected]>
Replace document-size pins with independent runner and verifier-slice
inventories and exercise same-size wrong documentation inputs. Align release
runner documentation with the committed workflow's on-demand labels and sizes.

Co-Authored-By: Codex <[email protected]>
Check the complete findings for each protected runner mutation and verify
the clean baseline and the replacement inside the actual parsed job.
Keep credential, matrix and routing refusals bound to the supplied input.

Co-Authored-By: Codex <[email protected]>
Require all four real subtest failures, their route identities and the
specific diagnostic for dotted and bracket token references. Preserve the
real predicate mutation and unchanged positive credential checks.

Co-Authored-By: Codex <[email protected]>
Comment on lines +4192 to +4193
self.assertTrue({'test_version_policy_mutation_breaks_real_rejection_assertion',
'test_isoc23_policy_mutation_breaks_real_rejection_assertion'} <= names)
import subprocess
import sys
import tempfile
import unittest

@graphify-labs graphify-labs Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Not checked: tests were not run; formal verification proved 0 of 5 changed function(s) (1 sampled, not proven, 4 not verified).

Formal verification. PR-changed functions: 1/5 verified (0 proven, 1 may-equivalent, 0 distinguished) · 4 not verified (4 vacuous).

Not verified on this run: app\_token\_environment\_findings (vacuous: never exercised), check\_commands (vacuous: never exercised), main (vacuous: never exercised), runner\_findings (vacuous: never exercised).


Graphify review — findings

Moves the x86_64 release prebuilt build into a preloaded, digest-pinned Bookworm container driven by scripts/build_prebuilt_bookworm.sh, with no registry pull, package install or host-compiler fallback, while the hosted arm64 cell keeps its apt Eigen and CMake steps. Adds a glibc-floor check over every staged .a/.so in both dry-run and publish, with raw checker output and exit status preserved. Also gates prebuilt behind the release environment, stops persisting checkout credentials, and moves release and notify-hub to on-demand gkp-od runners. With Configure now skipped on x86_64, STAGE_DIR and the image identity check in run_tool carry the new transport, and numeric_version decides how the glibc checker handles malformed tags.

Worth a look

  • nonnumeric GLIBC tags raise NameError instead of returning None — scripts/check_prebuilt_glibc.py:261 · Escalate · high
    • NOT verified (no proof, no reproducing execution) — needs human review
  • Compares Docker image ID to pinned manifest digest — scripts/build_prebuilt_bookworm.sh · Escalate · high
    • NOT verified (no proof, no reproducing execution) — needs human review
  • STAGE_DIR may be unset on x86_64 now that Configure is skipped — .github/workflows/release.yml:491 · Escalate · medium
    • NOT verified (no proof, no reproducing execution) — needs human review
  • Read-only builder container has no writable /tmp — scripts/build_prebuilt_bookworm.sh · Escalate · medium
    • NOT verified (no proof, no reproducing execution) — needs human review
  • Release CI docs still claim x86_64 installs and validates Eigen on the runner — docs/ci.md:1691 · Escalate · medium
    • NOT verified (no proof, no reproducing execution) — needs human review

Review partial — this diff was larger than one review pass covers, so later files were not reviewed; some findings may be missing.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1519 functions depend on the 1498 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: ci_workflow_findings() — 81 callers, 24 callees
  • worse: check_commands() — 22 callers, 5 callees
  • worse: _job_values() — 30 callers, 3 callees
  • worse: _job_block() — 18 callers, 3 callees
  • worse: runner_findings() — 2 callers, 25 callees
  • new: release_prebuilt_workflow_findings() — 5 callers, 5 callees
  • worse: _unparsed() — 6 callers, 4 callees
  • worse: _holds_credentials() — 3 callers, 6 callees
  • …and 6 more — each is listed as a finding

Verification — 1519 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1519 function(s) in the blast radius were not formally verified this run

Docs that may be stale (advisory)

…and 10 more.

Formal verification

No difference found (not proven): No behavior difference found in \_holds\_credentials (not a proof).

The verifier ran both versions of \_holds\_credentials on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify app\_token\_environment\_findings.

The verifier did not have enough to check app\_token\_environment\_findings, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 2 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

Could not verify: Could not verify check\_commands.

The verifier did not have enough to check check\_commands, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 90 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify main.

The verifier did not have enough to check main, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 1 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify runner\_findings.

The verifier did not have enough to check runner\_findings, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 2 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

· 7 grounded finding(s) anchored inline below; 7 more finding(s) on lines outside this diff (see the check run).

@@ -0,0 +1,321 @@
#!/usr/bin/env bash

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression — build_prebuilt_bookworm.sh script

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

raise


def discover(root: Path, root_fd: int, issues: list[dict]) -> tuple[list[dict], dict]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression — discover()

fans out to 8 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

**context, reference=reference)


def inspect_target(root_fd: int, target: dict, directories: dict,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression — inspect_target()

fans out to 12 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

return row


def main(argv: list[str] | None = None) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression — main()

fans out to 8 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread scripts/ci_preflight.py


def _holds_credentials(workflow: str, body: str, *, effective_permissions: bool = False) -> bool:
def _holds_credentials(workflow: str, body: str, *, effective_permissions: bool = False,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression — _holds_credentials()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread scripts/ci_preflight.py Outdated
return False


def _prebuilt_environment_admitted(name: str, job: str, workflow: str, body: str,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression — _prebuilt_environment_admitted()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread scripts/ci_preflight.py
return []


def release_prebuilt_workflow_findings(workflow: str) -> list[str]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression — release_prebuilt_workflow_findings()

high coupling complexity (Ca·Ce = 25).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@luisleo526

Copy link
Copy Markdown
Collaborator Author

Review of 85c9802 against main fc58eec (9 files, +3307/-121). The x86 build, the glibc scan and the upload path are correct. Four small changes are needed before merge.

What holds

  • The shipped bytes are the scanned bytes.
    • The checker scans STAGE_DIR (.github/workflows/release.yml:468).
    • After that, only LICENSE, NOTICE and VERSION are added, and tar packs that same directory.
    • The upload uses the exact step outputs. There is no rebuild, cache, glob or rename between scan and upload.
  • Incomplete inventories are refused. An empty inventory is refused, and a re-scan catches a changed one (scripts/check_prebuilt_glibc.py:632-646).
  • The checker reads the real tables and can fail.
    • It parses readelf's symbol-version, version-need and version-definition tables, with count checks, and matches ar membership against readelf.
    • It passes only with a nonempty inventory, every target inspected and no issues (:632-661).
    • Refusals name the file, the archive member and the version.
  • The tests run the real checker on real fixtures, built with the host's cc, ar and readelf. Each wrong implementation fails a named assertion:
    • versions are never read;
    • archive members are skipped;
    • GLIBC_2.38 is accepted;
    • __isoc23_* is ignored.
      The boundary is held from both sides (2.36 and 2.36.0 pass). Expected hashes come from the fixture bytes, never from checker output.
  • The toolchain is pinned. The image is pinned by digest, with no pull fallback. Exact versions are checked inside the image, and the build runs with no network. The x86 build cannot silently pick up a newer glibc.
  • Workflow security.
    • contents: write only.
    • The token is used only in the upload step.
    • persist-credentials: false.
    • No ${{ }} inside run: blocks.
    • Every action is pinned by SHA.
    • No || true, continue-on-error or set +e; every child exit code is kept.

Needed before merge

  1. The step pin cannot see a gate-disabling edit.
    • The checker call in release.yml:468-469 continues onto a redirection line.
    • Insert a blank or # line after the backslash and the call ends early. The redirection line then becomes a separate command that exits 0, and it is the last command of the if condition, so checker_rc is 0 whatever the checker found.
    • The exact-step pin compares steps through _code(), which drops blank and comment lines (scripts/ci_preflight.py:1102-1104, 3303-3305). So the edit passes the pin, despite the comment at :3140-3141.
    • Fix: put the checker call on one line, compare this job's steps on raw lines, and add blank-line and comment-line mutations to the pin tests.
  2. Two existing assertions are deleted.
    • test_post_fleet_docs_match_runner_policy and test_documented_slow_row_counts_match_literal_population each lose assertEqual(len(...splitlines()), 2063).
    • The docs/ci.md change adds and removes the same number of lines, so both assertions still hold.
    • Please keep them alongside the new semantic checks rather than removing them.
  3. Public wording. The module docstring at scripts/test_build_prebuilt_bookworm.py:4 uses internal host-role wording. Please reword it neutrally, for example "Run with an explicit --scratch-root".
  4. prebuilt-bookworm-tests is registered on every platform (scripts/ci_preflight.py:4508-4511).
    • The helper it exercises uses bash-4 syntax: ${1,,} at scripts/build_prebuilt_bookworm.sh:22.
    • The glibc stage beside it is Linux-only (:4516-4519).
    • If preflight runs this stage under macOS's bash 3.2, it fails. Either gate it like the glibc stage, or make the helper bash-3 compatible.

Status note. On this head the main-tree admission rules refuse the new release job ("must keep production credentials on a hosted standard runner", "must install Eigen and assert exactly 3.4.0"), so preflight and the proof legs were skipped. That is expected for a change to those rules, but no CI result exists yet for this head. The release CI build and scan of the new archive remain the evidence.

Follow-ups (not blocking)

  • Slim-LTO members. A slim-LTO archive member would pass unseen, because readelf does not decode .gnu.lto_* sections. The current build files enable no IPO or -flto, so there is no exposure today. Refusing members that carry __gnu_lto_slim would close it.
  • Files not covered by the scan.
    • Only .a, .so and .so.* names are scanned, so executables and other ELF files are not.
    • In static members, unversioned references to other post-2.36 functions (for example strlcpy) cannot be seen; the image pin is the real guard there.
  • The 2.36 ceiling is written twice, in the checker and in the helper, with nothing tying the two together.
  • The environment guard is a deny-list. PYTHONPATH, LD_PRELOAD, GIT_*, SHELLOPTS, TMPDIR and HOME pass at workflow or job scope. An allow-list would be stronger.
  • Smaller points.
    • docs/ci.md still describes the old Eigen install for the x86 cell.
    • The registration test leaves files in the system temp directory.
    • One added test_ci_release_gate.py case duplicates part of an added test_ci_preflight.py case.
    • The scanned and packed directories match only by construction.
    • Several existing public files already carry internal identifiers (release.yml:540, scripts/ci_preflight.py:7-10, 4482-4532); worth a separate cleanup.

Nothing was executed for this review.

import sys
import tarfile
import tempfile
import unittest
@luisleo526

Copy link
Copy Markdown
Collaborator Author

Delta review of d9fd57a: f637684 on 85c9802, plus a merge of main 4c9e763 that brings only main's three-file sanitizer-shard change.

Content: all four requested changes are in, plus the runner and Eigen changes. No faked pass found. Merge: NOT YET. The admission job refuses this head, so preflight and the proof legs have not run on it.

What the delta does

  1. Glibc gate tamper gap closed.
    • The checker call is now one physical line (.github/workflows/release.yml:477).
    • release_prebuilt_workflow_findings compares the named step on raw lines (scripts/ci_preflight.py:3336). _step_named returns the unprocessed step text.
    • test_glibc_invocation_rejects_raw_blank_and_comment_continuations (scripts/test_ci_release_gate.py:2344) checks that each of these produces the finding:
      • a blank line after the backslash;
      • a comment line after the backslash;
      • removing the call;
      • narrowing it to one library;
      • commenting it out.
  2. Both docs line-count assertions are restored (scripts/test_ci_preflight.py:2438, :4822). docs/ci.md is still 2063 lines at this head.
  3. The test module docstring is neutral: "Run on Linux with --scratch-root".
  4. prebuilt-bookworm-tests is registered only on Linux (scripts/ci_preflight.py:4547). Platform-patched tests cover darwin and win32 in the fast and full tiers.
  5. Runners.
    • release and notify-hub run on gkp-2cpu-4gb-amd64 (release.yml:80, :548).
    • The prebuilt matrix uses gkp-16cpu-32gb-amd64 and gkp-16cpu-32gb-arm64 (:370, :372).
    • The preflight pins and their tests follow.
  6. Eigen on ARM is exact.
    • It installs libeigen3-dev=3.4.0-4 and checks the installed version.
    • It checks the three Macros.h version defines.
    • It pins Eigen3_DIR, disables FetchContent, and checks CMakeCache.txt for that Eigen3_DIR line.
    • Each of seven mutations is refused (scripts/test_ci_preflight.py:960 onward).
  7. New: the x86_64 job consumes the archive it is about to upload (release.yml:507).
    • scripts/build_prebuilt_bookworm.sh --consume exports only:
      • the packed tarball and its checksum sidecar;
      • the two helpers;
      • examples/native/hello_kernel.cpp.
    • It runs the pinned image by digest with --pull=never, --network none, --read-only, all capabilities dropped and a fixed UID:GID.
    • scripts/check_shipped_archive_runtime.sh then:
      • verifies the sidecar with sha256sum --check --strict;
      • unpacks the archive and requires the headers, both libraries and the CMake package;
      • links hello_kernel against the full libpineforge.a and runs it.
    • The child's exit status is kept end to end. The transport tests use a recording Docker; they say they give no compatibility verdict, and they check both the exact exported inputs and that a failing child's status (23) is preserved.

Why admission still refuses

  • Admission runs the base branch's checker against this head's workflows (.github/workflows/gke-pr.yml, "Checkout the base checker" and --rules-only).
  • On this run it stopped at actionlint. Main's .github/actionlint.yaml does not list gkp-16cpu-32gb-amd64 or gkp-16cpu-32gb-arm64 (release.yml:370, :372). This PR adds both labels to the config, but admission reads main's copy.
  • Past actionlint, main's rules still pin:
    • RELEASE_RUNNER = 'gkp-od-4cpu-8gb-amd64';
    • PREBUILT_RUNNER = 'gkp-od-16cpu-32gb-amd64-128gb';
    • the hosted ubuntu-22.04-arm row (scripts/ci_preflight.py:103-104, :228 on main).
  • So this head cannot pass admission while it changes the rules admission enforces. The rule and label change has to reach main first, or the merge has to go through an administrator, with preflight and the proof legs run on this exact head as the evidence.

Before the first real release

  • Runner pools must exist. The two new labels need runner pools that actually serve them. Otherwise the prebuilt jobs wait in the queue at release time. docs/ci.md already lists the label catalog, live membership, image preload and UID:GID as open operational confirmations.
  • Preemption. release and notify-hub move from the on-demand 4 CPU set to the 2 CPU set. Please confirm that set is not preemptible: a release interrupted after its tag push would leave a tag without artifacts.

No build or test was run for this review.

Remove the deployment environment from the prebuilt matrix and retain artifact
upload through the existing workflow token. Treat deployment environments as
credentials, preserve raw secret checks, and enforce the artifact job's release
dependency and token permissions.

Keep packed-archive runtime checks ahead of release upload.
@luisleo526

Copy link
Copy Markdown
Collaborator Author

Delta review of 669de8d over feb9fa3 (one commit, b97140d, then main 09756a9 merged last, which brings only docs), read against main 09756a9; the whole PR is 10 files, +3997/-100. NOT YET: the required preflight job is red on this head (4 tests, 7 failing cases), and the cause is the credential-rule change that came with this commit. The design itself reads right: the admission check now passes, the prebuilt job keeps exactly the credential surface main already admits, and the checks that gate the upload run inside it. I ran nothing myself; the red below is from the CI log.

Blocking

  1. P1: preflight fails on this head. Run 41900937905, job 126823088070: unittest-shards: FAILED (501 tests across 8 shards; failed: shard 2, shard 4, shard 8). Four tests, seven failing cases, all in scripts/test_ci_preflight.py:

    • PreflightFailures.test_github_token_alone_and_credential_comments_keep_the_pool (:8001, :8013), four subtests (secrets.GITHUB_TOKEN and secrets['GITHUB_TOKEN'], each on a throwaway job and on the registered corpus job). A job whose only credential is the workflow token, with the comment line # secrets.DEPLOY_KEY, id-token: write, environment: production, now gets must not expose credentials on GKE and must keep production credentials on a hosted standard runner (the throwaway job also must not set PF_CI_LABELS on a hosted job). The test expects none of them.
    • PreflightFailures.test_wrong_github_token_classification_breaks_the_exact_positive_row (:8056): the same cause. The bracket-form rows now carry must not expose credentials on GKE, which that test requires them not to carry.
    • AppTokenEnvironments.test_real_minting_jobs_have_exactly_the_inventoried_environments (:747): the expected inventory still holds ('release.yml', 'prebuilt'): ['release'], and this head removed the environment from that job (.github/workflows/release.yml:360-372). The row cannot match; delete it.
    • AppTokenEnvironments.test_second_workflow_files_are_discovered_by_the_ci_entrypoint (:704, through CiFileLoading.make_tree at :3769): AttributeError: 'AppTokenEnvironments' object has no attribute 'HELPER'. make_tree copies self.HELPER, and this class borrows make_tree without defining it.

    preflight runs the whole file in eight shards, so the whole file has to be run before the next push, not only the tests that touch the change.

  2. P1: the credential rule changed for every job, and that is what breaks the first two. _holds_credentials (scripts/ci_preflight.py:2268) now scans raw text (the header, comments, the workflow-level env) for App-mint actions, key inputs (:2243) and whole or dynamic secrets contexts, and its last loop (:2317-2322) returns True for any dotted secrets.X other than GITHUB_TOKEN found anywhere in that text, comments included. The comment just above it (:2299-2301) says prose may mention secrets; the loop treats prose as a secret. Main's rule does not, and the test above exists to keep a comment mention from changing a job's pool. The strict scan is what the prebuilt job needs. Keep it for that job only and leave every other job on main's rule, or keep it everywhere on purpose, change the tests, and say so in the body. I found no input that main's rule treats as credential-bearing and this one does not, so nothing is loosened; I cannot name which shipped job flips, because the tests only assert runner_findings(shipped) == [].

  3. P2: the body describes a state this head no longer has. It says "Keep the prebuilt release environment and strict raw workflow credential checks". The environment is gone from the prebuilt job and the rule is now raw-bytes for every job. The body becomes the squash message and the public record: say that the prebuilt job keeps the token-only surface it has on main, that its release dry run and upload are unchanged, and what the rule change covers (after the fix above).

Not blocking, but settle before merge

  1. P2: the in-container consumer is neither pinned nor run by any test. scripts/check_shipped_archive_runtime.sh is the step that checks the packed archive's sidecar, unpacks that archive, links hello_kernel against the full libpineforge.a and runs it. RELEASE_BOOKWORM_HELPER_BLOB (ci_preflight.py:3367) pins build_prebuilt_bookworm.sh only, and neither ci_preflight.py, its two test files nor release.yml names the consumer script. scripts/test_build_prebuilt_bookworm.py:81-87 answers --inside-consume with exit 0 and "consumer transport only; no compatibility verdict", and :459-487 only copies its bytes and checks the argument list. The script is correct today (:34-86), but a later edit that returns early would pass every PR-side check, and docs/ci.md:1971-1973 rests on it. Either pin it by blob like the helper, or add a Linux test that runs it against a small real archive. Until then, read the consumer's own output in the first release dry run; that run also settles whether hello_kernel links against --whole-archive libpineforge.a alone, which nothing here executes.

Smaller points (P3)

  • ARM and static archives. The ARM cell is not run through the archive consumer (docs/ci.md:1973 says so). For .a members the glibc gate can only see explicit version-tagged symbols and __isoc23_* names (scripts/check_prebuilt_glibc.py:439-442, :485-487), because relocatable objects carry no version requirements; the module docstring (:2) reads as if it proved the floor for archives. The archive fixture uses GLIBC_3.0 (scripts/test_check_prebuilt_glibc.py:375-379). The real floor evidence for archives is the Bookworm link, x86_64 only. Say so in the docs.
  • Doc wording. docs/ci.md:1972 says "configure, link and run diagnostics"; the consumer has no configure stage, it checks that the CMake package files exist and then compiles and links with g++ (check_shipped_archive_runtime.sh:54-76). The file has no sentence for the all-library glibc check the workflow now runs before packing (release.yml:490).
  • Operational inputs the diff cannot show. The x86_64 build needs the repository variable ENGINE_TOOLCHAIN_IMAGE set to <repository>@sha256:72fb3f2a..., that exact image preloaded in the runner's own Docker daemon, Docker 29.9.2 or 29.8.1, UID:GID 1001:123 and the shared /home/runner/_work (build_prebuilt_bookworm.sh:7-9, :66-109, :295). Each fails closed and docs/ci.md says they still need confirmation; a release dry run on that pool is the first proof. The helper also prints the whole docker images list and the full docker image inspect JSON to the job log (:72-73, :85): confirm the image config holds nothing you would not publish.

Checks on this head

Passing: admit, both authz, both build, build-debug and both CodeQL analyses. corpus-parity-subset was cut by a runner shutdown (job 126823088280: "The runner has received a shutdown signal", exit code 130, in the middle of building the generated corpus strategies); it says nothing about the code and needs a re-run. build-arm was still pending when I read.

What holds

  • The credential surface is main's. The prebuilt job is needs: release, permissions: contents: write, with the same matrix and runs-on: ${{ matrix.runner }} (release.yml:360-372). It has no environment (the two in the file, :81 and :569, belong to release and notify-hub), its only secret is secrets.GITHUB_TOKEN in the upload step (:543), vars.ENGINE_TOOLCHAIN_IMAGE (:406, :530) is a variable, and the checkout no longer persists credentials (:381). No job was added; GKP_JOB_ENVIRONMENTS no longer lists prebuilt (ci_preflight.py:120).
  • The upload runs only after the gates. The order is build (x86_64 in the pinned image, :403), staged version (:481), the all-library glibc gate for both slices (:490), pack (:510), consume the packed archive (:527), upload (:540), then the dry-run artifact (:550). No gate has an if, continue-on-error or always(). The whole step list is pinned and compared exactly (RELEASE_PREBUILT_STEPS, ci_preflight.py:3176), and the glibc step is also compared by raw physical lines (ci_preflight.py:3379; scripts/test_ci_release_gate.py:2344).
  • The consume step reads the bytes that are uploaded. It takes the pack step's own outputs (release.yml:530-534) and demands the exact names pineforge-<tag>-linux-x86_64.tar.gz and .sha256. Inside the image it checks the sidecar strictly (one record, a 64-hex digest, the tarball's name, sha256sum --check --strict), unpacks that archive, requires the headers, both libraries and the CMake package, links and runs the host, and returns each status (check_shipped_archive_runtime.sh:34-86, build_prebuilt_bookworm.sh:200-259). Nothing runs between that step and the upload.
  • No host toolchain, no pull, no secret in the container. The image must be <repository>@sha256:72fb..., already present with that image id, linux/amd64 and the exact repo digest; empty, unset, tag-only or wrong digests fail before any docker call. The container runs with --pull=never --network none --user 1001:123 --read-only --cap-drop ALL --security-opt no-new-privileges, the source read-only, no -e, --env, --env-file, --privileged or socket mount (build_prebuilt_bookworm.sh:242-248, :403-409); the source is exported from tracked blobs with no .git (:307-391). The helper is pinned by its git blob id; I recomputed it from the head file and it matches.
  • The glibc checker fails closed. It scans every .a, .so and .so.*, and an empty stage, symlinks, protected names, unreadable or non-ELF members and a missing ar or readelf all produce an issue and a nonzero exit (check_prebuilt_glibc.py:92-97, :233-252, :632-661); the workflow step captures the status and re-exits it (release.yml:497-503). Its tests build real objects with cc, ar and readelf, and mutants of the ceiling comparison and the __isoc23_* match are each rejected (test_check_prebuilt_glibc.py:707-738).
  • Nothing weakened. No test is deleted or skipped (the only skip is skipUnless Linux on the registration test), each removed assertion is replaced by an equal or stronger one, and docs/ci.md stays at 2063 lines with both assertions kept (test_ci_preflight.py:2678, :5059).
  • Public text. The added lines and the two new commit messages contain no internal names; the body is the exception in point 3.

Preserve main's credential handling for every other workflow job. Update the
deployment-environment inventory and reusable isolated-tree helper fixtures.
Validate the delegated packed-archive consumer against its reviewed source
identity, with genuine, missing and changed-source fixture controls.
@luisleo526

Copy link
Copy Markdown
Collaborator Author

Delta review of 74d0476 over 669de8d: two new commits, dcbffbe ("Scope archive credential checks to prebuilt and pin its consumer") and then main ace3079 merged last, which adds only scripts/ci/rerun_preempted.py and its test. NOT YET, for one reason: the required preflight job is still red. The unit-test failures and the P1 and P2 findings of my first comment are fixed. The same job also fails two stages that I did not list the first time, and they fail on the earlier head as well. I ran nothing: the statements about the code are from reading it, and the red is from the CI log.

Blocking

  1. P1: preflight fails on this head, in the two documentation-revert stages. Run 41911017092, job 126858470540, step "Run the class-specific preflight". unittest-shards: 504 tests across 8 shards passed, so the earlier test failures are gone, but:

    • doc-reverts: "doc reverts: 14 commits since ace3079: 11 unnamed sentence deletions or reverts ... FAIL"
    • doc-reverts-net: "doc reverts: ace3079 -> 74d0476 (1 pages): 6 unnamed sentence deletions or reverts ... FAIL"

    All findings are in docs/ci.md. Per commit, for example: the "Hosted exceptions cover PR light jobs; release uses protected GKE and post light jobs use bare scalar GKE names." sentence and the "gkp-2cpu-4gb-amd64; x86_64 prebuilt:" line come back as older text over newer text (introduced by f637684), and "ARM installs exactly libeigen3-dev=3.4.0-4; x86_64 checks that package in its image." is deleted. For the branch as a whole: "Linux x64 prebuilt uses gkp-16cpu-32gb-amd64-128gb.", "The protected 2 CPU/4 GB set fits each orchestration job: ...", "The x86_64 prebuilt set reserves 16 CPU/32 GB and 128 GB disk ...", "Both Linux artifact cells install libeigen3-dev with ...", "Eigen is not baked into the protected image." (introduced by c70e828). scripts/check_doc_reverts.py requires every deleted or reverted sentence to be named by the message of the commit that removes it: the short hash of the commit that introduced the sentence, or six consecutive words of it. The per-commit stage judges each commit with its own message; the net stage judges the branch with the messages of every commit in it.

    I missed this in my first comment. The same two stages fail on 669de8d ("13 commits since 09756a9: 11 unnamed ... FAIL" and "09756a9 -> 669de8d (1 pages): 6 unnamed ... FAIL"); they print "... FAIL" rather than a test-failure marker, and I read only the unit-test failures, so "the only blocker" in that comment was wrong. These findings concern docs/ci.md history, so the last commit could not have fixed them. Fix: name each listed sentence in the message of the commit that removes it (which means rewriting or squashing the branch history so the messages carry the 11 and the 6; the documentation edits themselves are right, they only have to be named); run python3 scripts/check_doc_reverts.py and python3 scripts/check_doc_reverts.py --head HEAD before the next push.

  2. Not a code red, but still not green. corpus-parity-subset was cancelled again by the runner ("The runner has received a shutdown signal", job 126858470459, in its parity step); it needs a re-run. build-arm was still running when I read it. Passing: admit, both authz, both build, build-debug and both CodeQL analyses.

What was fixed (by reading)

  • The rule scope. _holds_credentials takes a strict flag (scripts/ci_preflight.py:2268-2269). The raw scan (header, comments, workflow-level env, App mints, key inputs, whole or dynamic secrets contexts) is in the if strict: branch (:2288-2320); the else branch is main's comment-stripped scan (:2321-2323), and the lines before and after it are main's, apart from the tail loop using _SECRET_REFERENCE, which main already defines with the same pattern and flags. Only the two prebuilt call sites pass the flag (strict_prebuilt = (name, job) == ('release.yml', 'prebuilt'), :2846, used at :2887-2888 and :2903-2905); the other callers (:2750, :3702) pass nothing. The refactor around it keeps behaviour: app_token_environment_findings now calls _uses_app_token (:2154-2163, :2178), which is main's inline condition with the same three indicators over the same text.
  • The four failing tests. The stale inventory row is gone (scripts/test_ci_preflight.py:734); make_tree takes the helper and the new consumer from CiFileLoading explicitly (:3766, :3780); the two token-pool tests are main's own, unchanged (:8047), and the restored rule satisfies them. The CI run above confirms all 504 pass.
  • The rewritten rows pin both sides. For the other jobs a reference injected as a real env value must be refused (:788-802); for prebuilt, the same references in a comment are ignored by the default scan, caught by strict=True and refused through runner_findings as "must keep production credentials on a hosted standard runner" (:804-817). Applying strict to every job would fail the token-pool tests; not wiring it for prebuilt would fail the last assertion.
  • The consumer is pinned and tested. RELEASE_BOOKWORM_CONSUMER_BLOB (ci_preflight.py:3380) equals the git blob id of scripts/check_shipped_archive_runtime.sh at this head; I recomputed it (d0669689...). The check is the helper's (:3392-3398), main() refuses a missing or symlinked consumer (:4915-4925), and the new rows run the real --check-ci-workflow stage: the genuine copy passes, and a missing file and an appended line each fail with exactly one finding (test_ci_preflight.py:3907, :3934, :3943). In the CI run, ci-workflow-contract passes on the real tree.
  • The body now says the strict secret-expression checks apply to prebuilt only, other jobs keep main's behaviour, prebuilt stays token-only with its matrix and runner pins, and the consumer source is pinned.

Carried over (P3, unchanged, not blockers)

  • The ARM slice still has no archive consumer, and the glibc gate sees only explicit version tags and __isoc23_* names in .a members; docs/ci.md:1972 still says "configure, link and run diagnostics" for a consumer that has no configure stage, and the file has no sentence for the glibc gate.
  • The operational inputs and the first real archive-consumer dry run are still pending, as the body says; read the consumer's own output there.
  • Pre-existing, not changed here: main's default scan does not recognise a whole secrets context such as toJSON(secrets) (it matches only secrets.NAME and secrets[...]), so for the other jobs that form stays unflagged; only the strict scan, for prebuilt, catches it.

Public text. The added lines and the commit message contain no internal names.

@luisleo526

Copy link
Copy Markdown
Collaborator Author

Superseded by #736, which carries the identical file tree as one commit whose message names every documentation sentence this change edits. The branch of this pull request is kept.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant