Skip to content

R5 INT20: v19 epoch B (journal window, event retention, acknowledgement, native_order_v7) and the fused one-lot settlement - #283

Merged
luisleo526 merged 18 commits into
mainfrom
r5/int20
Sep 24, 2026
Merged

luisleo526 merged 18 commits into
mainfrom
r5/int20

Conversation

@luisleo526

@luisleo526 luisleo526 commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

R5 INT20 — v19 epoch B and the fused one-lot settlement

This integration puts two lanes onto main (which already carries INT19 = V19-A + V19-C + K-COHORT + PERF-P7 + FPC, and TA1).

Lanes

V19-B — the journal window, event retention and host acknowledgement (value lane of the v19 epoch)

  • Request, core and event values move to native_order_v7 (checked against the frozen v18 provider).
  • The order core's journal is a window with absolute positions. Cohorts read a chain index (issued-incarnation ranges plus replace-chain roots) instead of the journal, so canonical_cohort_origin is O(1) and definition_for is gone.
  • A trail's arm ordinal lives in its tracking state, not in a journal scan.
  • NativeRunSpec::event_retention: Window (default), Commands or Full. It is validated and folded into the spec digest only when it is not Window, so native_run_spec_v3 is kept.
  • Hosts acknowledge what they have read: native_acknowledge_events / native_event_window_start (C++), strategy_native_acknowledge_events_v1 / strategy_native_event_window_v1 (C, additive; PF_ABI_VERSION stays 4). The Pine adapter declares Window and acknowledges its receipt cursor.
  • The window is enforced at every script-bar boundary. A C caller that sends no retention word gets Full.
  • Trades, equity, readbacks and events are identical to main everywhere. Hash values moved only where V19-B folds new state (chain roots, issued ranges, trail arm ordinals, FX instants), and they equal V19-B's own lane values.

PERF-L2 — fused settlement for the common case (byte-identical)

  • A settlement call whose book holds at most one lot settles in one pass on the stack (NativeSettlementStage::OneLot). Every other call keeps the staged chain. A differential test holds the two equal bit for bit.
  • Allocations per fill: 15.6 → 6.6 (bracket), 10.2 → 2.7 (market).

