instar 1.3.276 → 1.3.278

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
1
+ # Side-effects review — Mandates dashboard tab + coordination awareness backfill
2
+
3
+ Spec: `docs/specs/coordination-mandate.md` (approved A/A/B; decision 2A makes the
4
+ dashboard-behind-PIN the issuance surface). Change: a new dashboard **Mandates** tab
5
+ (`dashboard/mandates.js` external module + index.html shim) + the Migration-Parity
6
+ CLAUDE.md backfill for the three coordination surfaces shipped in #788/#797/#799.
7
+
8
+ ## 1. Blast radius
9
+
10
+ - **Dashboard:** additive — one new tab button, panel, TAB_REGISTRY entry, lazy
11
+ module import (the process-health.js pattern), and scoped `.mnd-*` CSS. No existing
12
+ tab, function, or style is modified. The dashboard ships inside the npm package, so
13
+ existing agents receive the tab on their next update with no migration.
14
+ - **PostUpdateMigrator:** three new content-sniffed CLAUDE.md backfill blocks
15
+ (Coordination Mandate / ReviewExchange / Cutover Readiness), appended only when the
16
+ distinctive route marker is absent — idempotent, never rewrites existing content.
17
+ This closes a REAL parity gap: #788/#797/#799 added the template blurbs for new
18
+ agents but no backfill, so the deployed fleet would never have learned these
19
+ surfaces exist.
20
+ - **templates.ts:** one line sharpened (point the operator at the "Mandates tab"
21
+ rather than the generic dashboard).
22
+
23
+ ## 2. Security model (the PIN discipline, in the UI this time)
24
+
25
+ - The tab's issue/revoke forms collect the PIN in a `type="password"` input with
26
+ `autocomplete="off"`, send it ONCE in the request body to the already-PIN-gated
27
+ routes, and **clear the field immediately after the response — success or failure**.
28
+ It is never written to localStorage, module state, or logs (behaviorally tested).
29
+ - The Bearer token the dashboard holds cannot issue/revoke — that is the engine's
30
+ server-side design (#788); this tab is just the human surface decision 2A names.
31
+ - Rendering is XSS-safe: every dynamic field (ids, scopes, fingerprints, authority
32
+ bounds, audit reasons — all potentially attacker-influenced via the API) is escaped
33
+ (tested with hostile payloads).
34
+ - A broken audit chain (`chain.ok:false`) renders as a loud "CHAIN BROKEN — possible
35
+ tampering" badge, never silently.
36
+
37
+ ## 3. State / data
38
+
39
+ None on the server. The tab reads `/mandate` + `/mandate/audit` and writes only
40
+ through the existing PIN-gated routes. Browser-side, nothing persists beyond the
41
+ already-stored dashboard Bearer token (unchanged).
42
+
43
+ ## 4. Failure modes
44
+
45
+ - Older server without the engine → 503 → friendly "engine unavailable" copy.
46
+ - Module load failure → console error, tab simply shows nothing (no crash).
47
+ - Wrong PIN → the route's 403 + attempt limiting surface as an inline error; the
48
+ field still clears.
49
+
50
+ ## 5. Test coverage
51
+
52
+ 18 tests: 5 HTML-at-rest wiring (tab/panel/registry/lazy-import/plain-language), 4
53
+ renderer (deny-by-default copy, badges, XSS with hostile payloads, broken-chain), 6
54
+ controller (PIN required → no request; PIN sent once then CLEARED on success AND on
55
+ 403; template prefill; 503 state), 4 migration (backfill lands with port baked in,
56
+ security copy verbatim, idempotent, no double-patch on fresh agents).
@@ -0,0 +1,113 @@
1
+ # Side-Effects Review — Vault key coherence (CMT-1038)
2
+
3
+ **Version / slug:** `vault-key-coherence`
4
+ **Date:** `2026-06-05`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required` (crypto algorithm unchanged; resolution/format layers only; every boundary side test-pinned; v1 reads preserved; failure modes strictly improve on today's silent-empty)
7
+
8
+ ## Summary of the change
9
+
10
+ Three layers killing the key-bifurcation disease: (1) per-agent keychain
11
+ accounts (`master-key:<stateDir>`) with read-time adoption of the legacy
12
+ global slot (which is never written again); (2) v2 store format with a keyId
13
+ header (`'ISv2'|keyId(8)|iv|tag|ct`) so wrong-key is a precise, named error
14
+ distinct from corruption — v1 files read forever; (3) dual-key read fallback
15
+ (primary first, then the other source, read-only alternates) with a loud
16
+ DegradationReporter on divergence and natural convergence on the next write.
17
+ Plus: `GET /secrets/sync-status` now reports `vault: {status:'ok'|'empty'|
18
+ 'decrypt-failed', error?}` instead of masking decrypt-failure as empty.
19
+ Keychain ops are injectable (`SecretStoreConfig.keychainOps`) so all of this
20
+ is unit-tested against a fake; the #789 real-keychain test guard holds
21
+ whenever no fake is injected.
22
+
23
+ ## Decision-point inventory (each side pinned)
24
+
25
+ 1. Per-agent slot present / legacy-only (adopt, global untouched) / neither
26
+ (generate → per-agent slot, NEVER global) — 3 tests.
27
+ 2. v2 keyId match / no-match (named error, "NOT empty") / matching-key
28
+ corruption (GCM auth error) — 3 tests.
29
+ 3. v1 primary-key read / v1 fallback-key read (THE incident case) /
30
+ write-after-fallback converges to primary / forceFileKey primary with
31
+ keychain alternate — 4 tests.
32
+ 4. Guard: no injected fake + VITEST ⇒ file-key only — 1 test.
33
+ 5. vaultStatus: ok / empty / decrypt-failed (route surface).
34
+
35
+ ## 1. Over-block
36
+
37
+ Nothing readable today becomes unreadable: every key the old code could
38
+ resolve is still a candidate, plus more. The new thrown errors fire ONLY
39
+ where today's code also throws (v1, both keys wrong) or silently lied
40
+ (reported empty); the route now reports those as `decrypt-failed` instead of
41
+ `[]`.
42
+
43
+ ## 2. Under-block
44
+
45
+ - The legacy global slot remains readable machine-wide until every agent
46
+ adopts — by design (deleting it would break un-migrated agents). It decays
47
+ naturally: nothing writes it, every reader adopts away from it.
48
+ - Dual-key fallback widens which keys may open a vault to keys the SAME agent
49
+ resolves from ITS OWN two sources — no new principals, no cross-agent keys.
50
+ - Convergence-on-write re-encrypts with the primary; if another process of
51
+ the same agent still resolves only the stale alternate, IT then uses its
52
+ own fallback (now in the other direction) and converges the same way — the
53
+ fixed point is both sources agreeing, reached after adoption.
54
+
55
+ ## 3. Level-of-abstraction fit
56
+
57
+ Resolution lives in MasterKeyManager (the single key authority); format +
58
+ fallback in SecretStore.read/write (the single at-rest codec); the route only
59
+ gains a status field. Wire-sync crypto (X25519/HKDF) untouched.
60
+
61
+ ## 4. Signal vs authority compliance
62
+
63
+ **Reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
64
+ No gating added. Errors got MORE precise; the degradation report is
65
+ signal-only; adoption/convergence are self-healing writes within the agent's
66
+ own key material.
67
+
68
+ ## 5. Interactions
69
+
70
+ - Store-first drop persistence (#789), sealed-handoff at-rest, and the sync
71
+ provisioner all read through the same read() — regression suites green
72
+ (21/21).
73
+ - Echo's live vault (v1, file-key, keychain slot deleted in the incident
74
+ remediation): reads via primary file key unchanged; first write upgrades to
75
+ v2 with the file keyId. No migration step needed anywhere — resolution-time
76
+ adoption + write-time format upgrade.
77
+ - Mixed fleet versions: older readers can't read v2 — but a vault is only
78
+ read by ITS OWN agent on ITS OWN machine (wire sync re-encrypts per peer),
79
+ so the only mixed-version reader of a given file is the same agent
80
+ mid-update, which restarts onto the new code. Residual window ≈ one
81
+ restart; documented.
82
+
83
+ ## 6. External surfaces
84
+
85
+ `vault` field on sync-status (additive); exported `perAgentKeychainAccount`/
86
+ `keyIdOf` helpers; `SecretStoreConfig.keychainOps` (test seam). No config
87
+ flags, no migration, no new routes.
88
+
89
+ ## 7. Rollback cost
90
+
91
+ Revert restores old code — which reads v1 files only. Any vault already
92
+ upgraded to v2 would need its 28-byte header stripped (`tail -c +13` of the
93
+ post-magic body) — documented here as the rollback runbook; low likelihood
94
+ (revert would only follow a fast-caught regression, before fleet-wide v2
95
+ rewrites).
96
+
97
+ ## Conclusion
98
+
99
+ The recurring "vault looks empty" disease removed at its three roots —
100
+ shared slot, mute wrong-key failures, single-key reads — with strictly more
101
+ diagnosable failure modes and zero operator action.
102
+
103
+ ## Second-pass review (if required)
104
+
105
+ Not required — see header.
106
+
107
+ ## Evidence pointers
108
+
109
+ - `tests/unit/secret-store-key-coherence.test.ts` (11, incl. THE incident
110
+ case verbatim and convergence-on-write).
111
+ - `tests/unit/secret-store.test.ts` 29/29; secret-adjacent regression suites
112
+ 21/21 (store-first integration + sealed-handoff + hardening + e2e).
113
+ - `docs/specs/vault-key-coherence.md` + `.eli16.md`. <!-- tracked: CMT-1038 -->