instar 1.3.270 → 1.3.272
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 +73 -11
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +15 -2
- package/dist/commands/server.js.map +1 -1
- package/dist/coordination/ReviewExchange.d.ts +135 -0
- package/dist/coordination/ReviewExchange.d.ts.map +1 -0
- package/dist/coordination/ReviewExchange.js +192 -0
- package/dist/coordination/ReviewExchange.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +33 -1
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SecretStore.d.ts.map +1 -1
- package/dist/core/SecretStore.js +10 -1
- package/dist/core/SecretStore.js.map +1 -1
- package/dist/core/TransferByNickname.d.ts +6 -0
- package/dist/core/TransferByNickname.d.ts.map +1 -1
- package/dist/core/TransferByNickname.js +18 -6
- package/dist/core/TransferByNickname.js.map +1 -1
- package/dist/core/seamlessnessConfig.d.ts.map +1 -1
- package/dist/core/seamlessnessConfig.js +12 -1
- package/dist/core/seamlessnessConfig.js.map +1 -1
- package/dist/core/types.d.ts +23 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/feedback-factory/cutoverReadiness.d.ts +103 -0
- package/dist/feedback-factory/cutoverReadiness.d.ts.map +1 -0
- package/dist/feedback-factory/cutoverReadiness.js +118 -0
- package/dist/feedback-factory/cutoverReadiness.js.map +1 -0
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +13 -1
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/scheduler/MentorAutonomousGuardian.d.ts.map +1 -1
- package/dist/scheduler/MentorAutonomousGuardian.js +1 -0
- package/dist/scheduler/MentorAutonomousGuardian.js.map +1 -1
- package/dist/server/AgentServer.d.ts +6 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +54 -6
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +28 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +12 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +255 -4
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/scaffold/templates.ts +13 -1
- package/upgrades/1.3.271.md +154 -0
- package/upgrades/1.3.272.md +40 -0
- package/upgrades/autoloop-gate-compliance.eli16.md +11 -0
- package/upgrades/side-effects/autoloop-gate-compliance.md +43 -0
- package/upgrades/side-effects/cutover-readiness.md +63 -0
- package/upgrades/side-effects/dashboard-pool-sessions.md +103 -0
- package/upgrades/side-effects/review-exchange-protocol.md +60 -0
- package/upgrades/side-effects/secret-drop-store-first.md +117 -0
- package/upgrades/side-effects/transfer-idempotency-exactly-once.md +105 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: minor -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
One additive line in the mentor autoloop prompt (`src/scheduler/MentorAutonomousGuardian.ts`), locked by three new unit-test assertions. Earned from PR #792's no-silent-fallbacks CI failure, where a spec's "best-effort, never throws" wording invited the swallowed catch the ratchet counts.
|
|
9
|
+
|
|
10
|
+
The dashboard sessions list now shows EVERY session across ALL machines in the
|
|
11
|
+
multi-machine pool, and every session row states which machine it runs on
|
|
12
|
+
(a machine-nickname badge). Sessions on the machine you're viewing stay fully
|
|
13
|
+
interactive (live terminal, close button); sessions on other machines appear
|
|
14
|
+
as informational rows with a tooltip pointing at the owning machine's
|
|
15
|
+
dashboard.
|
|
16
|
+
|
|
17
|
+
Under the hood: `GET /sessions` self-tags sessions with `machineId` /
|
|
18
|
+
`machineNickname` when the pool is wired, and a new `GET /sessions?scope=pool`
|
|
19
|
+
aggregates every reachable peer's session list (existing cross-machine Bearer
|
|
20
|
+
auth, 5s timeout per peer). A dead or slow peer degrades to a `pool.failed`
|
|
21
|
+
entry — the page never breaks. The plain `/sessions` route keeps its
|
|
22
|
+
back-compatible array shape.
|
|
23
|
+
|
|
24
|
+
Added the ReviewExchange protocol — the structured way two mandate-named agents complete
|
|
25
|
+
a mutual code-review sign-off without the operator relaying messages between them. One
|
|
26
|
+
exchange tracks one review package (content-addressed by checksum, fixed at creation)
|
|
27
|
+
through a strict linear lifecycle: proposed, delivered, verdict recorded, complete — or
|
|
28
|
+
changes-requested, which is terminal so a stale approval can never be replayed onto
|
|
29
|
+
reworked code. Both sign-offs — the reviewing peer's authenticated approval and the
|
|
30
|
+
owner's countersignature — are checked against the operator's coordination mandate
|
|
31
|
+
before acceptance, and every accepted signature stores the audit reference of the gate
|
|
32
|
+
decision that authorized it. With no mandate issued, every sign-off refuses: the
|
|
33
|
+
protocol inherits deny-by-default and ships inert.
|
|
34
|
+
|
|
35
|
+
A submitted Secret Drop is now persisted store-first to the durable
|
|
36
|
+
AES-256-GCM encrypted SecretStore the instant it is received, instead of
|
|
37
|
+
living only in the server's in-memory `received` map. The retrieve route
|
|
38
|
+
transparently falls back to the durable copy when the in-memory one is gone
|
|
39
|
+
(server restart, compaction, cross-machine handoff), and a successful consume
|
|
40
|
+
deletes both copies — one-time semantics are preserved. Opt out with
|
|
41
|
+
`secrets.persistDrops: false` in `.instar/config.json`.
|
|
42
|
+
|
|
43
|
+
This closes the recurring "the user dropped a secret and the agent lost it"
|
|
44
|
+
failure: with the fleet shipping many releases a day, every release restarted
|
|
45
|
+
the server and evaporated any un-consumed submission.
|
|
46
|
+
|
|
47
|
+
Also: the SecretStore master-key manager now forces file-key mode during test
|
|
48
|
+
runs (`VITEST` / `NODE_ENV=test`). The OS keychain entry is machine-global —
|
|
49
|
+
a test constructing a SecretStore against a fresh stateDir used to generate a
|
|
50
|
+
new master key and silently overwrite that shared entry, making every real
|
|
51
|
+
agent vault on the machine undecryptable for keychain-resolving readers. No
|
|
52
|
+
test can touch the real keychain anymore. A `secrets.forceFileKey` config
|
|
53
|
+
plumb is also available for operators who want to pin the key source.
|
|
54
|
+
|
|
55
|
+
The CLAUDE.md template's Secret Drop "Security" bullet is rewritten (the old
|
|
56
|
+
"in-memory only, never written to disk" claim is no longer true), and an
|
|
57
|
+
idempotent migration updates existing agents' CLAUDE.md in place.
|
|
58
|
+
|
|
59
|
+
Two fixes for the 2026-06-05 "move this to the laptop" noise incident, where
|
|
60
|
+
one user message produced contradictory "Moving to Laptop" / "I can't move
|
|
61
|
+
this right now (rate-limited)" replies and executed multiple times.
|
|
62
|
+
|
|
63
|
+
1. **Idempotent topic transfer.** The transfer planner now recognizes a move
|
|
64
|
+
request targeting the machine the topic is already on — or already pinned
|
|
65
|
+
to — BEFORE the rate limit. Duplicates of an already-satisfied move answer
|
|
66
|
+
"This conversation is already running on X — nothing to move" instead of a
|
|
67
|
+
rate-limit rejection. The rate limit keeps its real job: damping rapid
|
|
68
|
+
flip-flops between DIFFERENT machines.
|
|
69
|
+
|
|
70
|
+
2. **Exactly-once ingress turns on with the pool.** The exactly-once inbound
|
|
71
|
+
message gate (the dedupe ledger that stops retried/replayed messages from
|
|
72
|
+
re-executing commands) shipped default-OFF — even on machines whose
|
|
73
|
+
multi-machine session pool was live. The default is now stage-aware: a pool
|
|
74
|
+
at `live-transfer` or `rebalance` gets `exactlyOnceIngress` ON
|
|
75
|
+
automatically. An explicit `multiMachine.exactlyOnceIngress: false` still
|
|
76
|
+
wins.
|
|
77
|
+
|
|
78
|
+
## What to Tell Your User
|
|
79
|
+
|
|
80
|
+
Nothing user-visible. The autonomous mentor loop's instructions now include how to satisfy the repo's ratchet CI gates, so mentor-driven fixes stop tripping no-silent-fallbacks.
|
|
81
|
+
|
|
82
|
+
Open the dashboard on any of your machines and you'll see all your sessions in
|
|
83
|
+
one list — each with a badge saying which machine it's running on. No more
|
|
84
|
+
opening one dashboard per machine to find a session.
|
|
85
|
+
|
|
86
|
+
Your agent can now run a real code review with a partner agent under the permission
|
|
87
|
+
slip you issue — no more relaying review messages between them yourself. The agents
|
|
88
|
+
deliver the code package, record the reviewer's verdict, and counter-sign, and every
|
|
89
|
+
step is checked against your permission slip and written to the same tamper-evident
|
|
90
|
+
log. If you revoke the slip mid-review, the review stops on the spot. Until you issue
|
|
91
|
+
a slip, nothing changes — every review step refuses by default.
|
|
92
|
+
|
|
93
|
+
If you hand me a secret through a drop link, it now survives my server
|
|
94
|
+
restarting, updating, or the conversation moving between machines. You should
|
|
95
|
+
never have to re-send a secret because "I lost it" again. Secrets are stored
|
|
96
|
+
encrypted and are deleted as soon as they're consumed.
|
|
97
|
+
|
|
98
|
+
Asking me to move a conversation to a machine it's already on (or repeating
|
|
99
|
+
the request) now gets a calm "already running there" instead of a confusing
|
|
100
|
+
"rate-limited" error. And if my internals retry a message under load, the
|
|
101
|
+
command runs once — not four times.
|
|
102
|
+
|
|
103
|
+
## Summary of New Capabilities
|
|
104
|
+
|
|
105
|
+
- `buildAutoloopGoal` includes a gate-compliance paragraph: intentional fail-open catches must report via DegradationReporter or carry an inline `@silent-fallback-ok` justification — never a ratchet baseline bump.
|
|
106
|
+
- The mentor is instructed to spell out these gate notes in any task brief or spec it authors for another agent.
|
|
107
|
+
|
|
108
|
+
- Dashboard sessions list shows every session across the pool with a machine badge per row.
|
|
109
|
+
- `GET /sessions?scope=pool` → `{ sessions, pool: { peersOk, failed, ... } }` (agents can answer "what's running across my machines?").
|
|
110
|
+
- `GET /sessions` sessions carry `machineId`/`machineNickname` on pooled installs.
|
|
111
|
+
- CLAUDE.md template bullet + idempotent migration so existing agents learn the capability.
|
|
112
|
+
|
|
113
|
+
- Create a review exchange for an artifact under a mandate, content-addressed so the
|
|
114
|
+
signatures bind to exactly the code that was reviewed.
|
|
115
|
+
- Record delivery, the peer's authenticated verdict, and the owner's countersignature —
|
|
116
|
+
with both sign-offs evaluated through the mandate gate and refused on any deny.
|
|
117
|
+
- Inspect exchanges and their signatures, each carrying the audit reference of the
|
|
118
|
+
gate decision that authorized it.
|
|
119
|
+
- Maturity: stable engine and routes; ships inert (deny-by-default) until the operator
|
|
120
|
+
issues a mandate with the sign-code-review authority.
|
|
121
|
+
|
|
122
|
+
- Secret Drop submissions persist store-first to the encrypted SecretStore and survive server churn.
|
|
123
|
+
- Retrieval transparently falls back to the durable copy; consume deletes both copies.
|
|
124
|
+
- `secrets.persistDrops: false` opts out (default ON).
|
|
125
|
+
- Tests can never pollute the machine-global SecretStore keychain entry (structural guard).
|
|
126
|
+
- `secrets.forceFileKey` pins the master-key source to the per-agent file key.
|
|
127
|
+
|
|
128
|
+
- Duplicate "move to X" requests no-op as "already there" — never "rate-limited."
|
|
129
|
+
- The transfer planner accepts an optional `currentPinOf` hook (pin-aware idempotency).
|
|
130
|
+
- `exactlyOnceIngress` defaults ON whenever the session pool routes real traffic.
|
|
131
|
+
|
|
132
|
+
## Evidence
|
|
133
|
+
|
|
134
|
+
Three tiers green: `tests/unit/dashboard-sessionMachineBadge.test.ts` (8),
|
|
135
|
+
`tests/unit/PostUpdateMigrator-poolSessionsVisibility.test.ts` (3),
|
|
136
|
+
`tests/integration/sessions-pool-scope.test.ts` (5 — including a REAL second
|
|
137
|
+
HTTP server as the peer and a dead-peer degradation case), and
|
|
138
|
+
`tests/e2e/sessions-pool-scope-lifecycle.test.ts` (3 — real AgentServer,
|
|
139
|
+
feature alive single-machine). tsc + lint clean.
|
|
140
|
+
|
|
141
|
+
`tests/integration/secret-drop-store-first.test.ts` (5 route-level tests, both
|
|
142
|
+
sides of every boundary), `tests/unit/secret-store.test.ts` (keychain
|
|
143
|
+
pollution guard), `tests/unit/PostUpdateMigrator-secretDropHardenedRetrieve.test.ts`
|
|
144
|
+
(+3 migration tests), and the Tier-3 e2e
|
|
145
|
+
`tests/e2e/secret-drop-store-first-lifecycle.test.ts` — which boots two REAL
|
|
146
|
+
AgentServers on one stateDir and proves a secret submitted to process 1 is
|
|
147
|
+
retrievable from process 2 after a true restart. tsc + lint clean.
|
|
148
|
+
|
|
149
|
+
`tests/unit/TransferByNickname.test.ts` (+5, including the verbatim incident
|
|
150
|
+
case: already-on-target inside the rate window → noop) and
|
|
151
|
+
`tests/unit/seamlessnessConfig.test.ts` (+4, stage coupling both ways +
|
|
152
|
+
explicit-override both ways). tsc + lint clean. Production logs
|
|
153
|
+
(2026-06-05T04:20Z double pin, zero `exactly-once` lines on a live-transfer
|
|
154
|
+
pool) are the incident evidence.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: minor -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Added the cutover-readiness checker — the read-only surface that answers "is
|
|
9
|
+
everything up to the cutover door green?" from durable server-side state, never from
|
|
10
|
+
anyone's claim. It composes the two objective conditions the migration design names:
|
|
11
|
+
the persisted import-integrity report and the durable zero-divergence parity window
|
|
12
|
+
(now with a freshness bound, so a window that stopped being fed shows as stale instead
|
|
13
|
+
of silently counting as ready). The two mandate conditions that previously always
|
|
14
|
+
evaluated false now resolve from this real state — they still evaluate false until the
|
|
15
|
+
state genuinely clears, so nothing changes behavior today. An agent can trigger a live
|
|
16
|
+
parity check, but the server fetches and compares on its own; the request contributes
|
|
17
|
+
nothing to the result, and a failed check records nothing. There is deliberately no
|
|
18
|
+
route that fires the cutover itself: the flip stays a human action.
|
|
19
|
+
|
|
20
|
+
## What to Tell Your User
|
|
21
|
+
|
|
22
|
+
When a migration is gated on safety checks, you can now see at a glance whether
|
|
23
|
+
everything up to the final switch is green — data integrity verified and live parity
|
|
24
|
+
holding, both read from durable records the agents cannot fake. Your agent can ask the
|
|
25
|
+
server to run a fresh comparison, but it cannot vouch for the result or write it in.
|
|
26
|
+
And the final switch itself is still yours alone: there is intentionally no way for an
|
|
27
|
+
agent to flip it.
|
|
28
|
+
|
|
29
|
+
## Summary of New Capabilities
|
|
30
|
+
|
|
31
|
+
- Read the composed cutover-readiness status: data-integrity verdict, live-parity
|
|
32
|
+
window with freshness, and an explicit marker that the final flip is a manual
|
|
33
|
+
operator action.
|
|
34
|
+
- Trigger a server-side live parity comparison that records its result into the
|
|
35
|
+
durable window — failed checks record nothing.
|
|
36
|
+
- Mandate conditions for gated authorities now resolve from this real durable state
|
|
37
|
+
instead of always refusing, enabling future conditioned delegations to actually
|
|
38
|
+
clear when the evidence does.
|
|
39
|
+
- Maturity: stable; inert in behavior until integrity and parity records exist, and
|
|
40
|
+
the first issued mandate carries no conditioned authority.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Mentor autoloop prompt: gate-compliance line
|
|
2
|
+
|
|
3
|
+
Instar has an automated "mentor loop" — a prompt that tells a developer agent how to run one cycle of dogfooding a mentee agent: check its health, assign it a real task, observe, and fix what breaks as a proper PR.
|
|
4
|
+
|
|
5
|
+
That prompt already teaches the ship discipline (three test tiers, the dev gate, verify-before-claim). But it said nothing about the repo's *ratchet gates* — CI checks like no-silent-fallbacks that count known-bad patterns and fail if the count ever goes UP.
|
|
6
|
+
|
|
7
|
+
We learned why that matters the hard way: a spec told the builder a helper should be "best-effort, never throws." The builder did exactly that — wrapped it in a catch that swallowed the error — and CI went red, because a swallowed catch is exactly what the no-silent-fallbacks ratchet counts. The spec's own wording invited the failure.
|
|
8
|
+
|
|
9
|
+
This change adds one paragraph to the mentor-loop prompt: an intentional fail-open catch must either report the failure (through DegradationReporter) or carry an inline `@silent-fallback-ok` justification — and you never bump a ratchet baseline just to get CI green. It also tells the mentor to spell these notes out whenever it authors a task brief or spec for another agent, so the next builder doesn't get trapped by well-meaning "never throws" guidance.
|
|
10
|
+
|
|
11
|
+
One prompt paragraph, three new test assertions locking it in. No behavior change anywhere else.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Side-Effects Review — Mentor autoloop gate-compliance line
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `autoloop-gate-compliance`
|
|
4
|
+
**Date:** `2026-06-05`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `echo second-pass checklist`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds one static paragraph to `buildAutoloopGoal()` in `src/scheduler/MentorAutonomousGuardian.ts` — the deterministic prompt assembled for the autonomous mentor dogfooding loop. The paragraph teaches ratchet-gate compliance: intentional fail-open catches must report via DegradationReporter or carry an inline `@silent-fallback-ok` justification (never a baseline bump), and task briefs/specs authored for other agents must spell these notes out explicitly. Earned from PR #792's no-silent-fallbacks CI failure, where the spec's "best-effort, never throws" guidance invited the exact swallowed catch the ratchet counts.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `buildAutoloopGoal` — modified — one additional template line appended to the returned prompt string. Pure string assembly; no new branches, no I/O, no config.
|
|
15
|
+
- `tests/unit/MentorAutonomousGuardian.test.ts` — modified — three new content assertions on the assembled prompt.
|
|
16
|
+
|
|
17
|
+
## 1. Over-block
|
|
18
|
+
|
|
19
|
+
None possible. The function returns a string; nothing is gated, blocked, or filtered. The added text instructs the *consuming agent*; it cannot prevent any operation by itself.
|
|
20
|
+
|
|
21
|
+
## 2. Over-permit
|
|
22
|
+
|
|
23
|
+
None. No permission surface is touched.
|
|
24
|
+
|
|
25
|
+
## 3. Token/cost impact
|
|
26
|
+
|
|
27
|
+
The prompt grows by ~430 characters, sent once per autonomous-fix guardian cycle (budget- and min-interval-gated, single-instance). Negligible.
|
|
28
|
+
|
|
29
|
+
## 4. Behavior change for existing consumers
|
|
30
|
+
|
|
31
|
+
The only consumer is `MentorAutonomousGuardian` → spawned loop session goal text. Existing assertions on the prompt (cycle steps, discipline line, topics) are unchanged and still pass — the new line is purely additive. The `degrades gracefully when topics are unset` test still passes (the added text contains no topic interpolation).
|
|
32
|
+
|
|
33
|
+
## 5. Failure modes
|
|
34
|
+
|
|
35
|
+
A string literal cannot throw at runtime. The TypeScript build verifies the template syntax. If the guidance itself were wrong, the blast radius is advisory-only (an agent following it would still produce gate-passing code — DegradationReporter and `@silent-fallback-ok` are both legitimate, documented paths in `tests/unit/no-silent-fallbacks.test.ts`).
|
|
36
|
+
|
|
37
|
+
## 6. Migration parity
|
|
38
|
+
|
|
39
|
+
None required: the prompt is assembled at runtime from shipped code; every agent gets it on its next update through the normal release path. No installed files, hooks, config defaults, or templates change.
|
|
40
|
+
|
|
41
|
+
## 7. Rollback
|
|
42
|
+
|
|
43
|
+
Revert the commit. No persisted state references the new text.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Side-effects review — Cutover-readiness checker (coordination-mandate spec §7, G2.4)
|
|
2
|
+
|
|
3
|
+
Spec: `docs/specs/coordination-mandate.md` (approved by Justin, A/A/B, 2026-06-05) — §7
|
|
4
|
+
names G2.4 and decision 1A scopes it: everything UP TO the door, never the door. Change:
|
|
5
|
+
new `src/feedback-factory/cutoverReadiness.ts` + two `/cutover-readiness*` routes + the
|
|
6
|
+
REAL condition resolvers replacing the deny-safe stubs in `AgentServer`'s coordination
|
|
7
|
+
block.
|
|
8
|
+
|
|
9
|
+
## 1. Blast radius
|
|
10
|
+
|
|
11
|
+
Additive, with ONE deliberate behavior change inside the coordination block: the
|
|
12
|
+
`integrity-gate-pass` and `parity-zero-divergence` conditions were `() => false` stubs;
|
|
13
|
+
they now resolve from REAL durable state (the persisted import IntegrityReport and the
|
|
14
|
+
durable parity window). Until that state genuinely clears, both still evaluate false —
|
|
15
|
+
identical behavior to the stubs. The first mandate (per decision 3B) has no conditioned
|
|
16
|
+
authority, so nothing live consumes them yet; the change makes the future
|
|
17
|
+
execute-cutover authority REAL instead of permanently-denied. No other route, gate, or
|
|
18
|
+
store is modified.
|
|
19
|
+
|
|
20
|
+
## 2. State / data
|
|
21
|
+
|
|
22
|
+
Two new files under the `.instar/state/` convention, created on first use:
|
|
23
|
+
`feedback-parity-passes.jsonl` (append-only durable parity window, torn-line tolerant —
|
|
24
|
+
the merged #781 persistence) and `feedback-integrity-report.json` (the import tooling's
|
|
25
|
+
persisted IntegrityReport envelope). Absent files mean blocked/not-ready (deny-safe).
|
|
26
|
+
|
|
27
|
+
## 3. Security model (T7 — the assertion-proof boundary)
|
|
28
|
+
|
|
29
|
+
- **No "set the condition" input exists.** The ONLY writes: (a)
|
|
30
|
+
`POST /cutover-readiness/parity-pass` TRIGGERS a server-side live fetch+compare
|
|
31
|
+
(HttpParitySource → runDryRunCompare); the request body contributes NOTHING to the
|
|
32
|
+
result (integration-tested with a hostile body asserting cleanliness). (b)
|
|
33
|
+
`recordIntegrityReport()` is called by server-side import tooling — no HTTP route
|
|
34
|
+
writes it by design.
|
|
35
|
+
- **A failed live check records NOTHING** — a fetch error is absence of evidence, not
|
|
36
|
+
evidence of divergence, and it cannot extend the clean window either (unit-tested
|
|
37
|
+
both ways).
|
|
38
|
+
- **Freshness bound (readiness-layer addition):** the merged gate has no max-staleness —
|
|
39
|
+
a cleared streak stays cleared forever without new passes. The readiness layer marks
|
|
40
|
+
the window STALE when the last pass is older than 6h (default) and `ready` requires
|
|
41
|
+
fresh. Stated honestly: the gate's own policy is unchanged; staleness lives here.
|
|
42
|
+
- **The door is structural:** there is NO fire-cutover route (integration-tested), the
|
|
43
|
+
status carries `door: 'manual-operator-click'` machine-readably, and the agent
|
|
44
|
+
template explicitly forbids presenting `ready` as permission to flip.
|
|
45
|
+
- **Token handling:** the parity source token is read lazily from the encrypted
|
|
46
|
+
SecretStore (`portal.instarReadToken` by default) at check time — never persisted in
|
|
47
|
+
config, never logged, never in a response.
|
|
48
|
+
|
|
49
|
+
## 4. Failure modes
|
|
50
|
+
|
|
51
|
+
- No `feedbackMigration.paritySource` config → `runParityCheck` is null → the trigger
|
|
52
|
+
route 409s with a plain reason; the readiness read surface still works.
|
|
53
|
+
- SecretStore miss → the check throws → 409, nothing recorded.
|
|
54
|
+
- Engine init failure → coordination block's existing try/catch → routes 503, boot
|
|
55
|
+
unaffected.
|
|
56
|
+
|
|
57
|
+
## 5. Test coverage
|
|
58
|
+
|
|
59
|
+
9 unit + 5 integration + 3 e2e (17 total). The load-bearing e2e: a conditioned
|
|
60
|
+
execute-cutover authority DENIES on a fresh boot and flips to ALLOW only after the
|
|
61
|
+
durable state genuinely clears (pre-seeded parity window + integrity report written to
|
|
62
|
+
the production state path, no restart) — then re-blocks on a failed report. Conditions
|
|
63
|
+
provably resolve from REAL state on the production init path.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Side-Effects Review — Dashboard pool-wide sessions (every session, every machine)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `dashboard-pool-sessions`
|
|
4
|
+
**Date:** `2026-06-05`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `not required` (additive read-only aggregation + UI; no authority, no mutation; every boundary test-pinned across three tiers)
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
`GET /sessions` self-tags each session with `machineId`/`machineNickname` when
|
|
11
|
+
the pool is wired, and `GET /sessions?scope=pool` aggregates every reachable
|
|
12
|
+
peer's plain `/sessions` (new `resolvePeerUrls` route hook off the machine
|
|
13
|
+
identity registry), tagging remote sessions with their machine + `remote:true`.
|
|
14
|
+
The dashboard polls the aggregate every 15s and renders a machine badge on
|
|
15
|
+
every session row; remote rows are informational (no subscribe/close). CLAUDE.md
|
|
16
|
+
template + idempotent migration carry the agent awareness.
|
|
17
|
+
|
|
18
|
+
## Decision-point inventory
|
|
19
|
+
|
|
20
|
+
1. Pool wired vs not → self-tag vs omit fields. Both sides pinned (integration).
|
|
21
|
+
2. `scope=pool` vs plain → envelope vs back-compatible array. Both pinned
|
|
22
|
+
(integration + e2e).
|
|
23
|
+
3. Peer reachable vs dead → merged+tagged vs `pool.failed` entry. Both pinned
|
|
24
|
+
against a REAL second HTTP server / a dead port.
|
|
25
|
+
4. Dashboard row local vs remote → interactive vs informational (click, close,
|
|
26
|
+
active highlight, row key namespace). All pinned (unit).
|
|
27
|
+
5. Migration: section-present-without-marker / marker-present / no-section.
|
|
28
|
+
All three pinned.
|
|
29
|
+
|
|
30
|
+
## 1. Over-block
|
|
31
|
+
|
|
32
|
+
Nothing rejected. The plain route's shape and fields are unchanged for
|
|
33
|
+
existing callers (additive fields only). `scope=pool` is opt-in.
|
|
34
|
+
|
|
35
|
+
## 2. Under-block
|
|
36
|
+
|
|
37
|
+
- Remote sessions are view-only — no remote kill or terminal streaming (named
|
|
38
|
+
non-goals; the tooltip routes the user to the owning machine's dashboard).
|
|
39
|
+
- The aggregate trusts each peer's self-report; a peer that lies about its
|
|
40
|
+
sessions is already inside the trust boundary (same authToken).
|
|
41
|
+
- The 15s poll means a remote session can be up to ~15s stale — acceptable for
|
|
42
|
+
a visibility surface; local sessions remain real-time via the WebSocket.
|
|
43
|
+
|
|
44
|
+
## 3. Level-of-abstraction fit
|
|
45
|
+
|
|
46
|
+
Aggregation lives in the one route that owns session listing, reusing each
|
|
47
|
+
peer's EXISTING plain route (no new peer surface, no recursion — peers are
|
|
48
|
+
fetched without `scope`). Peer discovery (`resolvePeerUrls`) is wired in
|
|
49
|
+
`server.ts` from the same identity-registry source the lease/marker transports
|
|
50
|
+
already use. The dashboard keeps the WebSocket as the live local feed and
|
|
51
|
+
layers the poll on top — remote state is kept in a separate array precisely
|
|
52
|
+
because the WS replaces the local one each broadcast.
|
|
53
|
+
|
|
54
|
+
## 4. Signal vs authority compliance
|
|
55
|
+
|
|
56
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
57
|
+
|
|
58
|
+
No authority added anywhere: read-only aggregation, fail-soft per peer
|
|
59
|
+
(`failed` entries, never a 500), best-effort dashboard poll (a failed tick
|
|
60
|
+
retries next interval).
|
|
61
|
+
|
|
62
|
+
## 5. Interactions
|
|
63
|
+
|
|
64
|
+
- Cross-machine fetch uses the established Bearer-authToken pattern
|
|
65
|
+
(placement-proxy precedent) with a 5s `AbortSignal.timeout` so a hung peer
|
|
66
|
+
can't pin the route.
|
|
67
|
+
- No recursion: the aggregate always fetches peers' PLAIN `/sessions`.
|
|
68
|
+
- The WebSocket session feed is untouched; local rows merely inherit the self
|
|
69
|
+
nickname at render time from the poll response.
|
|
70
|
+
- tmux-name collisions across machines are handled by namespacing remote row
|
|
71
|
+
keys (`remote:<machineId>:<tmux>`).
|
|
72
|
+
|
|
73
|
+
## 6. External surfaces
|
|
74
|
+
|
|
75
|
+
One query param on an existing authed route; one optional RouteContext hook;
|
|
76
|
+
dashboard HTML/JS; CLAUDE.md template bullet + idempotent migration. No config,
|
|
77
|
+
no notifications, no new files on disk.
|
|
78
|
+
|
|
79
|
+
## 7. Rollback cost
|
|
80
|
+
|
|
81
|
+
Trivial — revert the PR. The plain route shape is unchanged, so nothing else
|
|
82
|
+
depends on the new fields; no state, no schema, no migration side effects
|
|
83
|
+
beyond an extra CLAUDE.md bullet (inert text).
|
|
84
|
+
|
|
85
|
+
## Conclusion
|
|
86
|
+
|
|
87
|
+
Additive, read-only, fail-soft visibility feature at the minimal scope that
|
|
88
|
+
satisfies the operator requirement verbatim: every session shows on the
|
|
89
|
+
dashboard, and every row states its machine.
|
|
90
|
+
|
|
91
|
+
## Second-pass review (if required)
|
|
92
|
+
|
|
93
|
+
Not required — see header.
|
|
94
|
+
|
|
95
|
+
## Evidence pointers
|
|
96
|
+
|
|
97
|
+
- `tests/unit/dashboard-sessionMachineBadge.test.ts` (8) — UI contract.
|
|
98
|
+
- `tests/unit/PostUpdateMigrator-poolSessionsVisibility.test.ts` (3) — parity.
|
|
99
|
+
- `tests/integration/sessions-pool-scope.test.ts` (5) — route contract incl. a
|
|
100
|
+
REAL peer server + dead-peer degradation.
|
|
101
|
+
- `tests/e2e/sessions-pool-scope-lifecycle.test.ts` (3) — feature-alive on the
|
|
102
|
+
production init path.
|
|
103
|
+
- `docs/specs/dashboard-pool-sessions.md` + `.eli16.md`.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Side-effects review — ReviewExchange protocol (coordination-mandate spec §7, G2.3)
|
|
2
|
+
|
|
3
|
+
Spec: `docs/specs/coordination-mandate.md` (approved by Justin, A/A/B, 2026-06-05) — §7
|
|
4
|
+
names G2.3: "autonomous code-review protocol over Threadline, gated by the
|
|
5
|
+
`sign-code-review` authority". Change: new `src/coordination/ReviewExchange.ts` engine +
|
|
6
|
+
six `/review-exchange*` routes wired through the existing coordination block in
|
|
7
|
+
`AgentServer` → `RouteContext`.
|
|
8
|
+
|
|
9
|
+
## 1. Blast radius
|
|
10
|
+
|
|
11
|
+
Additive only. No existing route, gate, store, or sentinel is modified. The engine rides
|
|
12
|
+
the EXISTING MandateGate — it adds no new authority kind, no new signing key, and no new
|
|
13
|
+
trust root. Inert by inheritance: with no mandate issued (the universal state at deploy),
|
|
14
|
+
every sign-off path refuses 403, so shipping this changes NO behavior until the operator
|
|
15
|
+
issues a mandate AND an agent drives an exchange.
|
|
16
|
+
|
|
17
|
+
## 2. State / data
|
|
18
|
+
|
|
19
|
+
One new file under the `.instar/state/` convention, created on first use:
|
|
20
|
+
`review-exchanges.json` (the exchange records). No schema migration; an absent file is an
|
|
21
|
+
empty list. Audit entries land in the EXISTING `mandate-audit.jsonl` hash chain (the gate
|
|
22
|
+
records them; this engine only stores the returned entry hashes).
|
|
23
|
+
|
|
24
|
+
## 3. Security model
|
|
25
|
+
|
|
26
|
+
- **No new authorization surface.** Both sign-offs (peer approve + owner countersign) are
|
|
27
|
+
evaluated through the same MandateGate (`sign-code-review` authority): named party,
|
|
28
|
+
bounds, expiry, revocation — every decision audited. A deny refuses the state
|
|
29
|
+
transition; the route surfaces it as 403 with the gate's reason (gate-deny precedence
|
|
30
|
+
over not-found, so deny-by-default cannot masquerade as a 404).
|
|
31
|
+
- **requester ≠ authorizer preserved.** The exchange routes are Bearer-gated (no PIN by
|
|
32
|
+
design): creating/recording an exchange delegates nothing — the GATE is what authorizes
|
|
33
|
+
sign-offs, and the gate's authority comes only from the PIN-issued mandate.
|
|
34
|
+
- **Peer asymmetry stated, not hidden.** The peer's instance may not run this engine, so
|
|
35
|
+
the peer's signature is their authenticated Threadline verdict message recorded BY
|
|
36
|
+
REFERENCE (`kind: 'authenticated-peer-verdict'`, evidence = the message ref). The gate
|
|
37
|
+
still evaluates the peer's fingerprint identically. The caller is responsible for only
|
|
38
|
+
recording verdicts that actually arrived over the authenticated relay — the same trust
|
|
39
|
+
the existing A2A surface already places in the relay's sender authentication.
|
|
40
|
+
- **Content addressing.** `packageSha256` is fixed at creation and has no update path; a
|
|
41
|
+
changed package is a different exchange. `request-changes` is terminal — rework
|
|
42
|
+
restarts as a new exchange, so a stale approval can never be replayed onto new code.
|
|
43
|
+
- **Kill switch reaches the protocol.** Revoking the mandate mid-exchange blocks every
|
|
44
|
+
subsequent sign-off (tested at unit + e2e tiers).
|
|
45
|
+
|
|
46
|
+
## 4. Failure modes
|
|
47
|
+
|
|
48
|
+
- Engine init failure → `coordination` stays null → all routes 503 (deny-safe, no boot
|
|
49
|
+
block; same try/catch as the mandate engine).
|
|
50
|
+
- Torn/absent state file → empty list (`@silent-fallback-ok` annotated).
|
|
51
|
+
- A gate deny mid-protocol leaves the exchange in its prior state (no partial advance) —
|
|
52
|
+
asserted by tests on both deny paths.
|
|
53
|
+
|
|
54
|
+
## 5. Test coverage
|
|
55
|
+
|
|
56
|
+
11 unit + 7 integration + 4 e2e (22 total): both sides of every decision boundary —
|
|
57
|
+
creation validation, linear-order enforcement, gate-deny on both sign-offs,
|
|
58
|
+
deny-by-default with no mandate, named-party refusals (stranger AND wrong-party), bounds
|
|
59
|
+
mismatch, request-changes terminality, revocation mid-exchange, audit-hash wiring
|
|
60
|
+
integrity on the production boot path, 503 when unavailable.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Side-Effects Review — Secret Drop store-first durable persistence
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `secret-drop-store-first`
|
|
4
|
+
**Date:** `2026-06-04`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `not required` (single decision boundary, both sides test-pinned; operator-mandated incident fix)
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
A submitted Secret Drop is persisted store-first to the durable AES-256-GCM
|
|
11
|
+
encrypted SecretStore (`secretDrops.<token>`) the instant it is received, the
|
|
12
|
+
retrieve route transparently falls back to that copy when the in-memory one is
|
|
13
|
+
gone, and a successful consume deletes both. Opt out with
|
|
14
|
+
`secrets.persistDrops: false` (absence = enabled). Plus a structural
|
|
15
|
+
test-environment guard in `MasterKeyManager`: any VITEST/NODE_ENV=test run is
|
|
16
|
+
forced to file-key mode so no test can overwrite the machine-global keychain
|
|
17
|
+
entry (the failure the first run of this PR's own test caused live on
|
|
18
|
+
2026-06-05, remediated same hour).
|
|
19
|
+
|
|
20
|
+
## Decision-point inventory
|
|
21
|
+
|
|
22
|
+
1. `persistDrops !== false` — persist on submit / fall back on retrieve / clean
|
|
23
|
+
on consume vs pre-existing in-memory-only behavior. Both sides covered in
|
|
24
|
+
`secret-drop-store-first.test.ts`.
|
|
25
|
+
2. `consume` true/false against the durable copy — delete vs preserve. Both
|
|
26
|
+
sides covered.
|
|
27
|
+
3. `inTestRun` in `MasterKeyManager` — file-key forced vs production keychain
|
|
28
|
+
resolution. Test side pinned in `secret-store.test.ts`; production side is
|
|
29
|
+
the unchanged existing behavior.
|
|
30
|
+
|
|
31
|
+
## 1. Over-block
|
|
32
|
+
|
|
33
|
+
Nothing is rejected. Submission, retrieval, and consume flows accept exactly
|
|
34
|
+
what they accepted before. A durable-persist FAILURE never blocks the
|
|
35
|
+
submission (best-effort + loud ERROR log) — the user's drop always succeeds if
|
|
36
|
+
the in-memory submit succeeded.
|
|
37
|
+
|
|
38
|
+
## 2. Under-block
|
|
39
|
+
|
|
40
|
+
- A secret submitted while `persistDrops:false` still has the original churn
|
|
41
|
+
exposure — explicit operator opt-out, documented.
|
|
42
|
+
- The durable copy does not yet sync cross-machine (Phase 4) — a drop made on
|
|
43
|
+
the laptop is durable on the laptop only.
|
|
44
|
+
- The broader key-coherence disease (machine-global keychain account, no
|
|
45
|
+
key-id header, silent-empty on decrypt failure) is NOT fixed here — filed as
|
|
46
|
+
a follow-up finding; this PR only makes tests structurally unable to trigger
|
|
47
|
+
it.
|
|
48
|
+
|
|
49
|
+
## 3. Level-of-abstraction fit
|
|
50
|
+
|
|
51
|
+
The persist lives in the submit route directly after the in-memory `submit()`
|
|
52
|
+
succeeds — the single place a submission is born. The fallback lives in the
|
|
53
|
+
one retrieve route the hardened helper uses. No new routes, no SecretDrop
|
|
54
|
+
class changes, no helper changes. The test guard lives in the
|
|
55
|
+
`MasterKeyManager` constructor — the single chokepoint every key resolution
|
|
56
|
+
passes through (Structure > Willpower: per-test `forceFileKey` conventions
|
|
57
|
+
would rot).
|
|
58
|
+
|
|
59
|
+
## 4. Signal vs authority compliance
|
|
60
|
+
|
|
61
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
62
|
+
|
|
63
|
+
No blocking authority added. Persist is best-effort (loud log, never 500s);
|
|
64
|
+
fallback only widens what retrieval can find; the test guard constrains TESTS
|
|
65
|
+
only, never production resolution.
|
|
66
|
+
|
|
67
|
+
## 5. Interactions
|
|
68
|
+
|
|
69
|
+
- The 15-min in-memory TTL/idle cleanup still runs; after cleanup the durable
|
|
70
|
+
copy now serves the retrieve instead of a 404 — that is the fix working.
|
|
71
|
+
- `onStuckConsumer` notify-only hardening unchanged.
|
|
72
|
+
- The Telegram confirm + agent nudge fire AFTER the persist, so by the time
|
|
73
|
+
the agent is told "retrieve me," the durable copy already exists.
|
|
74
|
+
- `secret-drop-retrieve.mjs` needs no changes — it talks to the same route.
|
|
75
|
+
- SecretStore `set()` is read-modify-write; a decrypt failure throws BEFORE
|
|
76
|
+
any write, so the persist path cannot clobber an existing store it cannot
|
|
77
|
+
read (verified against the live 2026-06-05 key-split incident).
|
|
78
|
+
|
|
79
|
+
## 6. External surfaces
|
|
80
|
+
|
|
81
|
+
No new HTTP routes. One new config block (`secrets.persistDrops`,
|
|
82
|
+
`secrets.forceFileKey` — both optional, absence = current defaults). CLAUDE.md
|
|
83
|
+
template Security bullet rewritten + idempotent PostUpdateMigrator migration
|
|
84
|
+
for existing agents. One new encrypted-at-rest artifact class:
|
|
85
|
+
`secretDrops.<token>` entries inside the existing vault file.
|
|
86
|
+
|
|
87
|
+
## 7. Rollback cost
|
|
88
|
+
|
|
89
|
+
Low. `secrets.persistDrops: false` is an instant behavioral rollback without a
|
|
90
|
+
deploy. Full revert restores in-memory-only semantics; any `secretDrops.*`
|
|
91
|
+
entries already in vaults are inert data deletable via `SecretStore.delete`.
|
|
92
|
+
No schema, no irreversible op.
|
|
93
|
+
|
|
94
|
+
## Conclusion
|
|
95
|
+
|
|
96
|
+
Operator-mandated incident fix at the minimal coherent scope: one persist
|
|
97
|
+
call, one fallback branch, one cleanup branch, one structural test guard.
|
|
98
|
+
Every decision boundary has both sides pinned by tests; the failure it fixes
|
|
99
|
+
was reproduced live twice during the build (auto-update restart ate a 1h
|
|
100
|
+
token; test run split the vault keys).
|
|
101
|
+
|
|
102
|
+
## Second-pass review (if required)
|
|
103
|
+
|
|
104
|
+
Not required — see header. The riskiest element (writing secrets to disk) is
|
|
105
|
+
the operator's explicit instruction, encrypted with the pre-existing vault
|
|
106
|
+
machinery, and consume-cleaned.
|
|
107
|
+
|
|
108
|
+
## Evidence pointers
|
|
109
|
+
|
|
110
|
+
- `tests/integration/secret-drop-store-first.test.ts` — 5 route-level tests.
|
|
111
|
+
- `tests/unit/secret-store.test.ts` — keychain pollution guard.
|
|
112
|
+
- `tests/unit/PostUpdateMigrator-secretDropHardenedRetrieve.test.ts` — 3
|
|
113
|
+
migration tests (rewrite, idempotency, fresh-inject wording).
|
|
114
|
+
- `docs/specs/secret-drop-store-first.md` + `.eli16.md` — spec + overview.
|
|
115
|
+
- Live remediation evidence: vault backup at
|
|
116
|
+
`/tmp/config.secrets.enc.backup-1780635105`; keychain entry cdat
|
|
117
|
+
`2026-06-05T04:46:39Z` matched the test run start `04:46:38`.
|