Uh oh!
There was an error while loading. Please reload this page.
Home-repo foundation: rt home + sops/age secrets + rt-client settings extraction + suite registry - #5
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…light catch, HEAD not main) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ume-safe createRepo Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tural redaction
Review verdict (2 Critical, 2 Important, 4 minor), all fixed:
- readAgeKey now returns a three-way result ({key} | {absent:true} | throw)
instead of collapsing every non-zero `security` exit to null; only exit
44 corroborated by the "could not be found" stderr marker counts as
absent, so a locked keychain or denied ACL dialog throws loudly instead
of reading as "no key" and letting ensureAgeKey mint over (and orphan)
the real one. Mint path also drops `-U`, so a duplicate item makes the
keychain write itself fail as a last-resort guard.
- keyExport no longer mints on a miss; it refuses with AgeKeyAbsentError
pointing at `rt home init`. Minting moves to `rt home init` itself
(ensureHomeAgeKey, commands/home.ts), run once as a distinct idempotent
post-init step, pinned with tests covering fresh init, the
already-initialized short-circuit, and --dry-run.
- createRealAgeKeySeam always returns the argv-redacting wrapper; the raw
seam is no longer exported, so there is no reachable unredacted default.
- Minors: fixed the false "rt will not print it again" claim in the export
warning, added a static zero-fs-import assertion alongside the
writeFileSync spy, named the `ps`-visibility exposure of `-w <key>` in
addCmd's doc, and pinned export to exactly one find call.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>Task 5 of the home-repo foundation plan. lib/secrets/store.ts reads and writes ~/.mattstack/user/secrets/<domain>.json through an injected sops exec seam, sourcing SOPS_AGE_KEY from lib/home/age-key.ts's readAgeKey (env only, never argv); a per-process memo caches decrypts and is invalidated on write. commands/secrets.ts wires the set/list/rotate verbs into the tree + module registry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…validation, key-presence assert Review found two Critical issues (secret values reaching rt's own CLI logs via argv; plaintext staging on a path that could survive a failed write) plus permissions, domain/key validation, and an early-assert gap that let a machine with no age key silently write an unreadable secret. - commands/secrets.ts: set/rotate take no Value arg; a no-echo TTY prompt or --stdin supplies it, so it never touches argv or shell history. - lib/cli-logger.ts: redactSensitiveArgs is command-aware, redacting anything past `secrets set|rotate <domain> <key>` in both the raw-argv and leaf-rest shapes, as defense in depth. - lib/secrets/store.ts: writeSecret now stages plaintext under the gitignored rt/tmp/, encrypts via --filename-override + --output to a .tmp path, fsync+renames atomically over the real target, and cleans up unconditionally — a failed write never leaves a file to delete. Every write chmods 0600; user/secrets/ and rt/tmp/ are created 0700. domain/key are validated before any fs/exec call. writeSecret asserts the age key exists up front, even for a brand-new domain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports loadSecrets/saveSecret/saveTeamConfig/getTeamConfig (lib/linear.ts) onto lib/secrets/store.ts, async, with an encrypted-store-wins-else- plaintext-file fallback for the transition window before the live import retires ~/.mattstack/rt/secrets.json. Updates every named caller (enrich.ts, freshness.ts, discussions.ts handler, browser-login.ts, settings.ts). Fixes the Task 5 carried review item: the real sops exec seam now spawns with cwd: mattstackHome() so sops always resolves this home's .sops.yaml, never a foreign cwd's. Adds a daemon secrets:read verb whitelisting exactly the two fields the VS Code extension reads (linearApiKey, gitlabToken), gated behind the local API token since its response body is a credential, not metadata. Ports the extension's read to it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, fail-closed loadSecrets
Review findings, addressed per ruling:
- secrets:read's api-token check moves into the handler (payload.token),
closing the unix-socket transport's zero-auth gap; HTTP still forwards
its verified X-RT-Token header in so callers are unchanged.
- lib/secrets/store.ts's domain memo gains an mtime/size staleness check
(SecretsExecSeam.statFile) so a token rotated via a sibling CLI process
reaches a long-lived daemon's next read without a restart.
- lib/linear.ts's loadSecrets throws the store's own error when an
encrypted read fails AND the plaintext transition file is absent,
instead of silently returning {} — fail-open was never the intent.
- Extension setSecret no longer writes ~/.mattstack/rt/secrets.json;
it directs the user to `rt secrets set` instead of a silent no-op.
- pickDaemonSecret distinguishes daemon-down from a token-gate refusal
and getSecret warns once per cause instead of collapsing to undefined.
- Minors: secrets:read log info->debug; corrected a wrong "mid-domain
failure" rationale in a comment; @types/bun added to the extension's
own devDependencies (tsc had been passing only via the monorepo
root's transitively-resolved types).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>lib/settings/{resolve,stores,identity,write}.ts and the registry
(split into registry-machinery.ts + registry-defs.ts) relocate to
packages/rt-client/src/settings/. lib/settings/*.ts become thin
re-export barrels so every existing rt importer keeps compiling
unchanged. Moved code duplicates the minimal path/exec helpers it
needs (paths.ts, exec.ts) rather than importing rt's lib/, matching
the existing transport.ts/repos.ts convention; lib/rt-paths.ts and
lib/subprocess.ts are untouched. jsonc-parser added to rt-client's
dependencies. rt-client's index now exports the settings surface;
package.json version bumped to 0.3.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>Appends 29 suite defs to registry-defs.ts (deck.apps/access/platform, board.* team/user/machine rows plus the two BOARD-14 doctor keys, gitq.workSlots/forges/board, mattstack.integrations/tracking/appPath, claude.marketplaces/plugins), scopes/types/merge per the spec's tables. migrated becomes optional (SettingDef) since suite keys carry no rt legacy file; isMigrated() centralizes "absence means resolver-backed" so write.ts's refusal ladder and resolve.ts/settings-keys.ts's labeling agree. REGISTRY is now readonly SettingDef[]. Carried from task 8's review: a lib/__tests__ parity test pins lib/rt-paths.ts against rt-client's duplicated paths.ts under a faked HOME; rt-client's tsconfig/package.json now exclude __tests__ from the published tarball; README states the Bun-only exec path. One e2e case proves a non-rt prefix round-trips (deck.access, set then get with provenance). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…enMembers, description cleanup Review found isMigrated missing from index.ts's public exports, stranding the in-process deck/board/gitq consumers the machinery's own docblock tells to call it instead of testing def.migrated. Exported it, and added a new surface-test class (test/index-surface.test.ts) asserting the full registry API reaches consumers through the package entry point. Adds board.hiddenMembers (user, array, replace) as the settings key for the spec's members[].hidden overlay — ruled to stay a separate user-scope key rather than widening board.members to user, which would let a personal store shadow the whole team roster instead of hiding entries from it. Enumeration test grown 46->47 keys. Description/comment cleanup per review: drop the plan-task-number reference from registry-defs.ts's header comment and the (BOARD-14) ticket id from both doctor-key descriptions (ticket ids don't belong in `rt settings list` output); board.doctorSkill no longer says "teammate's" (doctors run on your own MRs too); board.triage and board.triage.doctorSkill each now state they are sibling flat keys, not container/field, at different scopes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…/Applications resolution Nine whole-branch review findings fixed in one pass: - wire renderSopsYaml into rt home init (I1): ensureHomeAgeKey now backfills ~/.mattstack/.sops.yaml on any recipient mismatch or absence, at both the fresh-init and already-initialized call sites, and mints before printing success (M4). - widen the test gate back to lib+commands+packages (I3) — it silently dropped 209 tests. - the post-encrypt readback is now a real sops -d decrypt + value round-trip on the tmp output, before the rename, replacing a ciphertext-shape heuristic (I4); outputTmpPath is pid-qualified so concurrent writers can't cross-unlink (M2). - rt hardcodes ~/Applications in four places; the app bundles now legitimately live in /Applications too. Added installedTrayAppPath() (settings key -> /Applications -> ~/Applications -> null) and rewired verify/settings/daemon to it (Item 5), including the test-isolation fallout that surfaced once a real /Applications install leaked into two suites. - user/secrets/*.tmp added to the home-repo gitignore boundary (M1). - age-key.ts's fs-import guard regex now also catches node:fs, fs/promises, node:fs/promises (M3). - the VS Code extension's token-set commands no longer prompt for a value they can't save; they show the directed rt secrets set message immediately (M5). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (74)
📝 WalkthroughWalkthroughThis change adds home-repository initialization, age-key custody, encrypted secret commands and storage, authenticated daemon secret access, shared settings APIs, and installed tray-app resolution. It also adds extensive tests, migration specifications, package exports, and test-script coverage. ChangesHome repository and key management
Encrypted secrets and consumers
Shared settings client
Tray application resolution
Migration and test wiring
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant homeInit
participant gatherHomeState
participant buildInitPlan
participant executeInitPlan
participant ensureAgeKey
User->>homeInit: run rt home init
homeInit->>gatherHomeState: inspect repository and clones
gatherHomeState->>buildInitPlan: provide HomeState
buildInitPlan->>executeInitPlan: execute ordered initialization steps
executeInitPlan->>ensureAgeKey: ensure age key after initialization
ensureAgeKey-->>User: report initialized home
sequenceDiagram
participant VSCodeExtension
participant daemonQuery
participant apiServer
participant secretsHandler
participant encryptedSecretsStore
VSCodeExtension->>daemonQuery: request /api/secrets with token
daemonQuery->>apiServer: send authenticated request
apiServer->>secretsHandler: invoke secrets:read
secretsHandler->>encryptedSecretsStore: read whitelisted secrets
encryptedSecretsStore-->>VSCodeExtension: return secret response
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
…DME drift, escaping, log collision) #1: walkthrough.sh's cleanup() defaulted a missing phases.jsonl to success via ${f:-0}, so dying before the first vm_phase_end (e.g. no tart on a fresh machine) exited 0 with an empty report. Dropped the default so a missing ledger fails the `[ -eq 0 ]` test and falls through to exit 1, matching xcuitest.sh's existing fail-closed form. Pre-existing on main; reproduced the before/after with the review's no-tart repro. #2/#9: README described ax.sh/drive-setup.sh/trigger-update.sh as not yet in the tree and misattributed the screens-phase failure to an unstaged guest script; all three are staged into $GUEST_BIN by walkthrough.sh today. Corrected the Status/Layout prose to state what's actually gating `--scenario create/join` (L3's setup screens) and the update phase (L3's MATTSTACK_APPCAST_URL hook). Also corrected the disk-footprint line: the ~60 GB figure is cleanroom-only, and an --xcode golden needs substantially more (full Xcode install on top of the base OS). #3: check-vm-scripts.sh's ax.sh syntax-error net only matched "script error"/"Expected " literally, missing other osascript compile-failure shapes (e.g. "syntax error: A property can't go after..."). Widened to a bare "syntax error" alternative, which osascript writes for every compile failure and never for a runtime error. #4: ax_click_button_named defaulted its process arg to the already-escaped $AX_APP, then ran ax_esc on it again, double-escaping any AX_APP containing a quote or backslash. Now only escapes when an explicit (raw) $2 is given. #6: build-golden.sh's tart boot log was named golden-$VER-tart.log for both flavours, so an --xcode build silently overwrote the cleanroom golden's boot log. Named it after $GOLDEN instead, which already carries the -xcode suffix. Findings #5 (VM_APPCAST_PORT default duplication), #7 (--ver not version-validated), #8 (xcuitest.sh's guest-staging convention), and #10 (PAT/password on guest ssh argv) are parked per the reviewer's ruling — not touched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
R-T7-a (#1): tool.daemon's launchd/worktrees sub-facts are real negative signals now, not folded into a "ready" detail — either failing flips the row to "invalid" with the specific fact named. R-T7-b (#4): the legacy split-state branch (required, invalid) carries a {type:"steps"} merge-by-hand remedy instead of action:null; the detail also gets verify's plural handling back. R-T7-c (#6): fixes the bundle-memo hazard at its source. appBundleRoot() (lib/bundle-layout.ts) now memoizes only the true default (exists === existsSync); an injected exists (every Probes-driven caller) never reads or writes it. Validator tests drop the reset ceremony this made unnecessary. R-T7-d (#12): tool.rt-link's needs-you branch carries a {type:"run"} action to fix the link in one step. #2/#3: tool.fzf and tool.rt now distinguish "genuinely absent" (127) from "resolved but won't run" (any other exit) — the latter is "error", never "ready"/"missing". #5: tool.daemon and tool.app get recheck:"on-activate" (Task 6's convention for out-of-band, leave-the-app-and-come-back rows). #7: the five optional rows carry real optionalNotes. #8: tool.app's legacy note names the exact hit path(s), matching verify's phrasing. #9: interceptsRow wraps shimReport()/staleIntercepts() so a throw degrades to an "error" row instead of rejecting the whole plan. #10: tool.daemon's Login Items action is imported from permissions.ts (now exported as LOGIN_ITEMS_SETTINGS_ACTION) instead of a duplicate literal. #11: lib/shell-integration.ts gains detectShellFrom()/shellRcPathFor(), pure functions the real detectShell()/shellRcPath() now delegate to and tool.shell reuses over Probes; an unrecognized shell gets an honest "can't write automatically" detail instead of "Install writes it". #13: the tool.daemon describe saves/restores DAEMON_CONFIG_PATH's pre-existing content around the whole block instead of only deleting it, so status-fallback.test.ts's absence assumption can't be poisoned. #14/#15: header comment no longer cites the brief's table, the rt-link "no app" test asserts its reason string, and commands/verify.ts's docblock is trimmed to the one load-bearing line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s-keys Home-repo foundation: rt home + sops/age secrets + rt-client settings extraction + suite registry
…DME drift, escaping, log collision) #1: walkthrough.sh's cleanup() defaulted a missing phases.jsonl to success via ${f:-0}, so dying before the first vm_phase_end (e.g. no tart on a fresh machine) exited 0 with an empty report. Dropped the default so a missing ledger fails the `[ -eq 0 ]` test and falls through to exit 1, matching xcuitest.sh's existing fail-closed form. Pre-existing on main; reproduced the before/after with the review's no-tart repro. #2/#9: README described ax.sh/drive-setup.sh/trigger-update.sh as not yet in the tree and misattributed the screens-phase failure to an unstaged guest script; all three are staged into $GUEST_BIN by walkthrough.sh today. Corrected the Status/Layout prose to state what's actually gating `--scenario create/join` (L3's setup screens) and the update phase (L3's MATTSTACK_APPCAST_URL hook). Also corrected the disk-footprint line: the ~60 GB figure is cleanroom-only, and an --xcode golden needs substantially more (full Xcode install on top of the base OS). #3: check-vm-scripts.sh's ax.sh syntax-error net only matched "script error"/"Expected " literally, missing other osascript compile-failure shapes (e.g. "syntax error: A property can't go after..."). Widened to a bare "syntax error" alternative, which osascript writes for every compile failure and never for a runtime error. #4: ax_click_button_named defaulted its process arg to the already-escaped $AX_APP, then ran ax_esc on it again, double-escaping any AX_APP containing a quote or backslash. Now only escapes when an explicit (raw) $2 is given. #6: build-golden.sh's tart boot log was named golden-$VER-tart.log for both flavours, so an --xcode build silently overwrote the cleanroom golden's boot log. Named it after $GOLDEN instead, which already carries the -xcode suffix. Findings #5 (VM_APPCAST_PORT default duplication), #7 (--ver not version-validated), #8 (xcuitest.sh's guest-staging convention), and #10 (PAT/password on guest ssh argv) are parked per the reviewer's ruling — not touched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
R-T7-a (#1): tool.daemon's launchd/worktrees sub-facts are real negative signals now, not folded into a "ready" detail — either failing flips the row to "invalid" with the specific fact named. R-T7-b (#4): the legacy split-state branch (required, invalid) carries a {type:"steps"} merge-by-hand remedy instead of action:null; the detail also gets verify's plural handling back. R-T7-c (#6): fixes the bundle-memo hazard at its source. appBundleRoot() (lib/bundle-layout.ts) now memoizes only the true default (exists === existsSync); an injected exists (every Probes-driven caller) never reads or writes it. Validator tests drop the reset ceremony this made unnecessary. R-T7-d (#12): tool.rt-link's needs-you branch carries a {type:"run"} action to fix the link in one step. #2/#3: tool.fzf and tool.rt now distinguish "genuinely absent" (127) from "resolved but won't run" (any other exit) — the latter is "error", never "ready"/"missing". #5: tool.daemon and tool.app get recheck:"on-activate" (Task 6's convention for out-of-band, leave-the-app-and-come-back rows). #7: the five optional rows carry real optionalNotes. #8: tool.app's legacy note names the exact hit path(s), matching verify's phrasing. #9: interceptsRow wraps shimReport()/staleIntercepts() so a throw degrades to an "error" row instead of rejecting the whole plan. #10: tool.daemon's Login Items action is imported from permissions.ts (now exported as LOGIN_ITEMS_SETTINGS_ACTION) instead of a duplicate literal. #11: lib/shell-integration.ts gains detectShellFrom()/shellRcPathFor(), pure functions the real detectShell()/shellRcPath() now delegate to and tool.shell reuses over Probes; an unrecognized shell gets an honest "can't write automatically" detail instead of "Install writes it". #13: the tool.daemon describe saves/restores DAEMON_CONFIG_PATH's pre-existing content around the whole block instead of only deleting it, so status-fallback.test.ts's absence assumption can't be poisoned. #14/#15: header comment no longer cites the brief's table, the rt-link "no app" test asserts its reason string, and commands/verify.ts's docblock is trimmed to the one load-bearing line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
H1+S+E of the home-repo program (spec: docs/superpowers/specs/2026-08-20-suite-settings-migration.md): rt home init/key export (age key in keychain, password-manager channel), sops-backed rt secrets store + verbs (values never on argv or in logs; staged outside tracked paths; decrypt round-trip before rename), all rt secrets consumers on the encrypted store with a transition plaintext fallback, token-gated secrets:read daemon verb for the rt-context extension, settings machinery extracted to @mattstack/rt-client 0.3.0 (pure move, byte-verified), and the 47-key suite registry (rt/deck/board/gitq/mattstack/claude).
Live state already provisioned on this machine: ~/.mattstack is the home repo (m4ttheweric/mattstack-home) with mattstack-prefs history folded under user/; 13 credentials encrypted + pushed.
Gates: tsc 0; widened test gate 1552 unit + 75 e2e + 156 rt-client green on fresh dist/rt; extension isolated build green. Whole-branch review + fix wave applied; rulings in the session ledger.
🤖 Generated with Claude Code
Summary by CodeRabbit