Skip to content

PLAT-001: [EPIC] OS-agnostic from-zero bootstrap — macOS bring-up as the forcing function #2013

Open

Description

@mlorentedev

Why

A new macOS machine (macOS 27, arm64, Homebrew preinstalled) has to reach the same working state as the Linux and Windows boxes. We use it to make bootstrap OS-agnostic, from zero, and idempotent, so that no new machine on any OS fails the same way twice.

macOS support is listed as still open in #1843 and has no spec. DOCS-001 (#127) turned the claim into "planned". This epic closes that open item. It does not run beside #1843 and #131: wherever one of their rows already covers the work, this epic depends on that row instead of copying it.

We measured this on the Mac on 2026-10-05, using four read-only audits and one live dotf doctor run (52 pass / 37 fail / 22 warn). They produced 53 findings; the full ledger is the first comment. Three things stand out:

  1. Several failures already hit Linux arm64, not just macOS. Four of them are silent:
    • setup-linux.sh downloads age/eza/jq/gh/shellcheck from hard-coded linux-amd64 URLs. On any other OS or arch, setup logs SUCCESS and leaves ELF binaries that cannot run and that shadow working copies (~/.local/bin comes first in PATH). A broken eza breaks ls.
    • compile-harness.sh uses mapfile (bash ≥4, which violates ADR-003). On bash 3.2 no skills, agents or rules deploy for any agent, and setup turns that failure into a warning.
    • dotf tools install runs before uv, so the hive uv-tool entry fails on the first run on every OS.
    • On first run, setup overwrites the user's ~/.zshrc with no backup.
  2. The code branches on "is it Windows?" instead of declaring what each OS needs. Darwin inherits every Linux assumption:
    • doctor expects ~/Applications/<tool>-<ver>, *_HOME, and an apt install hint;
    • dotf update runs setup-linux.sh;
    • the scheduler is systemd-only;
    • env-contract.json has no darwin key.
  3. The darwin binary is built but never run in CI (ubuntu and windows only).

Constraints

  • The bootstrap stays in shell (ADR-020 C7). New logic goes into dotf, not into setup scripts (ADR-041). There will be no setup-macos.sh fork: Linux and macOS share ~90% of the path. The doctor's drift map is also regex-derived from one script's text, so a second script would be invisible to the ADR-012 assertion.
  • ADR-003: every shell file runs on bash 3.2 and zsh. "Require Homebrew bash" is not an acceptable fix.
  • Every fix lands together with the guard that would have caught it (test, doctor check or CI). If a step can fail while reporting success or "nothing found", it gets a preflight or post-condition probe (.claude/CLAUDE.md).
  • Windows must not regress. The Windows CI leg stays required, and each wave that touches shared paths books a Windows check.
  • Pins are floors (ADR-036). Auto-merge is forbidden. Each row is one PR under ~300 LOC.
  • One spec per track, opened only when its wave starts. ADR-043 allows 10 work-in-progress specs and there are 16 active today.
  • Setup is not run on the Mac until Wave 1 is green. Running it today would leave the machine worse off. Each Mac run after that feeds new findings into the ledger.

Decisions

