Skip to content

fix: classify cutover-set activation failures like single-generation activation - #1749

Merged
bcdonadio merged 2 commits into
mainfrom
fix/1673-s22
Oct 25, 2026
Merged

bcdonadio merged 2 commits into
mainfrom
fix/1673-s22

Conversation

@bcdonadio

Copy link
Copy Markdown
Contributor

Why

Fixes #1673. The cutover-set activator (src/migration/cutover-set-activate.ts) copied three pre-fix shapes from single-generation activation that #1618, #1648 and #1617 have since corrected there:

  • It created a scratch directory before any classification, so every early refusal allocated scratch, and on a home without .lcm the mkdtemp threw ENOENT instead of refusing.
  • A storage error from a destination identity query (S0, S5, S7), or a census read failure wrapped as PortableStreamError("source-unavailable"), rejected the whole activation instead of being classified.
  • An interruption's applied came from the journal phase alone, without reading back the live config and project map.

How

  • Lazy scratch: each member's S7 census allocates its own scratch (through the existing allocateScratch seam, keeping the cutover-set-activation-scratch- prefix) only after its identity check passes, and removes it when the read ends. An allocation failure marks that member unverifiable / state-unresolvable, so the S8 gate names it. A filesystem error reading the machine identity refuses at S0 with state-unresolvable; an unregistered identity keeps machine-identity-unregistered.
  • Target failures: the identity check returns drift or a classified target failure (target-unhealthy / target-unresolvable), unwrapping only PortableStreamError("source-unavailable"). Each member is recorded and the loop continues, so the set gate (or, after a checkpoint, the roll-forward interruption) reports it with every other member's evidence. Cancellations and unknown errors still reject.
  • applied: the same evidence-derived rule as single-generation activation. yes needs the set's completed publication and live selection matching the published witnesses; no needs the live selection to still match the pre-publication source or the journal's expected witnesses; anything else, including a readback failure, is unknown. The digest helpers are exported from activate-generation.ts (visibility only) so both activators share one digest convention.

Testing

  • New test/migration/cutover-set-activate-classification.test.ts: scratch laziness (including a fresh empty home refused at S0 with the real defaults and no allocation), identity and census failures at S0/S5/S7 before and after a checkpoint, a target failure that must not hide a later member's evidence conflict, and the full applied witness matrix. 14 of the original 15 cases fail at the base commit; the remaining one guards errors that must keep rejecting.
  • Two applied: "no" assertions in cutover-set-activate-faults.test.ts become "unknown": in both, no source witness exists yet.
  • Focused run (5 cutover-set activation files, activate-generation, cutover-set-rollback-replay, codecov-config): 343 passed; cutover-set-activate.ts and activate-generation.ts at 100% statements, branches, functions and lines. pnpm run typecheck (all configs) and eslint pass.

Reviewer guide

Start in src/migration/cutover-set-activate.ts: memberCensus (scratch), identityCheck / targetFailure and their three call sites, then classifyApplied / classifyAgainstSource. activate-generation.ts only gains three export keywords. Docs: one paragraph in docs/migration-cutover-set.md linking to the single-generation rules. Metadata: patch changeset and test/codecov-ownership/1673-cutover-set-activation-classification.yml.

Set activation repeated three defects the single-generation activation
already fixed (#1617, #1618, #1648).

Scratch: activateCutoverSet created its scratch directory before any
classification, so every early refusal touched the disk and an
uninitialized home threw ENOENT. The directory is now allocated per
member census through deps.allocateScratch, after the S7 identity check,
and removed in a finally. The set prefix is kept by overriding the
default allocator. An allocation failure marks the member unverifiable
(state-unresolvable) so the S8 gate names it. An errno from the machine
identity read is refused as state-unresolvable at S0.

Target failures: identityCheck rethrew a StorageOperationError from the
S0, S5 and S7 identity queries, and targetFailure missed the
PortableStreamError(source-unavailable) wrapper of the census reader.
Both now classify as target-unhealthy or target-unresolvable on the
member and the gate refuses (or interrupts after a checkpoint). Every
call site records the failure and continues, so a later member's
checkpoint conflict still surfaces. Cancellation and other errors still
reject.

Applied: classifyApplied derived yes and no from the journal phase
alone. It now mirrors the single-generation rules, comparing the live
config and project map digests with the published or expected witnesses,
or with the artifact's source witness when no journal of this set
exists; in-flight phases and any readback failure are unknown.

digestsMatch, recoveryFileDigests and APPLIED_IN_FLIGHT_PHASES are
exported from activate-generation.ts (visibility only).

Existing assertions that expected a phase-only no are updated to unknown
where no source witness exists.

Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
Review of 8ec42d4 asked for the uninitialized-home test to pin the
classification it relies on. It now runs the real default dependencies
with a pass-through spy on allocateScratch and asserts a refusal at S0
with machine-identity-unregistered, effectBegun false, no allocator call
and no scratch directory.

The set activation doc said an early refusal leaves nothing on disk, but
the append barrier still creates the empty LCM home. It now says only
that an early refusal allocates no census scratch directory.

Signed-off-by: Bernardo Donadio <bcdonadio@bcdonadio.com>
@bcdonadio bcdonadio self-assigned this Oct 25, 2026
@bcdonadio
bcdonadio enabled auto-merge October 25, 2026 04:10
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-25T04:11:28.795686Z 15db01e PR opened
🔒 Security Review ✅ Completed 2026-10-25T04:12:59.842330Z 15db01e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@bcdonadio bcdonadio changed the title fix: fix: classify cutover-set activation like single fix: classify cutover-set activation failures like single-generation activation Oct 25, 2026
@bcdonadio
bcdonadio merged commit 1ae6fb9 into main Oct 25, 2026
21 checks passed
@bcdonadio
bcdonadio deleted the fix/1673-s22 branch October 25, 2026 04:15
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.

Cutover-set activation repeats the #1617/#1618/#1648 classification defects

1 participant