11# authmux v0.1.25
22
3+ Now-horizon release: themes ** N1** (durability), ** N2** (account-service
4+ split), ** N3** (error taxonomy + ` --json ` ), ** N4** (lazy path resolvers),
5+ plus the ** P0 wave** from the 18k-line improvement protocol.
6+
37## Added
4- - ` src/infra/fs/atomic-write.ts ` — single durable file-write primitive with
5- fsync-before-rename and POSIX dir-fsync after rename, plus mode applied
6- before rename so 0600 files are never visible at a looser mode.
7- - ` src/infra/fs/registry-lock.ts ` — advisory lock around ` registry.json `
8- with PID-liveness probing plus a 30 s wall-clock stale-lock heuristic.
9- - ` persistRegistryAtomic ` in ` src/lib/accounts/registry.ts ` is now the single
10- registry write path; it reloads under the lock and merges accounts so two
11- concurrent writers (daemon + interactive) no longer race-lose each other's
12- mutations.
13- - ` src/tests/registry-durability.test.ts ` covers the SIGKILL-between-
14- writeFile-and-rename window and the stale-lock-reaping path.
8+
9+ - ** N1 — Atomic writes & registry lock.**
10+ - ` src/infra/fs/atomic-write.ts ` — single durable file-write primitive
11+ with ` fsync ` -before-rename, POSIX dir-fsync after rename, and mode
12+ applied before rename so 0600 files are never visible at a looser mode.
13+ - ` src/infra/fs/registry-lock.ts ` — ` O_EXCL ` advisory lock around
14+ ` registry.json ` with PID-liveness probe plus 30 s wall-clock stale-lock
15+ heuristic.
16+ - ` persistRegistryAtomic ` in ` src/lib/accounts/registry.ts ` — single
17+ registry write path; reloads under the lock and merges account entries
18+ so two concurrent writers no longer race-lose each other's mutations.
19+ - ` src/tests/registry-durability.test.ts ` — covers SIGKILL between
20+ ` writeFile ` and ` rename ` plus the stale-lock-reaping path.
21+ - ** N2 — ` account-service.ts ` decomposition.** 1675 LOC → 164 LOC
22+ orchestrator backed by 12 focused modules under
23+ ` src/lib/accounts/{sync,read,write,config,auto-switch,usage,safety,session,identity}/ `
24+ plus 4 ` _internal/ ` helpers. 31 new unit tests across the clusters.
25+ - ** N3 — Error taxonomy.** ` AuthmuxError ` base class in
26+ ` src/lib/accounts/errors.ts ` with stable ` code ` , ` severity ` , ` hint ` ,
27+ ` details ` , and ` toJSON() ` . Every existing error
28+ (` AuthFileMissingError ` , ` AccountNotFoundError ` ,
29+ ` NoAccountsSavedError ` , ` InvalidAccountNameError ` ,
30+ ` AccountNameInferenceError ` , ` SnapshotEmailMismatchError ` ,
31+ ` PromptCancelledError ` , ` InvalidRemoveSelectionError ` ,
32+ ` AmbiguousAccountQueryError ` , ` AutoSwitchConfigError ` ) now carries an
33+ ` E_* ` code. ` src/tests/error-taxonomy.test.ts ` enforces the §6.2 code
34+ allowlist and §6.3 exit-code table.
35+ - ** N3 — ` --json ` flag** on ` list ` , ` current ` , ` status ` , ` use ` , ` save ` .
36+ Emits a single JSON envelope on stdout (` { ok, data } ` or
37+ ` { ok, error: { code, severity, message, hint, details } } ` ).
38+ Interactive prompts are skipped under ` --json ` .
39+ - ** N4 — Lazy path resolver tests.** ` src/tests/paths.test.ts ` proves
40+ env-var changes apply after module load.
41+ - ** P0 — CI test matrix.** New GitHub Actions workflow runs ` npm test `
42+ across Ubuntu/macOS/Windows × Node 18/20/22 (previously only an LLM
43+ review bot existed; ` npm test ` was never run in CI).
44+ - ** Docs.** ` docs/future/* ` — 19 source-grounded improvement documents
45+ (~ 18 016 lines) covering architecture, accounts, paths, config, daemon,
46+ usage, hooks, multi-CLI, security, testing, release, observability,
47+ cross-platform, perf, docs, roadmap, glossary.
1548
1649## Changed
17- - ` secureWriteFile ` now delegates to ` atomicWriteFile ` ; behavior is the same
18- (atomic temp+rename, 0600 perms) but every snapshot, ` current ` , and
19- ` sessions.json ` write now also fsyncs the file and the containing directory
20- before returning.
21- - ` AccountService.useAccount ` no longer calls ` saveRegistry ` directly; all
22- registry writes go through the locked ` persistRegistry ` path.
50+
51+ - ** N1.** ` secureWriteFile ` now delegates to ` atomicWriteFile ` ; every
52+ snapshot, ` current ` , and ` sessions.json ` write fsyncs the file and the
53+ containing directory before returning.
54+ - ** N1.** ` AccountService.useAccount ` no longer calls ` saveRegistry `
55+ directly; all registry writes route through the locked
56+ ` persistRegistry ` path.
57+ - ** N2.** ` AccountService ` is now a thin orchestrator (164 LOC). All 21
58+ public method signatures preserved; the singleton in
59+ ` src/lib/accounts/index.ts ` is byte-compatible.
60+ - ** N3.** ` BaseCommand ` central error handler routes ` AuthmuxError `
61+ instances to the JSON envelope under ` --json ` and exits with the §6.3
62+ exit code (` 3 ` ` E_AUTH_MISSING ` , ` 4 ` ` E_ACCOUNT_NOT_FOUND ` ,
63+ ` 5 ` ` E_SNAPSHOT_EMAIL_MISMATCH ` , ` 6 ` ` E_REGISTRY_LOCKED ` ,
64+ ` 7 ` ` E_REGISTRY_CORRUPT ` , ` 8 ` ` E_PROVIDER_NOT_INSTALLED ` ,
65+ ` 64 ` ` E_PROMPT_CANCELLED ` , ` 1 ` generic).
66+ - ** P0.** Init-hook update prompt flipped from ` [Y/n] ` (default-yes) to
67+ ` [y/N] ` (default-no) in ` src/hooks/init/update-notifier.ts ` . Bare
68+ ` authmux ` invocations no longer auto-install updates by default.
2369
2470## Fixed
25- - Half-written ` registry.json ` after SIGKILL or laptop sleep no longer
26- shadows the on-disk file: the rename is the commit point.
27- - Concurrent ` authmux daemon --watch ` + ` authmux use foo ` mutations are
28- serialized through the registry lock.
71+
72+ - ** N1.** Half-written ` registry.json ` after SIGKILL or laptop sleep no
73+ longer shadows the on-disk file; the rename is the commit point.
74+ - ** N1.** Concurrent ` authmux daemon --watch ` + ` authmux use <name> `
75+ mutations are serialized through the registry lock.
76+ - ** P0.** ` registry.ts ` sanitization no longer drops ` "proxy" ` as a
77+ usage source; saved snapshots with ` source: "proxy" ` round-trip
78+ correctly instead of being coerced to ` "cached" ` .
79+ - ** P0.** ` src/commands/kiro.ts ` no longer throws ` ENOENT ` when the
80+ target file does not exist (the ` fs.existsSync(...) || fs.lstatSync(...).isSymbolicLink() `
81+ short-circuit was inverted).
2982
3083## Deprecated
31- - None.
84+
85+ - ` codexDir ` , ` accountsDir ` , ` authPath ` , ` currentNamePath ` ,
86+ ` registryPath ` , ` sessionMapPath ` in ` src/lib/config/paths.ts ` — the bare
87+ eager constants. Use the ` resolveX() ` getters instead so env-var
88+ overrides set after import are honored. Scheduled for removal in
89+ ** v0.2.0** .
3290
3391## Removed
92+
3493- None.
3594
3695## Security
37- - File mode 0600 is applied to the temp file BEFORE the rename, closing the
38- brief window where the previous chmod-after-rename path could expose
39- freshly-renamed files at the umask default.
4096
41- ## Durability
97+ - ** 0600 / 0700 perms.** ` secureWriteFile ` (now backed by
98+ ` atomicWriteFile ` ) applies file mode 0600 to the temp file * before*
99+ the rename, closing the brief window where chmod-after-rename could
100+ expose freshly-renamed snapshot, ` current ` , or ` sessions.json ` files at
101+ the umask default. Accounts dir is created 0700.
102+ - ** Atomic mode-before-rename** means there is no observable moment at
103+ which a refresh-token file exists on disk at a looser mode than 0600.
104+ - ** Default-no update prompt.** Flipping the init-hook update prompt to
105+ ` [y/N] ` reduces the chance of accidental unattended updates triggered
106+ by a bare ` authmux ` invocation in CI or scripts.
107+
108+ ## Migration
109+
110+ No migration is required for end users. The on-disk layout of
111+ ` registry.json ` , ` current ` , ` sessions.json ` , and per-account snapshot
112+ files is unchanged.
113+
114+ External library consumers of ` src/lib/config/paths.ts ` should switch
115+ from the bare constants (` codexDir ` , ` accountsDir ` , ` authPath ` ,
116+ ` currentNamePath ` , ` registryPath ` , ` sessionMapPath ` ) to the resolver
117+ functions (` resolveCodexDir() ` , ` resolveAccountsDir() ` , ` resolveAuthPath() ` ,
118+ ` resolveCurrentNamePath() ` , ` resolveRegistryPath() ` ,
119+ ` resolveSessionMapPath() ` ) before ** v0.2.0** .
120+
121+ CLI consumers that grep stdout still work — human-readable ` message `
122+ strings are unchanged. New scripts should prefer ` --json ` for stable
123+ parsing.
124+
125+ ---
126+
127+ ## Theme deep-dives
128+
129+ ### Durability (N1)
42130
43131This release closes the two data-loss windows tracked under Theme N1 of
44132` docs/future/17-ROADMAP.md ` :
@@ -48,19 +136,61 @@ This release closes the two data-loss windows tracked under Theme N1 of
48136 and (on POSIX) ` fsync ` s the containing directory. A power loss between
49137 any of these steps leaves the previous content of the target intact.
501382 . ** Concurrent-writer lost mutations.** ` withRegistryLock ` serializes
51- writers; ` persistRegistryAtomic ` reloads the registry under the lock and
52- merges accounts before writing so neither writer silently discards the
53- other.
139+ writers; ` persistRegistryAtomic ` reloads the registry under the lock
140+ and merges accounts before writing so neither writer silently discards
141+ the other.
54142
55- Crash-safety guarantees promised after this release match the table in
143+ Crash-safety guarantees match the table in
56144` docs/future/01-ARCHITECTURE.md ` §5.3 rows 1, 2, and 4.
57145
58146` fsync ` adds latency on spinning disks; on SSDs it is dominated by the
59- network/parse work the CLI was already doing and is not user-visible. The
60- dir-fsync is gated on ` process.platform !== "win32" ` because Windows does
61- not allow opening a directory as a file.
147+ network/parse work the CLI was already doing and is not user-visible.
148+ The dir-fsync is gated on ` process.platform !== "win32" ` because Windows
149+ does not allow opening a directory as a file.
62150
63- ## Migration
151+ ### Account-service split (N2)
152+
153+ | Module | LOC | Cluster |
154+ | --- | --- | --- |
155+ | ` sync/external-sync.ts ` | 216 | external-auth sync orchestrator |
156+ | ` read/listing.ts ` | 211 | listings + ` getCurrentAccountName ` + find |
157+ | ` write/save.ts ` | 159 | ` saveAccount ` + safety guard + name inference |
158+ | ` write/use.ts ` | 128 | ` useAccount ` + ` activateSnapshot ` |
159+ | ` write/remove.ts ` | 105 | remove one / by-query / all |
160+ | ` config/auto-switch-config.ts ` | 82 | status + threshold setters |
161+ | ` auto-switch/policy.ts ` | 141 | ` runAutoSwitchOnce ` + ` runDaemon ` |
162+ | ` usage/adapter.ts ` | 192 | refresh + proxy shim |
163+ | ` safety/snapshot-vault.ts ` | 125 | backup vault + clobber recovery |
164+ | ` session/pin.ts ` | 243 | sessions.json I/O + Linux PPID heuristic |
165+ | ` identity/equality.ts ` | 86 | snapshot identity comparisons (pure) |
166+ | ` naming.ts ` | 40 | ` normalizeAccountName ` + ` accountFilePath ` |
167+
168+ Orchestrator ` account-service.ts ` : ** 164 LOC** (was 1675; ceiling was
169+ 400 per N2 exit criteria).
170+
171+ ### Error taxonomy & --json (N3)
172+
173+ JSON envelope shape:
174+
175+ ``` jsonc
176+ // success
177+ { " ok" : true , " data" : { /* command-specific payload */ } }
178+
179+ // error
180+ { " ok" : false ,
181+ " error" : { " code" : " E_ACCOUNT_NOT_FOUND" , " severity" : " fatal" ,
182+ " message" : " ..." , " hint" : " ..." ,
183+ " details" : { " name" : " alice" } } }
184+ ```
185+
186+ ` CodexAuthError ` is preserved as a back-compat subclass of
187+ ` AuthmuxError ` so existing ` instanceof CodexAuthError ` catches continue
188+ to work.
189+
190+ ### Lazy path resolvers (N4)
64191
65- No migration is required. The on-disk layout of ` registry.json ` , ` current ` ,
66- ` sessions.json ` , and the per-account snapshot files is unchanged.
192+ The bare exports in ` src/lib/config/paths.ts ` were evaluated at module
193+ import time, so env-var overrides set after the first ` import ` had no
194+ effect. The ` resolveX() ` getters re-read the environment on every call;
195+ all internal call sites already use them. The bare constants are kept
196+ through one release for external consumers.
0 commit comments