Skip to content

feat(upgrade): read-only upgrade check and snapshot/rollback library (HACK-2189) - #790

Merged
roodboi merged 30 commits into
nextfrom
claude/hack-2189-upgrade-check
Nov 14, 2026
Merged

roodboi merged 30 commits into
nextfrom
claude/hack-2189-upgrade-check

Conversation

@roodboi

@roodboi roodboi commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

HACK-2189 is the first unit of the upgrade assistant (HACK-2188) and the foundation the later units build on. The goal: moving a machine from v4 to v5 becomes one guided, resumable and reversible flow, replacing a hand-written rollback sheet. This PR adds a read-only check, a snapshot and rollback library, and three commands on top of them.

flowchart LR
  C["hack upgrade check<br/>(read-only)"] -->|blockers| F[fix the named blocker] --> C
  C -->|plan| S["hack upgrade snapshot"] --> A["plan steps<br/>(later: hack upgrade apply)"]
  A -->|undo| R["hack upgrade rollback<br/>--rollback-manifest"]
Loading

hack upgrade check [--json]

It checks the machine and every registered project.

Machine:

  • The running version and channel (stable or next; candidate launcher).
  • The hack on PATH: its version and install kind.
  • hackd: whether it runs, who runs it, and whether a login service is registered (systemd or launchd).
  • The projects registry. Dead entries (the checkout, its .hack/, or all of hack.config.json, hack.toml and docker-compose.yml are gone) are counted apart from live entries that cannot be read. Dead ones point at hack projects prune --dry-run.
  • Hack's global proxy and DNS (installed and running), the fix(compose): verify loopback proxy reachability #773 loopback proxy ports check, and the Linux DNS route.
  • The global secret store and key. It reuses the feat(secrets): retire a store whose key is gone, and fail doctor only where the store is read (HACK-2177) #779 code, and reads the key file directly, so it never prompts the keychain.

Per live checkout (each project's primary checkout and each registered linked worktree):

  • Env format: YAML layers, legacy JSON, legacy dotenv, or a conflict.
  • Env key: present, private and untracked (doctor's key audit).
  • Tracked machine-local or sensitive files, and the committed .hack/.gitignore.
  • Agent integration files (hack setup state).
  • On Apple Silicon, the native preflight verdict. It runs in-process only and is never a blocker, because native is optional.

Output: an ordered plan and the single next command. Blockers come first, then hack upgrade snapshot, then machine steps, then project steps. The command exits 0 when nothing blocks. Otherwise it exits 1 and names the blockers. A blocker is something that would make the upgrade fail or lose data:

  • an env layer conflict;
  • a tracked secret key, or an env key error;
  • an unreadable or invalid registry;
  • a global store whose key is gone while a registered project reads it.

Changes nothing: no file, registry, service, container or key is written, started or provisioned, and nothing prompts. A test compares the whole fixture tree (paths, modes, digests) before and after. Doctor functions with side effects are avoided:

  • globalStatus/ensureDockerRunning can start Docker;
  • hack projects touches the registry;
  • the native runtime review spawns the binary and writes a temporary directory.

collectProjectNativePreflight is split out of the doctor command so the report can be collected without printing it or running the runtime review.

Fast: dead entries cost one stat. Live projects are checked eight at a time, with an 8-second limit per check and a shared 24-second budget. When the budget runs out, the remaining checks report "not checked" rather than running. Host probes have time limits too. An 80-entry fake registry (76 dead) finished its checks in about 130 ms.

Hermetic:

  • Every host probe is injected: Docker, the resolver, service managers, the PATH hack and native preflight.
  • HACK_HOST_PROBES_DISABLE=1 refuses them all and is set by the test preload, like HACK_SYSTEMD_DISABLE.
  • A refused or failed probe reports "not checked", never a pass.

Snapshot and rollback library: src/lib/upgrade-snapshot.ts

The API is posted in a comment for HACK-2191: #790 (comment). It is modelled on the hack setup migrate backup manifest, with these differences:

  • No content in the manifest. It holds only path, label, kind (file | absent), size, mode and SHA-256, before and after. Bytes live in content-addressed blobs/<sha256>, because env files hold secrets.
  • Private storage outside every working tree. Directories are 0700 and files 0600, created with no-follow exclusive opens and owner-checked on every read. A snapshot root inside a Git working tree is refused. Manifest writes are atomic: temporary file, fsync, rename.
  • All or nothing. A link, directory, unreadable or oversized target refuses the whole snapshot before anything is written.
  • Restore puts back exact bytes and modes. It writes a no-follow temporary file and renames it over the target.
  • It refuses a target that changed since the step recorded it (recordUpgradeSnapshotAfter) unless told to overwrite. A target with no recorded after state also needs overwrite.
  • It never deletes. What it replaces or removes is kept in the snapshot and named in keptAs.
  • It is idempotent, and reports every target it could not restore with a closed reason.

Commands

  • hack upgrade snapshot [--json] keeps:

    • the global config, the registry, and the secret store and key (when a file store is used);
    • per live checkout: the env migration's write set, which is its .hack/ config, env contract, Compose file, dotenv files, YAML layers (the base, machine-local and per-overlay layers it would create, recorded as absent when they don't exist yet), .hack/.gitignore and in-checkout env key files.

    It skips links. Printed commands run the CLI that produced them: during the prerelease, the hack-next launcher, with HACK_RUNTIME_BACKEND=compose for a Compose run.

  • hack upgrade rollback --rollback-manifest <manifest> [--overwrite] [--json] restores the files. Without a manifest it lists the snapshots and changes nothing, because non-interactive mode never guesses.

Docs

  • The upgrade guide gains a "Check first" section.
  • docs/cli.md lists the commands.
  • docs/reference/cli.md is regenerated.

Validation

  • New tests in tests/upgrade-snapshot.test.ts and tests/upgrade-check.test.ts (19, all passing), using a temporary HOME. No Docker, no daemon autostart, no real home or registry. They cover:
    • snapshot, then mutate, delete and create, then rollback: byte-for-byte equal with exact modes; the created file moved aside and kept; idempotent;
    • refusal on a changed target; overwrite keeps the edit; a target with no after state; a race during restore;
    • unsafe targets and roots; a non-private manifest; a damaged blob;
    • sentinel values absent from every manifest, result and report, with storage modes 0700/0600;
    • dead vs unreadable registry entries: 79 entries, 77 dead; a TOML-only or Compose-only project counts as live;
    • plan ordering; ready with an empty plan; secret-store blocker only when a project reads the store;
    • the check changes nothing (whole-tree digest); disabled or failing probes report not checked; the time budget;
    • tracked files, ignore rules and a tracked secret key in a real temporary Git repo;
    • CLI: check exits 1 naming blockers, and snapshot → mutate → rollback refuses without --overwrite and restores with it.
  • Related suites: 143 pass across 11 files. The one failure is "recovers a confirmed dead registry owner", which times out on this host on next as well, plus the unhandled error it leaves behind.
  • Gates: typecheck clean; check at the 44-warning baseline (none in touched files); docs:prepare then docs:check pass; privacy ok; gitleaks clean.

Review round 2 (head 3118605): what changed

  • Readiness: ready needs no blockers and no incomplete checks. A required check that fails or times out, disabled host probes, a key only the keychain could confirm, or a registry cut short by the deadline are all incomplete. The exit code is 1 when not ready. A live checkout whose config cannot be read blocks.
  • Commands vs actions: commands are built from the running CLI's prefix plus separately quoted arguments, never by rewriting text, so a path containing " hack " survives. Plan steps carry a command or a manual action. next_command is null when the first step is manual, and next_action then says what to do. The snapshot step now comes first.
  • Global key: read-only inspectStoreKeyState follows the runtime's order and refusals (env key, then the key file through the runtime's reader: link, mode, Git tracking). It never asks the keychain. An absent store is not a lost key.
  • Snapshot coverage: it includes the migration's own overlay selection, so layers for overlays only the secret store holds are recorded as absent. When those can only be listed through the keychain, the snapshot reports the gap.
  • Restore never loses an edit: the current entry is displaced by one rename to an invocation-owned sibling and its exact bytes are kept. Publication uses a hard link that refuses an occupied target, and a late edit is put back. Unknown restore paths are rejected. The manifest directory is fsynced, and the single-writer rule is documented.
  • One deadline, no stray children: the 25 s budget covers the registry read, classification, probes and checks. Config inputs must be regular files (no-follow, non-blocking, bounded). A new withProcessDeadline (AsyncLocalStorage) caps exec and the Git probes, so every child a check starts ends with that check. Previously unbounded Git calls in shared helpers (readGitPath, the generated-files ls-files, three ignore/tracking probes) now have 10 s limits, which helps doctor too.
  • Tests: 32 upgrade tests, including late-replacement races, store-only overlays, key audits, a FIFO config, hanging Git with child-death checks, and quoting through a real shell split. A 64-file related slice has only the failures that also occur on a clean next checkout on this host (adopted-project doctor tests, registry lock timing).

