Skip to content

fix(native): bound adoption and authored product clocks by the caller deadline - #499

Merged
roodboi merged 14 commits into
nextfrom
wf/g106-product-clock-bounds
Nov 3, 2026
Merged

roodboi merged 14 commits into
nextfrom
wf/g106-product-clock-bounds

Conversation

@roodboi

@roodboi roodboi commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Update at 3e85b9b (second next merge, at c019124): this brings in #557, #564, #562 and #563. The only conflict was at the end of tests/native-compose-adoption-routing-generation.test.ts. This PR adds live caller budget cases there, and #563 adds the HACK-1359 mutation deadline refusal; both are kept. Typecheck and Biome pass on Bun 1.4.4. The five adoption generation test files and the compose-env-at-rest tests pass (235/0). The file-owner tests need a built config compiler; once it was built with Rust 1.97.1, they passed on their own (29/0).

Update at 04abddb: merged next at 1df527b (#545, #419, #337). The one conflict was in tests/native-authored-project-start.test.ts: #419 renamed the stand-in owner cap to STAND_IN_CAP_MS, sized from the case ceiling. Both sides are kept, and the failed-case test's Bun timeout now uses the named fixture margin, which is the same 5000 ms. Typecheck and Biome pass on Bun 1.4.4.

  • tests/native-compose-adoption-generation.test.ts run on its own passes: 123 pass, 0 fail.
  • In one earlier 4-file run at a host load average of about 7–9, two of its cases failed. Both pass when rerun on their own. One of them refused with E_CONFIG_INVALID for a cancellation, tracked as HACK-1810 and noted on HACK-1458.
  • HACK-1458's 10 loaded runs have not been done.

Current state (bd55b86, merges next at e5d907d). This head adds the local Oct 27 to 31 recovery work that was never pushed: live adoption transaction deadlines, retained routing fixture budgets, retiring proven pre-ready host processes after cancellation, a faster startup compiler stand-in, and a per-case failure cleanup for stand-in host groups.

Checked at this head: tsc passes, and 233 tests pass with 0 failures across the adoption generation and routing files, authored project start, and the fixture helper tests. These ran on a host at load 8 to 10, once, with no added load generator.

Not done: the ticket's 10 consecutive loaded runs of the affected files have not been run at this head, and the failed Oct 26 verification has not been re-run. An independent read-only review by claude-hd (the Codex route is limited until Nov 4) read the code at c520acc and did not run it. It recommended merging, with four low findings. The mux cleanup finding is fixed at bd55b86, proven by two mutations, and the delta after that review has not been reviewed. The others are in HACK-1801. It raised two decisions for the owner: pre-Ready teardown stops host processes without running the authored down hooks, and teardown runs the compiler again before checking for Removed, which can slow cancellation. The Evidence and Gaps sections below describe earlier heads.

Refs HACK-1458 (unit G106). Product fix for the four product clocks that kept HACK-1367's loaded runs from passing. Test-side budgets stay with #419 (G52/G105); this PR adds new cases at the end of the two shared test files and edits none of #419's hunks.

Root cause

  1. Public adoption dispatch. tryLegacyComposeAdoptedCommand opened its adoption store without a deadline, so every store inspection phase (one probe owner) got the default 15 s aggregate clock, whatever HACK_COMPOSE_STARTUP_TIMEOUT_MS allowed. A slow engine expired it as E_NATIVE_COMPOSE_PROBE_TIMEOUT; translate() turned that into E_LEGACY_ADOPTION_STATE, and the command reported E_CONFIG_INVALID ("state is invalid, unsafe or changed"). The command's own deadline only started after loadActive, so inspection before it was never bound by the caller.

  2. Authored compiler calls. acquireNativeExecutionInputs never passed a compiler timeout, so each compiler invocation (and the later assertFresh/value checks) kept the transport's fixed 10 s default, even with minutes of startup budget left.

  3. Source5 cancellation after readiness hang. Reproduced: after onReady aborts, the cancel's down.before re-acquires inputs with the hard signal. That acquisition also kept the fixed 10 s compiler clock. When one compiler call took longer, down.before failed at acquire (no hook ran), onStopFailure fired, and the start retained the live owner and host processes, waiting for a hard cancel that the case never sends. With a scratch copy whose compiler sleeps 11 s after readiness, the case hung for its whole 420 s budget, with hook events down.before:acquire then down.before:stop-operation and no down hook, the same symptoms as the loaded report.

  4. Failed authored cases leave host process groups running. This is a product path, two of them:

    • (i) Startup inputs check freshness under the startup signal. Once a cancel or the startup deadline (its timer aborts the same signal) ends startup after the host processes started and before reservation, stopUnstartedHostProcesses could never pass hostProcesses.stop()'s freshness check (it re-reads inputs with the aborted signal), so the groups stayed up. A loaded local run of a new case showed it directly: startup expired after host-process-ready.json was written, the start failed, and the mux window group was still alive afterwards.
    • (ii) createNativeAuthoredHostProcesses().start() had no failure path. A failure after the launch intent (cancel, deadline or refused check during the launch) left the mux session running behind an intent that recovery never converts into authority.

    Failures after reservation still retain host processes on purpose, with complete owner/ready/state records that hack down recovery stops (retireNativeAuthoredHostProcesses). Ending those in a test is test cleanup (test: derive load-sensitive harness budgets from named constants #419).

Fix

  • Adoption (native-compose-adoption-generation.ts, native-compose-adoption-command.ts):
    • The store takes an optional caller deadline. With one, each inspection phase gets legacyAdoptionProbePhaseMs: the remaining time, capped at LEGACY_ADOPTION_PROBE_PHASE_MAX_MS (60 s). An expired deadline refuses as E_LEGACY_ADOPTION_DEADLINE, and an unclassified failure after a probe timeout or past the deadline becomes E_LEGACY_ADOPTION_DEADLINE, not E_LEGACY_ADOPTION_STATE. A store without a deadline is unchanged.
    • The command starts one deadline (HACK_COMPOSE_STARTUP_TIMEOUT_MS) before it opens the store and uses it for inspection, the retained operation, the plain container runner and ps inspection. E_LEGACY_ADOPTION_DEADLINE maps to E_STARTUP_TIMEOUT. The message stays the store's fixed text, so the closed legacy-deadline diagnostic token is unchanged.
  • Compiler (native-config-compiler.ts, native-project-validation.ts, native-execution-inputs.ts): named NATIVE_CONFIG_COMPILER_MIN_TIMEOUT_MS (1) and NATIVE_CONFIG_COMPILER_MAX_TIMEOUT_MS (60 000), nativeConfigCompilerTimeoutWithin(remainingMs), and an optional compilerTimeoutMs getter that every compiler invocation of a selection reads, including later freshness checks.
  • Authored start (native-authored-project-start.ts):
    • Startup compiler calls use the remaining startup deadline (floor 1 ms, ceiling 60 s). Calls outside startup (down-hook acquisition, checks after ready) have no startup deadline and take the 60 s ceiling, so a slow host no longer fails a graceful stop.
    • Before stopping unstarted host processes, teardown re-acquires inputs under the hard signal and refuses a changed selection, like the down hooks do.
  • Host processes (native-authored-host-processes.ts): a start that fails after its launch intent calls the controller's abort(), which ends the session through the ownership token it created. Owner bookkeeping is cleared only when every launched group was recorded and is proven absent within 5 s. Otherwise it stays retained.
  • Docs: docs/reference/native-compose-adoption.md, docs/reference/native-authored-commands.md.

Tests

New cases, each proven by mutation (product change reverted in a scratch copy):

Case File Clock Mutation result
HACK-1458: public dispatch inspection outlives the former fixed 15 s phase clock while the caller deadline remains adoption generation injected Date.now jump of 16 s after the first engine probe, 120 s deadline base: E_CONFIG_INVALID "state is invalid, unsafe or changed"
HACK-1458: an expired public dispatch deadline ends inspection as a startup timeout, not invalid config adoption generation injected 6 s jump, 5 s deadline base: returns 0 (inspection ignored the expired deadline and started the container)
HACK-1458: a startup compiler call may outlast the compiler's 10 s default while the startup deadline remains authored start real: one compile sleeps 10.5 s wiring reverted: stage inputs (E_COMPILER_TIMEOUT) after 11.4 s
HACK-1458: Source5 cancellation after readiness settles when its down acquisition compiler is slow authored start real: first compiler call after readiness sleeps 10.5 s pending (running)
HACK-1458: cancellation after host processes start and before reservation stops them authored start real: second frontend-plan held until cancelled teardown refresh removed: process-order is started only and the host group stays alive
HACK-1458: cancellation during the host process launch ends the launched group authored start real: first list-panes delayed 1 s abandonLaunch made a no-op: the owned mux session (mux.json) stays

Evidence

In progress: 10-consecutive loaded runs of both whole files are running locally (M5, Bun 1.4.2, private HOME/HACK_HOME/DOCKER_CONFIG, keychain fallback disabled, unreachable DOCKER_HOST, added CPU load stopped in a trap), on this branch plus #419's test-side budgets. Hosted CI at the exact head is the full-suite evidence. This section will be updated with results.

Gates

  • gitleaks 8.30.1 dir over a git log -p origin/next..HEAD export: no leaks.
  • Root bun run check (includes privacy:check): passed. bun run --cwd packages/cli typecheck: 0 errors. Biome on the changed files: no new warnings (the 8 existing complexity warnings in native-compose-adoption-generation.ts remain).
  • The CLI surface and consumer agent guidance did not change, so docs:cli-reference and plugin regeneration do not apply.
  • origin/next merged at 246f1b4.

Gaps

To be completed with the evidence.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

roodboi and others added 2 commits October 24, 2026 16:33
… deadline (HACK-1458)

Public adoption dispatch now starts one command deadline before it opens the
store. Each store inspection phase takes the time left on that deadline,
capped at 60 s, instead of a fixed 15 s clock, and an expired deadline
returns E_STARTUP_TIMEOUT with the store's fixed deadline message instead of
E_CONFIG_INVALID.

Authored startup compiler calls are bounded by the remaining startup deadline
(floor 1 ms, ceiling 60 s) instead of a fixed 10 s each. Compiler calls outside
startup, including the down-hook acquisition after a cancel, take the 60 s
ceiling: the fixed 10 s clock failed the graceful stop under load, which then
retained the live owner and host processes until a hard cancel (the
"Source5 cancellation after readiness" hang).

A start that fails or is cancelled after host processes began and before
reservation now stops them: their teardown re-acquires inputs under the hard
signal, because the startup inputs refuse once the startup signal aborts. A
host process launch that fails part-way ends its owned session through the
token it created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 24, 2026

Copy link
Copy Markdown

HACK-1458

roodboi and others added 12 commits October 24, 2026 16:39
…nslation

An adopted store with a caller deadline still reports a held lock as
E_LEGACY_ADOPTION_BUSY; only an otherwise unclassified failure becomes the
deadline refusal (HACK-1458).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit aea7084f57116a1aae2dc1e312e999fd61280cce)
(cherry picked from commit 86252283e20bfac031b49e25d95edd5cb1bf7fba)
(cherry picked from commit 4f8404ff9e03e7e75507c47f882daa58743444d2)
(cherry picked from commit 5c4ae64cb9de82a09ba97362f2fc9dd1f960a6aa)
(cherry picked from commit 537f0c26f51bc451809eb58724da6e2ead85c997)
A start that fails after reservation retains its host processes on purpose,
for `hack down` recovery that a failed test case never reaches, so failed
cases left stand-in mux groups running (HACK-1458 item 4). Add a per-case
failure cleanup to the native fixture harness, end the recorded mux window
groups through it, and cap the stand-in host process lifetime. A passing case
never runs the cleanup, so a leak the product causes still fails settlement.

Ported from the unreviewed rework-next-499 working tree (wf_b6312000-9d8-2).
Not run: tests are on hold; only typecheck and Biome ran on this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps both sides of the rollback routing deadline: this branch's named probe
clocks and next's released-binding read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cleanup signalled every recorded window pid, and a window that exited stays
recorded, so a reused id could be sent SIGKILL. It also stopped at a group that
ended between its check and its signal. The stand-in now records each window's
start time, and the cleanup skips a group whose live leader differs or that has
no recorded time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#419 renamed the stand-in owner cap to STAND_IN_CAP_MS and sized it from the
case ceiling; this keeps that and this PR's host process cap and fixture
helpers. The failed-case test's Bun timeout uses the named fixture margin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both sides appended tests to the end of the routing generation test file:
this PR's live caller budget cases and next's HACK-1359 mutation deadline
refusal (#563). Both are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi
roodboi marked this pull request as ready for review November 3, 2026 17:34
@roodboi
roodboi merged commit 85212e6 into next Nov 3, 2026
23 checks passed
@roodboi
roodboi deleted the wf/g106-product-clock-bounds branch November 3, 2026 17:34
roodboi added a commit that referenced this pull request Nov 3, 2026
…split (#570)

Repairs a typecheck break on `next` that came from merging two PRs back
to back.

## What broke

#566 (HACK-1811) split
`tests/native-compose-adoption-generation.test.ts`, moving its shared
setup into
`tests/helpers/native-compose-adoption-generation-fixture.ts`. #499
(HACK-1458) was squash-merged right after it. Its CI had run on a head
that merged `next` before #566 landed. Its two public dispatch deadline
cases then landed in the slimmed file without the names they use:

- `spyOn`
- `HackCliError`
- `LegacyComposeAdoptedGenerationError`
- `mutationCommands`
- the bare `root` and `projectRoot`

`tsc` on `next` at 1233175 fails with TS2304 for each of these.

## Fix

Import the missing names, take `mutationCommands` from the fixture, and
read `context.root` and `context.projectRoot` as the other split files
do. No test logic changes.

## Checked

- `tsc -p packages/cli` passes, and Biome is clean.
- `tests/native-compose-adoption-generation.test.ts`: 51 pass, 0 fail,
including both HACK-1458 cases.

Bun 1.4.4, M5, one run.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- codesmith:footer -->
---
<a
href="https://backend.blacksmith.sh/track/run-proof?expires=1796319595&installation_model_id=17053&pr_number=570&ref=codesmith_pr_footer&repository=hack-dance%2Fhack&return_to=https%3A%2F%2Fgithub.com%2Fhack-dance%2Fhack%2Fpull%2F570&variant=start_proof&signature=a07cab5fce585d7c1120766873c16944c4d2e29b77e7251a61d83cccfe26e15c"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-start-proof-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-start-proof-light.svg"><img
alt="Start Proof"
src="https://pr-comments-assets.blacksmith.sh/codesmith/proof-start-proof-dark-3x.png"
width="108" height="28"></picture></a>
<sup><a
href="https://app.blacksmith.sh/hack-dance/codesmith/hack/pr/570?autoLogin=true&ref=codesmith_pr_footer">Open
in [code]smith</a> · Need help on this PR? Tag
<code>@codesmith-bot</code> with what you need.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->

Co-authored-by: Dimitri Kennedy <344842+roodboi@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
roodboi added a commit that referenced this pull request Nov 3, 2026
Brings in #499, #557-#564, #566, #568-#570.
#563 added a file-owner case with its own timeout, which this PR's
two-argument test wrapper rejected. The wrapper now forwards a case
timeout and keeps the file's 60 s budget as its floor. The fixture git
spawn scan still passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roodboi added a commit that referenced this pull request Nov 4, 2026
…refusal (#504)

**Update at 9fe1186:** this merges `next` at 1f5a905, bringing in
#499, #557–#564, #566 and #568–#570.

The text merge was clean, but typecheck found one semantic conflict.
#563 added a file-owner case that passes its own timeout, and this PR's
two-argument `test` wrapper in `tests/native-compose-file-owner.test.ts`
rejected the third argument. The wrapper now forwards a case timeout,
and the file's 60 s budget stays the floor.

Checked on Bun 1.4.4, with the config compiler built with Rust 1.97.1:
- `tsc` passes, and Biome passes.
- The file-owner tests, the fixture git spawn scan, and the env, doctor
and lifecycle files: 507 pass, 0 fail.

**Update at 72661c0:** merged `next` at 1df527b (#545, #419, #337).
The e2e fixture conflict was two import additions; both are kept. This
PR's git spawn scan then caught three spawns that arrived on `next` with
their own git environment: `tests/project-env-worktree-local.test.ts`,
`tests/env-legacy-service-scope.test.ts` (which keeps its private `HOME`
and now commits with the shared fixture identity) and
`tests/e2e/scenarios/worktree-parallel-up.ts`. All three now use
`fixtureGitEnv`. Focused tests on Bun 1.4.4, with the scan and the env,
doctor and lifecycle files: 478 pass, 0 fail. Typecheck, Biome and
gitleaks pass. The e2e scenario change is covered only by hosted
docker-e2e.

## Current state (head ba522c9, 2 Nov)

Fixes the three findings of the independent review of a16f361.
`origin/next` (8621d90) is merged in. The older notes below describe
earlier heads.

- **Scanner:** each git spawn call is now checked inside its own
parentheses, ignoring brackets in string text, with inline negative
controls. It also recognizes `run` from `src/lib/shell.ts` and flags Bun
shell templates that start with git. An argv held in a variable is a
documented gap. The stricter scan found one real unrouted spawn from
`next`, in `tests/env-set-local-contract.test.ts`, now routed.
- **Native stop:** the last ownership check before `compose down` is
bounded by the smaller of the probe timeout and the time left, less a
reserve for the child: a fifth of the budget above 1 s, at most 5 s. A
slow engine is refused as `probe-timeout` before any child runs, and
`hack down --recover` completes the stop.
- **Budget:** `tests/fixture-hermeticity.test.ts` has a 120 s case
budget, because a case outlived the 5 s default on a loaded host. No
assertion changed.

Verified locally with Bun 1.4.2 and a private HOME, HACK_HOME and
DOCKER_CONFIG, HACK_DAEMON_AUTOSTART_DISABLE=1 and DOCKER_HOST at a
missing socket, at load about 8-15:

- Three runs each, all passing: `fixture-hermeticity`,
`env-set-local-contract`, `checkout-write-guard`,
`native-compose-stop-admission`. One run each, passing:
`native-compose-storage-witness-command`, `native-compose-file-owner`.
- 11 mutations of the new checks were each caught, then reverted.
- `packages/cli` typecheck, Biome on the changed files and gitleaks over
a16f361..ba522c9 pass. Hosted CI on this head is not yet confirmed
here.

Open: `tests/native-compose-down-command.test.ts` still fails under this
load (HACK-1768). A paired run gave 9 failures with this change and 9
without. Owner decision from the review: with the timeout unset, a hung
engine now takes up to 60 s, not 15 s, to refuse each ownership query in
a native `down`.

## Current state (head f3619d8, 2 Nov)

Rework after the failed verification of 0f51025. `origin/next`
(ae032e7) is merged in.

- **Ownership probe:** an ownership query in native `down` kept a fixed
15 s bound and ignored `HACK_COMPOSE_STARTUP_TIMEOUT_MS`, and no option
or env input could change it. It now follows that budget, at least 15 s
and at most the probe's 60 s ceiling. Other operations keep 15 s. On an
unset host, a hung engine now takes up to 60 s, not 15 s, to refuse a
native `down`.
- **Docs:** `docs/env.md` now says a regular-file `.hack/.internal`
gives `E_CONFIG_INVALID` for `env set` and `env unset`, which matches
their real output. `docs/cli.md` describes the new bound.
- **Budget:** the dependent route finalizer cases have a named budget.

Verified locally with Bun 1.4.2, a private HOME, HACK_HOME and
DOCKER_CONFIG, and no `docker` on PATH:

- `tests/native-compose-storage-witness-command.test.ts` passed 5 of 5
runs at load about 10 and 5 of 5 at load 33-98.
- A new slow-engine case reproduces the exact refusal
(`effect-ownership`, `probe-timeout`) when the wiring is removed.
- `packages/cli` typecheck, Biome on the changed files and gitleaks
pass.

Open: `tests/native-compose-down-command.test.ts` failed 10-11 of 14
cases at load 30-60 with this change. Many hit `invoke`'s 15 s kill
(exit 137), including `up` calls this change does not touch; the exit 1
failures were not diagnosed. Without the wiring it failed 11 of 14 at
load 30-54 and 13 of 14 at load 62-162. At load 13-20 this head failed 4
of 14: two exit 137 and two `down --recover` exit 1. This file uses
`invoke`'s default 1 s budget, so the query bound stays at the old 15 s
and the fix cannot change its results. The file has not been run at
idle. Hosted CI at this head is still to be read.

## Summary

Follow-ups from HACK-1459 (items 1-6 plus its comment) and the HACK-1460
flake. Test fixtures no longer read the caller's Git or Docker
environment, load-sensitive cases get named budgets sized for their
work, a timed-out case can no longer break the next one, and one env
refusal gets a stable code.

## Root causes

1. **Hermeticity (HACK-1459 item 1 and comment).** Fixture `git` calls
in about 50 test files inherited the runner's environment.
- An exported `GIT_DIR`/`GIT_WORK_TREE` sent the fixture's
`init`/`commit`/`worktree add` into that repository.
`tests/native-env-metadata.test.ts` cleared those variables in
`beforeEach`, but its fixture runs in `beforeAll`, before that.
- Under a private HOME, git still read the contributor's
`$XDG_CONFIG_HOME/git/config`, whose `core.hooksPath` guard refused
fixture commits. That is the actual cause of the local-only failure of
`native-compose-generation` "real linked worktree ...". The fixture
already set its identity with `-c user.*`; it fails on the hook, not the
identity.
2. **`down --recover` storage-witness cases fail locally.**
`DOCKER_HOST` did not cause this. The cases fail the same way with and
without it, and with a private or a real HOME. `invoke()` defaults
`HACK_COMPOSE_STARTUP_TIMEOUT_MS` to 1 s, and `down --recover` bounds
its owned stop by that budget. On a loaded host (load average 60-150
here) the stop's synthetic Docker processes take longer than 1 s, so the
CLI reports `native_compose_effect_refusal {effect-execution,
private-uncertain}` instead of `E_COMPOSE_FAILED`. With a 60 s budget
the same run gave `E_COMPOSE_FAILED` at load average 108. Two more
wall-clock bounds expired under load: the in-process carrier deadline
(`Date.now() + 30_000`, which gave "witness missing ... uncertain") and
`invoke`'s 15 s kill (exit 137).
3. **native-env-metadata beforeAll (items 2-3).** The template build
(five git processes) had Bun's 5 s hook budget. Bun starts `afterAll` as
soon as `beforeAll` times out, while the build keeps creating
`native-env-metadata-template-*` directories. Probe: a 200 ms hook
budget with a 300 ms body showed afterAll running while the body was
still in progress.
4. **HACK-1460 file-owner.** The `up` cancellation case had an explicit
5 s budget. Other cases had 15 or 30 s. When a case times out, Bun runs
`afterEach` while the body keeps running, and that body reads the
module-level `store`/`root`, which the next `beforeEach` replaces. A
late body can therefore act on the next case's store. That is how one
timeout becomes "state unsafe" in the next case.
5. **Items 4-6.**
- Item 4: since #437, a hung helper start is bounded only by the
carrier's remaining budget, so it reports `storage-budget`. That is the
right reason, because the remedy is the budget variable that the summary
names. The code, the probe-budget test and the reference already agreed;
the doc and the test now state it explicitly.
- Item 5: `hack env hydrate` with a `.hack/.internal` file is refused by
the earlier legacy adoption-state check (ENOTDIR counts as an input
access failure, never as absence), so it reports `E_CONFIG_INVALID`.
docs/env.md said `E_PATH_CONFLICT`.
- Item 6: `EnvLayerLockRefusedError` was an uncoded `ClosedCliError`, so
the CLI printed it without an `E_*` prefix and `--json` reported
`E_UNEXPECTED`.

## Fix

- New `tests/helpers/fixture-git.ts`: `fixtureGitEnv()`,
`fixtureGitShellEnv()` (for `src/lib/shell.ts`),
`runFixtureGit[Sync]()`.
- It builds the environment from `PATH`/`TMPDIR` plus the caller's
values.
- It removes every `GIT_*` variable, `DOCKER_HOST`/`DOCKER_CONTEXT` and
the runner's `HOME`/`XDG_CONFIG_HOME`.
- It sets `GIT_CONFIG_GLOBAL=/dev/null`, `GIT_CONFIG_NOSYSTEM=1`, a
fixed author and committer identity, and `GIT_TERMINAL_PROMPT=0`.
- `gitEnv` lets a test set a Git variable on purpose (used for
`GIT_INDEX_FILE` and `GIT_CEILING_DIRECTORIES`).
- Every fixture git spawn in `tests/` goes through it (about 50 files).
The e2e `runCommand` gets an `exactEnv` option. The checkout write guard
drops a caller's repository variables but keeps the contributor's
config.
- `tests/fixture-hermeticity.test.ts`:
- Exports `GIT_DIR`, the work tree, the index, the object directory, the
common dir, a hostile `GIT_CONFIG_GLOBAL` and a hostile identity, then
runs every fixture route. The scratch repository stays byte-for-byte
unchanged, and no hostile hook runs.
- A fixture HOME's own `.gitconfig` cannot add hooks, signing or
identity.
- An ambient `DOCKER_HOST`/`DOCKER_CONTEXT` never reaches fixture git or
the private CLI env.
- A source scan refuses any new fixture git spawn that does not use the
helper.
- `native-compose-storage-witness-command`: named `RECOVERY_DEADLINE_MS`
(30 s), `CLI_COMMAND_TIMEOUT_MS` (45 s) and a 3 min case budget. The
carrier deadline uses the product default. The source-unavailable
recovery case also runs with an ambient `DOCKER_HOST` at a missing
socket plus a `DOCKER_CONTEXT`.
- `native-env-metadata`: named `TEMPLATE_BUDGET_MS` (60 s) on both
hooks. `afterAll` waits for the build to settle, and a build that
outlives that wait removes its own directory.
- `native-compose-file-owner`: one named `FILE_OWNER_CASE_BUDGET_MS` (60
s) via `setDefaultTimeout` replaces the per-case 5/15/30 s budgets.
`afterEach` waits for the running body before teardown.
- `EnvLayerLockRefusedError` is now a coded `E_PATH_CONFLICT`
`HackCliError`. docs/env.md is aligned on hydrate's `.internal` file and
the lock refusal. The storage-carrier reference names `storage-budget`
as the reason for a hung start.

Assertions in the flaky cases are unchanged. Only budgets, deadlines and
teardown order changed.

## Evidence

In progress: local 30-run soaks under load and hosted CI at this head;
this section is updated when they finish.

## Gaps

Listed when the evidence above is complete.

Refs HACK-1459
Refs HACK-1460

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/hack-dance/codesmith/hack/pr/504?autoLogin=true&ref=codesmith_pr_footer"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v3.svg"><img
alt="View with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png"
width="162" height="28"></picture></a> <a
href="https://backend.blacksmith.sh/track/enable-autofix?expires=1795474807&installation_model_id=17053&pr_number=504&ref=codesmith_pr_footer&repository=hack-dance%2Fhack&return_to=https%3A%2F%2Fgithub.com%2Fhack-dance%2Fhack%2Fpull%2F504&signature=27b85b1220571c8b55a775f2a3ef4ffbd481011d47aec883a3beddb23deff445"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img
alt="Autofix with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png"
width="190" height="28"></picture></a>
<sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you
need.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dimitri Kennedy <344842+roodboi@users.noreply.github.com>
roodboi added a commit that referenced this pull request Nov 4, 2026
Refreshes the base after #499, #504, #554 and the other merges of Nov 3.
This PR and #570 made the same import repair to
tests/native-compose-adoption-generation.test.ts, so the merge listed
mutationCommands twice; this takes next's copy of that file. Typecheck
passes, and so does that file's run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roodboi added a commit that referenced this pull request Nov 4, 2026
…-1810, HACK-1801) (#571)

Deadline-driven adoption refusals now stay timeout-shaped through
generation and lower binding/probe layers. Real SIGINT/SIGTERM
cancellation returns `E_CANCELLED`, pending ownership stays retained,
and late fixture cleanup finishes before its caller removes the mux
state directory.

Originally stacked on #499 (`wf/g106-product-clock-bounds`, head
`3e85b9bd`). After #499 was squash-merged as `85212e6ac6`, this branch
merged `origin/next` (including #569, #568, #560 and #561) without
rebasing. This draft targets `next`; no merge or release is requested.

## HACK-1810 findings

- The reported “retained command dispatch uses only original IDs and
keeps anchors through stop and recovery” fixture creates a v1
generation. An explicit version assertion now verifies that boundary. V1
never enters `boundedMutationContext`; the public command's original
controller is aborted only by its SIGINT/SIGTERM handlers. The case
teardown closes the store and restores env, and does not abort this
signal. This is the caller-signal path, not the mutation deadline timer
or case teardown. The supplied historical failure does not identify
which external sender delivered the signal; the healthy case passed
locally without added load. Both direct command cancellation and
store-originated cancellation now map to `E_CANCELLED`, never invalid
config.
- Separately, the v5+ mutation timer can fire in the rounded-down final
millisecond before `Date.now() >= deadline`. Its abort previously looked
like caller cancellation. A private signal-provenance set now preserves
expiry in `cancelled()` and in the outer bounded catch, including
failures that surface through binding/state probes. Caller cancellation
still takes precedence, and observed `E_LEGACY_ADOPTION_CHANGED` keeps
its code.
- The v4 typed-locals case also passed locally without added load. A
slow engine-proof phase could nevertheless become invalid state: the
binding translator erased the known probe-timeout classification, then
generation translation emitted STATE without an aggregate caller
deadline. Known probe timeouts now become binding TIMEOUT and generation
DEADLINE. A synthetic sixteen-second phase overrun with no caller
deadline proves that path; no claim is made that the historical
20.5-second failure was reproduced.

## HACK-1801 disposition

1. Retained routing proof expiry now emits `E_LEGACY_ADOPTION_DEADLINE`
and the closed `proof-deadline` diagnostic. A regression expires the
clock after the real route proof returns, before the owner fence runs.
The existing full-proof expiry regression now also asserts the timeout
code.
2. Both publication and rollback build their transaction copy inside the
`try` that closes the originals handle. Separate tests expire the live
budget immediately after `holdDirectory` returns and assert that the
handle closes despite the getter throwing.
3. The case harness exposes `drain()` for overrun bodies and their
second cleanup. The authored fixture caller awaits it before removing
retained roots. Overrun ownership is captured before initial failure
cleanup, so a body that finishes during that cleanup still receives a
second cleanup. A real mux regression starts a window after the body
budget expires, verifies the root/state remain available through
cleanup, and confirms group absence; a separate race regression checks
completion during initial cleanup.
4. The removed/live/foreign/hard-canceled cases now observe fresh
teardown acquisition under a separate hard signal; only hard-canceled
sees that signal aborted. Unconfirmed-group must not reach acquisition.
Existing effect-order and retained-binding assertions remain.

## Mutation evidence

All mutations were temporary and restored before verification:

- Remove the owned timer's provenance mark: the generation abort
regression reports CANCELLED rather than DEADLINE.
- Remove the bounded catch's provenance condition: the independent
binding-layer abort regression reports STATE rather than DEADLINE.
- Remove binding timeout translation: the no-caller-deadline v4 phase
regression reports STATE.
- Move either transaction constructor outside its close guard: its
publication/rollback regression observes an unclosed handle.
- Restore routing proof STATE: the post-route-check regression rejects
the code.
- Make `drain()` return immediately: the mux regression observes early
drain completion. Check `bodyRunning` only after initial cleanup: the
race regression observes one cleanup instead of two.
- On M5 macOS, remove #499's `refreshPreReadyHostInputs` call from
`runPreparedLifecycle`: **live**, **foreign**, and **hard-canceled**
were run separately and each failed its fresh-admission assertion
(expected 1, observed 0). All five unmutated pre-Ready cases passed, and
the full authored file passed.

## Verification

Pinned Bun 1.4.4 and frozen dependencies. No load generator and no
whole-repo test suite were run on the shared host.

- `bun run typecheck`: pass.
- `bun run check` and explicit lint of all changed TypeScript: pass
(warnings only).
- `bun run build` and `./dist/hack --version`: pass.
- Targeted Linux run at the final source revision: **326 pass, 61 skip,
0 fail**, 4,471 assertions across 15 files. This covers all split
generation families, adoption binding/command/routing,
native-fixture-cases, authored-start, closed diagnostics, CLI envelope
and plugin generation. The skips are the macOS cases and the optional
previous-owner artifact check.
- Both timer-abort regressions and all other regressions passed within
that combined run. No tracked files changed while it ran.

- M5 macOS source snapshot after the `next` merge: full
`native-authored-project-start.test.ts` plus
`native-fixture-cases.test.ts`: **70 pass, 0 fail**. These
authored/helper files and their exercised product behavior are unchanged
by the later binding-abort correction.
- The new error code is registered and the optional plugin error-code
reference was regenerated. CLI-envelope, plugin-generation and closed
adoption-diagnostic contracts are included in the local test command.
- Pre-Ready tests use the file's stand-in driver/mux; this is
frontend/lifecycle test evidence, not physical guest, installed artifact
or release qualification.

## Independent review

One persistent Claude Opus 5.5 reviewer, read-only:

| Round | Revision | Result and response |
| --- | --- | --- |
| 1 | `d7882aa0fa3485db388faf7113d1002f867b7dd9` | CHANGES: P2 early
timer abort surfaced through binding could still become STATE; P3
generic PROBE text still mentioned time. Added the bounded-catch
provenance guard and independent lower-layer regression; corrected the
text. |
| 2 | `c403d2b8d48616dfedebed2bb781ac2ab262f3e5` | **VERDICT: CLEAN**.
Both findings confirmed fixed; no new findings in the incremental or
complete diff. |

Reviewer thread:
<t3-thread://v2/a784d2e4-ee60-4c79-b68f-439a5ba08e88/thread:delegated-task:command%3Amcp%3Ace9302ed-6cf7-46e9-ad48-7e7af31a45a7%3Adelegate-task%3Ahack-1810-1801-review-round-1-d7882aa0>

The review did not rerun tests; the verification results above are the
owner's recorded runs. Hosted CI and automated PR review are checked
separately after opening this draft.

Release intent: `fix`. No data format or cleanup authority was relaxed.
No merge, publication or deployment is part of this PR.

<!-- codesmith:footer -->
---
<a
href="https://backend.blacksmith.sh/track/run-proof?expires=1796323030&installation_model_id=17053&pr_number=571&ref=codesmith_pr_footer&repository=hack-dance%2Fhack&return_to=https%3A%2F%2Fgithub.com%2Fhack-dance%2Fhack%2Fpull%2F571&variant=proof_this_pr&signature=a308c7fa543708cca90804ca8e0617dd4ec7c9f47563cd1c782af63748b99182"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-proof-this-pr-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-proof-this-pr-light.svg"><img
alt="Proof this PR"
src="https://pr-comments-assets.blacksmith.sh/codesmith/proof-proof-this-pr-dark-3x.png"
width="121" height="28"></picture></a>
<sup><a
href="https://app.blacksmith.sh/hack-dance/codesmith/hack/pr/571?autoLogin=true&ref=codesmith_pr_footer">Open
in [code]smith</a> · Need help on this PR? Tag
<code>@codesmith-bot</code> with what you need.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->


<!-- hack-1810-ci-integration-ede1b8e9 -->
## CI integration update — `ede1b8e9`

GitHub's first CI run tested the merge of `c403d2b8` with newer `next`
commit `1f5a9054` (#570). Both branches repaired the same fixture
destructure at different positions, producing a duplicate
`mutationCommands` declaration in that synthetic merge. The
config-compiler and test-shard failures all traced to that declaration;
the aggregate test failure followed from the shards.

Merged the updated `origin/next` into this branch without rebasing
(`90c54bd5`) and removed the duplicate while retaining #570's slot
(`ede1b8e902678a7562e25087d6e00d9f0c97aa7b`). The adoption product code
is unchanged from the reviewed revision.

- Same persistent Opus 5.5 reviewer, **round 3: VERDICT: CLEAN at
`ede1b8e9`**. The deduplication, imports, fixture references, and intact
deadline cases were checked; no new findings.
- Fresh requested verification after the repair: `bun run --cwd
packages/cli typecheck` passed; the full
`tests/native-compose-adoption-generation.test.ts` passed **51 tests, 0
failures, 938 assertions**, including both HACK-1458 public deadline
cases (105.96 seconds).
- Root `bun run typecheck`, `bun run check`, and focused lint also
passed after the repair. The earlier 326-test local and 70-test macOS
evidence remains valid for the unchanged product paths.
- Pushed normally to this PR's own branch. Draft status and base `next`
are retained. Nothing was merged into `next`.
- CI and Codex code re-review are being checked at the new head; results
from the superseded head are not treated as new-head evidence.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dimitri Kennedy <344842+roodboi@users.noreply.github.com>
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