Skip to content

t27c seal: wire seal_write.t27 into the seal writers -- refuse collisions, recheck, stage then rename (Closes #10454) - #10466

Merged
gHashTag merged 2 commits into
masterfrom
claude/seal-write-guard
Oct 18, 2026
Merged

gHashTag merged 2 commits into
masterfrom
claude/seal-write-guard

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Closes #10454

What changes

#10458 put the rules for one seal write in specs/verified/seal_write.t27 (module VerifiedSealWrite), but nothing in t27c called them. This PR makes the three seal writers call the code generated from that spec (bootstrap/gen/rust/verified/seal_write.rs, from t27c gen-rust):

  • run_seal, with its twin seals;
  • the frontier --reseal write-back.

The Rust left in main.rs and service.rs is I/O glue only.

One home for the rules. An earlier version of this PR kept its own copy of these rules in seal_identity.t27. That copy is gone, and seal_identity.t27 is the same as on master. Rules the glue needed that seal_write.t27 lacked were added there, each with a test and a negative control:

Rule added to seal_write.t27 What it says Tests
dest_allowed(code, force) --force lifts WRITE_OWNER_UNKNOWN (a torn or legacy seal) and never WRITE_COLLISION (another spec's file). force_lifts_an_unknown_owner_and_never_a_collision, negative_control_force_for_everything_would_reopen_the_collision
gens_check(code, gens_same), WRITE_GEN_MOVED = 6 Generating the spec a second time must give the recorded gen hashes. a_second_generation_must_give_the_recorded_code, negative_control_the_spec_check_alone_misses_moved_gens
SEAL_STAGE_SUFFIX = ".part" The stage is .<seal>.part-<pid>, which is not a *.json seal name. the_stage_suffix_is_not_a_seal_name

How the glue calls it

  1. Before any write.

    • names_collide (exact or case-folded) picks the destination.
    • Its spec_path is read.
    • dest_check and dest_allowed decide.
    • A collision is refused even with --force. A seal with no readable spec_path is refused unless --force is given.
  2. Stage, judge, rename. write_seal_atomic (STAGE_THEN_RENAME):

    • writes the bytes to the stage and syncs;
    • lets post_write_check and gens_check judge the staged file;
    • then renames it over the seal, once.

    Any other answer removes the stage and leaves the old seal untouched. The inputs to post_write_check:

    • tested: the hash of the spec bytes test-report built its tests from (new Report.read field).
    • after: the spec hashed again after the tests.
    • readback_equal: the staged file read back.
  3. Twins and frontier --reseal go through the same staged write. A twin that fails is now named and fails the command; before, it was skipped silently.

Line counts. Foreign lines: main.rs +39/−80, test_report.rs +4, service.rs +1/−1. That is 44 added, within the foreign budget. hash_pairs replaces two hand-spelled lists of the five seal hashes, and the twin loop is folded. .gitignore gains .trinity/seals/.*.part-*.

Evidence (Railway lab, x86_64, master 1c83555 plus this PR)

  • Spec checks. t27c check specs/verified/seal_write.t27 is OK. test-report gives 19/19 tests passing, 0 vacuous.
  • Reseal. seal --save, then seal --verify on seal_write.t27: all hashes MATCH. The committed gen file's sha256 (3d720a32…) equals the seal's gen_hash_rust.
  • Collision control. specs/zzseal1/same/x.t27 and specs/zzseal2/same/y.t27 both declare module ZzCollide.
    • The first spec seals (exit 0).
    • The second is refused (exit 1), with and without --force. The message names the owner.
    • The first seal's sha256 is the same afterwards (7ee284ef…), and the first spec reseals fine.
  • Torn-seal control. A truncated JSON file at the derived name is refused without --force ("names no readable spec_path"). With --force it is replaced, and the result parses.
  • Mid-seal edit control. The spec was appended to at ten points from 0.05 s to 0.6 s into a seal of about 0.8 s. All ten were refused ("the spec changed while it was being sealed"), and no seal file was left.
  • kill -9 control. frontier --reseal ran in its own process group, and the group was SIGKILLed at 29 s, 38 s, 47 s, 56 s and 65 s.
    • After each round, all 2778 .trinity/seals/*.json files parse.
    • No .part- file is left over.
    • Rounds 1 to 5 wrote 119 seals before their kill.
  • No leftovers. No control left a .part- file.
  • Full test suite. cargo test -p t27c --no-fail-fast ran 138 test targets: 2999 passed, 0 failed, 2 ignored. The unit tests in src/main.rs were 1842 passed.

CI reds that are not from this PR

Check Cause Fix
duplicate-bodies clamp01 copied 3 times since #10452 #10471
t27b-native-ratchet UNLISTED 180 #10470
claw-sarif create-github-app-token 422, App permissions infrastructure, not code

#10460 and #10462 fixed the Corpus and test-ratchet reds from the old head; both are in master now.

Overlap. #10464 (claude/seal-write-fix, draft) wires the same spec into main.rs. One of the two should be closed.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-18 13:26:16 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 49
PRs with All Checks Green 1
READY 0
FAILING 49
PENDING 0
NO CHECKS YET 0

These columns do not partition: 0 + 49 + 0 + 0 = 49, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=fad12492937a != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

…ions, recheck, stage then rename (Closes #10454)

#10458 put the rules for one seal write in specs/verified/seal_write.t27,
but nothing in t27c called them. This commit makes the three seal writers
call the code generated from that spec. The Rust left in main.rs and
service.rs is I/O glue only. No second spec: seal_identity.t27 is the
same as on master.

seal_write.t27 gains the rules the glue needed that it did not have, each
with a test and a negative control:
- dest_allowed(code, force): --force lifts WRITE_OWNER_UNKNOWN (a torn
  or legacy seal) and never WRITE_COLLISION (another spec's file).
- gens_check(code, gens_same) and WRITE_GEN_MOVED: generating the spec a
  second time must give the recorded gen hashes.
- SEAL_STAGE_SUFFIX ".part": the staging file is .<seal>.part-<pid>,
  which is not a *.json seal name.
19 tests, 19 pass, 0 vacuous.

The glue:
- run_seal finds the destination with names_collide (exact or
  case-folded), reads its spec_path, and asks dest_check and
  dest_allowed before any write.
- write_seal_atomic stages the bytes beside the seal, syncs, lets
  post_write_check + gens_check judge the staged file, then renames. Any
  other answer removes the stage and leaves the old seal.
- tested = the hash of the spec bytes test-report built its tests from
  (new Report.read field); after = the spec hashed again after the tests.
- The twin seals and frontier --reseal go through the same staged write.

On the Railway lab, at master 1c83555 plus this commit:
- check: OK. test-report seal_write.t27: 19/19, 0 vacuous.
- seal --save, then seal --verify of seal_write.t27: all hashes MATCH. The
  committed gen file's sha256 equals the seal's gen_hash_rust.
- Collision: the second spec is refused, with or without --force; the
  first spec's seal is byte-identical afterwards.
- Torn seal: refused without --force, replaced with --force, parses.
- Spec edited mid-seal, at ten points from 0.05 s to 0.6 s: all ten are
  refused ("the spec changed while it was being sealed"), and no seal
  file is left.
- No .part file left behind in any control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gHashTag
gHashTag force-pushed the claude/seal-write-guard branch from a21034a to ec5af19 Compare October 18, 2026 13:54
@gHashTag gHashTag changed the title seal: refuse a seal name another spec owns, re-check after writing, write atomically (Closes #10454) t27c seal: wire seal_write.t27 into the seal writers -- refuse collisions, recheck, stage then rename (Closes #10454) Oct 18, 2026
@gHashTag
gHashTag marked this pull request as ready for review October 18, 2026 13:55
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-18 14:01:58 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 50
PRs with All Checks Green 0
READY 0
FAILING 50
PENDING 0
NO CHECKS YET 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=fad12492937a != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

This was referenced Oct 18, 2026
@gHashTag
gHashTag enabled auto-merge (squash) October 18, 2026 16:51
@gHashTag
gHashTag merged commit 321a35c into master Oct 18, 2026
34 of 37 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-18 16:55:13 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 44
PRs with All Checks Green 6
READY 2
FAILING 44
PENDING 0
NO CHECKS YET 0

These columns do not partition: 2 + 44 + 0 + 0 = 46, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=fad12492937a != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

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.

seal: refuse a seal name another spec owns, re-check hashes after writing, write atomically so a cut-off run keeps finished seals

1 participant