Limits and follow-ups

  • The daemon finding reports the owner and registration. Handover is the next unit.
  • hack upgrade snapshot does not cover the daemon's unit or plist or Docker state; the guide says so.
  • Native preflight is the in-process part only.
  • The check is not timed on a real 80-entry machine; the fixture measurement is above.
  • Env-key remedies are relative to the project root, so the plan step is hack doctor --path <root> and the remedy is in the finding text.

🤖 Generated with Claude Code

…2189)

A step that changes files snapshots them first, records what it wrote, and
can put every file back. Bytes are kept in a private directory outside
every working tree (0700/0600, no-follow, owner-checked) as content-
addressed blobs; the manifest holds only paths, labels, kinds, sizes,
modes and SHA-256 digests. Restore puts back exact bytes and modes,
refuses a target that changed since the step recorded it unless told to
overwrite, keeps whatever it replaces or removes, and reports every target
it could not restore.

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

vercel Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hack-docs Ready Ready Preview Nov 14, 2026 12:32am UTC

Request Review

@linear-code

linear-code Bot commented Nov 13, 2026

Copy link
Copy Markdown

HACK-2189

@roodboi

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

Snapshot and rollback API, for HACK-2191 (env migration) and a later hack upgrade apply. It's in src/lib/upgrade-snapshot.ts at 9ffb447, tested in tests/upgrade-snapshot.test.ts. The shape below is what I intend to keep. I'll post here before any change to it.

import {
  createUpgradeSnapshot,      // before the step: snapshot every file it may touch (all or nothing)
  recordUpgradeSnapshotAfter, // after the step (or after each write): record what it wrote
  restoreUpgradeSnapshot,     // put everything back
  readUpgradeSnapshot, listUpgradeSnapshots, defaultUpgradeSnapshotRoot,
} from "../lib/upgrade-snapshot.ts";

// 1. Before changing anything. List every path the step may write, create or remove:
//    a path that does not exist yet is recorded as absent.
const created = await createUpgradeSnapshot({
  purpose: "env-migration app-a",          // no values
  targets: [
    { path: "/abs/repo/.hack/hack.env.json", label: "project app-a: hack.env.json" },
    { path: "/abs/repo/.hack/.env",          label: "project app-a: .hack/.env" },
    { path: "/abs/repo/.hack/hack.env.yaml", label: "project app-a: hack.env.yaml" }, // absent now
  ],
  // root?: string   default ~/.hack/upgrade/snapshots (private, outside any Git working tree)
});
if (created.status !== "created") {
  // { status: "refused", reason: "targets", targets: [{ path, label, reason }] }
  //   reason: not-absolute | duplicate | symlink | not-a-file | too-large | unreadable | inside-snapshot-root
  // { status: "refused", reason: "root", root, rootReason }
  //   rootReason: inside-working-tree | not-private | not-owned | symlink | not-a-directory | unwritable
  // Nothing was written. Do not run the step.
}
const { manifestPath } = created.snapshot; // .../<id>/manifest.json

// 2. Run the step, then record what it wrote (all targets, or `paths`).
await recordUpgradeSnapshotAfter({ manifestPath /*, paths?: string[] */ });
// -> { status: "recorded" } | { status: "invalid-manifest", message } | { status: "refused", paths: [{ path, reason }] }