Integration commits

  • PERF-P7's consumer-side definition index is removed. V19-B's chain index answers cohort membership now, and test_native_definition_index holds that index to a whole-journal oracle and a twin that retires its journal at every bar boundary (667,341 checks, 0 failures). P7's adapter lookup indexes stay, with their witnesses unchanged.
  • Three main-side tests that read a run's whole event record now ask for Full retention. That is a readback choice only: the spec, its digest and every pinned value are the default's. Under the new Window default, PERF-L2's witness had been comparing 0 events.
  • Two slice-B witnesses take their v19-B hash values, once (24 values in 2 files). Every trade, event, count and hook call they pin holds.
  • Four mutation-guard tests that copy the source tree skip __pycache__/*.pyc. This fixes a parallel-ctest race found on x86.
  • The Pine adapter's quiet receipt read carries a comment on why it needs no acknowledgement (the object file is byte-identical).
  • Floors: kernel 245 → 249, release 631 → 635 (by ctest -N).
  • CI: the sanitizers job's timeout-minutes goes 60 → 90 (INT19's run took 54m20s).
  • Docs citations re-anchored once (1,628/1,628 anchors resolve).

Verification

x86-64 lab remote (NTU2/NTU, GCC 13) on 051425b (tree ef6856426151): preflight ci_preflight: passed (exit 0, 25 stages, 0 advisory reported); release ctest ran 635 rows (floor 635), runtime budget: candidate=0.279656s ab9714b=0.051497s ratio=5.431x limit=15.000x; sanitizers 100% tests passed out of 616; debug 100% tests passed out of 616; kernel ctest ran 250 rows (floor 249); baseline match=312, moved=0, unrecorded=0, missing=0, schema violations=0 (312 probes); Verified 312 strategies — excellent=311, strong=0, moderate=0, weak=0, minimal=0, anomaly=1, engine_only=0, missing=0; subset baseline match=30, moved=0, unrecorded=0, missing=0, schema violations=0; Doxygen warnings: 4

Sweep

experiment exp-r5-int20-v19b-l2-20260924 (engine 051425b, tree ef685642; codegen f3285d7; 4,190 probes, 72/72 jobs): coverage lost 0; band 1 (excellent+strong) +0/−0, band 2 (excellent) +0/−0, score 0; hard-lane regressions 0; leavers 0; 0 tier or outcome changes; pr_gate pineforge-pr-gate-mmcsk: target.not-positive, the neutral verdict at net 0 (no tier moved in either band).

Measurement

Spark, Cortex-A725 core 2, interleaved rounds (load 2.2–4.6). Values are median ns/bar; ratios are per-slot medians.

population magnifier 063e446 main (INT19) merged merged/main merged/063e
14 probes, 5 rounds off 3112 1431 1459 0.989 0.450
on 3313 1708 1577 0.925 0.458
100 slots, 2 rounds off 3217 1472 1376 0.957 0.444
on 3355 1648 1515 0.929 0.441

Kernel workloads, merged/main (best of 7): replay 0.923, idle 0.574 (tolerant) / 0.647 (canonical), bracket 0.740, reissue 0.710, market 0.649, K1 NY 0.820, grid-10 0.838, K24 0.758, resting-10 0.987, resting-100 1.020 (+0.69 % instructions: V19-B's per-commit index row and per-bar retirement). No-order floor: 810.5 → 575.5 ns/bar with the magnifier off, 906.2 → 749.2 with it on.

Peak RSS, main → merged: idle 1e6 bars 722 → 285 MB; idle 4e6 bars 2,878 → 1,128 MB; bracket 1e6 bars 1,309 → 317 MB; reissue 4e5 bars 1,429 → 132 MB. Allocations per bar (14-probe median): 1.71 → 1.40 with the magnifier off, 1.55 → 1.32 with it on.

Runtime budget on the Mac (check_runtime_budget.py, three interleaved rounds): main 4.08–4.12×, merged 3.96–3.98× of ab9714be. PyneCore multiple, an ESTIMATE from spark ratios applied to BENCH3's Mac times: about 34–37× (BENCH3 measured 15× on 063e4460).

CI: the sanitizers job took 43m52s (INT19: 54m20s), and ctest real time went from 1580 s to 1112 s. On the first attempt, one timing guard (test_native_state_continuation, ratio 5.01 against a limit of 5.0 on the hosted ubuntu Debug runner) failed. It passed on re-run, on NTU Debug and on macOS Debug. INT21 hardens it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LifAW9e5J6gFx7gatuxETE

luisleo526 and others added 18 commits September 24, 2026 15:09
…t the frozen v18 provider (R5 lane V19-B, step 1)

The journal window, RequestDefinition::root and the trail activation ordinal
(PERF-D1 §2.5, the v19 value epoch approved 2026-09-23) change native order
layouts; this commit takes the value namespace move alone, so the later steps
change layouts inside native_order_v7. It moves no layout, no value and no
behaviour.

- include/pineforge/native_order.hpp, src/native_order.cpp: inline namespace
  native_order_v6 -> native_order_v7; native_order_identity.hpp's pointer to
  the value epoch follows. Identity types stay native_order_v1.
- tests/fixtures/native_cpp_abi/host-fc7aad6/relocation-manifest-v18-v19.json
  records the move ("orderEpoch": native_order_v6 -> native_order_v7), and
  check_settlement_cpp_abi.verify enforces it: the live native_order.hpp must
  declare v7 and not v6 (self-test test_v6_order_epoch_is_refused). The
  frozen v18 closure (host-fc7aad6, native_order_v6) is the predecessor the
  receipt-gated rows already pair against; its cross-epoch rejection pairs
  are unchanged.
- check_native_cpp_versions.py pins native_order_v7 ownership; its self-tests
  and test_check_kernel_residuals' demangled fixture follow.
- Docs that state the tree's request epoch say v7; the two roadmap rows that
  record where L3 and L7 opened v6 keep it under a verified-HEAD marker.

ADR-0001 rule 2: not engaged -- a namespace and ABI-matrix move, no
mechanism, spec field, hook or hash input changes.

Verified: ctest build-on 608/608 with the seven providers prepared;
check_doc_lint 0 offenders; check_native_cpp_versions and the settlement
surface audit pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
(cherry picked from commit 22f578c792ad7accbaef1a5cb12aa1c6ce5d6aae)
… and cohorts read a chain index instead of the journal (R5 lane V19-B, step 2)

native_order_v7 layout (PERF-D1 §2.2, §3 s2/s4, §8 V19-B items 3, 5, 6):

- WorkingRequestCore keeps its command journal as a window. history() is the
  retained window; history_base() counts the events retired from its front,
  history_end() is the absolute journal length, history_at(absolute) reads a
  retained event and throws std::out_of_range for a retired one.
  EventRange::first_index, MutationPlan::history_size, the ordinal index and
  every consumer journal index (AppliedNotification::history_index,
  margin_call_index, the event-record fold and its Debug count check) are
  absolute, so a retired prefix moves none of them. catch_up_timeline, the
  drain causes and event_high_water read the core's last committed ordinal
  instead of the journal's last row.
- retire_history(through) drops the retained prefix at or below an ordinal,
  stopping ahead of the head of any live deferred group-adjustment chain (the
  one journal read a live request still makes, collect_pending_chain). A
  group-effect receipt whose outcome event is retired stands on its own
  record (receipt_lookup). retired_through() reports the newest retired
  ordinal. Nothing calls retire_history yet: every run keeps its whole
  journal until the retention policy lands.
- RequestDefinition::root (appended after origin): the first handle of the
  request's replace chain, set at the replace from the predecessor's own
  root, kept by the anchored-leg materialization. The chain index -- the
  issued incarnations as ascending [first, last] ranges and each replace
  successor's root, 16 bytes per replace -- replaces definition_for's
  backward journal scan: cohort_add / cohort_remove answer UnknownOrigin by
  issued(), canonicalize through canonical_cohort_origin (live row's root, or
  the index), and cohort_contains tests the root alone, because rosters only
  ever hold roots. The chain-walk guard D1's prototype had to fix
  (history_.size() as the walk bound) is gone with the walk.
- issued_incarnations() exposes the index's first half read-only.

No value moves: nothing retires, and the continuation folds what it folded.

ADR-0001 rule 2: (a) mechanism -- the core's own journal storage and a
lookup over its own requests, no host policy in either; every host's
cohort commands take the same path. (b) knob -- none: retire_history is a
core call the consumer will drive, no spec field, hook or hash input changes
here. A kernel implementation change with no calibrated behaviour, so no ADR
table row.

Witness: tests/test_native_journal_window.cpp (new, source-free, kernel
profile too): the window's mechanics; four randomized 900-command streams on
two cores, one retiring its whole journal after every command, whose cohort
receipts, rosters, membership answers and live definitions equal a journal
oracle of the pre-window semantics; a live deferred chain pinning the
window, with the arm that reads it back equal to an unretired twin; and a
scaling row. Fail-before on 6211dc94: the TU does not compile (20 errors,
first "no member named 'history_base' in
'pineforge::native_order::WorkingRequestCore'"); its public-surface variant
(the scaling row alone) runs there and fails: 2048 chain-membership queries,
400 -> 1600 replaces, 0.096006 s -> 2.843286 s (x29.62 for 4x); on the lane
0.000041 s -> 0.000048 s (x1.18).

Verified: ctest build-on 608/608 (607 + test_native_journal_window); kernel
residuals 0 findings.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
(cherry picked from commit 3214575d561733a3146e20c960db61c8487fa790)

INT20 conflict resolution: src/native_order.cpp, WorkingRequestCore::
definition_for. PERF-P7 (P7a, on main through INT19) had wrapped the
function's backward journal scan in a lambda so the consumer's published
DefinitionIndex could answer first (assert-checked against the scan). This
commit deletes definition_for outright -- cohorts read the chain index, and
nothing else calls it -- so V19-B's side is taken whole: the P7a body goes
with the function and history_at takes its place as the lane wrote it.
P7's now-unread remainder (the native_definition_index.hpp include, the
header itself, the consumer's publication and its test switch) is removed
in the INT20 reconciliation commit that follows the picks. Every other
hunk (native_order.hpp, the consumer, tests/CMakeLists.txt, the new
test_native_journal_window.cpp) applied as written.
…n a journal scan (R5 lane V19-B, step 3)

native_order_v7 layout (PERF-D1 §2.2, §8 V19-B item 4).
NativeTrailState::activation_ordinal was answered by trail_state()'s backward
scan of the command journal for the request's newest TrailArm ActivatedEvent,
so it would read 0 once the journal window retires that event. It is now
state:

- TrailTrack::activation_ordinal and TrailActive::activation_ordinal
  (appended after best / best_at_trigger, default 0). The TrailArm activation
  stamps its own ordinal into the TrailTrack it installs (and into the
  event's `after`), a TrailTrigger carries it into TrailActive, and a replace
  that retains its predecessor's trigger state zeroes it: the successor never
  armed, and the scan -- which matched the TrailArm event's handle, the
  predecessor's -- answered 0 for it too.
- trail_state() reads it from the live row; the scan is gone.

Every readback is what it was: a request arms at most once (TrailWaitArm is
entered only at acceptance), so the state holds exactly the event the scan
found. Nothing hashes the new field yet; the value step of this lane folds
it with the rest of the window's durable state.

ADR-0001 rule 2: (a) mechanism -- the kernel's own trigger state; every
trail on every host carries it. (b) knob -- none: no spec field, hook or
hash input moves here. A kernel representation change with no calibrated
behaviour, so no ADR table row.

Witness: test_native_journal_window part 5 -- the arm ordinal in the
TrailTrack and in the ActivatedEvent's after, kept in state after the arm
event is retired, 0 for a retaining successor, carried into TrailActive by
the trigger. Fail-before: on the previous step (3214575d) the part does not
compile ("no member named 'activation_ordinal' in
'pineforge::native_order::TrailTrack'"); on 6211dc94 the TU already stops at
history_base. Existing trail_state rows (test_native_trail_state_l5k, the C
trail readback) pass unchanged.

Verified: ctest build-on 608/608.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
(cherry picked from commit 77066316741bf8d037af264e2e5667b3adec2213)
… Full -- validated and folded only off the default (R5 lane V19-B, step 4)

PERF-D1 §2.2 / §2.5, §8 V19-B item 1: the spec names what a run keeps of
its event record. This step adds the value and its judgement; the policy is
not enforced yet (every run still keeps everything), so no behaviour and no
established value moves.

- native_run_spec.hpp (native_run_spec_v3 kept, D1 §2.5):
  enum class NativeEventRetention : std::uint32_t { Window = 0, Full = 1,
  Commands = 2 } and NativeRunSpec::event_retention (appended last, default
  Window). NativeRunSpecField::EventRetention and
  NativeRunSpecError::UnknownEventRetention are appended; the validator
  refuses a word outside the enumeration right after the open-bar view, so
  every existing first-error answer is where it was.
- The spec fold (hash_spec, hence native_run_spec_digest and the
  continuation's spec digest) takes the field only when it is not Window, so
  a spec that leaves the default keeps the digest it had before the field.
- The C words of the new error and field are named
  (PF_NATIVE_SPEC_ERROR_UNKNOWN_EVENT_RETENTION = 54,
  PF_NATIVE_SPEC_FIELD_EVENT_RETENTION = 64) and pinned in native_c_host.cpp;
  the retention word itself reaches C with the spec tail in the enforcement
  step.
- PineExecutionAdapter::project() declares Window: the adapter reads the
  journal above its own receipt cursor only, which is what the next step
  acknowledges (check_native_feature_rulings: the adapter declares the field,
  so no ruling row is needed).
- check_native_cpp_versions.py pins the enumeration, the member and its
  default, the field tag and the error, and -- for the two earlier steps --
  RequestDefinition ending with origin then root, and TrailTrack /
  TrailActive's arm ordinal after their best.

ADR-0001 rule 2: (a) mechanism -- a generic reporting policy over the
kernel's own event record, the same for every host. (b) knob -- a spec field
with no TradingView meaning; the adapter declares the kernel default. No
calibrated behaviour, so no ADR table row.

Witness: tests/test_native_event_retention.cpp (new, source-free, kernel
profile too), part 1: Window is the default and its spec digest is the one
6211dc94 computed for the same spec (12094809940376900009); Full and
Commands fold and differ; a word outside the enumeration is refused
(UnknownEventRetention at EventRetention) by validation and by
configure_native. Fail-before on 6211dc94: the TU does not compile (12
errors, first "no member named 'event_retention' in
'pineforge::NativeRunSpec'").

Verified: ctest build-on 609/609; check_native_c_api_surface and
check_native_cpp_versions pass; kernel residuals 0 findings.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
(cherry picked from commit 6e52b98e306f5b074fbfdf77c510afa07a2eb01c)
…and the Pine adapter acknowledges its receipt cursor (R5 lane V19-B, step 5)

PERF-D1 §2.2 / §2.3, §8 V19-B item 2, the reader side of the journal window.
Nothing retires yet, so every run keeps everything and no value moves; this
step puts the acknowledgement on every surface that will make it.

- NativeStrategyHost::native_acknowledge_events(through_ordinal) and
  native_event_window_start() (host capability unchanged: member functions,
  no storage). The consumer records the acknowledgement only while a run is
  Running, clamps it to the event high water (a host can only have read what
  exists), and never lowers it; the first call marks the host as one that
  polls. The window start is retired_through() + 1: 1 while nothing was
  dropped. The acknowledgement is reader bookkeeping -- it decides which
  retained events a later boundary retires, nothing else -- so, like the
  terminal-receipt watermark, it folds into nothing.
- C: strategy_native_acknowledge_events_v1 and strategy_native_event_window_v1
  (41 native C symbols, EXPECTED_NATIVE_C_API and both counts in
  check_c_abi_runtime.py; COVERAGE rows; PF_ABI_VERSION stays 4 and
  strategy_stream_api_version() stays 1 -- additive). The published symbol
  counts in README, the ADR, index.md and native-engine.md follow.
- Pine: PineStrategyHost::on_native_run_begin acknowledges 0 (a polling host
  with nothing read yet), and PineExecutionAdapter::observe_terminal_receipts
  acknowledges its receipt cursor once the commands through it are observed.
  Every later read the adapter makes (follows_same_bar_declined_reversal, the
  margin revival) starts above that cursor, and a cancel issued while
  observing lands above the high water the cursor stops at, so the window
  never retires an event the adapter has yet to read. The cursor's values,
  and so the adapter's hash extension, do not move.

ADR-0001 rule 2: (a) mechanism -- a generic reader acknowledgement over the
kernel's own event record, the same for every host. (b) knob -- no spec
field or hash input; the adapter acknowledges the cursor it already keeps.
No calibrated behaviour, so no ADR table row.

Witness: tests/test_native_c_api_c99.c calls both symbols as a strict C99
caller (window 1 after a run that dropped nothing, an acknowledgement after
the run is accepted and records nothing, NULL out-parameter and NULL handle
refused). Fail-before on 6211dc94: 5 errors, first "call to undeclared
function 'strategy_native_event_window_v1'". The acknowledgement's effect on
what is kept is witnessed with the enforcement in the next step.

Verified: ctest build-on 609/609 (the Pine hash witnesses unchanged);
check_c_abi_runtime, check_native_c_api_surface, check_doc_lint pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
(cherry picked from commit 684858b34f09ad2c37bbc31ff916cf0e7a682d04)
…t its host read at every script-bar boundary, Commands and Full keep the journal (R5 lane V19-B, step 6)

PERF-D1 §2.2 / §2.3 / §5.6, §8 V19-B items 1, 2, 4, 6. This is the value
step: what a run keeps of its event record now follows
NativeRunSpec::event_retention, and the state the journal used to carry is
folded into the continuation instead.

Retention (src/native_execution_consumer.cpp):
- Window (the default) keeps no driver point and no account row, and closes
  the command journal at every script-bar boundary (the three deliver_*
  report points): through the host's acknowledgement once it has made one,
  through everything for a host served by its callbacks alone. The
  consumer stops ahead of the oldest queued applied notification -- the
  kernel's own reader, the one being delivered included -- and the core
  stops ahead of any live deferred group-adjustment chain
  (WorkingRequestCore::retire_history). Commands keeps the journal and the
  account rows; Full keeps everything, as every run did before.
- A log the retention does not keep holds no storage (begin_ready releases
  it; reserve_driver_log and the batch presize reserve only a retained log;
  the Pine host's up-front driver-log reservation is gone).
- event_high_water() is the stream's high water whatever is kept (last
  command, driver and account ordinals), so the Pine adapter's receipt
  cursor -- folded by its hash extension -- does not move.
- The FX-roll margin check reads its previous point from two driver marks
  kept whatever the retention.
- A test-only stress switch, set_retire_every_point (or
  -DPINEFORGE_STRESS_RETIRE_EVERY_POINT for harnesses that cannot reach the
  consumer), retires at every driver point instead; set_retention_override
  lets a test read back what a Pine host (whose spec is the adapter's)
  committed. Neither is run state.

Value folds (the window is why they are state now; re-pinned once):
- the core's chain index: each replace successor's root, folded once at the
  replace (chain_roots_), and the issued ranges where they are not the one
  dense [1, last_incarnation] a consumer issues;
- a trail's arm ordinal in TrailTrack / TrailActive (only where it armed);
- the FX-roll check's two driver marks, under a staged curve and a margin
  model.
The ack cursor, the high waters and the applied count are readback
bookkeeping and fold nowhere.

C (PF_ABI_VERSION 4, additive): pf_native_event_retention_e, the
PF_NATIVE_SPEC_EXT_EVENT_RETENTION bit and pf_native_run_spec_ext_v1's fifth
layout (event_retention, reserved2; PF_NATIVE_RUN_SPEC_EXT_V1_AUXILIARY_SIZE
names the fourth). RULING: a C caller that does not send the word --
strategy_configure_native_v1 (c_abi.cpp and native_c_host.cpp), any earlier
layout, a clear bit -- keeps FULL, the record its layout was published with,
so a frozen caller that reads its events after the run reads what it did
(test_native_c_api_frozen_header passes unchanged).

Source: PineStrategyHost::fixture_applied_receipt_count reads the kernel's
count of committed fills (the window no longer holds the run), and
fixture_retain_all_events is the readback switch for Pine fixtures.

Tests: every host that reads its whole record after the run keeps Full (the
fixture spec builders, 30 test specs, 7 examples) or, for a Pine host, calls
fixture_retain_all_events; the C++ twin of the C suite keeps Full like the
C host. An instrumented run of the whole ctest found no read of a retired
window left.

Re-pinned once, each with "expectation corrected: <old> -> <new>, because
v19-B ..." beside it: the neutral spec digests of test_native_margin_model
(2), test_native_margin_hooks, test_native_price_grid,
test_native_anchored_legs and test_native_risk_limits (Full folds);
test_native_match_hash_witness 5 x 3 hash columns (Full, chain roots, arm
ordinals); test_native_intrabar_lower_lookup 6 (Full);
test_native_batch_log_presize 12 x 2 (Full; chain roots in the reissue
runs); test_adapter_continuation_view 188 of its arrays' values (chain
roots -- with the chain-root fold off, every old pin holds);
test_adapter_host_view_memo 6 batch broker hashes (FX-roll marks -- with
the three folds off, every old pin holds). Trades, trade digests, event
counts and digests did not move anywhere.

ADR-0001 rule 2: (a) mechanism -- a generic retention of the kernel's own
event record and a reader acknowledgement, the same for every host; the
folds are kernel state. (b) knob -- NativeRunSpec::event_retention, a
reporting policy with no TradingView meaning; the adapter declares the
default and acknowledges its cursor. No calibrated behaviour, so no ADR
table row.

Witnesses: tests/test_native_event_retention.cpp parts 2-5 (2040 checks):
Full is 6211dc94's record row for row over three randomized books (pinned
digests harvested on 6211dc94); Commands has no driver row; Window holds
exactly the commands since the last boundary, a polling host reads every
command once and is left exactly the unacknowledged suffix, a read below the
window starts at it; the acknowledgement's contract (before begin, beyond
the high water, twice, after the end, Full/Commands); the stress switch
books Full's trades and folds the per-bar window's continuation at every
bar and fill; a trail's arm ordinal and a margin call's receipt under an FX
curve read back after their events retired. tests/test_native_c_api.c
check_event_retention: the five layouts, FULL by default, COMMANDS, WINDOW
with and without an acknowledgement from on_run_begin, the refusals.
Fail-before on 6211dc94: the C++ TU does not compile (20 errors, first "no
member named 'event_retention' in 'pineforge::NativeRunSpec'"), the C TU
(19 errors, first "call to undeclared function
'strategy_native_acknowledge_events_v1'"); the public-surface variant (a
default-spec book after its run) keeps 889 rows (564 commands, 240 driver,
85 account) on 6211dc94 and 0 on the lane.

Verified: ctest build-on 609/609; corpus 312/312 byte-identical at 9182e3d,
excellent=311 anomaly=1; check_c_abi_runtime, check_native_c_api_surface,
check_native_cpp_versions, check_native_feature_rulings, check_doc_lint
pass; kernel residuals 0 findings.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
(cherry picked from commit 7d0c47b0719067fa220a8eed504e15de66d27584)

INT20 conflict resolution:
- src/native_execution_consumer.hpp, three hunks where both sides only
  added: before the class, main's NativeHostCache (PERF-P7) and this
  commit's kRetireEveryPointDefault; in the public test switches, main's
  set_match_band_precheck / set_point_guards (PERF-L5 / PERF-L1), P7's
  set_definition_index / request_core and host-cache members, then this
  commit's set_retire_every_point / set_retention_override /
  applied_event_count; at the end of the members, main's L5/L1 scratch,
  P7's definition_index_ and host_cache_, then this commit's
  retire_every_point_ / retention_override_ / chain_roots_. Main's lines
  first, then the lane's, each block verbatim.
- src/source/pine_strategy_host.cpp, prepare_native_begin: main had
  already removed the set_receipt_high_water_readers call (PERF-P4 reads
  the receipts in place), and this commit removes the up-front
  reserve_driver_log -- both deletions stand, nothing of the hunk is left.
Every other file's changed lines equal the lane commit's.
…retention tail, ADR-0001 rulings; citations re-anchored (R5 lane V19-B, step 7)

native-engine.md states what a run's NativeRunSpec::event_retention keeps
(Window: the unacknowledged command suffix, no driver or account rows;
Commands: every command row; Full: the whole record), the acknowledgement
contract (recorded while running, clamped to the high water, monotone,
applied at the next script-bar boundary, a silent host's window closed at
every script-bar end, never below what live state reads) and a read from
below the window start. abi-stability.md records native_order_v7, the
native-consumer/v9 identity and the additive C growth under PF_ABI_VERSION 4
with FULL kept for callers that do not send the retention word. ADR-0001
carries a rule-2 ruling row per V19-B kernel commit. The native_events
doc comments in native_host.hpp and native_c_api.h say what a read returns
under each retention. Every citation the lane moved is re-anchored with the
exact line map (1627/1627 anchors resolve; doc-lint clean).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
(cherry picked from commit 9addcaea955e23bc9e2482fcfe560b6cc6a188ee)

INT20 conflict resolution: 70 hunks in four pages (ADR-0001 15,
native-feature-parity 45, native-engine 6, pine-to-native 4). 68 changed
only citation digits on both sides and 2 (native-feature-parity) changed
digits only on this side while main changed words; every hunk keeps
main's text and digits (INT19's digit_resolve rule). This commit's own
content -- the retention, acknowledgement and C-tail prose, the two ADR
rule-2 rows -- lies outside every hunk and applied as written: with every
run of digits blanked, each page equals the 3-way merge of main, this
commit and its parent. The citations are re-anchored once, after the
picks.
…place the complete operator new/delete set (R5 lane V19-B, step 8)

test_native_command_history_read and test_adapter_receipts_in_place replaced
only the plain and sized forms of the global allocation functions. Under
Linux ASan (GCC 13.3, libasan8) the forms they left out resolve to the
sanitizer's own: libstdc++'s temporary buffer behind native_events'
std::inplace_merge takes operator new(size_t, nothrow_t), ASan records it as
new-allocated, and the TU's sized operator delete frees it with free(), an
alloc-dealloc-mismatch that aborts both rows (SIGABRT, 1.4 s and 2.0 s).

Fail-before: the same two rows abort identically at 6211dc94 (built with the
sanitizers profile on spark), so the defect predates this lane; the tree has
no shared replacement header (tests/global_allocation_replacement.hpp does not
exist), so each TU now defines the complete set itself -- nothrow, aligned
and aligned-nothrow new, and every matching delete -- allocating with malloc
or posix_memalign and freeing with free. The nothrow forms forward to the
counted throwing ones, as the library's own defaults do, so no count any
assertion reads moves on a build without sanitizers. Pass-after: both rows
pass under ASan/UBSan on spark and unchanged on the Mac.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

(cherry picked from commit bc749095ed6bd0da791c8da86bfe13735a205445)

INT20 conflict resolution: both hunks keep main, and the pick is empty.
main already carries INT17b's d292f19 (tests/global_allocation_replacement.hpp),
which had moved both of these tests -- and the six V19-B's report names --
off their partial operator new/delete sets onto the one complete shared
replacement, for the same ASan alloc-dealloc-mismatch; V19-B's lane base
6211dc94 predates it. On main each TU includes the helper and counts
global_allocation::allocations over the same spans, so taking this
commit's per-TU complete set would put a second definition of every
replaceable form into the executable. What the lane's step 6 changed in
these two tests (the Full retention readback) applied with that commit.
…he staged chain keeps every other call (R5 lane PERF-L2, step 1)

The fused settlement of PERF-D1 §8 L2. A fill staged its settlement
three times -- inspect_candidate's inspection, the precommit preview,
the commit -- each time into a NativeSettlementStage whose vectors
(closing indices and quantities, quoted costs, survivors) and close rows
(NativeSettlementRows) lived on the heap: 12 to 15 allocations per
closing fill and 3 per opening on 6976a80, and the preview built the
close row twice.

BacktestEngine::NativeSettlementStage::OneLot is that stage for the
common case, in one pass and on the stack. A call takes it when it is a
Flatten, Reduce or Transact on the Book or on its opening's scope, with
no selection and no lifecycle effect, or a ReverseTo; the book holds at
most one lot; and none of it survives: an opening from flat, the whole
lot closing, or that close and the opposite opening. Everything else --
a selection, two lots, an addition, a partial close, and every call the
chain would not stage Ready (a test it fails, or no effect) -- stages
through the chain exactly as before. The admission is the chain's own
tests in the chain's order (validate_native_settlement_book,
inspect_close_scope, order_action::plan, next_close_split, the quote and
ticket tests of finish_native_settlement_stage); it changes nothing and
consults no host, so a call it declines stages from scratch.

Each OneLot function is the chain function it names, on those values:
inspect_native_settlement_stage, project_native_settlement_stage,
prepare_native_settlement_commit, preview_native_settlement_commit,
preflight_native_settlement_effects and
commit_prepared_native_settlement_stage, with quote_execution_commissions
for at most one closed quantity. The close row is built by
build_close_trade_with_costs as often and at the same context as the
chain builds it, so a host that owns lot excursions is consulted exactly
as before; only without such a host does the preview hand its row to its
projection instead of building it again at the default context -- that
row is then a function of the lot, the fill and the fee, and the
projection reads only its pnl and commission, which the context does not
enter. The effects go through the chain's sinks in its order:
record_close_trade, reset_position_state_to_flat, open_quoted_position,
stream_refresh_action_metadata. Every exception (an exhausted cycle,
counter or stream sequence, effect capacity, an excursion owner's own)
is thrown at the same point with the same text.

The fused-capable entries are inspect_with_membership,
inspect_native_reversal_v1, project_with_membership,
project_native_reversal_v1, both preview_native_settlement_commit,
settle_with_membership and settle_native_reversal_at_v1; each gains a
prologue and keeps its body. internal::set_fused_settlement(false)
restores the chain for every call and internal::count_settlement_paths
counts which path each entry took, for the differential and counter
rows: process-wide atomics a run only reads, declared in the runtime-
private engine_internal.hpp; no host reaches them.

Rule 2 (ADR-0001): generic -- the settlement of the one physical book
every host shares; nothing names Pine. No knob: the switch and the probe
are internal, on no public surface; no spec field, hook, readback, hash
input or public header changes, no epoch, and no value moves.
include/pineforge/engine.hpp is untouched: OneLot is a member of the
stage the header already declares, defined in the .cpp.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
(cherry picked from commit d21041652d5b8b16099f6af70222a5deaeee0cbe)
… the counter and allocation witnesses (R5 lane PERF-L2, step 2)

test_native_fused_settlement runs everything twice, fused and with
internal::set_fused_settlement(false), and requires every value equal:
  - an admission table of 71 rows: each admission condition on both of
    its sides (39 admitted, 32 declined); the failures a fused call can
    still meet after admission (an exhausted cycle, counter or stream
    sequence, an overflowing sum or row pnl, an excursion owner that
    throws) are rows of their own; the probe says which path each entry
    took;
  - 6,000 randomized direct books of zero, one or two lots under every
    fee form, explicit tickets and rebates, account FX scalar and table,
    legacy unquoted entry fees, counters near exhaustion, stream
    observation and excursion owners that answer, count or throw, through
    every fused-capable entry in the consumer's order;
  - 72 randomized matcher runs (native_fused_settlement_fixture.hpp): fee
    forms, account FX unit/constant/stepping curve, the margin model under
    both liquidation sizings, magnifier off/synthesized/lower,
    calculate-on-fills, the price grid, host-owned excursions, batch and
    stream driving -- the continuation at every bar and fill, every
    fill's book, equity, lots and rows, every precommit view, every
    excursion consultation, the broker and stream hashes, the trades and
    the events;
  - K3's randomized working books and L5's band-edge books;
  - the counter witness: a market book and a bracket book take the one
    pass on every inspection, preview and settlement; a pyramid still
    reaches the chain; the switch sends everything to it.
8247 checks, ok. On 6976a80 it does not compile: "no member named
'SettlementEntry' in namespace 'pineforge::internal'". Nine mutants of
OneLot (the preview's row reused under an excursion owner, the
inspection's notional at the clock's rate, a booked row without its
close cause, no stream refresh, no cycle test in prepare, a partial close
taken as whole, the pinned ticket not installed, the realized balance
regrouped, an opening without its comment) each fail it, by 21 to 1984
checks; a capacity-only control passes.

test_native_fused_settlement_allocations counts every allocation
(global_allocation_replacement.hpp) around the inspection, projection,
preview and settlement of eight fill shapes, under three fee forms, with
stream observation off and on, on a warm engine: 192 calls, 0
allocations. On 6976a80 the same calls allocate 720 and 192 of its 342
checks fail. It also reports, unbounded, 2.72 allocations per fill for a
market book and 6.62 for a bracket book (10.21 and 15.62 on 6976a80,
spark gcc).

Both rows are source-free and also run in the kernel-only profile:
KERNEL_MIN_TESTS and RELEASE_MIN_TESTS are each owed +2.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
(cherry picked from commit 6b0b0dcdd262fc78574ae57e6f1384ba410907a3)
…cohort membership from its own chain index, and test_native_definition_index holds that index to a whole-journal oracle

V19-B deleted WorkingRequestCore::definition_for: cohort_add, cohort_remove
and cohort_contains read the core's chain index (issued ranges, successor
roots, RequestDefinition::root) and never the journal, which Window now
retires. PERF-P7's P7a index existed only to answer definition_for's
backward scan, so with the function gone nothing read it any more; the
V19-B step-2 pick already dropped P7a's hunk inside definition_for. This
removes the rest:
- src/native_definition_index.hpp (DefinitionIndex and its thread-local
  Publication), and native_order.cpp's include of it with the <cassert>
  P7a added for its assert (only static_assert is left). native_order.cpp
  and native_order.hpp are now byte-identical to V19-B's tip bc749095.
- NativeExecutionConsumer: definition_index_ / definition_index_enabled_,
  set_definition_index, definition_index_answers, publish_definition_index
  and its five publications (read_target_into, placement_scope_units,
  cohort_add, cohort_remove) and the begin_ready reset. request_core() stays,
  the re-targeted test's read of the core.
- tests/adapter_lookup_index_fixture.hpp: its set_definition_index call and
  the definitions_answered field it filled (no test read it). P7's adapter
  lookup indexes (purge, exit rows, same-bar closes) are untouched and their
  witnesses pass unchanged: test_adapter_lookup_index_witness 404 checks,
  test_adapter_purge_index 3377 indexed answers, test_adapter_exit_leg_index
  1025, test_adapter_lookup_index_scaling ok.

test_native_definition_index is re-targeted, not retired: it keeps P7a's
two workloads (24 randomized books of test_native_match_row_reuse under
every intrabar path, fill recalculation and the price grid; 8 replace-chain
hosts that re-price every bar and join every successor to the cohort, the
slot-055 shape) and holds the core's chain index to what the pre-window core
answered, recomputed from a whole journal (test_native_journal_window's
JournalOracle, folded incrementally). Each configuration runs twice:
- kept (the fixture's Full spec): every receipt the chain host asks for and
  every roster it builds is predicted by the oracle, every membership answer
  the core gives at every bar (open lots, chain members, never-issued
  handles, an absent cohort) equals the oracle's, and every roster member of
  a book is its own oracle root;
- windowed (the same spec, the record read back as Window through
  set_retention_override): the core retires its journal at every script-bar
  boundary and must equal the kept run on the continuation at every bar and
  fill, the broker hash, every trade, the position, every receipt, roster and
  membership answer.
  test_native_definition_index: 32 runs, 11541 trades, 10209 cohort
  receipts, 346316 membership answers, 354652 oracle answers, 105128
  journal events retired by the windowed twins; 667341 checks, 0 failures
The workload's own draws are P7a's (11541 trades and 10209 receipts, as the
old row printed on this tree); the questions draw from their own stream.
Fail-before, the TU against main 3eb2cb8's headers: 4 errors, first
"test_native_definition_index.cpp:94:20: error: no member named
'history_base' in 'pineforge::native_order::WorkingRequestCore'".

The ctest row count does not change (the row is kept). ADR-0001 rule 2:
nothing kernel-visible is added; a kernel-internal lookup nobody reads is
removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
… back keep reading it whole, as V19-B made its own readers do

V19-B made NativeEventRetention::Window the default: a run keeps no driver
point or account row and retires its command journal at every script-bar
boundary, and "every host that reads its whole record after the run keeps
Full (the fixture spec builders, 30 test specs, 7 examples)". Three tests
that reached main after V19-B's base (6211dc94) read the record and so
never got that declaration. An instrumented pass of the whole ctest on the
picked tree (events_after / first_command_after logging any read that
reaches a retired command or a row the retention did not keep) found them:
- test_native_fused_settlement (PERF-L2): finish() folds native_events(0)
  after the run into the fused-vs-staged comparison. Under Window both sides
  read 0 events, so the comparison passed with its event census empty
  ("72 configurations, ... 0 events"). Its fixture spec now declares Full,
  as the k3 book fixture's does; the census is back to the lane's report
  exactly: "5366 fills, 3249 trades, 5430 precommit views, 5004 excursion
  consultations, 72787 events ... fused == staged".
- test_native_lean_path (PERF-L1): witness 1 compares native_events(0).size()
  after each run with the guards on and off. It reads back Full through
  set_retention_override -- a readback choice alone, so the spec, its digest
  and every pinned value stay the default's: with the override the pinned
  rows read exactly what they read without it (only margin_fx_roll moves,
  by V19-B's FX-roll driver-mark fold, re-pinned separately).
- test_native_host_reads (PERF-L1): compares native_events(after) with the
  consumer's events_after(after) at every hook. It also reads back Full,
  so the forwarding it witnesses is compared over the whole record again
  (no pinned value in this TU).
The two other main-side readers the pass named need nothing:
test_native_cohort_sliced_close (K-COHORT) reads only command events, all
of which its tick-stream runs keep (no retired command was reached), and
test_native_c_api's window reads are V19-B's own retention checks.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…ir v19-B values, once -- every trade, event, count and hook call they pin holds

V19-B folds into the continuation what its journal window turned into state
(the chain roots, the issued ranges, the trail arm ordinals, the FX-roll
driver marks) and a non-Window retention into the spec digest. Two of the
six files INT19 re-harvested under V19-A read those values; the other four
do not move (harvested the same way: main, this tree and V19-B's tip print
byte-identical rows for test_adapter_command_allocations,
test_native_point_checks, test_adapter_quiet_bar and
test_adapter_lookup_index_witness), and V19-B's own 19 re-pinned sites
arrived with its step-6 pick and hold here unchanged.

- test_native_match_band_witness (PERF-L5), 18 values in 6 rows: the trace
  digest, continuation and broker hash. Three folds move them together: the
  Full retention the k3 book fixture declares (V19-B's step 6), each
  replace successor's chain root, each armed trail's arm ordinal; with the
  three masked in a scratch build the rows are main's, with any two masked
  they are not. Trades, trade digest, events, event digest and the three
  counters did not move.
- test_native_lean_path (PERF-L1), 6 values in 2 rows: margin_fx_roll's
  fold, continuation and broker hash, by the FX-roll check's two driver
  marks (with that fold masked the rows are main's). Every hook's call count
  and the trades did not move; the other 28 rows hold.

Harvested as INT19 did, each TU with its own switch (-DPINEFORGE_L5_HARVEST;
-DPF_L1_HARVEST with PF_LEAN_PATH_DUMP=1) against three libraries: main
3eb2cb8 reproduces every committed pin verbatim; this tree and V19-B's tip
bc749095 (the TU compiled against V19-B's own headers) print byte-identical
rows and identical hook counts; the splice refuses a row whose non-hash
column moved. Each moved value is listed old -> new beside its table.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
… __pycache__ and *.pyc, so a parallel guard's bytecode cannot vanish under their copy

lab remote found the race on x86 (job rj-20260923t214220-169bc8, release
step, row test_aggregate_cpp_versions):
  shutil.Error: [('.../src/scripts/__pycache__/derive_corpus_feeds.cpython-312.pyc.139885164026480',
  ..., "[Errno 2] No such file or directory: ...")]
Four Python guards clone the whole repository
with shutil.copytree(ROOT, ...) before they mutate it, and none of their
ignore lists names bytecode:
  test_aggregate_cpp_versions               scripts/test_aggregate_cpp_versions.py
  test_broker_state_hash_coverage_mutations scripts/test_broker_state_hash_coverage.py
  test_market_admission_schema_mutations    scripts/test_market_admission_schema.py
  test_pending_intent_view_mutations        scripts/test_pending_intent_view_mutations.py
Under parallel ctest another Python guard compiles its imports into
scripts/__pycache__ through a temporary file it then renames or replaces;
a file copytree has listed and that is gone when it copies fails the clone
(shutil.Error / FileNotFoundError), and with it the row. Bytecode is never
source input to a clone, so each list now also ignores "__pycache__" and
"*.pyc". The other copytree calls in scripts/ copy include/ or a fixture
directory, where no bytecode is written.

Fail-before, the mechanism in isolation (INT20-scratch/tools/copytree_race.py:
a 200-module tree whose __pycache__ churns the way the interpreter writes
it): the old ignore list fails 283 of 300 copies, the new one 0 of 300. The
four rows pass on the Mac (3.2 s, 5.2 s, 9.3 s, 20.7 s).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…, release 631 -> 635

Counted with `ctest -N` on the integrated tree in each ci_verify profile
(configured with ci_verify's own argv), not summed from the lanes' reports:

  build-ci-release  631 -> 635 registered, all run
  build-ci-kernel   246 -> 250 registered, 249 run (test_native_live_websocket
                    still skips on a system libcurl)
  build-ci-debug    612 -> 616 registered (no floor)

The four new names, from a comm(1) of the ctest -N lists against main
3eb2cb8's in each profile (the same four in all three, none removed):

  V19-B    test_native_journal_window, test_native_event_retention
  PERF-L2  test_native_fused_settlement,
           test_native_fused_settlement_allocations

That is what the two reports claimed (V19-B +2/+2, PERF-L2 +2/+2,
release/kernel). Reconciliation (i) retires no row: test_native_definition_index
is re-targeted, not removed. The six citations of the floor lines, two in
CONTRIBUTING.md and four in contributing-llm.md, follow them
(ci_verify.py:186 -> :195, :257 -> :269).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…no acknowledgement

A semantic meeting point, not a textual conflict. V19-B (step 5) has
PineExecutionAdapter::observe_terminal_receipts acknowledge receipt_cursor_
at its end, so the kernel's journal window retires only what the adapter
has read. PERF-L4 (slice B, on main but not on V19-B's base) had given the
same function an early return for a quiet read -- no command above the
cursor -- that only moves the cursor to the high water. The merged quiet
path therefore returns before the acknowledgement.

That is exact, and both mechanisms stand as their lanes wrote them: the
acknowledgement is never above the cursor, commands above the cursor are
never retired, and a quiet read sees no command between the cursor the last
full read acknowledged and the high water it moves to -- so acknowledging
there would retire nothing more, and the next full read acknowledges past
it. The comment records this where a reader would otherwise think the quiet
path forgot to acknowledge. Comment only: the Release object of
pine_adapter.cpp is byte-identical before and after (md5 64fc2fe1...,
compiled without ccache).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
One exact line map, INT19's method (int19_exact_reanchor.py, generalized to
several origins): every doc citation is carried from the tree its digits
were written against onto this tree through a difflib map of the cited
file, and a claimed anchor must still pass check_doc_anchors' own judge.
Origins, in precedence order: main 3eb2cb8 (INT19's re-anchor; 1454
anchors), V19-B's tip bc749095 (its docs commit re-anchored its new prose
against its own tree; 168), and HEAD for the six floor citations set by
hand in the floors commit (6).

  1628 anchors: 312 moved, 1316 kept, 0 without a passing origin, 0 where
  two passing origins disagreed.
  305 of the 312 moved spans cite text identical to their origin's; 6 keep
  both endpoint lines exactly (a line inside changed); 1 is the
  invoke_applied_callback block whose first line V19-B itself edited
  (history().at -> history_at), carried by its neighbour.
  Digits only: every page keeps its line count and equals its previous text
  with digits blanked (ADR-0001 24 lines, native-feature-parity 109,
  native-engine 6, pine-to-native 31).
  check_doc_anchors: TOTAL 1628 1628 0 0 0; check_doc_lint clean.

The citations moved because the picks and reconciliations moved the code
they cite: V19-B's kernel and consumer steps, PERF-L2's OneLot block in
engine_execution.cpp (its own mechanical re-anchor fd8a8b23 was not picked),
P7a's removal from the consumer, the floors comment block in ci_verify.py and
the adapter's quiet-read comment.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
It took 54m20s on INT19's PR #282 (run 35962386862: ctest 1579.6 s real,
6029 s of row time over 612 rows) against its 60-minute limit. The value
changes on its own line, so no line a document cites moves.

INT20's sanitizer row time, measured on x86 (lab remote, NTU2, CI's exact
ci_verify sanitizers job, 616 rows, 1954 s of row time; INT19's tree on NTU
gave 2192 s over 612 -- the two hosts time an unchanged row alike, median
ratio 0.99 over 31 rows above 5 s):
  new   test_native_event_retention             120.5 s   (V19-B)
        test_native_fused_settlement             21.1 s   (PERF-L2)
        test_native_journal_window                1.1 s   (V19-B)
        test_native_fused_settlement_allocations  0.7 s   (PERF-L2)
  moved test_native_definition_index    193.5 -> 41.4 s   (re-targeted: its
        reference runs no longer walk the journal backwards per link)
        test_native_host_reads / test_native_lean_path / test_native_match_band_witness
        41.2 / 13.9 / 8.3 -> 40.6 / 13.8 / 5.2 s
So +143 s of new rows against -152 s from the re-targeted row: about zero
net. Calibrated to CI as INT19 did (CI/NTU2 per-row median 2.87, IQR
2.19-3.22, over the same 31 unchanged rows against PR #282's log), the new
rows cost about 410 s (314-462 s) of CI row time and the re-targeted row
returns about 427 s of the 546 s it took on #282; the job should stay near
its INT19 length, and 90 minutes restores the margin the 60-minute limit had
lost. test_native_event_retention dominates the new time (about 6 min of CI
row time, the fifth-slowest row after match_band_precheck, ci_verify,
match_row_reuse and quiet_bar_differential); it stays under CTest's 1500 s
default, so it needs no sanitizer-specific budget yet, but it is the first
candidate for a lighter ASan variant if the job grows again.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@luisleo526
luisleo526 merged commit 10f2019 into main Sep 24, 2026
22 of 24 checks passed
@luisleo526
luisleo526 deleted the r5/int20 branch October 5, 2026 02:43
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
…#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…
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