instar 1.3.269 → 1.3.271

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.
Files changed (109) hide show
  1. package/dashboard/index.html +73 -11
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +24 -3
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/coordination/MandateAudit.d.ts +48 -0
  6. package/dist/coordination/MandateAudit.d.ts.map +1 -0
  7. package/dist/coordination/MandateAudit.js +95 -0
  8. package/dist/coordination/MandateAudit.js.map +1 -0
  9. package/dist/coordination/MandateGate.d.ts +48 -0
  10. package/dist/coordination/MandateGate.d.ts.map +1 -0
  11. package/dist/coordination/MandateGate.js +104 -0
  12. package/dist/coordination/MandateGate.js.map +1 -0
  13. package/dist/coordination/MandateStore.d.ts +60 -0
  14. package/dist/coordination/MandateStore.d.ts.map +1 -0
  15. package/dist/coordination/MandateStore.js +106 -0
  16. package/dist/coordination/MandateStore.js.map +1 -0
  17. package/dist/coordination/ReviewExchange.d.ts +135 -0
  18. package/dist/coordination/ReviewExchange.d.ts.map +1 -0
  19. package/dist/coordination/ReviewExchange.js +192 -0
  20. package/dist/coordination/ReviewExchange.js.map +1 -0
  21. package/dist/coordination/conditions.d.ts +32 -0
  22. package/dist/coordination/conditions.d.ts.map +1 -0
  23. package/dist/coordination/conditions.js +54 -0
  24. package/dist/coordination/conditions.js.map +1 -0
  25. package/dist/coordination/types.d.ts +74 -0
  26. package/dist/coordination/types.d.ts.map +1 -0
  27. package/dist/coordination/types.js +17 -0
  28. package/dist/coordination/types.js.map +1 -0
  29. package/dist/core/ApprovalLedger.d.ts +132 -0
  30. package/dist/core/ApprovalLedger.d.ts.map +1 -0
  31. package/dist/core/ApprovalLedger.js +191 -0
  32. package/dist/core/ApprovalLedger.js.map +1 -0
  33. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  34. package/dist/core/PostUpdateMigrator.js +33 -1
  35. package/dist/core/PostUpdateMigrator.js.map +1 -1
  36. package/dist/core/SecretStore.d.ts.map +1 -1
  37. package/dist/core/SecretStore.js +10 -1
  38. package/dist/core/SecretStore.js.map +1 -1
  39. package/dist/core/SessionBuildContextStore.d.ts +33 -0
  40. package/dist/core/SessionBuildContextStore.d.ts.map +1 -0
  41. package/dist/core/SessionBuildContextStore.js +107 -0
  42. package/dist/core/SessionBuildContextStore.js.map +1 -0
  43. package/dist/core/SessionManager.d.ts +4 -0
  44. package/dist/core/SessionManager.d.ts.map +1 -1
  45. package/dist/core/SessionManager.js +44 -3
  46. package/dist/core/SessionManager.js.map +1 -1
  47. package/dist/core/TransferByNickname.d.ts +6 -0
  48. package/dist/core/TransferByNickname.d.ts.map +1 -1
  49. package/dist/core/TransferByNickname.js +18 -6
  50. package/dist/core/TransferByNickname.js.map +1 -1
  51. package/dist/core/seamlessnessConfig.d.ts.map +1 -1
  52. package/dist/core/seamlessnessConfig.js +12 -1
  53. package/dist/core/seamlessnessConfig.js.map +1 -1
  54. package/dist/core/types.d.ts +34 -0
  55. package/dist/core/types.d.ts.map +1 -1
  56. package/dist/core/types.js.map +1 -1
  57. package/dist/feedback-factory/migration/immutableGuard.d.ts +87 -0
  58. package/dist/feedback-factory/migration/immutableGuard.d.ts.map +1 -0
  59. package/dist/feedback-factory/migration/immutableGuard.js +163 -0
  60. package/dist/feedback-factory/migration/immutableGuard.js.map +1 -0
  61. package/dist/feedback-factory/migration/importIntegrity.d.ts +112 -0
  62. package/dist/feedback-factory/migration/importIntegrity.d.ts.map +1 -0
  63. package/dist/feedback-factory/migration/importIntegrity.js +202 -0
  64. package/dist/feedback-factory/migration/importIntegrity.js.map +1 -0
  65. package/dist/feedback-factory/monitor/parityMonitor.d.ts +75 -0
  66. package/dist/feedback-factory/monitor/parityMonitor.d.ts.map +1 -0
  67. package/dist/feedback-factory/monitor/parityMonitor.js +98 -0
  68. package/dist/feedback-factory/monitor/parityMonitor.js.map +1 -0
  69. package/dist/feedback-factory/monitor/parityMonitorStore.d.ts +44 -0
  70. package/dist/feedback-factory/monitor/parityMonitorStore.d.ts.map +1 -0
  71. package/dist/feedback-factory/monitor/parityMonitorStore.js +80 -0
  72. package/dist/feedback-factory/monitor/parityMonitorStore.js.map +1 -0
  73. package/dist/scaffold/templates.d.ts.map +1 -1
  74. package/dist/scaffold/templates.js +19 -1
  75. package/dist/scaffold/templates.js.map +1 -1
  76. package/dist/scheduler/MentorAutonomousGuardian.d.ts.map +1 -1
  77. package/dist/scheduler/MentorAutonomousGuardian.js +1 -0
  78. package/dist/scheduler/MentorAutonomousGuardian.js.map +1 -1
  79. package/dist/server/AgentServer.d.ts +11 -0
  80. package/dist/server/AgentServer.d.ts.map +1 -1
  81. package/dist/server/AgentServer.js +102 -1
  82. package/dist/server/AgentServer.js.map +1 -1
  83. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  84. package/dist/server/CapabilityIndex.js +45 -0
  85. package/dist/server/CapabilityIndex.js.map +1 -1
  86. package/dist/server/routes.d.ts +19 -0
  87. package/dist/server/routes.d.ts.map +1 -1
  88. package/dist/server/routes.js +482 -4
  89. package/dist/server/routes.js.map +1 -1
  90. package/package.json +1 -1
  91. package/src/data/builtin-manifest.json +65 -65
  92. package/src/scaffold/templates.ts +19 -1
  93. package/upgrades/1.3.270.md +106 -0
  94. package/upgrades/1.3.271.md +154 -0
  95. package/upgrades/approval-ledger-phase2.eli16.md +56 -0
  96. package/upgrades/autoloop-gate-compliance.eli16.md +11 -0
  97. package/upgrades/feedback-phase3-monitor.eli16.md +51 -0
  98. package/upgrades/feedback-phase4-integrity.eli16.md +54 -0
  99. package/upgrades/respawn-build-context-reestablishment.eli16.md +5 -0
  100. package/upgrades/side-effects/approval-ledger-phase2.md +82 -0
  101. package/upgrades/side-effects/autoloop-gate-compliance.md +43 -0
  102. package/upgrades/side-effects/coordination-mandate-enforcement.md +86 -0
  103. package/upgrades/side-effects/dashboard-pool-sessions.md +103 -0
  104. package/upgrades/side-effects/feedback-phase3-monitor.md +37 -0
  105. package/upgrades/side-effects/feedback-phase4-integrity.md +35 -0
  106. package/upgrades/side-effects/respawn-build-context-reestablishment.md +81 -0
  107. package/upgrades/side-effects/review-exchange-protocol.md +60 -0
  108. package/upgrades/side-effects/secret-drop-store-first.md +117 -0
  109. package/upgrades/side-effects/transfer-idempotency-exactly-once.md +105 -0
