instar 1.3.265 → 1.3.266

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,91 @@
1
+ # Side-Effects Review — Secret-Sync push opt-in (receive-only default)
2
+
3
+ **Version / slug:** `secret-sync-push-guard`
4
+ **Date:** `2026-06-04`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required` (Tier 2; single-author, safety-tightening change)
7
+
8
+ ## Summary of the change
9
+
10
+ Adds an anti-clobber safety guard to cross-machine secret-sync (PR #771 / v1.3.258). Previously
11
+ `multiMachine.secretSync.enabled` activated BOTH receive and push, and a boot best-effort
12
+ push fired unconditionally — so a machine whose local secret store is stale/divergent would
13
+ auto-push its stale set to peers and overwrite their good secrets. This change splits push out
14
+ behind `multiMachine.secretSync.pushEnabled` (default **false**): `enabled` alone is now
15
+ RECEIVE-ONLY; outbound (boot push + `POST /secrets/sync-now`) requires `pushEnabled: true`,
16
+ set only on the machine whose store is authoritative. Files: `src/commands/server.ts` (gate +
17
+ handle field + boot-push guard), `src/core/SecretSync.ts` (`SecretSyncHandle.pushEnabled`),
18
+ `src/core/types.ts` (config field), `src/server/routes.ts` (sync-status reports `mode`;
19
+ sync-now 409s when receive-only), `src/scaffold/templates.ts` + `PostUpdateMigrator.ts`
20
+ (agent awareness, incl. a migration for agents that already have the section).
21
+
22
+ Earned live: 2026-06-04 the mini's store drifted (keychain↔file master-key); enabling the
23
+ original (push-on) secret-sync there would have clobbered the laptop's authoritative secrets.
24
+
25
+ ## Decision-point inventory
26
+
27
+ - `multiMachine.secretSync.pushEnabled` gate (server.ts) — **add** — splits outbound from inbound.
28
+ - Boot best-effort `provisionAll()` (server.ts) — **modify** — now gated on pushEnabled.
29
+ - `POST /secrets/sync-now` (routes.ts) — **modify** — returns 409 when receive-only.
30
+ - `GET /secrets/sync-status` (routes.ts) — **modify** — now reports `pushEnabled` + `mode`.
31
+
32
+ ## 1. Over-block
33
+
34
+ The guard "blocks" outbound push when `pushEnabled` is false — that is the intended behavior, not
35
+ over-block. A user who genuinely wants push must set `pushEnabled: true` (one config line). No
36
+ legitimate RECEIVE is blocked. The 409 response names the exact remedy.
37
+
38
+ ## 2. Under-block
39
+
40
+ The guard does not validate that the push-enabled machine's store is ACTUALLY authoritative — it
41
+ trusts the operator's `pushEnabled: true`. A misconfiguration (pushEnabled on a stale machine)
42
+ could still clobber. Mitigated by: default-off (you must deliberately opt in) + the doc guidance
43
+ ("set only on the machine whose store is authoritative"). A value-hash reconciliation (only push
44
+ keys the peer is missing/older) is a sound follow-up but out of scope here.
45
+
46
+ ## 3. Level-of-abstraction fit
47
+
48
+ Right layer: a config flag + a wiring gate, consistent with the rest of secret-sync. It reuses the
49
+ existing provisioner/handle; no new primitive. The default-off posture matches the project's
50
+ "dark by default" rollout discipline for risky features.
51
+
52
+ ## 4. Signal vs authority compliance
53
+
54
+ - [x] No — this change has no block/allow surface over user messages. (It gates an internal
55
+ outbound sync action behind an explicit config flag — deterministic, not a heuristic gate.)
56
+
57
+ ## 5. Interactions
58
+
59
+ - **Shadowing:** none — `pushEnabled` only narrows when the existing push path runs.
60
+ - **Double-fire:** reduces it — the boot push no longer fires on receive-only machines.
61
+ - **Races:** unchanged — same SecretStore read-modify-write as before; receive path untouched.
62
+ - **Feedback loops:** none. A receive-only machine that gets corrected by an authoritative push
63
+ does NOT re-push (it can't), so no ping-pong.
64
+
65
+ ## 6. External surfaces
66
+
67
+ - **Peers:** strictly safer — a receive-only machine no longer sends to peers at all.
68
+ - **Config:** new optional field `multiMachine.secretSync.pushEnabled` (absent ⇒ false ⇒ no push).
69
+ No migrateConfig default needed (absence = safe default).
70
+ - **API:** `/secrets/sync-status` gains `pushEnabled` + `mode` fields (additive); `/secrets/sync-now`
71
+ can now return 409 (receive-only) in addition to its prior 200/503/500.
72
+ - **Persistent state:** none changed.
73
+
74
+ ## 7. Rollback cost
75
+
76
+ Pure code change. Revert = secret-sync returns to push-on-enable (the less-safe prior behavior).
77
+ No persistent state to unwind. Because push was never enabled on any real machine between #771 and
78
+ this change (the mini incident is exactly why), there is no in-the-wild push to reconcile.
79
+
80
+ ## Conclusion
81
+
82
+ A small, safety-tightening change with a default-off posture; it closes the divergent-store clobber
83
+ risk that the 2026-06-04 mini incident exposed and makes laptop→mini the safe reconciliation
84
+ direction for the live verification. The one residual (operator opts pushEnabled on a non-authoritative
85
+ machine) is documented and a value-hash reconciliation is the natural follow-up. Clear to ship.
86
+
87
+ ## Evidence pointers
88
+
89
+ 16 tests green: 8 unit, 5 integration (incl. the new receive-only guard test: sync-now 409s + peer
90
+ vault untouched), 3 e2e. tsc + lint clean. Spec updated (Guarantees → "Push is opt-in"). Related: the
91
+ SecretStore keychain-drift repair that surfaced this (re-key file→keychain).
@@ -1,22 +0,0 @@
1
- # Upgrade Guide — vNEXT
2
-
3
- <!-- assembled-by: assemble-next-md -->
4
- <!-- bump: patch -->
5
-
6
- ## What Changed
7
-
8
- `SessionManager.isMarkerStuckAtPrompt()` now recognizes Gemini CLI's input box. It checked the pane line for `❯` (Claude) or `›` (codex) to find the prompt; Gemini has neither — its active input is a `│ * <text>` box line — so a Telegram message injected into a Gemini session was never detected as stuck, and `verifyInjection`'s Enter-recovery never fired for Gemini. Forwarded prompts stalled unsubmitted in the box (the recurring mentee-layer auto-submit friction; it stalled a message to Codey live this session until manually nudged). The fix adds `│ *` to the prompt-indicator set. The downstream gate is unchanged — a line only counts as stuck if it also contains the exact injected marker, so `│ *` alone (or Gemini's empty-box placeholder) never false-fires, and Claude/codex detection is untouched. Sibling of the earlier codex `›` fix; all three CLIs now get the same stuck-input auto-recovery.
9
-
10
- ## What to Tell Your User
11
-
12
- None — internal change (no user-facing surface).
13
-
14
- ## Summary of New Capabilities
15
-
16
- None — internal change (no user-facing surface).
17
-
18
- ## Evidence
19
-
20
- - `vitest run tests/unit/codex-stranded-draft-recovery.test.ts tests/unit/session-multishot-recovery.test.ts` → **27/27 green** (Gemini box-detect + empty-box placeholder immunity + Claude/codex no-regression + no false-fire on a markerless `│ *` line).
21
- - `tsc --noEmit` clean.
22
- - Fixture mirrors a real Gemini idle pane captured live (`│ * [telegram:1] [Long message saved to …]`).