Skip to content

feat(audit): cross-file consistency check for cred-blob producer/consumer paths (catches PR #5640/#5644 bug class at CI time)#5649

Merged
AceHack merged 1 commit into
mainfrom
feat/audit-cred-path-cross-file-consistency-2026-05-27
May 27, 2026
Merged

feat(audit): cross-file consistency check for cred-blob producer/consumer paths (catches PR #5640/#5644 bug class at CI time)#5649
AceHack merged 1 commit into
mainfrom
feat/audit-cred-path-cross-file-consistency-2026-05-27

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented May 27, 2026

Summary

Extends `audit-installer-substrate.ts` with a `CROSS_FILE_ASSERTIONS` class. First assertion: cred-blob path producer/consumer consistency.

Bug class this catches

Producer (zeta-install.sh `--output`) writes one path; consumer (zeta-creds-restore.nix `blobPath` default) reads another. `ConditionPathExists` silently evaluates false. Restore service never fires. Operator wonders why creds keep re-prompting.

Surfaced empirically by Copilot review on #5640 + #5644 — took manual code review to spot the install-vs-installed mount-path mismatch. This audit makes the check automatic.

What the assertion encodes

  • Producer: must be on `/mnt/boot/` (target ESP mount during install per zeta-install.sh Step 5)
  • Consumer: must be on `/boot/` (target ESP mount post-reboot per disko `mountpoint = "/boot"`)
  • Equivalence: `/mnt/boot/` ↔ `/boot/`

Producer paths NOT on `/mnt/boot/` (e.g., `/esp/` which doesn't exist on live USB) trigger the cross-file-mismatch failure path.

New exit code

Exit 4 = cross-file consistency assertion(s) failed (isolated). Mixed failure classes still exit 1 prioritized.

Test plan

Composes with

🤖 Generated with Claude Code

…consumer paths (catches PR #5640/#5644 bug class at CI time)

Adds CROSS_FILE_ASSERTIONS class to audit-installer-substrate.ts. Each
assertion names a pair of files that MUST agree on a shared contract
(producer/consumer paths, shared filenames, shared env var names), with
producer/consumer extract functions + a consumerEquivalence transform
that maps producer state to expected consumer state.

First assertion: cred-blob path producer/consumer consistency.

- Producer: zeta-install.sh Step 6.95-picker --output path
  (must be on /mnt/boot/ since that's where the target ESP is
  mounted during install per zeta-install.sh Step 5)
- Consumer: zeta-creds-restore.nix blobPath default
  (must be on /boot/ since that's where disko remounts the same
  ESP post-reboot per disko-shapes/2nvme.nix)
- Equivalence: /mnt/boot/<file> ↔ /boot/<file>

Bug class this catches: producer writes one path, consumer reads
another, ConditionPathExists silently evaluates false, restore
service never fires, operator wonders why creds keep re-prompting.
Surfaced empirically by Copilot review on PR #5640 + PR #5644 —
took manual code review to spot the install-vs-installed mount-path
mismatch. This audit makes the check automatic.

Also asserts that producer is ON /mnt/boot/ (not e.g. /esp/ which
doesn't exist on the live USB at all). A producer path off
/mnt/boot/ returns a sentinel that can never match consumer,
surfacing the producer-on-wrong-mount bug via the same cross-file
fail path.

New exit code 4 for cross-file-mismatch isolated failures (mixed
failure classes still exit 1 prioritized). Audit output groups all
failure kinds in a single FAIL report so CI logs show everything
that needs fixing in one pass.

Substrate-honest: this PR depends on PR #5644 landing first (which
moves both sides to the correct paths). Open after #5644 merges;
without it, the audit correctly identifies current main as failing.

Composes with:
- PR #5640 (the row that introduced the restore-service default-on
  with bad paths)
- PR #5644 (the fix-fwd that aligns both sides to /mnt/boot ↔ /boot)
- B-0852 cred-persistence cascade

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

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 27, 2026 21:25
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@AceHack AceHack enabled auto-merge (squash) May 27, 2026 21:25
@AceHack AceHack merged commit 3a81db5 into main May 27, 2026
33 of 34 checks passed
@AceHack AceHack deleted the feat/audit-cred-path-cross-file-consistency-2026-05-27 branch May 27, 2026 21:28
AceHack pushed a commit that referenced this pull request May 27, 2026
…D018 fix (Copilot 6 threads on #5648)

Comprehensive accuracy rewrite addressing all 6 Copilot findings:

1. "no more re-entering" overclaim — passphraseMode=interactive
   DOES prompt every boot via systemd-ask-password. Reframed
   accurately: N per-tool login flows → ONE cred-blob passphrase.
   The improvement is atomicity, not zero typing.

2. Install log lines mismatch — restored to match actual zeta-install.sh
   output (Step 6.56 + Step 6.95-picker actual strings).

3. /boot path correctness — preserved (#5644 already fixed
   producer/consumer alignment to /mnt/boot ↔ /boot).

4. Manifest coverage — included gemini + codex paths
   (~/.gemini/oauth_creds.json, ~/.codex/auth.json) plus the
   full default-manifest table.

5. Second-reboot expectation — corrected: interactive mode prompts
   every boot by design. Operator who wants no-prompt-at-boot can
   switch to passphraseMode="file" (with security tradeoff named).

6. Filename reference — zeta-creds-cli.ts → zeta-creds-manifest.ts
   (actual canonical location of defaultManifest).

Also fixes MD018 lint failure: line "#5639 + #5640 + #5643 + #5644 +"
was being parsed as an ATX heading because # was at column 1. Replaced
the line-wrapped PR-number prose with the default-manifest table
(more useful + no MD018 trigger).

Composes with:
- B-0852 cred-persistence cascade (PRs that ACTUALLY ship: #5635,
  #5637, #5639, #5640, #5641, #5642, #5644, #5645, #5646, #5648,
  #5649, #5650; #5638 + #5643 were superseded → closed without merge)
- common.nix passphraseMode=interactive default (PR #5640)

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

Co-Authored-By: Claude <noreply@anthropic.com>
AceHack added a commit that referenced this pull request May 27, 2026
…o-end verification checklist for operator) (#5648)

* docs(provisioning): add cred-restore smoke-test section — first-boot + post-reboot + second-reboot verification + troubleshooting table (B-0852 end-to-end)

The B-0852 cred-persistence cascade (PRs #5635 + #5637 + #5638 +
#5639 + #5640 + #5643 + #5644 + #5646) closes the operator's
'don't re-enter creds over and over' pain point. This docs addition
gives operators a concrete checklist to verify the full path works
after a fresh USB install:

- First-boot verification: what install log lines to look for
- Post-reboot verification: systemctl + ls + auth-status commands
- Second-reboot verification: confirm no re-entry needed
- Troubleshooting table: 4 common symptoms with likely causes

Closes the gap between 'cascade is shipped' and 'operator can
confirm cascade works on their hardware'. The operator no longer
has to figure out which systemd unit to query or which paths to
check — the checklist names them.

Composes with:
- PROVISIONING.md (existing operator-facing install doc)
- B-0852 cred-persistence substrate
- The audit-extension PR (separate; catches drift at CI time)

Substrate-honest scope: this is operator docs, not a TS tool. A
follow-on TS smoke-test runner (run on the installed system to
auto-verify the checklist) is a candidate for follow-up work but
out of scope for this commit.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* fixup(docs): rewrite cred-restore smoke-test section for accuracy + MD018 fix (Copilot 6 threads on #5648)

Comprehensive accuracy rewrite addressing all 6 Copilot findings:

1. "no more re-entering" overclaim — passphraseMode=interactive
   DOES prompt every boot via systemd-ask-password. Reframed
   accurately: N per-tool login flows → ONE cred-blob passphrase.
   The improvement is atomicity, not zero typing.

2. Install log lines mismatch — restored to match actual zeta-install.sh
   output (Step 6.56 + Step 6.95-picker actual strings).

3. /boot path correctness — preserved (#5644 already fixed
   producer/consumer alignment to /mnt/boot ↔ /boot).

4. Manifest coverage — included gemini + codex paths
   (~/.gemini/oauth_creds.json, ~/.codex/auth.json) plus the
   full default-manifest table.

5. Second-reboot expectation — corrected: interactive mode prompts
   every boot by design. Operator who wants no-prompt-at-boot can
   switch to passphraseMode="file" (with security tradeoff named).

6. Filename reference — zeta-creds-cli.ts → zeta-creds-manifest.ts
   (actual canonical location of defaultManifest).

Also fixes MD018 lint failure: line "#5639 + #5640 + #5643 + #5644 +"
was being parsed as an ATX heading because # was at column 1. Replaced
the line-wrapped PR-number prose with the default-manifest table
(more useful + no MD018 trigger).

Composes with:
- B-0852 cred-persistence cascade (PRs that ACTUALLY ship: #5635,
  #5637, #5639, #5640, #5641, #5642, #5644, #5645, #5646, #5648,
  #5649, #5650; #5638 + #5643 were superseded → closed without merge)
- common.nix passphraseMode=interactive default (PR #5640)

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

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Lior <lior@zeta.dev>
Co-authored-by: Claude <noreply@anthropic.com>
@AceHack AceHack review requested due to automatic review settings May 27, 2026 21:46
AceHack added a commit that referenced this pull request May 27, 2026
…re the just-written blob at install time (operator catches bad blob BEFORE reboot, not at first boot) (#5655)

Adds opt-in --verify flag to zeta-creds-picker.ts. When set, after
zeta-creds-persist succeeds, the picker spawns zeta-creds-restore.ts
with --dry-run + the same passphrase source + a tmpdir as
--target-root. If restore-dry-run exits 0, the blob is confirmed
cryptographically valid + manifest-parseable. If non-zero, the
operator sees an actionable error at install time + can re-run the
picker to retry.

Operator-experience improvement: without --verify, a corrupt blob
(wrong passphrase captured, disk write error, persist bug) only
surfaces at first reboot when zeta-creds-restore.service fails its
ConditionPathExists or scrypt-decrypt step. At that point the
operator must reboot back into the live USB + re-run the install.
With --verify, the same failure surfaces SECONDS after persist,
inside the running install flow, with the live USB still mounted.

New exit code 5 for verify-failed (distinct from persist-failed=4).

API addition:
- PickerArgs gains `verify: boolean` (default false; opt-in)
- New export buildVerifyArgs(parsed, tmpTargetRoot) — pure
  composer of the restore-CLI argv list; testable in isolation

Tests added (3 new + 2 parseArgs-extension):
- --verify flag default false
- --verify flag parsed when passed
- buildVerifyArgs composes restore-CLI args with --dry-run + tmpdir
- buildVerifyArgs propagates --passphrase-file when picker used file
- buildVerifyArgs propagates --persona when set

21 pass / 0 fail (was 16; +5).

Substrate-honest scope: opt-in only. Future PR can flip default-on
after operator empirical testing confirms verify doesn't introduce
new failure modes (e.g., tmpdir permission, restore-CLI changes).
zeta-install.sh Step 6.95-picker currently does NOT pass --verify;
that flip can land in a follow-up after operator tests.

Composes with:
- B-0852 cred-persistence cascade (#5635 + #5637 + #5639 + #5640 +
  #5642 + #5644 + #5645 + #5646 + #5648 + #5649 + #5650)
- tools/installer/zeta-creds-restore.ts (existing --dry-run mode)

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

Co-authored-by: Lior <lior@zeta.dev>
Co-authored-by: Claude <noreply@anthropic.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