@@ -0,0 +1,86 @@
1
+ # Side-effects review — Coordination Mandate enforcement (spec §4, G2.2)
2
+
3
+ Spec: `docs/specs/coordination-mandate.md` (approved by Justin, A/A/B, 2026-06-05).
4
+ Change: new `src/coordination/` module (types, MandateStore, MandateGate, MandateAudit,
5
+ ConditionsRegistry) + six `/mandate*` routes wired through `AgentServer` → `RouteContext`.
6
+
7
+ ## 1. Blast radius
8
+ Additive only. No existing route, gate, sentinel, or store is modified. The engine is
9
+ **deny-by-default and inert**: with no mandate issued (the universal state at deploy),
10
+ every `/mandate/evaluate` call denies — so shipping this changes NO behavior anywhere
11
+ until the operator issues the first mandate through the PIN-gated route. It never
12
+ weakens an existing safety check; it adds a controlled delegation surface beside them.
13
+
14
+ ## 2. State / data
15
+ Two new files under the `.instar/state/` convention, created on first use:
16
+ `coordination-mandates.json` (the signed mandates) and `mandate-audit.jsonl`
17
+ (append-only, hash-chained). No schema migration; absent files mean an empty store and
18
+ an empty audit. Torn-trailing-line tolerant reads.
19
+
20
+ ## 3. Security model (the load-bearing dimension)
21
+ - **requester ≠ authorizer preserved:** the human-authored mandate is the authorizer.
22
+ The agent's Bearer token CANNOT issue or revoke — `/mandate/issue` and
23
+ `/mandate/:id/revoke` require the operator's dashboard PIN (sha256 + timingSafeEqual +
24
+ per-IP attempt limiting, mirroring `/dashboard/unlock`). Tested at all three tiers.
25
+ - **Authorship proof (T1/T2):** HMAC over the canonical authored bytes (key-order-stable
26
+ serialization), produced only at issuance. A forged or widened mandate fails
27
+ `verifyAuthorship` → the gate denies. `revoked` is excluded from the proof so the
28
+ kill switch works post-issuance; revocation is checked on every evaluation (T5).
29
+ - **Conditions (T7/T10):** objective resolvers from real state; an unregistered or
30
+ throwing resolver evaluates FALSE (deny-safe). The first mandate (per decision 3B)
31
+ carries no conditioned authority; `integrity-gate-pass` and `parity-zero-divergence`
32
+ are registered deny-safe stubs until the real state is wired for a future cutover
33
+ authority — which per decision 1A is not delegated anyway.
34
+ - **Audit (T3/T8):** every decision (allow AND deny) appends a hash-chained entry;
35
+ `verifyChain()` detects any edit/deletion and is surfaced on `GET /mandate/audit`.
36
+ - **Stated trust boundary (T12):** an attacker with local disk write access could edit
37
+ server-managed state — the same trust root as today; the proof stops forged AUTHORED
38
+ content, and the audit chain makes tamper detectable. Out of scope per the spec.
39
+
40
+ ## 4. Failure modes
41
+ - No `stateDir` → engine is null → all `/mandate*` routes 503 (tested).
42
+ - Engine init failure → own try/catch, never cascades to other inits.
43
+ - Malformed issuance body → 400 with a specific reason (agents pair, non-empty
44
+ authorities, future expiry — each tested both sides).
45
+ - No `dashboardPin` configured → issuance/revocation 503 (PIN auth unavailable) — the
46
+ gate cannot be bypassed by the PIN simply being absent.
47
+
48
+ ## 5. Interactions
49
+ - `RouteContext` gains a required `coordination` field; the only production assembler
50
+ (`AgentServer`) is updated. Route tests use partial `ctx: any` → unaffected (tsc green).
51
+ - Express route-order hazard handled: `/mandate/audit` and `/mandate/evaluate` are
52
+ registered BEFORE `/mandate/:id` (tested explicitly).
53
+ - No locks, queues, LLM calls, or budgets shared with other components.
54
+
55
+ ## 6. Performance
56
+ O(mandates) reads of a small JSON file per evaluation + one appendFileSync per decision.
57
+ Evaluation volume is a handful of A2A actions per migration step — negligible.
58
+
59
+ ## 7. Migration parity
60
+ No `.claude/settings.json` hook, config default, or hook-script change → no
61
+ `PostUpdateMigrator` entry needed. Server-side code activates on the next server start
62
+ after update. Agent-Awareness satisfied: CLAUDE.md template gains a Coordination
63
+ Mandate blurb (`generateClaudeMd`). The feature is inert-by-default everywhere (deny
64
+ without a mandate), so fleet exposure is zero until an operator issues one.
65
+
66
+ ## Tests
67
+ - Unit (`tests/unit/coordination-mandate.test.ts`, 17): authorship valid/forged/widened,
68
+ revoke-preserves-proof, canonical key-order stability, audit chain intact/tampered,
69
+ conditions (true/false/unknown/compound/throwing), the gate's FULL deny ladder
70
+ (missing, forged, expired, revoked, non-party, unlisted action, out-of-bounds params,
71
+ unmet condition) + both allow paths, bounds helper both sides.
72
+ - Integration (`tests/integration/mandate-routes.test.ts`, 9): PIN-less issue refused,
73
+ wrong PIN refused, issue/list/evaluate/revoke round-trip, deny-by-default audited,
74
+ route order, validation 400s, 503s.
75
+ - E2E (`tests/e2e/coordination-mandate-lifecycle.test.ts`, 5): real AgentServer boot,
76
+ alive + Bearer-gated, deny-by-default on fresh boot, full PIN-issue→allow→deny→
77
+ revoke→deny lifecycle with chain-verified audit, and the production-HMAC
78
+ wiring-integrity proof (persisted authProof verifies; a widened mandate fails).
79
+
80
+ ## Post-review amendment (same PR, pre-merge)
81
+
82
+ - **Discoverability:** the `/mandate` prefix is classified in `CapabilityIndex.ts`
83
+ (CAPABILITY_INDEX → surfaced in `/capabilities` with all six endpoints + the
84
+ evaluate-before-acting + PIN-gating semantics), satisfying the
85
+ capabilities-discoverability gate. Agents discover the gate; they still cannot
86
+ issue through it.
@@ -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,37 @@
1
+ # Side effects — Phase-3 parity monitor + cutover gate
2
+
3
+ ## New files (additive only)
4
+ - `src/feedback-factory/monitor/parityMonitor.ts` — `ParityMonitor` (records passes, tracks
5
+ the zero-divergence streak, exposes the `gate()` cutover signal) + `DEFAULT_GATE_POLICY`.
6
+ - `tests/unit/feedback-factory/parity-monitor.test.ts` (9 tests).
7
+
8
+ ## Runtime impact
9
+ - **None at boot / on the live server.** Not imported by `server.ts`, any route, job, or
10
+ hook. No new endpoint, no config key, no `PostUpdateMigrator` entry. No new dependency
11
+ (pure logic over the existing `ParityResult` type).
12
+
13
+ ## Behavioral guarantees
14
+ - `gate()` clears ONLY when all four conditions hold: ≥ requiredCleanPasses consecutive clean
15
+ passes, clean window ≥ minWindowMs of real time, ≥ minClustersObserved clusters across the
16
+ streak, and zero divergence in the streak. Any divergent pass resets the streak.
17
+ - The monitor never mutates anything and never triggers cutover itself — it only computes a
18
+ cleared/blocked verdict that a downstream executor reads.
19
+ - Conservative failure mode: an over-strict policy keeps the gate BLOCKED longer (cutover
20
+ waits); it can never clear early.
21
+
22
+ ## Reversibility
23
+ - Fully reversible: delete `monitor/parityMonitor.ts` + its test. Nothing references it yet.
24
+
25
+ ## Follow-on wiring (not in this PR)
26
+ - The Coordination Mandate condition registry (G2.2) maps `parity-zero-divergence` →
27
+ `monitor.gate(now).cleared`. The Phase-4 cutover executor (G2.4) consults it before the flip.
28
+ - A durable wrapper (persist passes to JSONL so the window survives a restart) + a read-only
29
+ `/parity/status` route land with the cutover-executor wiring.
30
+
31
+ ## Durability (added)
32
+ - `parityMonitorStore.ts` — `DurableParityMonitor` + `JsonlPassPersistence`. The zero-divergence
33
+ window spans HOURS; without persistence a restart silently resets the streak. Passes are
34
+ appended to an append-only JSONL and reloaded on construction, so the window survives a
35
+ restart. Torn final line (crash mid-append) is skipped on reload, never corrupting the prior
36
+ window. Injectable `PassPersistence` keeps the logic unit-testable without disk. Still not
37
+ wired into the running server (the cutover executor constructs it with the live path).
@@ -0,0 +1,35 @@
1
+ # Side effects — Phase-4 feedback-migration integrity tooling
2
+
3
+ ## New files (additive only)
4
+ - `src/feedback-factory/migration/immutableGuard.ts` — `GuardedFeedbackStore` decorator +
5
+ `isClusterImmutable` / `hasGovernanceNotes` invariants.
6
+ - `src/feedback-factory/migration/importIntegrity.ts` — pure import-gate core (checksums,
7
+ fingerprint-uniqueness scan, schema-equivalence, referential integrity, sequence reset,
8
+ `runIntegrityGate`).
9
+ - `tests/unit/feedback-factory/immutable-guard.test.ts` (15 tests).
10
+ - `tests/unit/feedback-factory/import-integrity.test.ts` (21 tests).
11
+
12
+ ## Runtime impact
13
+ - **None at boot / on the live server.** These modules are NOT imported by `server.ts`, any
14
+ route, job, or hook. They are library building blocks the Phase-4 cutover execution will
15
+ call. No new endpoint, no new config key, no migration to `PostUpdateMigrator`.
16
+ - No new dependencies (uses `node:crypto` only).
17
+
18
+ ## Behavioral guarantees
19
+ - `GuardedFeedbackStore` only ever REFUSES a mutation of an immutable cluster (pre-cutover
20
+ createdAt or non-null governance note) and records a `GuardViolation`; it never alters
21
+ data and passes all reads + non-curated writes straight through to the inner store.
22
+ - A failing `onViolation` audit sink can never break the store or surface as a mutation
23
+ (try/caught, annotated `@silent-fallback-ok`).
24
+ - Checksums collapse null/undefined/"" so the import gate does not flap on the
25
+ null-vs-empty governance-note distinction (that distinction is asserted separately by
26
+ schema-equivalence).
27
+
28
+ ## Reversibility
29
+ - Fully reversible: delete the two `migration/` files + their tests. Nothing else
30
+ references them yet.
31
+
32
+ ## Follow-on wiring (not in this PR)
33
+ - Phase-4 cutover executor (G2.4) constructs the `GuardedFeedbackStore` around the real
34
+ Prisma-backed store with the cutover timestamp from config, and runs `runIntegrityGate`
35
+ over the export/import pair as the Phase-2 gate.
@@ -0,0 +1,81 @@
1
+ # Side-Effects Review — Respawn build-context re-establishment
2
+
3
+ **Version / slug:** `respawn-build-context-reestablishment`
4
+ **Date:** `2026-06-04`
5
+ **Author:** `instar-codey`
6
+ **Second-pass reviewer:** `instar-codey second-pass checklist`
7
+
8
+ ## Summary of the change
9
+
10
+ This change hardens the interactive SessionManager respawn path for developer build sessions. A new sidecar store records each running session's live tmux pane working directory, using the generic state layer's temp-file-plus-rename atomic write. When a resumed interactive session has a fresh saved cwd under an agent worktree, SessionManager prepends a `[BUILD-CONTEXT RESTORE]` continuation note to the resumed message. Normal home-only sessions, stale records, missing worktrees, and non-enabled agents receive no note.
11
+
12
+ The feature is dark by default for normal agents. Server boot resolves the session-level feature flag as an explicit override if present, otherwise the standard development-agent gate.
13
+
14
+ ## Decision-point inventory
15
+
16
+ - `SessionBuildContextStore` — add — owns sidecar state validation, crash-safe persistence through StateManager, cwd eligibility, staleness, missing-path checks, branch enrichment, and restore-note formatting.
17
+ - `SessionManager.monitorTick` — modified — records pane cwd after liveness is confirmed, without changing session lifecycle decisions.
18
+ - `SessionManager.spawnInteractiveSession` — modified — prepends a restore note only for resume spawns with an eligible saved worktree context.
19
+ - `server` startup wiring — modified — resolves the feature dark by default and live for development agents unless explicitly overridden.
20
+ - Session-management e2e — modified — proves a real tmux pane under a fixture worktree gets the restore note while a home-only control stays unchanged.
21
+
22
+ ## 1. Over-block
23
+
24
+ The restore can skip a legitimate build context if the tmux cwd is not under a `.worktrees` path. That is intentional. The spec's blast-radius guard is more important than restoring every possible hand-navigated directory. Agents can still recover manually from conversation context; the automated note is limited to the known build-worktree convention.
25
+
26
+ The restore can also skip when the sidecar is stale or the worktree directory was removed. That prevents a respawn from sending the agent back into a deleted or long-finished checkout.
27
+
28
+ ## 2. Under-block
29
+
30
+ The sidecar records cwd for running sessions when the feature is enabled, including home-only sessions. Home-only records are not eligible for restore, so this is additional local state but not additional behavior. The record contains local paths and an optional branch name; it does not include prompt content or credentials.
31
+
32
+ The branch name is best-effort enrichment. If Git cannot read the branch, the restore note still works with the worktree path alone. Failure to read branch never blocks monitoring or respawn.
33
+
34
+ ## 3. Level-of-abstraction fit
35
+
36
+ SessionManager owns tmux lifecycle, liveness checks, interactive respawn, and initial-message injection, so it is the correct place to sample pane cwd and prepend the resume note. The sidecar store keeps persistence and eligibility logic out of the large SessionManager class and makes the boundary unit-testable.
37
+
38
+ The change does not force tmux to spawn inside the worktree. That matches the design spec: the session home remains stable, and the resumed agent receives deterministic instruction to return to the build checkout only when appropriate.
39
+
40
+ ## 4. Signal vs authority compliance
41
+
42
+ Required reference: [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
43
+
44
+ - [x] Not applicable to conversational/product judgment — this is structural session lifecycle restoration.
45
+
46
+ The cwd sample is a mechanical tmux fact. The restore decision is a deterministic filesystem/scope guard: fresh, existing, different from spawn cwd, and under a worktree path. It does not judge user intent or message meaning.
47
+
48
+ ## 5. Interactions
49
+
50
+ - **Shadowing:** The restore note is prepended before the normal initial-message injection. Existing InputGuard and injection mechanics still handle the resulting message through the same path.
51
+ - **Double-fire:** The note is computed once per resumed spawn. Reusing an already-live tmux session does not inject a restore note.
52
+ - **Races:** A worktree can be removed between sidecar read and agent acting on the note. The respawn path checks existence immediately before injecting; after that, the note is advisory and the agent can report if the path disappears.
53
+ - **Feedback loops:** Monitoring persists only when cwd changes, avoiding a write every tick for steady sessions.
54
+
55
+ ## 6. External surfaces
56
+
57
+ The visible behavior is a new continuation preamble for eligible resumed developer sessions. The preamble is intentionally plain text and starts with `[BUILD-CONTEXT RESTORE]` so the agent can act on it deterministically. Normal topic sessions that remain in agent home see no new text.
58
+
59
+ The persistent surface is a local sidecar state record keyed by tmux session name. It stores spawn cwd, current cwd, optional branch, and update time.
60
+
61
+ ## 7. Rollback cost
62
+
63
+ Rollback is a code revert of the sidecar store, SessionManager wiring, server gate wiring, tests, and artifacts. The sidecar state file can remain harmlessly on disk; without the code path it is ignored. No migration or state repair is required.
64
+
65
+ ## Conclusion
66
+
67
+ The change addresses the respawn amnesia failure without changing normal spawn cwd, without broadening behavior for home-only sessions, and without relying on conversation memory to rediscover the checkout. The test set covers pure eligibility/staleness behavior, mocked tracker-to-respawn wiring, and a real tmux e2e with both worktree and home-only controls.
68
+
69
+ ## Second-pass review
70
+
71
+ **Reviewer:** `instar-codey separate pass`
72
+ **Independent read of the artifact:** concur
73
+
74
+ The risky surface is SessionManager. The implementation keeps the new behavior behind a feature gate, samples only after liveness is confirmed, writes through crash-safe state persistence, and injects only a continuation note for eligible resumed spawns. The scope guard is the core safety property and is covered in all three test tiers.
75
+
76
+ ## Evidence pointers
77
+
78
+ - `npm test -- --run tests/unit/session-build-context-store.test.ts tests/integration/session-build-context-respawn.test.ts` passed.
79
+ - `npm test -- --run tests/e2e/session-management-e2e.test.ts -t "restores a worktree build context"` passed.
80
+ - `npm test -- --run tests/unit/no-silent-fallbacks.test.ts tests/unit/session-build-context-store.test.ts` passed after marking best-effort branch enrichment as an intentional fail-open.
81
+ - `npm run lint` passed.
@@ -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`.
@@ -0,0 +1,105 @@
1
+ # Side-Effects Review — Transfer idempotency + exactly-once-when-live
2
+
3
+ **Version / slug:** `transfer-idempotency-exactly-once`
4
+ **Date:** `2026-06-05`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required` (pure-planner reorder + config-resolution default; every boundary side test-pinned)
7
+
8
+ ## Summary of the change
9
+
10
+ 1. `planTransferByNickname`: the already-satisfied checks (`owner === target`,
11
+ NEW `pin === target` via optional `currentPinOf`) now run BEFORE the rate
12
+ limit, so a duplicate of an already-satisfied move plans `noop` ("already
13
+ there") instead of `reject/rate-limited`. The consumer wires `currentPinOf`
14
+ from `TopicPlacementPinStore` and words the `already-on-target` noop as
15
+ "already running on X — nothing to move."
16
+ 2. `resolveSeamlessnessConfig`: `exactlyOnceIngress` defaults ON when the
17
+ session pool stage is `live-transfer`/`rebalance` (a live pool without the
18
+ ingress dedupe ledger re-executes retried/replayed user commands — the
19
+ 2026-06-05 4×-execution incident). Explicit values always win.
20
+
21
+ ## Decision-point inventory
22
+
23
+ 1. `owner === target` → noop vs continue. Both sides pinned (incident test +
24
+ transfer test).
25
+ 2. `pin === target` → noop vs continue; absent `currentPinOf` → old behavior.
26
+ All three pinned.
27
+ 3. Rate-limit window for a real move (different target) → still rejected.
28
+ Pinned.
29
+ 4. Stage-derived `exactlyOnceIngress` default: off (none/dark/shadow), on
30
+ (live-transfer/rebalance), explicit override both directions. All pinned.
31
+
32
+ ## 1. Over-block
33
+
34
+ Nothing new is rejected. The planner now rejects strictly LESS (duplicates
35
+ that previously rate-limited now noop). The exactly-once default only
36
+ activates a gate that itself fails open (ledger errors fall through to normal
37
+ routing, per the existing route code).
38
+
39
+ ## 2. Under-block
40
+
41
+ - The rate limit no longer applies to same-target duplicates — deliberate:
42
+ re-pinning the same value is idempotent. Rapid same-target spam costs one
43
+ pin-store write + one "already there" reply per message; the per-topic
44
+ Telegram traffic itself is bounded upstream.
45
+ - The exactly-once default does NOT retroactively enable the ledger on pools
46
+ still in dark/shadow — pre-live stages keep the old dark default by design.
47
+
48
+ ## 3. Level-of-abstraction fit
49
+
50
+ The idempotency fix lives in the pure planner (the single decision function
51
+ both the natural-language path and `POST /pool/transfer` flow through), not in
52
+ the Telegram consumer. The exactly-once coupling lives in config RESOLUTION
53
+ (`resolveSeamlessnessConfig`) — the one place defaults are derived — not in
54
+ the wiring site.
55
+
56
+ ## 4. Signal vs authority compliance
57
+
58
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
59
+
60
+ No new blocking authority. The planner reorder converts rejections into
61
+ no-ops. The exactly-once gate it activates was already authority-bearing by
62
+ design (spec §8 G3a) and fails open on any ledger error.
63
+
64
+ ## 5. Interactions
65
+
66
+ - `noop` consumer behavior unchanged structurally (pin set + release-if-self
67
+ + message): re-setting an identical pin refreshes `updatedAt`, which can
68
+ extend the rate window for a SUBSEQUENT different-target move by up to 10s
69
+ — bounded, harmless.
70
+ - Activating `exactlyOnceIngress` on live pools also activates the
71
+ reply-marker transport (same flag, existing wiring) — that is the intended
72
+ pairing per spec §8 G3a.
73
+ - Sentinel intercepts (emergency stop / pause) run BEFORE the ingress gate in
74
+ the route — unchanged; an emergency stop is never deduped away.
75
+
76
+ ## 6. External surfaces
77
+
78
+ No new routes, no new config keys (an existing key's DEFAULT becomes
79
+ stage-aware). One user-visible message wording improvement. No migration:
80
+ config absence means "follow the default," which is the point.
81
+
82
+ ## 7. Rollback cost
83
+
84
+ Trivial. `exactlyOnceIngress: false` is an instant per-agent opt-out without
85
+ deploy; reverting the PR restores both old behaviors. No state, no schema.
86
+
87
+ ## Conclusion
88
+
89
+ Two small, surgical changes at the correct layers, each killing one half of a
90
+ user-facing incident: duplicates narrated as failures, and duplicates
91
+ executing at all. Strictly less rejection, strictly more dedupe, both
92
+ test-pinned on every side.
93
+
94
+ ## Second-pass review (if required)
95
+
96
+ Not required — see header.
97
+
98
+ ## Evidence pointers
99
+
100
+ - `tests/unit/TransferByNickname.test.ts` — 5 new idempotency tests (incl. the
101
+ verbatim incident case).
102
+ - `tests/unit/seamlessnessConfig.test.ts` — 4 new coupling tests.
103
+ - Production evidence: `logs/server.log` 2026-06-05T04:20:43/04:20:55 double
104
+ pin + zero `exactly-once` lines; topic 13481 screenshot (Moving →
105
+ rate-limited → Moving → rate-limited).