Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 44 additions & 12 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,20 +278,29 @@ the pre-PR gate before a draft PR is opened:
`gh pr create` bypasses the local guard and is prohibited for agent-driven
work. CI rejects an absent or stale HEAD-bound attestation.

Every code PR must still receive independent review after it is opened and
before it is merged:
Every code PR must still receive one independent review cycle after it is
opened and before it is merged:

1. Run the host-native review with `scripts/review-bounded.sh` and a fresh
independent adversarial reviewer on the final pushed diff.
2. Verify every finding against the code. Apply valid findings and explain
rejected ones.
3. Batch related valid findings into one coherent follow-up commit whenever
possible. Do not make one follow-up commit per review comment; separate
commits are for independent changes or a necessary checkpoint.
4. Rerun `scripts/pre-pr.sh` after any follow-up and refresh the PR attestation
with `scripts/update-pr-preflight.sh`.
5. Record both clean post-open reviews against the pushed SHA with
`scripts/record-post-review.sh`. Wait for CI and only then merge.
independent adversarial reviewer on the pushed diff. Each reviewer must
inspect the complete assigned scope and report all findings in that pass.
2. Verify every finding against the code. Apply all valid findings, explain
rejected ones, and batch the fixes into one coherent follow-up commit
whenever possible.
3. Close an ordinary follow-up with a finding-to-fix ledger and the focused
owner checks for the changed lines. Do not request another full-diff review
merely because valid P2/P3 findings were fixed.
4. Require another independent review only when the follow-up changes a public
contract or strategy, changes ownership, cleanup, FFI, ABI, or an IR shape,
crosses three or more compiler layers, exceeds 250 hand-written changed
lines, responds to a P1 by redesigning the implementation, or the user asks
for another review.
5. Refresh the final-SHA attestation, record the original review and bounded
finding closure, wait for CI, and only then merge.

The normal cycle is therefore review once, fix all findings once, run the
affected owner checks once, and finish. Repeated review is an explicit
high-risk exception, not the default completion loop.

Review execution follows the progress-monitoring rules above. If a review tool
reaches its configured invocation bound without a verdict, record the elapsed
Expand All @@ -304,6 +313,29 @@ the change scope explicitly requires that expanded verification.

Do not open and immediately merge a code PR.

Do not rerun the same broad review or broad test gate on an unchanged tree.
After a bounded review fix, run the smallest owner targets that can detect a
regression in the changed lines; CI remains the final broad gate. Preserve a
successful earlier result when only documentation or review records change.

## Throughput and checkpoint discipline

Implementation progress is measured by independently correct, mergeable
source-and-test checkpoints, not by document length, review-log volume,
formatting churn, or elapsed agent activity.

- After the narrow source-of-truth read, reach a compiling, owner-test-backed
implementation checkpoint within 60 minutes.
- Keep the existing two-hour PR-ready limit. If the checkpoint cannot be made
mergeable by then, reduce it to the next smaller correct vertical slice and
record the reason in `HANDOFF.md`.
- Target at most 500 hand-written changed lines per implementation PR. Above
1,000 requires the existing written proof that the work cannot be split
safely.
- Once the one review cycle and one coherent fix are complete, merge or
explicitly re-scope. Do not start another general improvement or discovery
loop inside that PR.

### Claude Code review adapter

- A human starts the dedicated review with `/code-review`.
Expand Down
134 changes: 122 additions & 12 deletions HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ about the present state, the next decision, and operational facts. The former
per-PR journal is preserved in
[`docs/archive/HANDOFF-2026-07-25.md`](docs/archive/HANDOFF-2026-07-25.md).

_Last updated: 2026-07-30. `main` includes the shipped wave through #672.
_Last updated: 2026-07-30. `main` includes the shipped wave through #674.
#667 adds the canonical recursive Drop plan and sound `Option<string>` fields;
#668 admits one direct recursively Move payload per tagged arm; #669 admits multiple Move payloads;
#670 completes nested tagged payload representation and the exact pkg.db L1b acceptance shape.
Expand Down Expand Up @@ -165,6 +165,52 @@ preflight happen before a draft PR, review processes have a bounded watchdog,
and related review fixes are batched instead of committed one finding at a time;
see `CLAUDE.md` and `docs/impl/16-test-policy.md`.

## Delivery retrospective: 2026-07-27 through 2026-07-30

The recent pkg.db prerequisite wave delivered nontrivial compiler foundations
but did not deliver a user-visible database package or SQLite/Postgres driver.
The merged capability sequence is recursive Drop and `Option<string>` fields
(#667), direct and multiple Move sum payloads (#668-#669), nested payload
representation (#670), parameter-mode and empty return-provenance
representation (#672), named return provenance (#673), and product return
provenance (#674). PR #675 closes eager MIR continuation lowering, but remains
compiler infrastructure rather than a usable pkg.db surface.

GitHub's earliest-commit-to-merge wall-clock proxy shows the throughput failure
clearly:

```text
PR observable wall time production / test / docs changed lines
#666 8h17m 0 / 0 / 10,746
#667 7h13m 1,736 / 1,397 / 158
#668 15m 808 / 447 / 155
#669 24m 68 / 313 / 82
#670 47m 3,889 / 496 / 156
#671 14m 0 / 0 / 169
#672 8h42m 4,468 / 2,068 / 72
#673 23h27m 7,380 / 3,057 / 549
#674 2h20m 1,150 / 620 / 113
#675 4h06m at audit 4,595 / 295 / 258
```

These wall times exclude work before the first commit and may include idle
review or CI waits. Changed-line counts also overstate implementation progress
when formatting moves dominate, as in #675. They are evidence of delivery cost,
not productive coding hours.

The failure was operational: cross-cutting compiler slices were too broad,
contract and matrix authoring expanded before a small executable checkpoint,
ordinary findings caused repeated full-diff review, broad gates were repeated
after narrow changes, and document or formatting churn was allowed to resemble
implementation progress. The fast #668-#671 sequence proves that narrow,
mergeable slices do not have the same failure mode.

The canonical correction is in `CLAUDE.md`: one complete review pass, one
coherent all-findings fix, no ordinary re-review, narrow owner checks after the
fix, no repeated broad gate on an unchanged tree, a 60-minute implementation
checkpoint, and a 500-line target for implementation PRs. Exceptions are
limited to materially risky redesigns or explicit user direction.

#660's final verification records 48/48 `align_driver` `par_map` tests,
including 65,537-element worker-range tests for both materializing chunks and
direct chunk reduction, a chunk filter, a cross-worker i8 wrapping fold, and
Expand Down Expand Up @@ -287,15 +333,79 @@ borrow-preserving `if`/`match` sources retain selected owner roots until action;
success payload and a value-producing loop instead transfer and null their old container/source.
Terminating return and outer-break paths remove the analysis snapshot from current and saved loop
states.
The next work is L2b-a2, now split by its closure matrix into three mergeable verticals:
L2b-a2-s adds the finite projection fact and exact struct/tuple construction, selection, partial
replacement, destructuring, and block/`if`/loop behavior; L2b-a2-a then adds fixed-array and
element selection/replacement plus pipeline `Project`/`WhereField`; L2b-a2-t completes
user-sum/`Option`/`Result`, `match`, `else`, `?`, and `map_err`. The original two-way split was
narrowed before PR preflight when the aggregate implementation and owner tests exceeded the
repository's roughly 1,000 changed-hand-written-line review bound. The public interface remains the
L2a parameter-index summary, so a single aggregate actual deliberately remains conservative.
Unknown extern and indirect targets retain the all-compatible-input fallback.
L2b-a2-s is complete in #674. The remaining L2b-a2 work is split by its closure matrix into seven
mergeable verticals. L2b-a2-ac first closes MIR continuation after every terminating eager child.
L2b-a2-am-g then validates global type/function tables, callable symbol identity, declarations,
and every stored-function header before direct handcrafted-HIR lowering. L2b-a2-am-b validates
every statement, expression type relation and target, action discriminator, id, path, arity, and
result wrapper. L2b-a2-af adds validated fixed-array formation and exact/dynamic element and element-field
selection/replacement on that completed substrate. L2b-a2-ar closes retained storage across
non-fixed index/range, `ArrayChunks`, and `HttpRespHeader` actions. L2b-a2-ap separately closes
pipeline `Project`/`WhereField` under an explicit stage/terminal state machine. L2b-a2-t completes
user-sum/`Option`/`Result`, `match`, `else`, `?`, and `map_err`. The public interface remains the L2a
parameter-index summary, so a single aggregate actual deliberately remains conservative. Unknown
extern and indirect targets retain the all-compatible-input fallback.
The ac implementation uses one required-child continuation protocol across every eager MIR parent:
a terminating child may not feed a typed operation, lower a later sibling, append an action, build
a helper CFG, allocate, or transfer cleanup. Direct tail delegation is permitted only when the
caller performs no later work, and its first non-tail parent must apply the guard. The owner matrix
requires every recursive child-lowering entrypoint (`lower_expr`, borrow/block wrappers, consumed
argument lowering, and delegating helpers) to be classified, plus representative
fixed/non-fixed/index/native/call/aggregate/control/pipeline no-action tests and whole/per-unit
parity. Normal driver input is semantically checked HIR; exhaustive handcrafted-HIR action
metadata validation is the following am boundary.
Block reachability is an O(1) `BuilderCtx` bit maintained by `new_block`/`terminate`, not a CFG scan
after every child. Pre-child synthetic-owner, cleanup-bit, explicit-region setup, and infallible
checked-HIR type/layout fact derivation remain permitted when the terminating edge owns any
required cleanup. Fallible compiler-table/path lookup and every parent action wait until all
required children fall through; no post-child transfer/action may use non-fallthrough as success.
The L2b-a2-ac implementation is complete on its branch: the caller-local required-child protocol
covers expression, statement, call, aggregate, native, index/range, and pipeline lowering; all
value-carrying joins reject an unterminated zero-predecessor block. Missing indirect-function
signatures and invalid element-field paths terminate without a parent action as narrow defense in
depth, not as an exhaustive malformed-HIR contract. The author-side matrix-to-diff pass classifies
every recursive lowering entrypoint as an immediate required child, explicit predecessor-backed
control continuation, or side-effect-free tail delegation. The focused whole/per-unit MIR and
codegen owners pass. Positive MIR/codegen owners also preserve origin-compatible indirect and
`map_err` callback signatures and the settled owned-`string` field to borrowed-`str` read. The
final revised adversarial preflight is clean. The post-open independent review found that dynamic
and SoA element-field lowering still emitted `SliceLen` before its narrow malformed-path check;
the follow-up moves that check before the first length/field action and adds fixed/dynamic/SoA
negative owners. The refreshed Apple Silicon provenance benchmark
records 3.136 ms/check and
22,848 bytes for summary inference, 1.835 ms/import, and
`mir-continuation-lowering` at 1.105 ms/lower over 2,561 basic blocks. The passing ac depth owner is
the end-to-end `within_limit_chain_compiles_and_runs` test; the broader `expr_depth` binary
currently reproduces the same sema-time `deep_within_limit_expression_is_accepted` stack overflow
on unchanged `origin/main` under local Rust 1.96.1 and is recorded as a baseline failure rather than
a clean ac gate. The ac debug `lower_expr` frame is 75,808 bytes versus main's 77,168 bytes, so the
slice improves rather than consumes MIR-lowering headroom.
The hand-written ac diff is roughly 1,700 lines. It cannot split safely by expression family:
landing any subset would retain the same reachable placeholder operand while knowingly leaving
another eager parent able to append after termination. Reachability state, the caller-local guard,
every recursive parent family, and their whole/per-unit tests are therefore one compatibility
boundary and the smallest independently correct vertical. A fresh adversarial preflight found
that the original matrix also promised fail-closed checks for every handcrafted-HIR action family
while the implementation only hardened the two lookups touched by ac. The matrix was reopened:
the broad contract is now L2b-a2-am-g/am-b. A second revised-matrix review found that action-only
validation still omitted callable symbol uniqueness and universal `Expr.ty`/operator consistency,
so the boundary was redesigned instead of patched again. Am-g validates MIR-reachable global type
graphs, callable symbols, declarations, and stored-function headers, returning a canonical
all-empty MIR program on failure; producer-permitted unreachable generic-template tagged entries
remain allowed and omitted. Its roots are table entries, declarations, and stored-function
headers only. Am-b exclusively owns every body-local type/id reference and callable-symbol use,
derives the only permitted type for every expression discriminator, and validates each body,
emitting an Unreachable-only function for invalid action metadata while
preserving neighboring valid source, monomorphized, and lifted functions. A valid caller of such a
rejected function reaches its Unreachable-only body. Valid MIR remains byte-identical. Am-g is one
independent global/header fail-closed slice and fixes the exact link-name and callable-symbol byte
predicates before any MIR/LLVM/link side effect. Am-b uses an iterative enter/exit worklist so the
validator cannot replace ac's lowered-expression stack improvement with a new recursive overflow.
Am-b's exhaustive discriminator walk and mutation
owners may exceed 1,000 hand-written lines, but activation is atomic: a partial body validator would
leave the same direct-lowering claim able to panic on an unchecked family. If it cannot remain one
reviewable exhaustive match, dormant construction and final activation must be re-split before
coding. Both distinct mergeable boundaries must land after ac and before af.
The final L2b-a2-s vertical is approximately 1,900 lines because its adversarial review required
malformed constructor/read/write fail-closed validation, common eager-child source-order snapshots,
snapshot-generation invalidation, checked-expression identity, action-boundary validation, and
Expand All @@ -305,9 +415,9 @@ Its final local provenance benchmark reports 3.147 ms/check, 22,848 interface by
1.844 ms/import on Apple Silicon.
Do not begin a
SQLite/PostgreSQL driver or add database-named compiler
variants before L1a–L7 are complete. L2 is implemented as seven conceptual milestones in nine
variants before L1a–L7 are complete. L2 is implemented as seven conceptual milestones in fourteen
closed PRs so no incomplete borrow surface is exposed: L2a parameter-mode and provenance-summary
representation, L2b-a1/a2-s/a2-a/a2-t/b
representation, L2b-a1/a2-s/a2-ac/a2-am-g/a2-am-b/a2-af/a2-ar/a2-ap/a2-t/b
return-provenance slices, L2c cleanup-ABI record plus dynamic Move-return bit, L2d shared borrow,
then L2e
mutable borrow/out and all-peer
Expand Down
5 changes: 4 additions & 1 deletion bench/library_boundary/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ and empty return-borrow/return-region summaries. It reports:
`provenance` builds a deterministic 256-function caller-before-callee dependency chain whose final
exported function returns only parameter 1. This declaration order requires reverse-worklist
propagation rather than succeeding through one in-place source-order scan. It also validates a
deterministic 128-definition generic chain through 256 borrowing signatures. L2b-a1 reports:
deterministic 128-definition generic chain through 256 borrowing signatures. The same group includes
a 256-function, high-CFG fixture with three expression-valued branches per function. It reports:

- `summary-inference`: full frontend-check milliseconds per iteration plus the canonical serialized
interface bytes, so inference cost and summary-size growth are recorded together.
- `import-validation`: semantic-import milliseconds per iteration, including the complete type-shape
walk, borrow/growth fixed points, dependency-cycle check, and provenance-root validation.
- `mir-continuation-lowering`: L2b-a2-ac whole-program MIR-lowering milliseconds per iteration and
the fixture's total basic-block count, tracking the O(1) required-child continuation protocol.

L2b-b adds the `indirect-return` row to the same group after target-relative function-value
provenance lands.
Expand Down
44 changes: 44 additions & 0 deletions bench/library_boundary/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,21 @@ fn provenance_fixture() -> String {
source
}

fn mir_continuation_fixture() -> String {
let mut source = String::new();
for index in 0..256 {
source.push_str(&format!(
"fn branch_{index:04}(x: i32) -> i32 {{\n\
\u{20}\u{20}first := if x == {index} {{ x + 1 }} else {{ x - 1 }}\n\
\u{20}\u{20}second := if first > 0 {{ first * 2 }} else {{ 0 - first }}\n\
\u{20}\u{20}return if second == x {{ first }} else {{ second }}\n\
}}\n"
));
}
source.push_str("fn main() -> i32 = branch_0255(1)\n");
source
}

fn import_validation_fixture() -> InterfaceSummary {
let parameter = ITypeParam {
name: "T".to_string(),
Expand Down Expand Up @@ -216,6 +231,35 @@ fn run_provenance() {
let elapsed = start.elapsed();
let milliseconds = elapsed.as_secs_f64() * 1_000.0 / iterations as f64;
println!("import-validation\t{milliseconds:.3}\tms/import");

let continuation_source = mir_continuation_fixture();
let mut source_map = align_span::SourceMap::new();
let checked = align_driver::check(
&mut source_map,
"mir-continuation.align",
&continuation_source,
);
assert!(
!checked.diags.has_errors(),
"MIR continuation fixture must check"
);
let mir = align_driver::lower_to_mir(&checked.hir);
let block_count: usize = mir.fns.iter().map(|function| function.blocks.len()).sum();
assert!(
block_count >= 2_000,
"fixture must retain a high-CFG lowering workload"
);

let mut iterations = 0_u64;
let start = Instant::now();
while start.elapsed() < minimum {
let mir = align_driver::lower_to_mir(black_box(&checked.hir));
black_box(mir);
iterations += 1;
}
let elapsed = start.elapsed();
let milliseconds = elapsed.as_secs_f64() * 1_000.0 / iterations as f64;
println!("mir-continuation-lowering\t{milliseconds:.3}\tms/lower\t{block_count}\tblocks");
}

fn main() {
Expand Down
Loading
Loading