# Decision State
D1 One POSIX bootstrap. The only OS-specific step is the class-3 package-manager step (apt / brew / winget). Everything else is declarative in dotf. proposed
D2 OS is a declared dimension. The platforms vocabulary from #1843 B1 (absent = all; unlisted = skipped, not failed) covers packages.json, ai/deploy.json, env-contract.json (gains a darwin key) and doctor checks. Code stops treating "anything but Windows" as Linux. proposed
D3 Toolchains and pinned CLIs via mise (widened by D7); dotf stays orchestrator and verifier. Homebrew can't pin; Nix, SDKMAN and asdf have no native Windows. mise ships from the catalog as a checksummed github-release, not from brew. versions.conf stays the source of truth: dotf renders the global mise config and runs mise install --locked. The committed mise.lock covers linux-x64, macos-arm64 and windows-x64. Java is temurin-21 (vendor always named), Maven comes from aqua, Python from mise; uv keeps venvs and uv tool. JAVA_HOME comes from mise activate, plus a static env file for GUI and IDE use. Gates: (a) a spike proves Java-via-mise on Windows before Windows switches; (b) legacy ~/Applications/<tool>-<ver> installs are reported as leftovers and removed only after the mise version passes. Rollout uses the Mac as canary. accepted (owner, 2026-10-05)
D4 On macOS, Homebrew is for class-3 tools, casks/GUI apps and system libraries only, never toolchains. It is driven from packages.json (D8); a Brewfile, if one exists, is generated from it and never kept by hand. decided (owner, 2026-10-07)
D5 One scheduling table emits systemd, launchd or a Scheduled Task (#1843 B8, dotf schedule). No hand-written launchd blocks. Whether to port or retire the hive-upgrade timer is decided against the ADR-036 floor amendment (2026-10-03). proposed
D6 Orca is replaced by herdr on every OS, completely (owner, 2026-10-05): every role Orca has today (cockpit, parallel agents, worktrees and their setup hooks, keybindings, agent hooks, the remote CDE on ace2) moves to herdr, and Orca is uninstalled from all three machines. Diffs are reviewed with lazygit. This reverses the 2026-10-04 HOLD in #1586 for the cockpit only. In ADR-032, herdr replaces Orca in the level-2 row and remains a deletable backend, not the substrate (C7 unchanged). Defaults: the deployed config sets a prefix that does not collide with tmux's ctrl+b; agent integrations are a declared dotf deploy step, not a hand-run herdr integration install; the ADR-032 agent.Backend on herdr is out of scope (the v0.9.3 API has no pane exit code). accepted (owner)
D7 Pinned CLIs go through mise too (aqua backend, with checksums and attestations): age, zoxide, shellcheck, jq, bats, golangci-lint, sops, direnv, fzf, herdr and lazygit. packages.json keeps npm, uv-tool and mise itself. Gap: eza has no aqua entry and publishes no macOS asset (only vfox/cargo backends); W2 verifies vfox:jdx/vfox-eza on all three OSes, and if it fails eza goes class-3 on darwin. accepted (owner, 2026-10-05)
D8 One package SSOT, tiered by install mechanism, not one list per OS. mise (versions.conf) for toolchains and pinned CLIs, identical on every OS; packages.json for npm, uv-tool and github-release; and, extending D7, packages.json also carries the class-3 packages as source.type: system entries that name the package per manager (apt, brew, winget). A manager with no name skips the entry on that OS. dotf tools sync drives the managers idempotently and doctor verifies the result. No hand-kept package list survives in setup-linux.sh, setup-windows.ps1 or a Brewfile. decided (owner, 2026-10-07)
D11 SSH trust for the whole mesh is a CA, in two phases (owner, 2026-10-13; #2373). Scope: every sshd on the mesh, about 10 hosts (homelab, vps and gcp1, workstations); principals manu and deployer. Phase 1 in dotfiles: an offline root in the secrets plane signs host certificates and per-machine user certificates (30 to 90 days) through a dotf converge step, with a KRL; it retires the shared id_ed25519, the pin file and Ansible's accept-new, and stays the break-glass path. Phase 2 in kubelab: step-ca as an online intermediate on the VPS, outside the cluster it protects, with Authelia as identity. No public SSH: 22 closes on vps and gcp1, with a terraform break-glass variable (kubelab#2224). accepted (owner, 2026-10-13)

Tracks

Each row is one PR. "After" lists hard dependencies, and "Ledger" lists the findings the row closes.

W — Stop the silent failures (pure bug fixes that also hit Linux; no macOS framing needed)

Id PR After Ledger
W1 feat(tools): post-install exec probe. After every catalog install, run <tool> --version and fail the step on non-zero. — F-030 guard
W2 refactor(setup): setup calls dotf tools sync (T2) and the five hard-coded linux-amd64 URL blocks (age, eza, jq, gh, shellcheck) are deleted. gh stays class-3. Old ~/.local/bin/<tool> copies are reported by doctor as leftovers that may shadow mise's. T1, T2 F-030, F-014, F-006
W2b feat(env): the mise PATH hook on all three OSes: shims dir on PATH for non-interactive callers, mise activate in the bash/zsh/pwsh rc files. Without it dotf tools sync succeeds and nothing is on PATH, a new false success. Doctor checks mise doctor reports the shims on PATH. T2 —
W3 fix(harness): remove mapfile from compile-harness.sh (4 sites); add a lint guard for bash-4-only constructs; run bats under bash 3.2. — F-031, F-017
W4 fix(setup): a failure in compile-harness or deploy makes setup fail loudly, with a preflight. W3 F-048
W5 fix(setup): install order puts managers (uv, node) before dotf tools install. — F-047, F-029 (part)
W6 fix(config): .gitconfig resolves the gh helper via PATH; .bashrc prepends to PATH and never resets it; .zshrc guards oh-my-zsh. — F-032, F-033, F-034
W7 fix(setup): back up any existing unmanaged rc file before the first overwrite. — F-035
W8 fix(setup): replace stat -c, chmod --reference and a bare sha256sum (shasum fallback) with portable forms. — F-037, F-038, F-011
W9 fix(secrets): render opencode.jsonc with mode 0600; doctor checks the mode. — F-050
W10 #2063 ci: macOS leg running go test, bats (bash 3.2 and zsh) and GOOS=darwin go vet. Non-required until green, then required. This is #1843 E10, pulled forward. Three layers, one suite: OS behaviour that can be injected (doctor's System.GOOS, deploy's goos) is tested on Linux; the real macOS runs only the tests tagged os-sensitive (bats --filter-tags, bash 3.2 and zsh), the full suite on main; from-zero end to end is X1. Includes the Mac bats triage: every failure classified (script bug, non-hermetic test, Linux-only) and resolved before the leg is switched on. — F-012

P — Declare the OS (the agnostic contract)

Id PR After Ledger
P0 docs(adr): OS-agnostic bootstrap. Covers tier-0 (shell: package manager + dotf) vs tier-1 (dotf converge), records D1, D2, D4, D5 and D8, extends ADR-036 class 3 with brew, and refreshes the stale ADR-012/036 text. — F-046, F-019
P1 = #1843 B1 (platforms selector on deploy entries). P0 —
P2 feat(contract): add a darwin key to env-contract.json. Doctor reports the resolved OS rather than silently mapping darwin to linux. P0 F-001
P3 refactor(doctor): checks declare the platforms they apply to; remediation hints come from the catalog per OS and package manager; path comparison folds case on darwin; the git floor is per OS. P1, P2 F-002, F-018, F-025, F-043
P4 feat(bootstrap): OS-dispatching entrypoint (detect OS and arch, run class-3 managers, install dotf, hand off). install.sh and dotf update dispatch through it. Covers #366. P0 F-020, F-036, F-029
P5a feat(tools): dotf tools reads source.type: system (apt/brew/winget names, an OS with no name skips) and plans and installs through the OS manager. Reader first: today an unknown type is an error (install.go, "unsupported source type"), so the installed dotf would fail every sync if entries shipped in the same PR (#1814 class, same shape as B1). T2 F-006 (part)
P5b feat(tools): class-3 entries in packages.json (git, gh, tmux, xclip, parallel, and the darwin casks from the owner's app list). It deletes the hand-kept winget list in setup-windows.ps1 and the tmux/xclip install warnings in setup-linux.sh, and closes the cleanup half of #2057 (undeclared brew formulae). P5a, then the release carrying it as the DOTF_VERSION pin F-006 (part)
P6 fix(bootstrap): deploy the rc files and run dotf env generate before any agent is installed, so CLAUDE_CONFIG_DIR and the path vars exist on the first run. P4 F-005, F-052
P7 = #1843 B2 (home dotfiles as deploy entries, which retires the regex-over-setup drift map). P1 F-045
P8 feat(bootstrap): scripts/macos-defaults.sh, the opt-in analog of windows-defaults.ps1 (-WithDefaults), codifying the manual baseline in the vault runbook runbook-macos-workstation-setup (debloat, defaults, firewall, FileVault check). Idempotent: a second run reports 0 changes. P4 F-057
P9 #2016 feat(bootstrap): instructions-first deploy as the first step on any new machine: dotf harness mirror, compile-harness.sh --deploy and dotf harness bind write only agent config dirs (~/.claude, ~/.gemini, ~/.config/opencode, ~/.pi, ~/.copilot), never rc files or binaries. Doctor FAILs when an agent binary is present without its instruction file. W3 F-060
P10 feat(converge): the tools step installs the packages.json catalog on every OS (install, mise sync, install), so a Mac, where no setup runs, gets its brew, release, npm and uv tools; a manager nothing installs or a sudo password is reported, not failed. PLAT-001b AC12. Found by the 2026-10-10 ledger audit. P4 F-006, F-029, F-047 (part)

T — Toolchains and pinned CLIs (D3, D7 accepted)

Split by risk. T0–T2 land in Wave 1 on every OS, rendering CLIs only. Those are ~/.local/bin binaries with no legacy layout to collide with. Java, Go, Python, Maven and Node enter the rendered config only in Wave 3. Rollout for toolchains: the Mac is the canary. T3, T4 and T6 land on the Mac first. It has no legacy ~/Applications/<tool>-<ver> installs, so nothing can break and no leftover handling is needed yet. Linux and Windows move (T6) only after the Mac has run dotf doctor clean for a while.

Id PR After Ledger
T0 docs(adr): toolchains and pinned CLIs via mise (amends ADR-036; records D3 and D7). Also archives DX-007-orca-cli-bootstrap as abandoned (D6), which frees a WIP slot. D3, D7 F-003
T1 feat(tools): add mise as a checksummed github-release catalog entry. Pin mise itself in versions.conf and set min_version, because mise releases are date-versioned. T0, W1 —
T2 feat(tools): dotf tools sync renders the global mise config from versions.conf (Wave 1: CLIs only) and runs mise install --locked. mise.lock is committed and CI regenerates it for linux-x64, macos-arm64 and windows-x64. W1's post-condition carries over: every tool is exec-probed after install (mise which + --version), not trusted on mise's exit 0. T1 —
T3 refactor(doctor): check versions with mise ls --json plus exec probes; replace the *_HOME and ~/Applications checks; report legacy installs as leftovers. T2 F-003, F-041
T4 refactor(shell): mise activate (bash, zsh, pwsh) replaces the APPS_HOME/JAVA_HOME block; dotf writes a static env file for GUI and IDE use. T2 F-041
T4b refactor(shell): Node via mise retires .zsh/nvm.zsh and the nvm wiring (bash, zsh). T4 —
T5 spike(windows): Java via mise on Windows. This gates T6's Windows half. Fallback if it fails: the JDK stays a winget class-3 install on Windows, and mise handles everything else, so T5 cannot block the whole track. T2 —
T6 chore: migrate the existing machines; doctor --fix removes leftovers only after T3 passes. T3, T5 —

S — Services and OS integration

Id PR After Ledger
S1 = #1843 B8 (dotf schedule), with a launchd backend as an explicit requirement. Moves vault maintenance from crontab to dotf vault maintain. P1 F-010, F-026, F-039, F-044
S2 Revised 2026-10-13 (owner: "En hive"). hive owns the supervisor: hive service install gains a launchd backend (hive#488, PR hive#489; verified on the Mac). Dotfiles half, after a hive release carries it: a hive-service converge step that runs hive service install on every OS (replaces the setup-linux.sh block); the darwin remedy in checks_hive_daemon.go (it tells the user to run hive serve by hand); the X1 ratchet entry darwin the hive daemon is down retired or re-owned in the same PR, depending on whether bootstrap gui/<uid> works on a GitHub macOS runner. Worker credential on macOS: #2351. hive release F-051
S3 feat(env): a launchd agent runs launchctl setenv so GUI-launched apps see VAULT_PATH and the other path vars. S1 F-021
S4 feat(cli): notifier abstraction (notify-send / osascript / Windows); darwin process probe for worktree sweep. — F-022, F-023
S5 Verify first, then fix: Orca paths and process detection on macOS, and agy's invalid mcp_config.json. — F-024, F-008
S6 fix(config): tmux clipboard via OSC52 or a detected bridge; terminfo; terraform completion and the obsidian alias resolved via PATH or OS. — F-040, F-042
S7 feat(bootstrap): SSH key provisioning on a fresh machine (restore from the age/Bitwarden escrow); doctor check. W2 F-007
S8 = #1843 B10 (MCP manifest rendered per tool). — F-049
S9 docs: README platform table, the stale ai-tools-setup.md runbook, the retired codex payload, and the vault context drift. P4 F-016, F-053
S10 fix(deploy): first deploy over configs that already exist backs them up instead of reporting endless drift. P1 F-009
S11 feat(ssh): D11 phase 1. ADR first, then the dotf signing step, HostCertificate and TrustedUserCAKeys in the workstation-target drop-in, @cert-authority *.kubelab.internal replacing the pins, per-machine keys (S7 becomes "generate here and sign"), KRL through converge. Closes #2373 and kubelab#2223; phase 2 opens as a kubelab ticket. S7, PLAT-001b pin PR #2373

H — Orca → herdr (D6; supersedes the #1586 HOLD, #1586 stays the history)

The Mac is the canary again: it has neither Orca nor herdr, so H1–H5 land here first with nothing to retire. Retirement (R) starts only after gate V is green on all three OSes.

Id PR After Notes
H1 feat(tools): herdr through mise (aqua:herdrdev/herdr), pinned at 0.9.3; checksums and attestations verified per target. T2 linux and macOS assets carry build attestations; the Windows zip does not
H2 spike(windows): does mise install herdr keep conpty/ next to herdr.exe? Fallback if not: on Windows only, herdr's own install.ps1 as class-3, so H2 cannot block the track. H1
H3 feat(doctor): herdr pin-drift check (linux x64/arm64, darwin arm64, windows x64). H1, H2 0.x churn: set update.version_check=false
H4 feat(deploy): ai/herdr/config.toml (non-colliding prefix, the ported Orca tab keys, [worktrees] directory, pane_history=false, update checks off), plus HERDR_CONFIG_PATH in env-contract.json so one dst works on every OS (Windows default is %APPDATA%). This is the first worked example of D2. H3, P2
H5 feat(doctor): herdr checks (binary, config deployed, server/socket reachable). H3
H6 feat(deploy): herdr agent integrations (claude, opencode, pi, copilot, agy) as declared deploy steps; they coexist with harness bind, which never touches foreign hooks. H4
H7 Port the orca.yaml worktree setup hooks (mise install, deps install, .env.local copy, compose down) as a dotf worktree setup step bound in herdr (keybinding or plugin). The owner uses them daily. H4
H8 feat(herdr): ace2 as a remote machine (herdr machine add), plus a runbook that supersedes the vault's Orca CDE runbook. H4
H9 feat(tools): lazygit pinned through mise, plus a herdr keybinding that opens it in the active worktree. It replaces Orca's diff viewer. T2, H4
V Gate, no code: herdr in daily use on Linux, Windows and macOS (including H7 worktree hooks, H8 ace2 and H9 lazygit), dotf doctor green on each, recorded here. H3–H9
R0 Per machine, in this order: remove the Orca hooks (~/.claude/settings.json hook groups, ~/.copilot/hooks/orca.json, ~/.pi/agent/extensions/orca-*.ts, the agy hooks.json orca-status block), check that every agent still starts, and only then uninstall the Orca binary. Doctor gains an "Orca leftovers" warning with --fix guidance. Reversing the order leaves every agent hitting failing hooks. V
R1 Retire the orca-keybindings deploy entry, ai/orca/, the ORCA.md manifest target (drop it, don't replace it) and initrepo/templates/orca.yaml (a user-facing dotf init change), with their tests. R0
R2 Retire cli/internal/orca/, cmd/orca.go, the deployOrcaHooks call, checks_orca.go and their tests. R0
R3 Clean up setup and bats; per-OS uninstall runbook (AppImage/.deb, Windows NSIS, brew cask). R1, R2
R4 Docs: ADR-032 §2 amendment (herdr in the level-2 row, still deletable), mark old lessons superseded, close #462/#899/#436/#942/#1748, archive DX-007. Vault: archive the Orca CDE runbook and update MEMORY.md and session-protocol. R3

X — Proof

Id PR After
X1 ci: end-to-end from-zero job on clean macos-latest and ubuntu-latest runners: bootstrap, then dotf doctor exits 0, then a second run reports 0 changes. It asserts the first run converged before it checks idempotence (lesson 328). Runs on PRs to main only, because macOS minutes cost 10×. W*, P4
X2 ci: fail on any linux-amd64, x86_64-unknown-linux or linux_amd64 literal outside packages.json. Extends #1843 C2. W2
X3 Windows parity run on the work laptop: setup, doctor and idempotence. T5, P4
X4 ci(test-windows): run dotf doctor --scope machine and drop the fabricated identity (vault, age key, Bitwarden stand-ins) its full doctor needs today. Same reading as X1 since D10 (#2337). D10

Waves

  1. Wave 1: W1 to W10, plus T0, T1 and T2 (CLIs only). Plain bug fixes that also protect Linux arm64; W2 needs T1 and T2.
  2. Wave 2: P0 to P7, plus the first real bootstrap of the Mac. New findings from that run go into the ledger.
  3. Wave 3: T0 to T6 (needs D3), together with S1 to S3 and H1 to H6 (needs D7).
  4. Wave 4: the rest of S (S11, the SSH CA, after S7 and the PLAT-001b pin PR), gate V, then R0 to R4 per machine, then X1 to X3. X1 becomes a required check.

Owner actions, when their wave arrives

  • Provide the list of apps and casks for the Brewfile (P5).
  • Hand-carry the age identity key to the Mac (~/.config/age/key.txt, mode 0600) and log in to Bitwarden. Secrets, SSH and hive depend on it.
  • Accept the cost of the macOS runner for X1.
  • Book a Windows session for T5 and X3.

Acceptance

  • On a clean macos-latest runner and a clean ubuntu-latest runner, one bootstrap run leaves dotf doctor exiting 0, and a second run reports 0 changes, with first-run convergence asserted before idempotence is checked.
  • No catalog install can report success for a binary that does not execute (W1 probe).
  • No hard-coded OS/arch release literal exists outside packages.json (X2).
  • setup-linux.sh and setup-windows.ps1 shrink measurably, tracked per PR.
  • Every ledger ID is closed with its guard, or explicitly deferred with a reason.
  • This Mac runs the same daily workflow as the Linux and Windows machines: shell, agents with skills and hooks, MCP and hive, secrets, vault.

Activity

  1. mlorentedev commented on Oct 6, 2026

    @mlorentedev
    OwnerAuthor

    Findings ledger (measured on the macOS box, 2026-10-05)

    Each row closes only with its guard, or is explicitly deferred. New findings from each Mac run are appended here.

    Machine: macOS 27.0.1 arm64, Homebrew preinstalled, dotf 0.64.0 (release binary).
    Rule: every finding gets root cause + agnostic fix + regression guard (test/doctor/CI) so it
    never fails again on ANY new machine.

    ID Finding Layer Root cause Agnostic fix Guard Status
    F-001 env-contract.json has only linux/windows defaults, no darwin contract OS keys enumerate 2 OSes posix default (linux+darwin) with per-OS override, or explicit darwin test: every contract var resolves for GOOS∈{linux,darwin,windows} closed: #2284, env.ForOS (darwin reads its own key, else linux) / TestCheckContractOnDarwin, TestTheRealContractKeysByKnownOSAndCopiesNothing
    F-002 doctor hint sudo apt install -y tmux on macOS cli/doctor hardcoded apt in remediation text remediation derived from catalog per OS/pkg-manager test: no apt string in hints when GOOS=darwin closed: #2048, tmuxInstall answers brew on darwin / TestCheckTmux_TheRemedyNamesThisOSPackageManager
    F-003 Versioned toolchains expected as tarballs in ~/Applications/<tool>-<ver> (JAVA/MAVEN/PYTHON/GO/MINIKUBE_HOME) versions.conf/doctor Linux tarball model assumed decision pending: brew-pinned vs tarball vs mise/asdf; doctor checks version, not dir layout doctor version check by --version, not path needs decision
    F-004 timeout missing on macOS (GNU coreutils) scripts/tests GNU userland assumed audit uses; portable wrapper or require coreutils in catalog shellcheck/bats on macOS runner closed (moot): no timeout call in any script, test, hook or Go exec, now or at the 2026-10-05 snapshot
    F-005 dotf env generate ran but paths.sh not sourced (vars unset in shell) bootstrap order rc files not yet deployed bootstrap must deploy rc before/with env generate; idempotent doctor already flags partial: zsh fixed (#2043, #2245); .bashrc deploys only via setup, so bash on a Mac still misses it — P7b
    F-006 Core tools absent: go uv shellcheck bats tmux fzf eza zoxide direnv wget docker kubectl age bw opencode catalog no darwin install path / no brew channel catalog-driven install with brew channel on darwin dotf tools install --check in CI matrix partial: brew channel + mise pins landed; converge's tools step installs the packages.json catalog on every OS and reaches what it places whatever the caller's PATH (#2289). Go, kubectl, node have no darwin path; the Mac converges once 0.67.0 ships
    F-007 No ~/.ssh — ssh/config deploy + key provisioning undefined for a fresh machine bootstrap — define key bootstrap (generate/restore from Bitwarden) doctor partial: ssh config + pubkey deployed (#2051). S7 decided 2026-10-10: one key per machine, registered on GitHub and in kubelab's authorized list; the age identity stays offline-rooted (ADR-028 §4)
    F-008 Antigravity master ~/.gemini/config/mcp_config.json invalid JSON ai/agy (to investigate) — doctor already flags not reproduced 2026-10-10: the file parses; it was rewritten 2026-10-08 00:07 (likely that day's agy deploy), so the invalid copy was not ours to keep. Guard: doctor's existing check
    F-009 Deploy drift on pre-existing configs (claude settings, pi, agy, orca) — tools installed before dotfiles ai/deploy first-run ordering dotf deploy idempotent, back up foreign files doctor closed: one-time .pre-dotf backup for every deploy entry incl. merges (#2043); configs-deploy runs it on darwin (#2245)
    F-010 doctor "hive backend" skips: "no systemctl" — supervision only systemd/Windows; systemd/ units (selfupdate, hive-upgrade) have no launchd twin cli/hive no launchd backend service abstraction: systemd / launchd / Windows scheduled task test per GOOS open
    F-011 install-dotf.sh:155 uses sha256sum — present at /sbin on macOS 27 (measured), absent on older macOS → add shasum -a 256 fallback bootstrap GNU userland portable checksum helper in utils.sh bats on macOS runner closed: _dotf_sha256 falls back to shasum -a 256 (#2145, scripts/install-dotf.sh:146)
    F-012 goreleaser ships darwin binaries but CI never tests darwin (= #1843 E10) CI matrix = ubuntu+windows add macos-latest leg (go test + bats + setup idempotence) CI required check ref #1843 E10
    F-013 setup-linux.sh:972 stat -c %s (BSD stat differs) setup GNU userland portable helper / move to dotf bats on macOS closed: same as F-037 (#2145)
    F-014 setup-linux.sh:221,250 hard-coded linux-amd64 for age/jq setup arch+OS hardcoded resolve via packages.json {os}/{goarch} catalog catalog test per GOOS/GOARCH closed: age/jq are mise pins; setup's own installers deleted (#2195); guard setup-linux-only-downloads.bats
    F-015 Vault clone needs pre-commit install (secret gate) — manual README step vault bootstrap manual step bootstrap/doctor --fix installs it doctor already FAILs closed: doctor --fix runs pre-commit install in the vault (#553, TestCheckVaultHooks_InstalledOnFix)
    F-016 Vault 10_projects/dotfiles/context.md + _index.md claim Linux/macOS/Windows; README says macOS "planned" docs drift reconcile once macOS lands check-doc claims open

    Tracking conventions found: tasks → bitácora GitHub Project #1 (ADR-018); OS-agnostic vehicle = CLI-091 epic #1843 Track B (declarative convergence, os selector in deploy entries, dotf converge); bootstrap stays shell (ADR-020 C7); memory rule: never grow setup scripts.

    From Go CLI audit

    ID Finding Layer Root cause Agnostic fix Guard Status
    F-017 Harness drift check runs bare bash → macOS /bin/bash 3.2 lacks mapfile → false FAIL every run (checks_deploy.go:732) cli/doctor bash ≥4 assumed; not via mem.ResolveBash route through ResolveBash + require bash≥4 in catalog/bootstrap (brew bash) or remove mapfile test: doctor on bash 3.2 closed: compile-harness runs on bash 3.2 (#2015), so the bare-bash drift check no longer false-FAILs; Mac doctor 2026-10-10 shows no harness FAIL
    F-018 Doctor gates "POSIX-only" checks with != windows; darwin inherits Linux expectations (*_HOME, ~/Applications/<tool>-<ver>, core tools list, tmux apt hint, Obsidian CLI) cli/doctor OS as binary windows/else capability/profile-driven checks declared in env-contract (per-OS required_on, darwin key) table test over GOOS closed: #2048 (darwin read as darwin; the APPS_HOME layout skips naming mise) and #2284 (contract sections name the OS) / TestAppsLayout_DarwinSkipsNamingMise, TestVersionedPaths_ASetHomeIsCheckedOnDarwinToo. A full dotf doctor on the Mac (2026-10-10) shows no linux-only expectation left
    F-019 No brew channel in catalog nor ADR-036 class 3 (only apt/winget) cli/tools, ADR 2-OS assumption extend ADR-036 + catalog; or delegate to toolchain manager (pending research) catalog test per GOOS needs decision
    F-020 dotf update off-Windows always runs setup-linux.sh (update.go:111-119) cli/update 2-OS assumption dispatch via OS-aware bootstrap entry (bootstrap.sh #366) / dotf converge test GOOS=darwin closed: dotf update converges (#2274); legacy-setup skips darwin
    F-021 env persist no-op off Windows; GUI/Dock apps on macOS don't see VAULT_PATH etc. cli/env no launchd env launchctl setenv via LaunchAgent at login (darwin backend) doctor check closed in code (#2256, launchd env-persist); owner's Dock check pending
    F-022 worktree sweep never reaps on darwin (no libproc impl, sweep_proc_other.go) cli/worktree Linux /proc only darwin impl via lsof/libproc test darwin closed: #2293, one ps+lsof table feeds Gate f and the done ancestor guard / sweep_proc_darwin_test.go, caller-walk tests shared via done_proc_unix_test.go
    F-023 vault maintain notifications only notify-send cli/vault Linux only notifier abstraction (osascript / terminal-notifier / BurntToast) unit test closed: #2292, notifyCommand per GOOS, osascript with the text as argv / TestNotifyCommand_ReachesEachOSNotifier
    F-024 Orca: ~/.config/orca + pgrep orca-linux.*AppImage — wrong on macOS (~/Library/Application Support, .app) cli/orca Linux paths per-OS app-dir resolver test partial: #2295, UserDataDir follows Electron's appData; running check reads Chromium's SingletonLock (linux, darwin) / TestUserDataDir_FollowsElectronAppData, TestRunning_ReadsTheSingletonLock. Orca absent on the Mac, so a live tune there is an owner check; the Windows lockfile check waits on #2291. Found: Orca's profile layout, #2294
    F-025 Path comparison case-sensitive except Windows; APFS default is case-insensitive cli/doctor — case-fold on darwin too unit test closed: #2099, sameDir compares the files (os.SameFile), not an OS-folded spelling / TestSamePath_OneDirectoryInAnotherCaseIsTheSame
    F-026 setup-linux.sh crontab block will install weekly vault cron on macOS (crontab exists) — unsupervised, different from Linux systemd path setup command -v as OS proxy single scheduler abstraction (systemd/launchd/schtasks) — #1843 B8 dotf schedule — open
    F-027 bw serve not supervised across reboots on any POSIX cli/secrets — optional launchd/systemd user unit via dotf schedule doctor info open
    F-028 dotf self-update (ADR-041 §7) not implemented — binary updates only via install-dotf.sh (#1843 A4) cli — covered by #1843 A4 — ref #1843
    F-029 Nothing provisions node/npm, uv, git, jq on a fresh machine before catalog npm/uv-tool entries can run bootstrap catalog presumes managers present bootstrap tier-0 (pkg manager: brew/apt/winget) installs managers; catalog then converges e2e from-zero CI partial: uv/jq via mise; converge runs the catalog install (#2289); nothing installs node/npm

    From setup-linux.sh audit

    ID Finding Layer Root cause Agnostic fix Guard Status
    F-030 FALSE-OK: age/eza/jq/gh/shellcheck downloaded as hardcoded linux-amd64 assets (221,237,250,263,355) → on darwin (and Linux arm64!) setup logs SUCCESS, binary is ELF, shadows brew in ~/.local/bin; broken eza breaks ls alias setup OS/arch hardcoded move to packages.json with {os}/{goarch} (ADR-036 D1 already says so) — #1843 C1 post-install exec probe (<tool> --version) in catalog installer partial: no linux-amd64 binary reaches a Mac/arm64 (#2166, #2195); eza/gh interim blocks remain in setup
    F-031 compile-harness.sh uses mapfile (761,1254,1405,1439) → fails on bash 3.2 → no skills deployed for any agent; violates ADR-003 harness bash4-ism remove mapfile (ADR-003) or port to Go (CLI-026) bats run under bash 3.2 in macOS CI; lint rule banning mapfile closed: mapfile removed, compile-harness runs on bash 3.2 (#2015)
    F-032 .gitconfig:5,8 credential helper !/usr/bin/gh (brew: /opt/homebrew/bin/gh) and empty helper = clears osxkeychain → HTTPS auth breaks config absolute path !gh auth git-credential (PATH lookup) doctor: credential helper resolves closed: repo .gitconfig deleted (#2208); doctor checks the helper
    F-033 .bashrc:137 resets PATH dropping /opt/homebrew/bin config hardcoded PATH prepend, never reset; brew shellenv in managed profile bats: PATH keeps preexisting entries closed: .bashrc only prepends PATH, guarded brew shellenv (#2283)
    F-034 .zshrc:13 sources oh-my-zsh unconditionally; nothing installs it config/bootstrap undeclared dependency guard + declare (catalog or drop) bats: zsh -i starts with no stderr closed: oh-my-zsh sourced only when present, else compinit (#2043)
    F-035 Deployed .zshrc lacks brew shellenv; first run overwrites user ~/.zshrc without backup setup no backup policy backup-on-first-overwrite; pkg-manager shellenv in managed profile per OS test closed: .pre-dotf backup on first overwrite for every deploy entry (#2043); brew shellenv in both rcs (#2043, #2283). .bashrc itself still deploys via setup until P7b
    F-036 install.sh:52 hardcodes exec ./setup-linux.sh bootstrap 2-OS OS-dispatching entrypoint (#366 bootstrap.sh) e2e open
    F-037 stat -c %s (972) → .claude.json truncation guard silently OFF on macOS setup GNU stat wc -c < bats on macOS closed: `stat -c …
    F-038 chmod --reference (828) → pi enabledModels never syncs, warns each run setup GNU chmod portable mode copy bats on macOS closed: chmod --reference removed (#2145)
    F-039 systemctl-gated blocks (autodeploy, hive drop-in, hive-upgrade) skip silently; environment.d written + SUCCESS logged though nothing reads it setup command -v as OS proxy dotf schedule emitting systemd/launchd/schtasks from one table (#1843 B8); hive-upgrade maybe retire (ADR-036 2026-10-03 floor amendment) doctor per-OS service check open
    F-040 tmux.conf pipes to xclip; tmux-256color terminfo may be missing on macOS config Linux clipboard clipboard via OSC52 or pbcopy/xclip/clip.exe detection — closed: #2290, copy-command picks pbcopy/wl-copy/xclip/clip.exe by display; default-terminal falls back to screen-256color without the terminfo entry / tests/tmux.bats (real tmux)
    F-041 APPS_HOME=$HOME/Applications collides with macOS's real ~/Applications; JAVA_HOME pointing to nothing breaks /usr/bin/java config/versions Linux tarball layout depends on toolchain decision (F-003) doctor needs decision
    F-042 complete -C /usr/bin/terraform, alias obsidian='obsidian --no-sandbox' hardcoded config absolute paths / Linux flag command -v resolution; OS-gated alias bats closed: zsh terraform + obsidian alias (#2043, #2182); .bashrc terraform (#2290) / tests/bashrc-guards.bats
    F-043 Git floor 2.55 (Windows rationale) WARNs on Apple git 2.54 versions floor applied to all OS per-OS floor or brew git — closed: #1350, the floor applies only on Windows (git-for-windows hooksPath defect) / TestCheckGitWindowsFloor
    F-044 Crontab entry points at checkout $CURRENT_DIR/scripts/..., not deployed copy; should target dotf vault maintain setup — via dotf schedule — open
    F-045 Doctor drift map derived by regex over setup-linux.sh text (TestHomeDeployMapCoversSetup) — couples truth to one script cli/test implicit manifest home dotfiles as deploy entries (#1843 B2) → doctor reads manifest — ref #1843 B2
    F-046 ADR-012 text stale (cites healthcheck.sh); ADR-036 catalog list ≠ packages.json docs drift update ADRs check-doc open

    Toolchain research (decision input for F-003/F-019/F-041)

    Recommendation: mise for toolchains + pinned CLIs (native Linux/macOS/Windows, mise.lock with per-platform URL+checksum, --locked, aqua backend w/ cosign/SLSA). dotf stays orchestrator+verifier: mise bootstrapped as github-release catalog entry (not brew — mise docs advise against); versions.conf stays SSOT → dotf renders global mise config + mise install --locked; committed mise.lock regenerated in CI for linux-x64,macos-arm64,windows-x64.
    Homebrew: casks/GUI/system libs only (no version pinning; Brewfile lock removed by design).
    Java: temurin-21 (always name vendor), lock pins exact build; Maven via aqua; Python via mise (python-build-standalone), uv stays for venvs/uv tool. Open risk: Java on Windows via mise undocumented → test before switching Windows.
    *_HOME: mise activate sets JAVA_HOME in shells; dotf writes static env for IDE/GUI (+ optional /Library/Java/JavaVirtualMachines symlink on mac).
    Doctor: compare mise ls --json vs versions.conf/lock, exec version probes, JAVA_HOME inside mise dir; legacy ~/Applications/- reported as leftovers, removed only after mise version passes.
    Rejected: Nix (no native Windows), SDKMAN (no native Windows), asdf (no Windows, no lock), brew for toolchains (no pinning).
    Sources: mise.jdx.dev/dev-tools/mise-lock.html, /installing-mise.html, /dev-tools/backends/aqua.html, /lang/java.html, docs.brew.sh/Brew-Bundle-and-Brewfile

    From harness / AI-layer audit

    ID Finding Layer Root cause Agnostic fix Guard Status
    F-047 Install order: dotf tools install (:293) runs before uv (:599) → hive uv-tool entry fails on first run on every OS; only MCP prerequisite rescues it bootstrap implicit ordering tier-0 managers (uv, node, mise) before catalog; catalog declares requires_manager and orders e2e from-zero test asserts first-run converges (lesson 328) partial: Linux order fixed (#2186); converge's tools step installs the catalog after mise on every OS (#2289). Guard pending: X1's from-zero run
    F-048 setup downgrades compile-harness failure to WARN (:519/537, :1531) → whole harness can silently not deploy setup fail-soft on a required step required steps fail loud; preflight (CLAUDE.md rule "reports absence → needs preflight") e2e asserts skills deployed partial: converge records-harness fails loud (#2025); direct setup-linux.sh still WARNs
    F-049 MCP registration diverges per tool (Claude/pi hive client daemon proxy; opencode/copilot/agy per-session uvx hive-vault; agy gets no context7) ai 5 hand-written configs one MCP manifest → per-tool render (#1843 B10) drift test ref #1843 B10
    F-050 opencode.jsonc rendered with plaintext secrets, no file mode set ai/secrets — render with 0600 like pi models.json doctor mode check closed: deployed by dotf deploy with mode 0600 (#2269); doctor reports mode drift (#2111)
    F-051 hive launchd supervision missing (KeepAlive SuccessfulExit=false, ThrottleInterval 30, absolute paths, dotf secrets run wrapper); upstream hive service install on darwin was a non-zero stub (AI-022) — reverify on 4.2.2 hive systemd/Windows only dotf schedule/service backend for launchd; doctor via launchctl print doctor verify first
    F-052 CLAUDE_CONFIG_DIR not exported before first claude run → .claude.json at ~ root vs in config dir bootstrap ordering env generate + rc deploy before agent installs doctor open
    F-053 Stale runbook docs/runbooks/ai-tools-setup.md (Gemini CLI, drawio MCP, "macOS: ./setup-linux.sh"); codex payload still written though retired docs/harness drift update / retire check-doc-paths open (runbook, rides S9 after P4). The codex half is a false finding: Codex is a supported harness (AGENTS.md:5); only its model pool retired (harness/model-map.json:29), so ~/.codex/AGENTS.md stays
  2. mlorentedev commented on Oct 6, 2026

    @mlorentedev
    OwnerAuthor

    Ledger update — 2026-10-05

    ID Finding Layer Root cause Agnostic fix Guard Status
    F-054 First bats run on darwin (bats 1.13.0, main @ 946afa5): 1632/1776 pass, 144 fail. About 65 are compile-harness/skill deploy (mapfile, F-031 → W3). About 35 are pr-agent, release-please and review-attestation workflow tests (cause TBD: GNU tool or missing yq/python). About 25 are vault-health and crystallize parity (TBD). The rest are scattered (chmod --reference F-038, eol, guards). tests never run on darwin triage per cluster; each fix in its owning W row W10 cannot become required until this reaches 0 open
    F-055 Baseline good news: go build/vet/test ./... passes on darwin/arm64 (30 packages). The Go layer needs no darwin fix to run its own tests. cli — — W10 measured

    Progress: W1 → #2014 (exec probe; on real assets, 0.64.0 installed the linux ELF on darwin with exit 0, while the branch refuses it and places nothing).

  3. mlorentedev commented on Oct 6, 2026

    @mlorentedev
    OwnerAuthor

    Ledger update — 2026-10-05 (end of session 1)

    ID Finding Layer Root cause Agnostic fix Guard Status
    F-056 eza has no aqua entry in the mise registry (only vfox:jdx/vfox-eza, cargo:eza) and publishes no macOS release asset catalog upstream W2 verifies vfox on 3 OSes; else class-3 on darwin W2 exec probe open
    F-057 vault 00_meta/runbooks/runbook-macos-workstation-setup.md (written 2026-10-05 in another session, uncommitted) documents the Mac baseline as manual steps: debloat, defaults, FileVault, firewall. A manual runbook is the failure mode this epic removes. bootstrap not codified new row P8: scripts/macos-defaults.sh, the opt-in analog of windows-defaults.ps1 (-WithDefaults), generated from that runbook; the runbook keeps the rationale and points at the script idempotence test (2nd run = 0 changes) open
    F-058 ci.yml (bats, shellcheck, test-windows) runs only on PRs into main. A stacked PR (base = another branch) shows green without running them. CI trigger filter merge order: base PR first, so the stacked PR retargets to main and CI runs before its merge process note noted
    F-059 Darwin-only test failures still open after W3: the doctrine over-cap warning tests (char vs byte count) and the antigravity tests that read the real ~/.gemini (F-008) tests TBD triage in W10 W10 open

    Progress: W1 → #2014 (review addressed: in-Dest staging for noexec /tmp); W3 → #2015 (review addressed: guard covers tests, bash 4.2+ constructs, zsh-safe nan-quality-bench.sh).

  4. self-assigned this
    on Oct 6, 2026
  5. mlorentedev commented on Oct 6, 2026

    @mlorentedev
    OwnerAuthor

    Ledger update — F-060 (found at handoff, 2026-10-05)

    ID Finding Layer Root cause Agnostic fix Guard Status
    F-060 Harness-enforced rules are absent until setup runs, and a fresh machine works for hours before that. On the Mac ~/.claude/CLAUDE.md was never deployed, so the "no AI attribution" rule (AGENTS.md §counter-defaults) never reached Claude Code, and the CLI's default attribution leaked into every commit and PR of this session. #2015 was rewritten; the squash commit 9db6df4 on main keeps a Co-authored-by trailer (owner decides). This is the exact failure AGENTS.md line 202 describes, triggered by F-031 (no deploy on bash 3.2) plus the "don't run setup yet" constraint. bootstrap order instructions deploy only as part of the full setup new row P9: a safe instructions-first deploy (dotf harness mirror + compile-harness.sh --deploy + dotf harness bind, which write only agent config dirs and never rc files or binaries) as the first bootstrap step on any new machine, before any agent session commits. Doctor FAILs loudly when an agent binary is present but its instruction file is missing. doctor check, plus X1 asserting the instruction files exist after bootstrap step 1 open, high
  6. 80 remaining items

  7. mlorentedev commented on Oct 12, 2026

    @mlorentedev
    OwnerAuthor

    Windows mise update (isolated x64 fixture; no catalog activation yet):

    • Root cause of the direnv failure: the aqua channel places a PE binary without .exe, so mise which direnv cannot find it. The Windows renderer now declares github:direnv/direnv with bin = "direnv.exe". Pinned mise 2026.9.13 resolves and runs direnv 2.37.1; a repeated install adds 0 tools.
    • With the current WIP renderer, dotf tools sync installed and probed all 27 marked CLIs plus PyYAML in an isolated Windows fixture. The second run says nothing to do; a dry-run says unchanged/nothing to install. Go tool/converge/cmd suites, vet, and 123 Windows setup BATS tests pass. The catalog still excludes Windows.
    • Remaining from-zero blocker: bats requires the real npm installer to create bats.cmd (aube on mise 2026.10.8 still produces no launcher). npm.package_manager = "npm" applies to every npm tool in the user's global mise config. Node/npm are present on this host, but not provisioned by packages.json or versions.conf; setup-windows.ps1 installs Node in legacy-setup, after converge's tools step. A fresh install.ps1 therefore cannot sync npm-backed bats in one pass.
    • Options to settle before enabling Windows: provision pinned Node through mise on all platforms in a staged reader/pin change, and explicitly accept the npm-wide installer selection; or keep Windows excluded until bats has a supported per-tool channel that does not require a global setting. Validate from-zero converge, shim PATH and a zero-change second pass before retiring winget/uv duplicates.

    The updated spec evidence and troubleshooting guide are in the feat/mise-windows worktree; no changes were made to main.

  8. mlorentedev commented on Oct 12, 2026

    @mlorentedev
    OwnerAuthor

    Follow-up on the Windows mise reader: the installer-policy and first-run Node blockers are resolved in feat/mise-windows without changing main.

    • NODE_VERSION=24.11.1 is now a marked mise pin (same owner on every OS). On Windows, sync refuses bats without that pin and installs Node before bats when needed.
    • The npm installer selection is scoped to the mise install --force npm:bats child using MISE_NPM_PACKAGE_MANAGER=npm. The managed config no longer sets a global npm package manager; a real isolated from-zero run without npm on PATH installed all 28 pins plus PyYAML, the next run did nothing, and mise settings get npm.package_manager remained auto.
    • A second isolated run using a minimal catalog with the real checksum-verified mise entry (no mise or npm on PATH) exercised dotf converge --only tools: first pass installed mise and four representative pins, second pass returned 0 changed, 1 ok.
    • Do not remove the Windows platforms gate yet. The currently pinned dotf 0.67.0 still selects unsupported aqua bats on Windows. Ship this Go reader first, then bump DOTF_VERSION to a release carrying it before enabling Windows and removing duplicated winget/uv setup blocks. That activation still needs a from-zero full production-catalog verification.
  9. mlorentedev commented on Oct 13, 2026

    @mlorentedev
    OwnerAuthor

    S2, the "first verify": hive-vault 4.3.0 hive service install on darwin is a stub (hive/_service.py:10: "macOS: a documented stub (launchd); a follow-up, not in the CI matrix"; line 332 tells the user to create a launchd agent with KeepAlive by hand). So on a Mac nothing ever starts the daemon, ~/.local/share/hive/daemon.token never exists, and every agent whose MCP config runs hive client fails to connect (observed 2026-10-12 in pi: "could not read daemon credential at …/daemon.token"). The X1 gate lists this FAIL under S2 (.github/scripts/from-zero-known-failures.txt).

  10. mlorentedev commented on Oct 13, 2026

    @mlorentedev
    OwnerAuthor

    New row, A1 (owner, 2026-10-12): workstation-target converge step, tracked in #2356 (SEC-011). It hardens a machine that accepts SSH (sshd drop-in, mesh-only firewall, power policy, unattended tailscale), opt-in per machine, with Inspect as the dry run, a preflight before Apply and changed=0 on the second run. It sits next to git-hooks and hive-service under PLAT-001b. Until it lands, docs/runbooks/guide-workstation-ssh-target.md is the interim procedure.

  11. mlorentedev commented on Oct 13, 2026

    @mlorentedev
    OwnerAuthor

    Overnight plan, 2026-10-13 (agent, owner-independent)

    Serial: one PR open at a time (strict protection re-runs every other PR on each merge). v0.68.0 is out; everything below goes to 0.69.0.

    # Item State
    0 Hygiene: merged worktrees removed, refs pruned, dotf pr triage-queue exit 0 done
    1 P7b (PLAT-001a): .bashrc, .profile, .inputrc as platforms: [linux, darwin] deploy entries, manifest v4, setup-linux copy blocks deleted, X1 ratchet entries dropped done: #2377 (ff5b01a2). Review fix: the rc-file tail deploy stays fatal under set -e
    2 X1 runs doctor --scope machine; test-windows drops the fabricated identity (X4) X1 already did; X4 in #2380 (spec-issue-state kept as its own dotf spec audit step; DOTFILES_DOCTOR_NO_IDENTITY deleted)
    3 PR 6d follow-up: plugin-install failure cause done: BUG-126, #2383 (954526a7, lesson 404): marketplace sources declared in ai/claude/marketplaces.json, registered before the first install
    4 WIP reduction: independent archive review of FIX-WIN-TUI-001 done: FAIL, recorded in #2382 (666cd436); #2019 Blocked on the owner's ADR-028 decision
    5 #2375 (dangling mise pwsh shim) fix (a) done: #2389 (ee8fdb2, require_pwsh); fix (b), the orphaned install, stays on #2375
    6 Runbook guide-workstation-ssh-target.md: Linux sections collapse to the dotf flow done: #2386 (6f4261f7), with the review's three rollback fixes
    7 PLAT-001b row: AddKeysToAgent + UseKeychain in ssh/config done: #2386
    8 from-zero red on main: the hive port taken on the runner done: #2390 (4315f95); design finding hive#492
    9 W4 (F-048): setup stops on a failed dotf install, dotf deploy or skill deploy done: #2391 (d611728); from-zero 38387691830 green on both OSes
    10 P5c PR2 (#2317): Claude Code and agy through mise at latest #2392 ready, CI green on 80a6131a, review triaged (1 applied in a9c0a9fb, 1 declined); lands before the 0.69.0 cut (owner decision 2026-10-13). When Windows mise activates (W2b) and renders claude, delete the vendor install block in setup-windows.ps1 in the same change (review finding 2)
    11 BUG-127 (#2387): converge registers Claude's MCP servers and plugins (agent-extensions) done: #2394 (df443d6); from-zero 38391592787: first converge 4 added; 3 added, second "changed": 0 on both OSes
    12 CLI-105 (#2384): agy's MCP configs written by dotf, the twins' blocks deleted done: #2396 (3e83a635); from-zero evidence with P5c in 38396154386; #2392 rebased and its stale darwin known failure removed (c875e04a). Found on the Mac: the master still launched uvx pdf-modifier-mcp (lesson 405)
    13 BUG-128 (#2397): Claude MCP registrations with stale arguments re-registered from the SSOT done: #2398 (021cd632, lesson 406); scope guard, restore on a failed re-add, Lost, settings guard from review. Mac plan: pdf-modifier to re-register
    14 CLI-106 (#2393): doctor reports Claude servers and plugins that differ from the declarations done: #2400 (127d71cc); Mac: one WARN, pdf-modifier
    15 CLI-107 (#2395): one name for agy's app-data dir, the contract's AGY_HOME done: #2401 (e22c868c); AGY_APP_DATA read only as a fallback until #2404; guard keeps it out of shell, PowerShell and rc files
    16 #2180: skills-pipeline test mutates a copy of the tree, not the committed record done: #2403 (57a0fa56)
    17 BUG-123 (#2350): doctor FAILs on Claude permission rules a merge deploy keeps; --fix removes them through deploy's staged write done: #2405 (d35802cf); Mac read-only: six FAILs, the six rules #2349 corrected
    18 BUG-119 (#2310): knowledge-gate strips fences, comments and code spans in one pass, as CommonMark reads them done: #2406 (d59dcecf); four cases of the family fixed, three review rounds against cmark-gfm, lesson 407
    19 HARNESS-162 (#1738): spec and doc-paths gates run under zsh and bash through one shared helper; guard for BASH_REMATCH without the zsh shim done: #2409 (beb9d24); seven suites on run_both_shells, guard requires KSH_ARRAYS and BASH_REMATCH, lesson 408
    20 BUG-129 (#2408): doc-paths probes stage in a private index, not the shared one done: #2412 (3bf2f5a); race on main 7/10, branch 0/15, lesson 409
    21 CLI-098 (#2034): archive the zombie pr land spec after its independent review done: #2411 (45750a8); review PASS, F1 fixed in the spec, F2-F4 filed as #2410
    22 CLI-111 (#2410): pr land reports a landed merge as merged, names a stale lock PID, finds the registry from any directory done: #2413 (13b4f46, skip-sdd: findings of an archived, reviewed spec); cross-repo registry filed as #2414
    23 Windows install.ps1 on 0.68.0: poetry: reports 2.4.1 after install, below the pin 2.5.1, and every run reinstalls with --force #2415: the error names every copy on PATH in LookPath order. The box fix waits for 0.69.0 on Windows, where the message names the shadowing copy. Suspect: two homes (state under C:\SPB_Data, mirror under C:\Users\mlorente)
    24 Converge run logs: keep the last 20 runs (.json report + full .log transcript) layout and requirements decided on #1098 (<StateDir>/converge/runs/, redact secrets on write, 0700/0600, never committed or mirrored). Built in #2446 (2026-10-13); live Windows-box run open after the release that carries it
    stretch PR 7e Windows half, code only not started: Windows batch

    Findings for the ledger:

    • from-zero is green with 0 added, 3 failed plugins: setup-linux.sh runs dotf deploy \|\| log_warning inside the opaque legacy-setup entry. That is W4's class (a failed deploy must fail setup); run 38366816961 is a concrete instance.
    • Claude Code is unpinned on the runners (native build latest), which is why CI and every workstation disagreed on the default marketplace.

    Owner-gated, not tonight: sudo ufw status verbose on msi (is port 22 also confined to tailscale0?), ace2 fingerprint, Hetzner console (kubelab#2224), vps host key (kubelab#586), AC11 Dock relaunch. S11 (#2373) stays queued behind S7 and the WIP ceiling.

    Filed overnight: #2381 (CLI-104), #2384 (CLI-105), #2387 (BUG-127), #2388 (SEC-014), hive#492. BUG-127 and CLI-105 share one mechanism (#1843 B10) and are planned as one converge step. Later: #2393 (CLI-106, doctor misses a missing Claude plugin or server), #2395 (CLI-107, AGY_HOME vs AGY_APP_DATA).

    Also filed: #2397 (BUG-128), #2399 (CLI-108, delete the Claude MCP twin; the Windows half waits for hive in the Windows catalog, since the twin's prerequisite_command is the only Windows install of hive-vault), #2402 (CLI-109, deploy.json spells contract-named agent dirs with {HOME}), #2404 (CLI-110, drop the AGY_APP_DATA fallback one release after #2401), #2407 (REFACTOR-018, one markdown stripper for the knowledge and spec gates; carries the theoretical review findings of #2406), #2408 (BUG-129), #2410 (CLI-111, the code findings of CLI-098's archive review), #2414 (CLI-112, read the registry of the repository --repo names).

  12. mlorentedev commented on Oct 13, 2026

    @mlorentedev
    OwnerAuthor

    Ledger update (2026-10-13, evening): where every row stands

    About 31 of the epic's ~62 rows are done in 8 days (10-05 to 10-13), a sustained 3.9 rows a day. The overnight burst on 10-12/13 (22 items in ~36 h) is not that rate.

    Wave Done In flight Open Blocked on
    1 silent failures (W1-W10, T0-T2) W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, T0, T1, T2 — W2b: shims on PATH for non-interactive callers, plus its doctor probe T1b Windows half (AC4): a release carrying the Windows reader, then a Windows sitting
    2 declare the OS (P0-P10) P0 (ADR-045 on main; the tasks.md:28 box is stale), P1, P2, P3 (no code), P4, P5a, P6, P7a/b, P9, P10 P5c: #2392 (CI on a9c0a9fb) P5b second half: 16 casks, docker apt, kubectl/helm, git lfs. P8 macos-defaults.sh, not started P5b data waits on release pins. A1 / PLAT-001b PR 7: 7d needs the owner's pf answers on #2356; 7e Windows half; host keys pinned; runbook collapse
    2.5 PLAT-003 #2344 (cross-OS fabric) — — 3 PRs: PowerShell shift-left, Lima, Windows VM a spec slot (WIP)
    3 toolchains, services, herdr (T3-T6, S1-S3, H1-H6) T3, S3, H1, H4a — T4 (rc activation, partial), T4b (.zsh/nvm.zsh still on main), mise.lock, H3, H4, H5, H6 S1 needs a spec slot. S2's dotfiles half waits on a hive release (hive#489). T5, H2: Windows. T6 per-machine migration: owner
    4 rest of S, gate V, R, X S6, S9, S10 (backupOnce), X1 (#2331, not yet required), X2, X4 S8 = #1843 B10 (#2394, #2396 landed) S4 notifier, S5 verify, S7 key provisioning, H7, H8, H9 keybinding, R0-R4 S11 / SSH CA (#2373) needs a spec slot. Gate V needs herdr in daily use on three OSes (calendar time). X3 Windows parity

    WIP: 13 active specs on origin/main against ADR-043's limit of 10. Archivable after their reviews: AI-045 and TOOL-013. Owner-blocked: FIX-WIN-TUI-001 (#2019). Starting S1, PLAT-003 or S11 needs four archives.

    Issue balance: 429 open. Since 10-12: 19 closed, 31 opened (net +12).

    Critical path, not PR count. The agent effort is about 45-55 PRs, but the calendar is set by:

    1. Release cycles the owner merges: 0.69.0 (feat(tools): install Claude Code and agy through mise at their latest release #2392, fix(tools): name the copies on PATH when a package-manager install is shadowed #2415), then the gated data halves (P5b casks and kubectl/helm, H4, T1b Windows). About 4-5 cycles.
    2. Owner decisions: the pf questions on SEC-011: converge the workstation SSH target hardening instead of applying it by hand #2356, T6 per machine, making X1 required, ADR-028 for FIX-WIN-TUI-001 spec stays active after its fix merged, and names no tracking issue #2019.
    3. Windows sittings: T1b AC4, T5, H2, 7e, X3. Two or three batched sessions, fewer if PLAT-003 lands first.
    4. Gate V: herdr in daily use on three OSes, at least a week, which cannot be compressed. R0-R4 follow it.
    5. The WIP unlock before S1, PLAT-003 and S11.

    Estimate

    Scenario Done by Requires
    Optimistic ~3 weeks (early November) a release every 1-2 days, the WIP unlock this week, one Windows sitting next week, gate V passing first time
    Realistic 4-6 weeks (mid to late November) a release every 3-4 days, two Windows sittings, gate V taking 1-2 weeks
    Pessimistic 8+ weeks S11 (the SSH CA plus kubelab phase 2) or gate V slipping; herdr's 0.x churn reopening H rows
  13. mlorentedev commented on Oct 13, 2026

    @mlorentedev
    OwnerAuthor

    Plan to close waves 1, 2 and 2.5 and start herdr (2026-10-13, owner priority)

    Finding: the pin on main is DOTF_VERSION=0.68.0, and 0.68.0 already carries every reader these rows were waiting for: the TOML merge (e0be8242, H4), versionArgs (f6557b46, kubectl/helm), --no-remove, and the cask reader (040c1fcc). By the repo's own rule (#1814 class), H4, kubectl/helm and the casks can land now. Ordering hazard: an older dotf refuses the whole catalog on the new cask key, and the Mac runs 0.67.0, so the cask PR lands after the Mac runs 0.69.0.

    Agent, in order (no owner input needed):

    1. W2b: shims on PATH for non-interactive callers, plus doctor's probe (Wave 1's last row)
    2. X1 asserts claude, pi and agy on PATH after the first converge: claude and agy done in feat(tools): install Claude Code and agy through mise at their latest release #2392 (from-zero.yml, "The agents resolve on PATH"); pi waits on Move the pi CLI install from the setup twins into packages.json #2278, two PRs (reader now, entry after the release carrying it is the pin; sequencing on Move the pi CLI install from the setup twins into packages.json #2278)
    3. herdr: H4 (the herdr deploy entry, TOML merge), H3 (pin drift and attestations), H5 (doctor checks), H6 (agent integrations), H9 (lazygit keybinding). Track H lives under PLAT-001c, so no new spec
    4. P5b: kubectl 1.35.8 and helm 3.21.4 as # mise: cli pins
    5. P8: scripts/macos-defaults.sh, code and dry run only (applying it on the Mac is the owner's call)
    6. PLAT-001b 7e, Linux half: the UFW rule as a converge step, code only
    7. TOOL-013 (TOOL-013: PR-Agent on NaN inference as a uniform reusable review workflow #786): the inline COMMENTED PR-Agent reviews on feat(tools): install Claude Code and agy through mise at their latest release #2392 and fix(tools): name the copies on PATH when a package-manager install is shadowed #2415 are AC1's live evidence. Record them, rewrite verification.md AC1, dotf spec review
    8. AI-045: dotf spec review, archive on PASS

    Owner, ordered by what each unblocks:

    1. Merge release chore(main): release 0.69.0 #2379 once feat(tools): install Claude Code and agy through mise at their latest release #2392 and fix(tools): name the copies on PATH when a package-manager install is shadowed #2415 land, then install 0.69.0 on the Mac (guided, plan first). Unblocks PLAT-001a task 104 and its archive, the cask PR, and the Windows poetry diagnosis
    2. WIP: closing TOOL-013, AI-045 and PLAT-001a brings 13 to 10, but PLAT-003 (Wave 2.5) needs 9 or fewer. Decide on an explicit exception for PLAT-003, with S1 (comment 6104629910) as the precedent
    3. The six pf questions on SEC-011: converge the workstation SSH target hardening instead of applying it by hand #2356 (PLAT-001b 7d)
    4. Host-key fingerprints by a second channel: ace2, and the vps (kubelab#586)
    5. One batched Windows sitting: T1b AC4, the gh/eza winget rows, 7e's Windows half, H2, poetry. Or, for PLAT-003 PR3, VMware Fusion and a Windows 11 ARM image on the Mac
    6. msi: whether it is touched at all (7e UFW check, PLAT-001a's vault-health measurement, and the look at its compose plugin that the docker-apt rows need)
    7. herdr in daily use (gate V is calendar time); confirm the 16-cask list in runbook-macos-workstation-setup

    Nothing is dropped: before any spec archive, its open boxes move to a row here or to their owning issue. A deferral with no existing home is the one case that gets a new ticket, and it is said so when filed.

  14. mlorentedev commented on Oct 13, 2026

    @mlorentedev
    OwnerAuthor

    Debt ledger, 2026-10-13 (after 0.69.0)

    Found while landing #2416–#2422 and converging the Mac on 0.69.0. The Mac now reads doctor: 173 pass, 0 FAIL, 5 WARN.

    # Item Owner / next step
    D1 AI-045 second archive review: PASS-WITH-GAPS. Free to fix now: preflight script header says ::error:: (shipped: ::warning:: + hand-over), verification.md:136 overclaims the guard, the third promotion line unanswered. Contract set, frozen until archive: f2/f5/f8 still pass on an empty filter, f1's mimo-v2\.5" is quote-shape dependent and negates a grep error, f9's behavior sentence predates the weighted pool. Tests: AC4's shipped concurrency numbers are pinned by no test agent: free items + AC4 test in the archive PR; contract items as a follow-up right after the archive
    D2 msi's uncommitted harness/skills/teardown/SKILL.md is not a stray edit: it matches the vault's 00_meta/skills/teardown/SKILL.md (the SSOT) byte for byte below the generated header. main's generated record is the stale one (generated_sha 9789e43d vs the vault's b83fcf48) agent: regenerate the committed records with compile-harness.sh in a PR; then msi runs git checkout -- harness/skills/teardown/SKILL.md && git pull --ff-only
    D3 Remote branch ci/macos-leg: PR #2063 closed, 7 commits not on main, 6 days old owner: delete or revive Resolved 2026-10-13: branch deleted (owner); its content had already landed via #2145, #2146, #2147.
    D4 dotf --version is an unknown flag; only dotf version works agent: small fix
    D5 Doctor WARN: the VM runs 2 CPU / 2 GiB, the repo declares 4 / 8. doctor --fix restarts Colima and stops its containers owner: run it when no container matters Resolved 2026-10-13: Colima runs 4 CPU / 8 GiB under brew services (colima list); the adoption path doctor lacked is #2427.
    D6 Doctor WARN: DR escrow describes the vault as of 2026-10-10 (1 item added since); no recovery drill ever recorded owner: re-run escrow; schedule the drill (runbook guide-secrets-governance § RECOVER) Escrow resolved 2026-10-13 (#2426, d916959, 181 → 182 items). The drill half stays open as dotf secrets usb verify + yearly full RECOVER (owner decision on #1770), PLAT-001a rows.
    D7 Reclassified 2026-10-14 (owner). The per-skill decision was already made in the vault: each persona AGENT.md (builder, curator, planner, shipper) states its bare-string skills are deliberately ungated because they are situational, and the roster guard (SKILLS_MIN = 3) forbids dropping them. Remaining defect: the persona schema cannot declare "situational", so doctor's Persona skill enforcement WARN (13 of 27) can never clear on a state the repo calls correct. Fix shape: a declarable third enforce value the gate ignores and doctor counts as declared, touching persona.go, the gate, the doctor check, compile-harness.sh parsers and check-roster-consistency.py; spec-sized, queued until WIP allows a spec agent: spec when WIP < 10; the WARN stays expected meanwhile
    D8 PLAT-001b 7e acceptance on msi (firewall). Converged: applied: firewall, ENABLED=yes, input policy DROP, witness written; from the Mac ssh -o BatchMode=yes msi true over the mesh exit 0, nc -z -w 5 10.0.0.9 22 over LAN exit 1 owner: second ./install.sh --only workstation-target must report 0 changed; agent records the evidence in PLAT-001b verification.md and ticks row 210
    D9 Root cause of D2: nothing regenerates the committed harness/skills/*/SKILL.md records from the vault on converge, and no check compares the committed record with the vault, so a vault skill edit leaves main stale silently agent: a doctor/CI check that compares generated_sha with the vault source
    D10 Console output is inconsistent across install.sh, the legacy setup-linux.sh and dotf converge/doctor: different status markers, the tool catalog printed twice, legacy and converge output interleaved agent: one output contract owned by dotf (status helpers), legacy setup output routed through it; design via the advisor before code
    D11 Watch: ufw default deny incoming can block docker bridge (172.x) traffic to host services on msi agent: verify on msi after the next container run; open a ticket only if it bites
    D12 dotf doctor run from a non-login shell (ssh host 'dotf doctor') reports the five tool-home variables (APPS_HOME, JAVA_HOME, MAVEN_HOME, GO_HOME, MINIKUBE_HOME) as FAIL and the env contract as WARN, because no profile was read; it cannot tell that from real drift. Measured on msi 2026-10-13: 5 FAIL non-login, 0 FAIL under zsh -lic agent: doctor should detect an unloaded profile and say so once, instead of failing each variable
    D13 Four doctor checks still default VAULT_PATH to the literal ~/Projects/knowledge instead of the ADR-025 resolver (checks_automemory.go, checks_vault_hooks.go, checks_trigger_targets.go, checks_deploy.go:61); #2425 fixed only its own check agent: one PR routing all four through env.ResolvePath

    Not debt, recorded so nobody re-opens it: agent-extensions re-registering pdf-modifier was a first-run add, not a loop; the Mac's next plan reads 0 MCP server(s) to add, 4 present (msi to confirm on its second run).

    Also not debt: doctor --fix removed six deny rules of the form Bash(...:*) that matched nothing; the working forms (Bash(git push *--force*) etc.) are present, deployed from ai/claude/settings.json.

  15. mlorentedev commented on Oct 14, 2026

    @mlorentedev
    OwnerAuthor

    Windows from-zero production-catalog run (EGW-LEN029, corporate box, 2026-10-13): not converged. The catalog stays excluded.

    The bats channel is already solved and released. npm:bats with MISE_NPM_PACKAGE_MANAGER=npm scoped to the install child landed in #2345 (007c92fc), and git merge-base --is-ancestor 007c92fc ef5d135a confirms that 0.69.0 carries it. What this run tested is the remaining AC4 row: the full production catalog, run from zero with the released binary, twice.

    Setup: installed dotf 0.69.0; fixture = git archive e7338fb2, with platforms removed from the mise entry in the copy only (packages.json on main is untouched). HOME, XDG_CONFIG_HOME, XDG_STATE_HOME and MISE_{DATA,CACHE,STATE}_DIR point under %TEMP%\z2. PATH has no node, npm or mise. dotf converge --repo <fixture> --only tools --plan listed only sops, mise plus copilot, yarn (npm), with no winget or uv-tool installs. The real ~\.local\bin held 22 entries before and after.

    Result
    catalog pass 1 sops 3.13.1, mise 2026.9.13 installed into the fixture home
    mise sync every pin installed (mise ls --installed = 37); bin-paths has bats.cmd, direnv.exe, node.exe; npm.package_manager stays auto
    catalog pass 2 (npm) FAIL: copilot: npm exited 0 but copilot does not run on PATH; the same for yarn. Launchers land in <MISE_DATA_DIR>\installs\node\24.11.1\, because mise's Node has no npmrc setting prefix=%APPDATA%\npm. Filed as #2438 (TOOL-027), which blocks activation
    run 2 npm reinstalls both and fails the same way, so there is no changed=0
    mise exec -- bats --version fails with /bin/bash: ...\bats: No such file or directory, because bash resolves to C:\WINDOWS\system32\bash.exe (WSL) first on this PATH. With C:\Program Files\Git\bin prepended it reports Bats 1.13.0. That is #2346 (BUG-122), now measured

    Discarded artefact: a first attempt (%TEMP%\z1) also overrode USERPROFILE. Ten aqua tools then failed Sigstore verification with TUF error: Could not determine cache directory. With only HOME overridden (z2), all of them installed. Record this for any future isolated Windows run: do not override USERPROFILE.

    Not exercised (satisfied by host copies on PATH): opencode, bw (npm), poetry, pre-commit (uv-tool), and the winget system entries gh, eza, docker, aws. A truly empty box would also hit #2438 for opencode and bw.

    Next for activation: #2438, then #2346, then repeat this run. Fixture dirs %TEMP%\z1 and %TEMP%\z2 are left on the box for the owner to remove.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

debtKnown tech-debt paydownfeatureNew capability

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions