Skip to content

feat(#536): Gate A preflight before the first CUDA call (CC-536-01-02 N-T2) - #565

Merged
raylei50653 merged 4 commits into
mainfrom
feat/536-preflight-gate-a
Oct 10, 2026
Merged

raylei50653 merged 4 commits into
mainfrom
feat/536-preflight-gate-a

Conversation

@raylei50653

Copy link
Copy Markdown
Owner

Summary

Implements #536 / CC-536-01-02 Gate A (N-T2) as the authorized next slice: every CUDA-free check runs after saccade_track has taken <out> and before the first CUDA API call. A refusal exits 2 with the journal set to failed and makes no CUDA call.

Gate A checks, in order (cheapest first):

  1. Config: the strict loader plus every CUDA-free plan the runtime builds from it (ingest, sequence output, post-detector, pipeline, schedule including the developer serial override), and the detector plan from the lineage and attestation (N-R4).
  2. Sequences: every sequence's seqinfo.ini and img1 listing, read the same way the runtime reads them (same --max-frames).
  3. Outputs: a probe file is created and removed in <out>, in the --report directory (which must already exist) and in each --trace/<seq>/.
  4. Model files: the operator library, head artifact and backbone engine are regular files under the model root, and their sha256 equals the plan's.

Implementation:

  • Gate A lives in saccade_shipping_preflight (shipping/{include,src}/…/preflight.*). This library links no CUDA library.
  • RunCompletion::check_writable() handles the output check.
  • SerialRuntime and DoubleBufferRuntime gain a constructor that takes the DetectorPlan, so Gate B loads exactly the bindings Gate A hashed and doesn't re-read the lineage. The detector load still checks the hashes.
  • resolve_model_path is shared between Gate A and the detector load.
  • On success, stderr gets <entrypoint>: preflight passed after the run-id line.

Not in scope: identity.level stays null. This PR makes no checksum_matched or expected_source_verified claim and does not mark CC-536-01-02 complete overall. Identity binding, the expected source (#549 S1), package re-pin and the report-parse P3 are separate slices.

Evidence

CI (CUDA-free)

  • tests/native/test_shipping_preflight.cpp builds a self-consistent stand-in bundle. It shows the bundle passes, and that each check refuses its own failure: config ×2, lineage ×2, attestation bound to another lineage, sequence ×2, max-frames beyond the listing, report dir, trace dir, and 3 missing plus 3 replaced model files. Each refusal leaves the journal failed with preflight:, identity.level null, and no probe file. 237 checks.

Local, at the built entrypoint (tests/unit/test_saccade_track_preflight_cli.py)

  • "No CUDA call" is observed directly. The CUDA driver dlopens CUDA_INJECTION64_PATH inside cuInit, and LD_DEBUG=files logs that dlopen. Loading libcuda.so.1 is not used as the signal, because libcublasLt's constructor dlopens it before main in every run.
  • Positive control: the stand-in bundle passes Gate A, cuInit is observed, and Gate B refuses it (dlopen of the stand-in operator library fails).
  • Negative controls: replaced head (on both binaries), self-consistent lineage with the committed attestation, missing attestation on the real model files, unreadable sequence, and missing --report directory. Each is refused in Gate A with no cuInit.

Mutation check (local): all 9 mutants were caught: no artifact check, no sequence check, no output check, attestation ignored, max-frames ignored, unprefixed errors, probe file left behind, report dir not checked, and runtime built before preflight (caught by the CLI test through cuInit). One mutant, max_frames_ignored, survived at first because the test kept the old img1 entries. I fixed the test in 7c24b9e3 and the rerun caught it. Raw logs: gitignored results/536_preflight/mutation_35bdcd80/.

GPU (local, gpu0 lease, clean tree at 7c24b9e3, build-release): raw output in gitignored results/536_preflight/runtime_7c24b9e3/ (run.sh mirrors #562's).

  • Shipping double buffer, 7 sequences with trace, all EXACT (--against the feat(#536): run completion for saccade_track (CC-536-01-01) #562 formal run and the PR-C2 formal run): MOT txt, trace hash and graph counts are identical, so the MOT bytes are unchanged.
  • Measurement none and --schedule serial: EXACT.
  • 7 parity negative controls: 7/7 CAUGHT.
  • Measurement-surface and link-surface checks on the shipping binary pass. The expected failure on the measurement binary is present.
  • Gate A controls on the release binaries: 7/7.
  • The attested operator library sha256 is unchanged before and after the run (aa84cccd…), and the tree is clean after the run.
  • GPU native tests (serial_runtime, double_buffer_runtime, detector_s2, post_detector_host, ingest_host, native_build): 6/6.

Known limits

  • Gate A checks bytes, not their source: a self-consistent replaced lineage, without the attestation, passes Gate A and reaches Gate B. That is the positive control above and the CC-536-01-02 known limit.
  • TOCTOU between Gate A and use (F4) remains. The runtime re-reads each sequence's input and the load re-hashes the model files, so a change after Gate A still fails closed, but only later.
  • Gate A decodes no frame: a corrupt JPEG still fails at its sequence.
  • A kill during the output check can leave a .saccade_track.preflight.<run_id>.tmp file behind (same as the other temp files).
  • A measurement-build mutation name is checked after Gate A.
  • The packaged entrypoint (entrypoint_pin.json) is still the PR-C2 build. Re-pin is a later slice; local-only.

Runtime coordinate republication follows as a stacked PR (runbook §3.2).

🤖 Generated with Claude Code

raylei50653 and others added 3 commits October 10, 2026 11:57
… N-T2)

saccade_track and saccade_track_measurement now run Gate A right after the
run takes <out> and before any runtime is built: the strict config and every
CUDA-free plan built from it (ingest, sequence output, post-detector,
pipeline, schedule), the detector plan from the lineage and the attestation
(N-R4), every sequence's seqinfo.ini and img1 listing (read_sequence_input,
the same max_frames), the output directories (RunCompletion::check_writable:
<out>, the --report directory, each --trace/<seq>/), and the operator
library, head artifact and backbone engine present under the model root with
the plan's sha256. A refusal exits 2 with the journal failed and a message
starting "preflight: "; passing prints "<entrypoint>: preflight passed".

Gate A lives in saccade_shipping_preflight, which links no CUDA library. The
runtimes gain a constructor taking the detector plan, so Gate B loads exactly
the bindings Gate A hashed without reading the lineage again; the detector
load still checks the hashes. identity stays {"level": null}: no
checksum_matched or expected_source_verified claim in this slice.

Tests: test_shipping_preflight.cpp (CI, CUDA-free) runs each check on a
self-consistent stand-in bundle and refuses each failure with the journal
failed. test_saccade_track_preflight_cli.py observes cuInit at the built
entrypoint through CUDA_INJECTION64_PATH + LD_DEBUG=files: the positive
control passes Gate A and initializes CUDA, then fails in Gate B; replaced
head, self-consistent lineage with the attestation, missing attestation,
unreadable sequence and missing --report directory refuse with no cuInit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he refusal beyond the listing

The mutation check found max_frames_ignored surviving: write_sequence kept the
earlier img1 entries, so pass_max_frames listed every frame and passed without
--max-frames. write_sequence now starts from an empty directory, and
max_frames_beyond_listing pins the refusal past the listing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Archive the outgoing canonical (implementation 6848dfaa31137ba2, the #562/#563
Completion publication) and promote a fresh capture from the controlled host
(build/runtime_identity, Release, --run-probe --require-complete) at the
implementation head 7c24b9e.

Axes:
* implementation MOVED 6848dfaa31137ba2 -> 9ef960e0fd1683ee; files 292 -> 294.
  - Added (2): shipping/include/saccade_shipping/preflight.hpp,
    shipping/src/preflight.cpp (Gate A, CC-536-01-02 N-T2).
  - Changed (13): shipping/CMakeLists.txt (saccade_shipping_preflight and its
    CUDA-free test; the entrypoints link it), detector_plan.hpp/.cpp
    (resolve_model_path), detector_host.cpp (uses it),
    serial_runtime.hpp/.cpp and double_buffer_runtime.hpp/.cpp (constructor
    taking the detector plan), run_completion.hpp/.cpp (check_writable),
    tools/track_driver.hpp, tools/saccade_track.cpp and
    tools/saccade_track_measurement.cpp (Gate A before the runtime).
  - Removed: none.
* environment, decision_surface, identity_semantics, runtime_inputs:
  unchanged.
* probe: re-run fresh, not reused. Behavior 2dabed0b, equal to the previous
  publication. The build witness moved because build/runtime_identity was
  reconfigured (cmake -S . -B) and rebuilt. Probe equality is not an
  equivalence claim; equivalence stays unproven.

check_runtime_identity_staleness.py --mode attested: exit 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chore(provenance): republish the runtime coordinate for #536 Gate A
@raylei50653
raylei50653 merged commit d075f1d into main Oct 10, 2026
8 checks passed
raylei50653 added a commit that referenced this pull request Oct 10, 2026
…alf-publish boundary

#567 review P2 ×2:
- Gate A known limits: only the three model artifacts are re-hashed at load;
  sequence re-read checks seqinfo.ini and the img1 listing but never hashes
  frame content, so a same-name, valid, correctly sized JPEG swapped in after
  Gate A is processed normally. Listed as an uncovered TOCTOU boundary.
- CC-536-08-02 unverified: keep the #559-era evidence boundary, add that since
  #565 a replaced head with a still-consistent lineage+attestation is refused in
  Gate A before CUDA (covered by the replace-head negative control), while the
  full interrupted half-publish flow remains unverified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
raylei50653 added a commit that referenced this pull request Oct 10, 2026
docs(#536): record CC-536-01-02 Gate A (N-T2) implementation status after #565
raylei50653 added a commit that referenced this pull request Oct 11, 2026
Review P2 on #574: the §4 introduction still said CC-536-01-02 had only
Gate A and no identity level. State that legacy checksum_matched is
implemented at source level while expected_source_verified, the
recognised expected source and the enforcing policy remain open, and
scope the §6 item 3 'identity.level stays null' note to the #565 slice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@raylei50653
raylei50653 deleted the feat/536-preflight-gate-a branch November 13, 2026 11:10
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