Skip to content

train b49 integration gate (do not merge) - #450

Closed
roodboi wants to merge 14 commits into
nextfrom
train/b49
Closed

roodboi wants to merge 14 commits into
nextfrom
train/b49

Conversation

@roodboi

@roodboi roodboi commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Integration gate for merge batch b49: origin/next + #396 (HACK-1350) + #377 (HACK-1333) + #379 (HACK-1332) + #433 (HACK-1375) + #435 (HACK-1376). Tree acf4fbf0. Closed after the batch squashes.

🤖 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 14 commits October 21, 2026 18:04
`hack project target` let the shared project input guards escape as
detail-less errors. A project directory the caller cannot search, or an
unparseable legacy adoption receipt, printed a detail-less
E_CONFIG_INVALID envelope, which the MCP adapter turned into data: null.

The inspector now maps those guard refusals wherever they are raised,
including inside config reads: native or conflicting inputs become
project_inputs_unsupported, an unsafe or uncheckable adoption state
becomes the new provisional adoption_state_unsafe refusal, and an input
access failure becomes input_unreadable. The adoption guard error now
carries its kind so callers classify it without parsing its message.

Paired CLI/MCP fixtures cover each case, and the MCP argv builder is
pinned literally so a dropped selector is caught independently of the
paired runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The evaluator computed a window mean with built-in sum(), which rounds after
every addition and changed its algorithm in Python 3.12. Ten idle samples of
0.01 averaged to 0.009999999999999998 on the macOS system Python 3.9, so a
mean exactly at the exclusive 1% idle ceiling passed there and failed on 3.12+.

Use math.fsum so every interpreter judges the same correctly rounded mean, and
add a regression test that fails with plain sum() on both 3.9 and 3.12+.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The protocol page says every number in a protocol or summary must be finite and
within +/-(2^53 - 1), but the checker only bounded the fields it judged with
_number. A count read with _count (p95_min_samples, expected) and any field the
checker never reads accepted 2^53, 10^400, NaN or Infinity: a protocol with
p95_min_samples = 2^53 validated, and a summary with expected = 10^400 was judged
inconclusive instead of being refused.

Scan every number in the whole document iteratively and add the closed codes
protocol_number_out_of_range and summary_number_out_of_range, reported
alongside any field-specific code. The page now names both codes and says the
bound covers unread fields.

Refs: HACK-1333, HACK-1169

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nspector

The shared project input guard checks each candidate input with lstat and
treats only ENOENT as absence. Any other fault escaped as a plain Error,
so a `.dev` file, or a `.dev` directory the caller cannot search, beside a
valid `.hack` made `hack project target` print a detail-less E_UNEXPECTED
envelope (and the MCP adapter returned data: null).

The guard now throws a typed, value-free ProjectInputAccessError that
classifies the fault as permission (EACCES, EPERM) or unreadable without
keeping the OS message or its path. The target inspector maps it to the
closed input_permission_denied / input_unreadable refusals. Other commands
now see E_CONFIG_INVALID with the same value-free message as the adoption
guard's input-access refusal, instead of E_UNEXPECTED.

Paired CLI/MCP fixtures and domain tests cover both cases; each was
proven by mutation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rain late

The foreground graph owner drained its child's stdout and stderr for only
250 ms after exit. Under heavy oversubscription the pipe readers could be
scheduled after that window, so the closed native error code (or readiness
line) was dropped and a generic startup failure was reported.

Replace the window with the named, documented NATIVE_EXIT_DRAIN_BOUND_MS
(5 s), still measured from the observed exit. EOF still ends a normal drain
at once; the bound only ends a drain that a descendant holding the pipes
keeps open.

Tests: a writer that emits the code only after the stand-in is reaped is
kept (fails with the 250 ms window), and a held descendant still settles
within the bound while it is alive (fails with a widened drain).

Refs HACK-1350

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve LegacyComposeAdoptionSelectionError: keep this branch's public kind
field, typed over next's message map (adoption, input-access, permission).
target-inspect now maps next's new permission kind (#402, unsearchable .hack)
to input_permission_denied instead of adoption_state_unsafe; the
unsearchable-directory test expects it, and fails if the mapping is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HACK-1375. In a git checkout without a key, a failed root .gitignore write
(read-only file, read-only mount, full disk) refused `hack env set --secret`
but left `.hack.secret.key` behind. The retry the refusal suggested then
found the key, skipped the ignore step and succeeded, so `git status` offered
the key for commit.

- Write the root .gitignore entry before a new checkout-local key; a failed
  entry write refuses with no key on disk.
- Before `--secret` set or hydrate uses an existing checkout-local key that
  git does not ignore, add the entry; if that fails, or a later rule
  re-includes the key, refuse closed and encrypt nothing. Keys git already
  ignores, and checkouts outside git, cause no write.
- `hack doctor` reports a present, un-ignored checkout-local key as
  `env-key-unignored` (warn) with a value-free remedy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ep every key

Two concurrent `hack env set` commands with different keys on the same
layer both exited 0 and one key was lost: each read the layer, changed it
in memory and renamed a new file over it with no lock, so the last rename
won (HACK-1376).

Every env layer writer now holds a per-layer cooperative lock at
`<layer dir>/.internal/<layer name>.lock` for its whole read, change and
write: `hack env set`/`add`/`unset`, `hack env hydrate`, and the legacy
`.hack/.env` set and remove. The lock reuses the projects-registry owner
protocol (exclusive hard-link receipt, dead-owner recovery only on
ESRCH), waits at most 10 seconds and then refuses closed, naming only
the layer, with nothing written. set and unset also compare-and-swap
against the bytes they read, so a writer that takes no lock (an editor,
Hack v4) turns into a retryable AtomicWriteConflictError instead of a
silent overwrite. A symlinked `.internal` is refused; a layer whose
directory is not writable runs unlocked, since no writer can replace it.

Refs HACK-1376

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi roodboi closed this Oct 23, 2026
@roodboi
roodboi deleted the train/b49 branch October 23, 2026 07:13
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