// 3. Roll back.
const result = await restoreUpgradeSnapshot({ manifestPath /*, overwrite?: boolean, paths?: string[] */ });
// -> { status: "invalid-manifest", message }
//  | { status: "restored" | "incomplete", results: [{ path, label, status, keptAs?, message? }] }

Per-target restore statuses

  • restored: the snapshot's exact bytes and mode are back.
  • moved-aside: the target was absent at snapshot time; the file the step created is moved into the snapshot directory.
  • already-restored: the target already matches the snapshot. This makes restore idempotent.
  • conflict: the target changed since the step recorded it, or nothing was recorded for it. It is left unchanged; overwrite: true restores it anyway.
  • not-a-file: a link, directory or other entry is at the target. It is left unchanged, even with overwrite.
  • backup-damaged: the snapshot's copy is missing or fails its digest check.
  • error: the restore failed at this path.

Guarantees you can rely on

  • No values in metadata. The manifest holds only path, label, kind (file | absent), size, mode and SHA-256 per target, before and after. Bytes live only in blobs/<sha256>. Results and messages carry paths, labels, statuses and closed strings. Keep labels and purpose value-free.
  • Private storage. Directories are 0700 and files 0600, created with no-follow exclusive opens. Every read checks the owner and that the file is private. Manifest writes are atomic: temporary file, fsync, rename.
  • "Changed since" is measured against the recorded after state. Call recordUpgradeSnapshotAfter right after the step writes, or after each write for multi-file steps. A target with no after state restores only with overwrite, which covers an interrupted step.
  • Restore never deletes data. Anything it replaces or removes is first kept as a blob in the snapshot directory, and the result names it in keptAs. Each target is re-checked immediately before it is replaced.
  • Not transactional. Restore is not atomic across targets or against a concurrent editor, and it reports per target. It creates a missing parent directory (0700) when it puts a file back.
  • Size limit. 64 MiB per file; larger targets refuse the snapshot.

Commands on top of this (hack upgrade snapshot / --rollback-manifest-style restore) come in the next commits of this PR.

roodboi and others added 2 commits November 13, 2026 19:35
…mmands (HACK-2189)

hack upgrade check [--json] reads the machine and every registered
project and ends with an ordered plan and the single next command:
installed versions and channel, the daemon's owner and login service,
the projects registry (dead entries counted apart from unreadable ones),
global services, proxy ports, Linux DNS, the global secret store and key,
and per live project the env format, env key, tracked machine-local files,
ignore rules, agent integrations and (Apple Silicon) native preflight.
It changes nothing, prints no value, and exits 1 naming the blockers.
Host probes are injected and time-limited; HACK_HOST_PROBES_DISABLE=1
(set by the test preload) refuses them all.

hack upgrade snapshot keeps the files the upgrade changes through the
snapshot library; hack upgrade rollback --rollback-manifest restores
them, refusing changed files unless --overwrite, and lists snapshots
without a manifest. The native preflight report can now be collected
without printing it or running the native runtime review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…; keep the check under 30 s (HACK-2189)

An entry is dead only when its checkout or .hack/ is gone, or none of
hack.config.json, hack.toml and docker-compose.yml remains (the inputs
project discovery and prune accept). Project checks share a deadline: no
check starts after 24 s, and those left report "not checked".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi
roodboi marked this pull request as ready for review November 13, 2026 19:37
@roodboi

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-13T22:16:21.658527Z 1d2803e Manual request
🔒 Security Review ✅ Completed 2026-11-13T22:19:54.667860Z 1d2803e Manual request

Security findings

Advisory findings (1)

ℹ️ 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.

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

HACK-2191 is binding to the posted snapshot API behind a small adapter. One additional contract would help keep cleanup inside the snapshot owner: can you export a read-only coverage verifier that accepts a manifest and selected paths and validates each original blob as well as the manifest? readUpgradeSnapshot currently validates metadata only, while finish must prove the files it removes remain recoverable. I can compare the returned original path/kind/size/mode/SHA-256 with the migration journal. I will call recordUpgradeSnapshotAfter after each known publication and after cleanup, and keep rollback guarded against journal-recorded current checksums. The new env preview is names/paths/counts/checksums only; raw payloads remain private outside working trees.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b011e0c944

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/upgrade-snapshot-targets.ts Resolved
Comment thread src/lib/upgrade-check.ts Resolved
Comment thread src/lib/upgrade-check.ts Resolved
…rint the running CLI (HACK-2189)

- Each registered linked worktree is classified and checked on its own,
  so a broken worktree blocks even behind a healthy primary checkout.
- hack upgrade snapshot records the env migration's write set: the
  Compose file it repairs and the YAML layers it creates (absent ones as
  absent), so a rollback also moves created layers aside.
- Printed commands run the CLI that produced them: during the prerelease
  the hack-next launcher (with HACK_RUNTIME_BACKEND=compose for a Compose
  run), not the stable hack on PATH.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f77d7b0c80

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/upgrade-snapshot.ts Resolved
… tree (HACK-2189)

The working-tree check walked only the path as written, so a symlinked
ancestor (a hack home inside a dotfiles checkout) carried snapshot blobs
into a repository. The root is now checked as written, as resolved before
it is created, and again on the created directory before anything is
written into it.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab1e18769a

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/upgrade-check-project.ts Resolved
Comment thread src/lib/upgrade-check.ts Resolved
@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: ab1e18769a

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

…ry required check (HACK-2189)

Second-family review of #790:
- Restore moves the current entry to an invocation-owned sibling with one
  rename, keeps exactly those bytes, and publishes with a hard link that
  refuses an occupied target; a late edit is linked back and kept.
- The snapshot takes the migration's own overlay selection, including
  overlays only the secret store holds, and reports a gap when listing
  them would need the OS keychain.
- The global key is audited with the runtime's own reader (link, mode,
  Git tracking); no store is not a lost key; an env key is tested without
  exposing it; a key only the keychain may hold is incomplete, not gone.
- A live checkout that cannot be read blocks; a required check that fails
  or runs out of time is incomplete, and incomplete means not ready.
- One deadline covers registry discovery, classification, probes and
  checks; config inputs must be regular files (no-follow, non-blocking,
  bounded); child processes inherit the check's deadline through
  withProcessDeadline, and the shared git probes gain time limits.
