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.
- package/dashboard/index.html +104 -0
- package/dashboard/mandates.js +205 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +11 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +44 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SecretStore.d.ts +61 -10
- package/dist/core/SecretStore.d.ts.map +1 -1
- package/dist/core/SecretStore.js +248 -115
- package/dist/core/SecretStore.js.map +1 -1
- package/dist/core/SecretSync.d.ts +7 -0
- package/dist/core/SecretSync.d.ts.map +1 -1
- package/dist/core/SecretSync.js.map +1 -1
- package/dist/scaffold/templates.js +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +3 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/src/scaffold/templates.ts +1 -1
- package/upgrades/1.3.277.md +50 -0
- package/upgrades/1.3.278.md +36 -0
- package/upgrades/side-effects/mandates-dashboard-tab.md +56 -0
- package/upgrades/side-effects/vault-key-coherence.md +113 -0
|
@@ -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 -->
|