- Commands are built from a prefix and separately quoted arguments, never
  by rewriting text; plan steps carry a command or a manual action.
- The snapshot step comes first; unknown restore paths are rejected; the
  manifest directory is fsynced; the single-writer rule is documented.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

…ent (HACK-2189)

tests/fixture-hermeticity.test.ts requires every fixture git spawn to use
fixtureGitEnv; the upgrade-check fixtures set their own env.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3118605844

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/upgrade-snapshot.ts Resolved
Comment thread src/lib/upgrade-check-project.ts Resolved
…led Git listing is incomplete (HACK-2189)

- If the snapshot cannot keep the displaced file's bytes (a full disk), the
  file is linked back to its path; if another file took the name
  meanwhile, it stays at its sibling path and the result names it.
- tracked-files: Git failing on a checkout (unreadable index, refused
  repository) is incomplete, not "not a Git checkout", so readiness is
  not claimed without knowing whether sensitive files are tracked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 3118605844

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@roodboi

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@roodboi

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

HACK-2191 integration note: env rollback now deliberately retains project keys (including a newly created git-common-dir key), because another checkout may already use one for its ciphertext. It also retains the corresponding root ignore entry when needed. Its snapshot adapter filters these targets and restores only journal-guarded selected paths. Please keep this invariant in the generic rollback entry point too: a manifest with purpose env-migration should route users to hack env migrate --rollback-manifest <path>, rather than restoring all absent targets and moving away a live shared key.

The env adapter also supports an explicit --preserve-changes recovery option: it admits the freshly observed changed bytes only on that request, records that revision, and relies on the provider to retain replaced bytes privately. Default rollback still refuses later edits. No values go into output or metadata.

The small adapter is ready to bind statically once this library lands on next; production-provider and compiled-CLI integration remain gates on HACK-2191. The earlier request for a provider-owned original-blob coverage verifier still applies.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0733d15c45

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/upgrade-check.ts Resolved
…-2189)

A project may select the global store with its own keyPath. The global
store finding now checks the key each reader's configuration selects
(this shell's HACK_SECRETS_FILE_KEY first, as at runtime): readers whose
own key opens the store are not blocked, and retiring is not suggested
while another configured key opens it (retire-store refuses then).

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 049249eb01

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/upgrade-check.ts Resolved
Comment thread src/lib/upgrade-check.ts Resolved
… gap (HACK-2189)

A live checkout whose .hack/ can be searched but not listed got an empty
inventory, so its dotenv files and layers were silently left out while the
snapshot claimed to be complete. The listing failure is now a coverage gap.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0614bc4b23

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/upgrade-check-project.ts Resolved
…HACK-2189)

The env key check audits the key file's hygiene only, so encrypted env
layer values with no key, or a wrong key, passed as "none needed". A new
required checkout check, env-secrets, uses the read-only, value-free
inspectProjectEnvLayerKeys: secrets with no key available, or that do not
all open, block; a layer that cannot be inspected is incomplete.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 64fa4716d1

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/upgrade-snapshot.ts Resolved
…oes back in place (HACK-2189)

A regular target that became unreadable or too large between the checks
and the displacement was treated as a non-file and left in displaced/.
It is now a late edit: linked back to its path (no-clobber) and reported
as a conflict.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Codex Security Review · Automatically triggered

Here are some automated security review suggestions for this pull request.

Reviewed commit: b97c9f0538

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread src/lib/upgrade-snapshot.ts Resolved
…t (HACK-2189)

GIT_DIR or GIT_WORK_TREE can make any directory (for example $HOME) part
of a working tree with no .git marker above it, so the snapshot root's
working-tree check passed and secret blobs could be staged by git add. With
either set, creating a snapshot is refused (inside-working-tree) and a
restore writes nothing.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

…e included (HACK-2189)

Trimming treated GIT_DIR=' ' as unset, though Git takes it as a literal
path. A defined value of any kind, empty included, now fails closed.

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

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 1d2803e5fd

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 1d2803e5fd

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

roodboi and others added 2 commits November 13, 2026 22:42
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

Snapshot and rollback library: current public interface (at c0f7d1e). This replaces the earlier API comment and is the version HACK-2191, HACK-2213/2216 and HACK-2214 should code against. Module: src/lib/upgrade-snapshot.ts. Everything is read-only except create, recordAfter and restore.

import {
  createUpgradeSnapshot,      // before a step: snapshot every path it may write, create or remove
  recordUpgradeSnapshotAfter, // after the step (or after each write): record what it wrote
  restoreUpgradeSnapshot,     // roll back
  readUpgradeSnapshot,        // validate and read one manifest
  listUpgradeSnapshots,       // newest first
  defaultUpgradeSnapshotRoot, // <hack home>/upgrade/snapshots
  MAX_SNAPSHOT_FILE_BYTES,    // 64 MiB per file
} from "../lib/upgrade-snapshot.ts";

createUpgradeSnapshot({
  purpose: string,                                   // e.g. "env-migration app-a" (no values)
  targets: readonly { path: string; label: string }[], // absolute paths; labels hold no values
  root?: string,                                     // default defaultUpgradeSnapshotRoot()
  now?: Date, id?: string,                           // test seams
}) → { status: "created", snapshot: { id, dir, manifestPath, manifest } }
   | { status: "refused", reason: "targets", targets: [{ path, label, reason }] }
     //   reason: not-absolute | duplicate | symlink | not-a-file | too-large | unreadable | inside-snapshot-root
   | { status: "refused", reason: "root", root, rootReason }
     //   rootReason: inside-working-tree | not-private | not-owned | symlink | not-a-directory | unwritable

recordUpgradeSnapshotAfter({ manifestPath, paths?: string[], now? })
  → { status: "recorded", snapshot } | { status: "invalid-manifest", message }
  | { status: "refused", paths: [{ path, reason }] }   // throws on a path that is not a target

restoreUpgradeSnapshot({ manifestPath, overwrite?: boolean, paths?: string[] })
  → { status: "invalid-manifest", message }
  | { status: "restored" | "incomplete", results: [{ path, label, status, keptAs?, message? }] }
  //   throws on a path that is not a target

What a snapshot holds: a private directory <root>/<id>/ (0700, outside any Git working tree) containing:

  • manifest.json (0600, atomic writes). Per target it holds the path, label, and before/after states. Each state is either { kind: "file", size, mode, sha256 } or { kind: "absent" }. It never holds file content.
  • blobs/<sha256> (0600): the bytes of each before file.
  • staging/ and displaced/: used by restore.

All snapshot creation is all or nothing: a refused target refuses the whole snapshot and nothing is written. A path that doesn't exist yet is recorded as absent, so restore can move away what a step creates there.

How rollback is called and behaves:

  • When to record after: call recordUpgradeSnapshotAfter right after the step writes. A target is put back only if it still holds what the step wrote, or already matches the snapshot. Otherwise the result is conflict, unless overwrite: true. A target with no recorded after state also needs overwrite; that covers an interrupted step.
  • Never deletes.
    • What restore replaces or removes is moved, as the same file, into displaced/ (one atomic rename) and named in keptAs.
    • Publication is a hard link from staging/, which refuses an occupied name.
    • A late edit is linked back into place and reported as conflict.
    • No temporary or displaced file is ever written beside a target in your checkout.
  • Per-target statuses:
    • restored;
    • moved-aside (the target was absent in the snapshot);
    • already-restored (idempotent);
    • conflict;
    • not-a-file (kept in displaced/);
    • backup-damaged;
    • error, with closed messages: target on another filesystem than the snapshot, no hard links, GIT_DIR/GIT_WORK_TREE set, or publication failed (the file is put back, or named in keptAs).
  • Same filesystem required: a target must be on the snapshot's filesystem, or it is refused untouched. A caller can pass root to keep a snapshot beside the files it covers.
  • One writer per snapshot: two concurrent recordUpgradeSnapshotAfter calls on one snapshot can lose each other's updates.

The CLI on top:

  • hack upgrade snapshot [--json] is built on collectUpgradeSnapshotTargets() in src/lib/upgrade-snapshot-targets.ts. It returns { targets, skipped, gaps }.
  • hack upgrade rollback --rollback-manifest <path> [--overwrite].
  • hack upgrade check puts hack upgrade snapshot first in its plan.

Not covered yet (for HACK-2213/2216 to add through this interface): the snapshot does not cover:

  • the daemon's login service unit files: the launchd plist ~/Library/LaunchAgents/dance.hack.hackd.plist and the systemd user unit ${XDG_CONFIG_HOME:-~/.config}/systemd/user/hackd.service;
  • daemon state under ~/.hack/daemon/ (pid, socket, log);
  • the Git index, services and containers.

To include the unit files, pass them as targets, absent ones included, before rewriting a unit. A file-level rollback restores the unit's bytes only: reloading or restarting the service manager after a restore is the caller's step. Daemon runtime state such as pid and socket is not meaningful to restore; a caller should stop or restart the daemon instead.

@roodboi

roodboi commented Nov 14, 2026

Copy link
Copy Markdown
Contributor Author

Ready for lead: head c0f7d1e (HACK-2189). This includes next b1e123b via normal merges.

  • Persistent second-family reviewer: CLEAN at c0f7d1e (and at every head since b97c9f0). Its remaining follow-ups belong to the apply unit:
    • persist snapshot coverage and gaps in the apply journal;
    • keep timing claims qualified for disk I/O already in progress, which cannot be cancelled.
  • Codex review and Codex security review: both clean at 1d2803e. After that, only conflict-free merges of next.
  • Bot threads: computer pr review-status gives CLEAR at c0f7d1e. Every bot finding was fixed and answered; one thread I resolved early by mistake was answered afterwards.
  • CI at c0f7d1e: every job passed except test shard 2/8 (macos-15). All Linux test shards, all four docker-e2e shards, the config compiler and candidate core on every OS, runtime-images, linux-process-lifetime, models, docs and secret-scan are green.
  • The macOS failure: tests/python-free-download.test.ts ("five-file official sequence shares a monotonic total and bounded cleanup: cleanup"). It tests download and installer code that came from next; this PR does not touch it. It passes 3 out of 3 times locally. The aggregate test job is red only because of that shard.
  • Snapshot library interface for HACK-2191, HACK-2213/2216 and HACK-2214: feat(upgrade): read-only upgrade check and snapshot/rollback library (HACK-2189) #790 (comment). It does not yet cover the login service units or daemon state, and the comment says how to add them.

Not merged.

roodboi added a commit that referenced this pull request Nov 14, 2026
## Summary

Unit 1 of the minimal native desktop app (HACK-2214): a design document
only. No code, no change outside `apps/desktop/`.

`apps/desktop/DESIGN.md` covers:
1. Every view and its states as tables: project list, detail header per
project state, Services, Logs, Env (names only), Activity, status bar.
2. First-run onboarding: checks in order, which need admin rights,
system prompt vs terminal hand-off (recommendation: terminal hand-off on
both platforms for v0), what a skipped step shows.
3. Doctor cards grouped by area, machine findings shown once.
4. Upgrades over `hack update --check` and the #790 `hack upgrade
check|snapshot|rollback` contract, with the daemon restart (HACK-2216)
and failure states.
5. Daemon states with one action each.
6. Always-on (HACK-2213): states and the exact fields and commands
needed from the CLI side.
7. Data contract per screen (command or endpoint, interval, timeout) and
12 numbered CLI/daemon requests, each one PR.
8. Scope: v0, v0.1, later, never.
9. Distribution: Homebrew cask beside the formula, signing and
notarization steps (nothing set up), no auto-update, Linux tarball,
minimum OS versions.

Diagrams are Mermaid sources in `apps/desktop/diagrams/*.mmd`, rendered
to SVG with the pinned Pele renderer and embedded in the doc.

```mermaid
flowchart LR
  App["Hack desktop app"] -->|"hack … --json (as the user)"| CLI["hack CLI"]
  App -->|"GET /v1/status, /v1/projects"| D["hackd socket"]
  CLI --> D
  CLI --> E["Engine"]
  App -.->|"admin steps"| T["Terminal"]
```

## Validation

- `bun run privacy:check`: ok.
- All six diagrams render with `pele-render` (no errors).
- The data contract was read from source on this branch
(`src/daemon/server.ts`, `src/commands/*`, PR #790); no `--json` command
was run against a live daemon or Docker.

## Not in this PR

The crates (`apps/desktop/core`, `apps/desktop/app`) follow in a
separate PR (`feat/desktop-app-core`).

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

<!-- codesmith:footer -->
---
<a
href="https://backend.blacksmith.sh/track/run-proof?expires=1797205571&installation_model_id=17053&pr_number=800&ref=codesmith_pr_footer&repository=hack-dance%2Fhack&return_to=https%3A%2F%2Fgithub.com%2Fhack-dance%2Fhack%2Fpull%2F800&variant=run_proof&signature=21c30054e774d8aaffbe431ce236d2ee8497fdacef2aa734bef4f98a9968595a"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-run-proof-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-run-proof-light.svg"><img
alt="Run Proof"
src="https://pr-comments-assets.blacksmith.sh/codesmith/proof-run-proof-dark-3x.png"
width="102" height="28"></picture></a>
<sup><a
href="https://app.blacksmith.sh/hack-dance/codesmith/hack/pr/800?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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi
roodboi merged commit fa3f03d into next Nov 14, 2026
29 checks passed
@roodboi
roodboi deleted the claude/hack-2189-upgrade-check branch November 14, 2026 02:11
roodboi added a commit that referenced this pull request Nov 14, 2026
The next.17 section omitted the tmux breaking-change warning that
already existed at the page tail. Move it into the release section and
put its v4 stop procedure before the v5 trial. The env breaking entry
was already correct; keep its scoped single-key/terminal/stdin migration
guidance.

Audit all **71 commits** from `v5.0.0-next.16` (`b21c3b1898`) through
candidate **`5002cf87bb50686b17ceae8a4c835f524ee1b3a5`** against merged
code and PR text. Add missing upgrade-assistant, activity,
observe/plan/version, interactive exec, recovery, background-command and
host/secret-custody notes. Preserve the complete native env-file
behavior table and mark the four new JSON contracts **shape may change
before 5.0.0**, with the planned HACK-2238 envelope migration.

Correct stale native interactive-exec/network/metadata/Ctrl-C claims and
the short upgrade assistant wording. Credit the publisher-shutdown fix
to #719, already in next.16; keep next.17 lifecycle-command retirement
separate. Retake a private stopped-state backup after v4 stops zellij,
keeping the first copy too. Keep next.16 warnings scoped to that bundle.
Keep original-key backups before older Doctor migrations and distinguish
file rollback from runtime/data recovery.

Only eight Markdown files change. Runtime, tests, workflows, generated
CLI reference, diagrams, immutable snapshots and the site pin are
unchanged. No diagram changed, so a new visual would add nothing. The
site re-pin remains a separate release follow-up.

Merge timing: respect the lead's candidate freeze; intended for the
batch immediately after next.17 publication and the site re-pin. The
moved tmux breaking-change notice is called out for the lead's pre-cut
decision. This PR does not merge or publish anything.

Validation at **`d95cfc554fabc4a0d70e3ba6caeb6746f63a198c`**:
- Bun 1.4.4, frozen dependencies. Docs prepare/check/typecheck/build and
site tests (2 passed, 25 assertions) pass. Production-mode HTTP smoke:
97 docs, 82 SVGs, 217 local links/anchors; task server stopped.
- All 50 Hack commands named anywhere in the notes exist in the current
generated CLI reference; 19 option contracts checked. CLI-reference
regeneration has no diff.
- All 71 commits have one row below, including the test commit and merge
for #786. All 70 source evidence paths exist at the fixed candidate.
Source release-note local links/anchors pass.
- Privacy and complete reachable-history Gitleaks pass (1,330 commits,
no findings). Persistent Claude Opus second-family review is CLEAN at
this exact head. Hosted run 43585059152 passed docs site, secret scan
and every planned Linux unit shard (4–8), plus the required test fan-in.
Typecheck/privacy/quality ran on shard 8. Focus had no selected tests.
Compiler, native core, runtime images/models, process-lifetime and
Docker execution were out of scope; a green Docker fan-in is not Docker
execution evidence. All bot comments were read; CLEAR at d95cfc5,
zero unresolved threads/change requests. Codex code/security reviews
found no issues.
- No real install, migration, daemon/DNS/trust change, Docker or native
VM run was performed for this documentation audit. Native acceptance and
the candidate's hosted release qualification remain separate.

## Candidate commit coverage

`entry present` means the behavior already had an entry within the
next.17 section (some now have fuller detail). `added` means missing
behavior or migration guidance was added there; #824 moves the existing
warning into that section. `not user-visible` means no shipped
CLI/runtime change: documentation, test/CI/repository housekeeping or
the internal desktop preview. Source links pin the candidate, rather
than a moving branch.

| Commit / PR | Coverage | Entry or reason | Audited source |
| --- | --- | --- | --- |
|
[70d328e](70d328e)
[#743](#743) | added | Host
setup: privileged/NSS safeguards |
[src/lib/linux-trust-setup.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/linux-trust-setup.ts)
|
|
[51f15c8](51f15c8)
[#746](#746) | not user-visible |
Docs-only upgrade checklist and site generation; no installed behavior |
[scripts/docs/prepare.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/docs/prepare.ts)
|
|
[c14f3f5](c14f3f5)
[#744](#744) | added | Native
setup JSON accounting |
[packages/runtime-core/src/provider/setup.rs](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/packages/runtime-core/src/provider/setup.rs)
|
|
[b0c6bcd](b0c6bcd)
[#705](#705) | added | Retained
background exec/run and cursor tools |
[src/commands/background-command-start.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/background-command-start.ts)
|
|
[c89f4fd](c89f4fd)
[#745](#745) | added | Linux
daemon query/home refusals |
[src/commands/daemon-systemd.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/daemon-systemd.ts)
|
|
[cce4b59](cce4b59)
[#752](#752) | not user-visible |
Site pin/snapshots and editorial pages only |
[scripts/docs/versions.json](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/docs/versions.json)
|
|
[56676fc](56676fc)
[#754](#754) | added | Setup
reuse/offline accounting; pin-watch changes are internal |
[packages/runtime-core/src/provider/setup.rs](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/packages/runtime-core/src/provider/setup.rs)
|
|
[7a496f1](7a496f1)
[#755](#755) | added | MCP
registered selectors and background tool guidance |
[src/mcp/server.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/mcp/server.ts)
|
|
[e7a7039](e7a7039)
[#747](#747) | entry present |
Native follow text shape; added session ends/restart recovery |
[src/backends/native-log-follow.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-log-follow.ts)
|
|
[87a6df5](87a6df5)
[#758](#758) | added | Compose
short one-shot startup |
[src/lib/compose-startup-state.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/compose-startup-state.ts)
|
|
[2afb5c4](2afb5c4)
[#761](#761) | added | Native
Hack networks and inert generated labels |
[src/lib/native-hack-compose-conventions.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/native-hack-compose-conventions.ts)
|
|
[342a585](342a585)
[#771](#771) | not user-visible |
Docs-only next.16 trial record |
[docs/versions/v5/guides/upgrade-from-v4.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/versions/v5/guides/upgrade-from-v4.md)
|
|
[6f0acf1](6f0acf1)
[#769](#769) | entry present |
Migration live-key/alias preservation |
[src/lib/project-env-config.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-env-config.ts)
|
|
[c96c444](c96c444)
[#756](#756) | added | Child key
filtering, fixed diagnostics, legacy ignore/YAML API refusals |
[src/lib/child-environment.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/child-environment.ts)
|
|
[e2c73c6](e2c73c6)
[#748](#748) | not user-visible |
Contributor CI selection/coalescing, outside product release notes |
[scripts/ci-change-scope.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/ci-change-scope.ts)
|
|
[ebf794e](ebf794e)
[#760](#760) | added |
Agent/output custody warning; runtime unchanged |
[src/agents/instruction-source.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/agents/instruction-source.ts)
|
|
[021b16b](021b16b)
[#763](#763) | added | One-pass
background redaction scan; no public benchmark claim |
[src/lib/background-command/output-scanner.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/background-command/output-scanner.ts)
|
|
[8564ec4](8564ec4)
[#765](#765) | added | Shared
preflight/start admission and detached failure reporting |
[src/backends/native-project-review.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-project-review.ts)
|
|
[0c0fba2](0c0fba2)
[#767](#767) | entry present |
Breaking env exposure/protect/stdin/scope/API guidance |
[src/commands/env.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/env.ts)
|
|
[9435da9](9435da9)
[#762](#762) | added | Local
Git/link refusals and no-follow dotenv |
[src/lib/project-env-config.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-env-config.ts)
|
|
[edf23ae](edf23ae)
[#753](#753) | added | Daemon
stop proves the live Hack home |
[src/commands/daemon-systemd.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/daemon-systemd.ts)
|
|
[573ceec](573ceec)
[#766](#766) | entry present |
Doctor layer/integrity error; added store/key write refusals |
[src/lib/secret-store.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/secret-store.ts)
|
|
[a0e9760](a0e9760)
[#757](#757) | added | Orphan
output truncation and final-sync evidence |
[src/lib/background-command/recovery.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/background-command/recovery.ts)
|
|
[10e1958](10e1958)
[#759](#759) | added | Private
lifecycle logs/FIFO and expiring mux payloads |
[src/lib/private-file.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/private-file.ts)
|
|
[bd899ee](bd899ee)
[#775](#775) | not user-visible |
Portable test fixtures only |
[tests/single-file-tar.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/single-file-tar.test.ts)
|
|
[80dae3d](80dae3d)
[#776](#776) | added | Candidate
missing-daemon diagnosis is optional/healthy |
[src/commands/doctor.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/doctor.ts)
|
|
[5b2a2d0](5b2a2d0)
[#772](#772) | added | Bounded
attaching-network retry; strict saved reads remain |
[src/lib/native-compose-ownership.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/native-compose-ownership.ts)
|
|
[ab29d49](ab29d49)
[#777](#777) | not user-visible |
CI scheduling only |
[docs/reference/ci-test-selection.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/reference/ci-test-selection.md)
|
|
[31339c7](31339c7)
[#770](#770) | entry present |
Native inferred readiness, 240 s, HTTP/service_healthy limits |
[packages/runtime-core/src/project/compose/service.rs](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/packages/runtime-core/src/project/compose/service.rs)
|
|
[156ee7f](156ee7f)
[#773](#773) | entry present |
Loopback proxy port/Doctor/open checks |
[src/lib/proxy-loopback.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/proxy-loopback.ts)
|
|
[a7ec3c7](a7ec3c7)
[#768](#768) | entry present |
Cleanup receipts/partial outcomes and tracked-file wording |
[src/lib/project-env-config.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-env-config.ts)
|
|
[ceb3e23](ceb3e23)
[#778](#778) | entry present |
Native bridge socket readiness |
[packages/runtime-core/src/provider/bridge.rs](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/packages/runtime-core/src/provider/bridge.rs)
|
|
[af91049](af91049)
[#783](#783) | entry present |
Pointer reconciliation after partial cleanup |
[src/commands/doctor.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/doctor.ts)
|
|
[0a0bfdb](0a0bfdb)
[#780](#780) | not user-visible |
Site-only Linux guide publication; no CLI behavior |
[scripts/docs/versions.json](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/docs/versions.json)
|
|
[d0cf767](d0cf767)
[#786](#786) | not user-visible |
Consent test wording only (both commits) |
[tests/env-migration-tracked-consent.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/env-migration-tracked-consent.test.ts)
|
|
[7e2e630](7e2e630)
[#786](#786) | not user-visible |
Consent test wording only (both commits) |
[tests/env-migration-tracked-consent.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/env-migration-tracked-consent.test.ts)
|
|
[91c311e](91c311e)
[#779](#779) | entry present |
Restore or reversible retire; missing store keys scoped to readers |
[src/lib/secret-store-retire.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/secret-store-retire.ts)
|
|
[6ab1ace](6ab1ace)
[#785](#785) | not user-visible |
Docs-only release/upgrade/env notes |
[docs/guides/v5-release-notes.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/guides/v5-release-notes.md)
|
|
[25bd63c](25bd63c)
[#764](#764) | entry present |
Doctor hygiene, local modes, ignored staging and live-input protection |
[src/lib/secrets-hygiene.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/secrets-hygiene.ts)
|
|
[d9df57c](d9df57c)
[#784](#784) | not user-visible |
Resolver/proxy and consent test corrections only |
[tests/project-open-loopback.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/project-open-loopback.test.ts)
|
|
[2c1f665](2c1f665)
[#791](#791) | added | Detached
failure names, open DNS hint, init placeholder; prune guidance |
[src/commands/project.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/project.ts)
|
|
[f6fe55f](f6fe55f)
[#787](#787) | not user-visible |
Docs-only retirement/hygiene follow-up |
[docs/guides/v5-release-notes.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/guides/v5-release-notes.md)
|
|
[7f9548c](7f9548c)
[#792](#792) | not user-visible |
Start-here docs, navigation and contributor rule only |
[docs/versions/v5/install.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/versions/v5/install.md)
|
|
[a0cfc73](a0cfc73)
[#793](#793) | added | Verified
updater publication, backups and reversible candidate removal |
[src/lib/self-update.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/self-update.ts)
|
|
[292d312](292d312)
[#782](#782) | added | Lock-free
native ps and proved failed-start down |
[src/backends/native-project-observe.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-project-observe.ts)
|
|
[b1e123b](b1e123b)
[#796](#796) | added | Doctor
reader count/worktree grouping in store recovery |
[src/lib/doctor-secret-integrity.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/doctor-secret-integrity.ts)
|
|
[286d00f](286d00f)
[#789](#789) | added | Foreground
command-before-wrapper retirement |
[src/lib/project-lifecycle-processes.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-lifecycle-processes.ts)
|
|
[bb6a7d1](bb6a7d1)
[#788](#788) | not user-visible |
Linux qualification runbook only |
[docs/guides/linux-host-qualification.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/guides/linux-host-qualification.md)
|
|
[a262d42](a262d42)
[#800](#800) | not user-visible |
Internal desktop design; outside supported CLI release scope |
[apps/desktop/DESIGN.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/apps/desktop/DESIGN.md)
|
|
[6b161ba](6b161ba)
[#774](#774) | added | Native
interactive exec, finite fallback and receipt recovery |
[src/backends/native-project-exec.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-project-exec.ts)
|
|
[1249272](1249272)
[#799](#799) | not user-visible |
Portable output-contract CI selection only |
[scripts/test-shard-plan.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/test-shard-plan.ts)
|
|
[fa3f03d](fa3f03d)
[#790](#790) | added | Read-only
upgrade check, snapshot and rollback |
[src/commands/upgrade.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/upgrade.ts)
|
|
[c3b79bc](c3b79bc)
[#807](#807) | added |
Observe-only global status and unknown evidence |
[src/commands/global-status-observe.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/global-status-observe.ts)
|
|
[29be043](29be043)
[#812](#812) | added | Read-only
local CA trust diagnosis |
[src/lib/ca-trust-status.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/ca-trust-status.ts)
|
|
[f0102fd](f0102fd)
[#814](#814) | not user-visible |
Prepared-base verifier test fixture only |
[tests/qualify-provider-pin.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/qualify-provider-pin.test.ts)
|
|
[b4ebfec](b4ebfec)
[#815](#815) | added | Compact
upgrade plan, snapshot-backed apply/resume |
[src/lib/upgrade-apply.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/upgrade-apply.ts)
|
|
[c44b266](c44b266)
[#801](#801) | not user-visible |
Internal optional desktop preview; not shipped CLI behavior |
[apps/desktop/Cargo.toml](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/apps/desktop/Cargo.toml)
|
|
[56d1d0f](56d1d0f)
[#804](#804) | added | Automatic
mux sweep preserves live/retired config inputs |
[src/lib/live-inputs.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/live-inputs.ts)
|
|
[1958363](1958363)
[#805](#805) | added | MCP
timedOut result and Codex installer timeout |
[src/mcp/server.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/mcp/server.ts)
|
|
[e6207e8](e6207e8)
[#810](#810) | added | Lifecycle
activity writes including handler refusals; activity JSON |
[src/commands/lifecycle-activity.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/lifecycle-activity.ts)
|
|
[23fe08f](23fe08f)
[#825](#825) | added | Read-only
batch review and permission-only key repair |
[src/lib/upgrade-review.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/upgrade-review.ts)
|
|
[cab596c](cab596c)
[#781](#781) | entry present |
Full native env-file behavior table; added support/precedence summary |
[src/backends/native-compose-environment.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-compose-environment.ts)
|
|
[1e84684](1e84684)
[#798](#798) | not user-visible |
Rust test process isolation/nextest; internal qualification only |
[scripts/test-runtime-core.sh](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/test-runtime-core.sh)
|
|
[518af29](518af29)
[#817](#817) | not user-visible |
Remove accidental empty checkout files; no installed CLI change |
[.gitignore](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/.gitignore)
|
|
[07192f7](07192f7)
[#824](#824) | added | BREAKING
warning moved from page tail into next.17; v4 stop procedure retained |
[src/mux/mux-config.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/mux/mux-config.ts)
|
|
[fe09a5b](fe09a5b)
[#816](#816) | added | Read-only
install/trust admin plans and exits |
[src/commands/global.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/global.ts)
|
|
[8977676](8977676)
[#827](#827) | added |
Registry-preserving project observe, runtime opt-in |
[src/commands/projects.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/projects.ts)
|
|
[5965b23](5965b23)
[#828](#828) | added | Running
CLI version identity JSON |
[src/lib/version-report.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/version-report.ts)
|
|
[2922210](2922210)
[#830](#830) | added | Upgrade
safe/admin/manual action kinds |
[src/lib/upgrade-action-kind.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/upgrade-action-kind.ts)
|
|
[51b55c0](51b55c0)
[#820](#820) | not user-visible |
Archive/plan/shared-helper verification fixtures only |
[tests/python-free-download.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/python-free-download.test.ts)
|
|
[5002cf8](5002cf8)
[#809](#809) | added | Retirement
read retry, second Ctrl-C and native cancellation cleanup |
[src/lib/project-lifecycle-processes.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-lifecycle-processes.ts)
|

---------

Co-authored-by: Kit <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