instar 1.3.1320 → 1.3.1321
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/dist/commands/machine.d.ts +20 -0
- package/dist/commands/machine.d.ts.map +1 -1
- package/dist/commands/machine.js +135 -21
- package/dist/commands/machine.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +64 -5
- package/dist/commands/server.js.map +1 -1
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +8 -0
- package/dist/commands/setup.js.map +1 -1
- package/dist/core/Config.d.ts +12 -0
- package/dist/core/Config.d.ts.map +1 -1
- package/dist/core/Config.js +38 -1
- package/dist/core/Config.js.map +1 -1
- package/dist/core/MachineIdentity.d.ts +10 -0
- package/dist/core/MachineIdentity.d.ts.map +1 -1
- package/dist/core/MachineIdentity.js +15 -4
- package/dist/core/MachineIdentity.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +8 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +129 -2
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SubscriptionPool.d.ts +30 -0
- package/dist/core/SubscriptionPool.d.ts.map +1 -1
- package/dist/core/SubscriptionPool.js +52 -1
- package/dist/core/SubscriptionPool.js.map +1 -1
- package/dist/core/SubscriptionPoolAuthority.d.ts +33 -0
- package/dist/core/SubscriptionPoolAuthority.d.ts.map +1 -1
- package/dist/core/SubscriptionPoolAuthority.js +150 -0
- package/dist/core/SubscriptionPoolAuthority.js.map +1 -1
- package/dist/core/shutdownDeadline.d.ts +26 -0
- package/dist/core/shutdownDeadline.d.ts.map +1 -0
- package/dist/core/shutdownDeadline.js +25 -0
- package/dist/core/shutdownDeadline.js.map +1 -0
- package/dist/data/standards-guard-index.json +1 -1
- package/dist/data/standards-guard-index.meta.json +2 -2
- package/dist/data/standards-registry.meta.json +1 -1
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +12 -1
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/monitoring/CodexRolloutScan.worker.d.ts +16 -0
- package/dist/monitoring/CodexRolloutScan.worker.d.ts.map +1 -0
- package/dist/monitoring/CodexRolloutScan.worker.js +49 -0
- package/dist/monitoring/CodexRolloutScan.worker.js.map +1 -0
- package/dist/monitoring/QuotaCollector.d.ts +8 -0
- package/dist/monitoring/QuotaCollector.d.ts.map +1 -1
- package/dist/monitoring/QuotaCollector.js +73 -32
- package/dist/monitoring/QuotaCollector.js.map +1 -1
- package/dist/monitoring/TokenLedger.d.ts +11 -1
- package/dist/monitoring/TokenLedger.d.ts.map +1 -1
- package/dist/monitoring/TokenLedger.js +79 -37
- package/dist/monitoring/TokenLedger.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +7 -4
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/machineRoutes.d.ts.map +1 -1
- package/dist/server/machineRoutes.js +16 -0
- package/dist/server/machineRoutes.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +4 -3
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/ThreadlineBootstrap.d.ts +9 -0
- package/dist/threadline/ThreadlineBootstrap.d.ts.map +1 -1
- package/dist/threadline/ThreadlineBootstrap.js +5 -1
- package/dist/threadline/ThreadlineBootstrap.js.map +1 -1
- package/dist/threadline/client/ThreadlineClient.d.ts +14 -0
- package/dist/threadline/client/ThreadlineClient.d.ts.map +1 -1
- package/dist/threadline/client/ThreadlineClient.js +18 -0
- package/dist/threadline/client/ThreadlineClient.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/data/standards-guard-index.json +1 -1
- package/src/data/standards-guard-index.meta.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/src/scaffold/templates.ts +7 -4
- package/upgrades/1.3.1321.md +153 -0
- package/upgrades/luna-stack-ci-ratchets.eli16.md +9 -0
- package/upgrades/side-effects/agent-health-reopen-duplicate.md +58 -0
- package/upgrades/side-effects/auth-read-after-secret-migration.md +56 -0
- package/upgrades/side-effects/codex-rollout-scan-yield.md +58 -0
- package/upgrades/side-effects/join-autostart-lifeline.md +59 -0
- package/upgrades/side-effects/join-refuses-copied-identity.md +62 -0
- package/upgrades/side-effects/luna-stack-ci-ratchets.md +24 -0
- package/upgrades/side-effects/quota-jsonl-off-event-loop.md +54 -0
- package/upgrades/side-effects/second-machine-copied-config.md +62 -0
- package/upgrades/side-effects/shutdown-hard-deadline.md +62 -0
- package/upgrades/side-effects/subscription-pool-legacy-migrate.md +68 -0
- package/upgrades/side-effects/threadline-relay-standby.md +54 -0
- package/upgrades/side-effects/threadline-wire-thread-id.md +60 -0
- package/upgrades/side-effects/wakeup-targets-awake-machine.md +58 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Side-Effects Review — graceful shutdown has a hard deadline
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `shutdown-hard-deadline`
|
|
4
|
+
**Date:** `2026-10-06`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `not required (bounded exit path; no decision authority)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
instar#2122: "The Luna server on the Studio ignores SIGTERM. Only SIGKILL stops it, whereas the laptop's stops on SIGTERM." The server's `shutdown` handler awaits ~10 subsystem stops in sequence (origin worker close, notification flush, tunnel, Threadline relay, Telegram, HTTP server) with no overall bound, so one hanging stop kept the process alive indefinitely. Now:
|
|
11
|
+
|
|
12
|
+
1. `armShutdownDeadline` (new `src/core/shutdownDeadline.ts`) arms an unref'd timer at the start of teardown (default 20 s, `INSTAR_SHUTDOWN_DEADLINE_MS` override, floor 2 s). On expiry it logs the step in flight, closes SQLite handles and releases the single-instance lock best-effort, and exits 1.
|
|
13
|
+
2. A second SIGINT/SIGTERM during teardown exits immediately with the same cleanup, naming the step.
|
|
14
|
+
3. The awaited steps are labelled (`telegram-origin`, `notification-flush`, `tunnel`, `threadline`, `telegram`, `http-server`, `sqlite`) so the log says what hung.
|
|
15
|
+
|
|
16
|
+
## Decision-point inventory
|
|
17
|
+
|
|
18
|
+
None. An exit-path bound.
|
|
19
|
+
|
|
20
|
+
## 1. Over-block
|
|
21
|
+
|
|
22
|
+
A teardown that genuinely needs more than 20 s is cut short with exit code 1; the operator can raise `INSTAR_SHUTDOWN_DEADLINE_MS`. The existing `process.on('exit')` SQLite net still runs. Resume-UUID saving and the sidecar flush happen early in the sequence, before the steps that have hung in practice.
|
|
23
|
+
|
|
24
|
+
## 2. Under-block
|
|
25
|
+
|
|
26
|
+
Does not diagnose WHICH subsystem hangs on Luna's Studio; the new step label in the log will. The lifeline's own supervisor restart behaviour is unchanged.
|
|
27
|
+
|
|
28
|
+
## 3. Level-of-abstraction fit
|
|
29
|
+
|
|
30
|
+
The bound belongs where the sequence is awaited; the helper is extracted only so the timing is testable.
|
|
31
|
+
|
|
32
|
+
## 4. Signal vs authority compliance
|
|
33
|
+
|
|
34
|
+
No authority added.
|
|
35
|
+
|
|
36
|
+
## 4b. Judgment-point check
|
|
37
|
+
|
|
38
|
+
Not a decision point.
|
|
39
|
+
|
|
40
|
+
## 5. Interactions
|
|
41
|
+
|
|
42
|
+
The restart/update paths that send SIGTERM (lifeline supervisor, launchd, `instar server restart`) now see the process exit within the bound instead of escalating to SIGKILL after their own timeouts. Exit code 1 on a forced exit lets a supervisor tell a clean stop from a cut-short one.
|
|
43
|
+
|
|
44
|
+
## 6. External surfaces
|
|
45
|
+
|
|
46
|
+
Two new log lines on an overrun or a second signal.
|
|
47
|
+
|
|
48
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
49
|
+
|
|
50
|
+
Machine-local by design; each machine's process bounds its own teardown.
|
|
51
|
+
|
|
52
|
+
## 8. Rollback cost
|
|
53
|
+
|
|
54
|
+
Revert; no state change.
|
|
55
|
+
|
|
56
|
+
## Conclusion
|
|
57
|
+
|
|
58
|
+
A SIGTERM now always ends the process, and the log names the step that would have hung forever.
|
|
59
|
+
|
|
60
|
+
## Class-Closure Declaration (display-only mirror)
|
|
61
|
+
|
|
62
|
+
`{defectClass: "unbounded-self-action", closure: "n/a", reason: "no self-triggered action: a one-shot bound on an operator/supervisor-initiated shutdown; it fires at most once per process"}`
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Side-Effects Review — subscription pool: legacy shared file → per-machine authority
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `subscription-pool-legacy-migrate`
|
|
4
|
+
**Date:** `2026-10-06`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `independent subagent (see below)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Spec `docs/specs/subscription-pool-authority-foundation.md` (converged 2026-08-27, approved by Justin) defines the per-machine authority store and names "PostUpdateMigrator (legacy single-file staged migration)" as a changed file — but that migration was never built: the witness type carries `legacy-migrate`, nothing wrote it. Meanwhile instar#2122 showed the cost: the legacy `.instar/subscription-pool.json` was shared between Luna's laptop and Studio through the agent home's git repo, so the Studio inherited the laptop's login locations and its sessions used `/Users/justin/...` paths.
|
|
11
|
+
|
|
12
|
+
1. `SubscriptionPoolAuthorityStore.migrateLegacy(root, { removeLegacy })`: publishes the authority from the validated, untouched legacy source; the witness is `legacy-migrate` bound to the source's SHA-256 + size; staging under `subscription-pool.candidate-legacy-<gen>`; cleanup of staging/witness on failure. `recoverLegacyMigrate` implements the spec's recovery table (finalize committed directory; rename complete staging; rebuild from a byte-matching source; fail closed on a mutated/missing source).
|
|
13
|
+
2. `SubscriptionPool.migrateLegacyToMachineLocal({ removeLegacy })`: reads the legacy file, drops rows whose `configHome` does not exist on this machine (reported), publishes the rest. `persist()` now writes to the authority whenever a published generation was loaded, even if a legacy file is still on disk (before, load read the authority but persist would have written the legacy file — a divergence).
|
|
14
|
+
3. `PostUpdateMigrator.migrateSubscriptionPoolToMachineLocal`: adds `.instar/subscription-pool.json` to the agent home's `.gitignore`; runs the migration once per machine; removes the legacy file only on git's POSITIVE "not tracked" answer (`git ls-files --error-unmatch` via `SafeGitExecutor.readSync`, tri-state: tracked / untracked / unknown — a missing repo, a refused source-tree guard or any other failure is `unknown` and keeps the file). `migrateSubscriptionPoolInteractiveReady` now reads the authority when one is published (machineId-aware pool) instead of gating on the legacy path. `GITIGNORE_ENTRIES` gains the same entry for new homes.
|
|
15
|
+
|
|
16
|
+
**Deliberate deviation from the spec's "remove matching legacy":** a git-tracked legacy file is left in place. Deleting it would be committed by the agent home's git-sync and propagate to every peer on its next pull; a peer that has not yet migrated would lose its only copy. The authority wins on load and persist, so the lingering shared copy is inert. Removal of tracked copies is a later step once every machine of an agent has migrated.
|
|
17
|
+
|
|
18
|
+
## Decision-point inventory
|
|
19
|
+
|
|
20
|
+
- Which rows are published: `fs.existsSync(configHome)` on this machine.
|
|
21
|
+
- Whether the legacy source is removed: git-tracked or not.
|
|
22
|
+
|
|
23
|
+
## 1. Over-block
|
|
24
|
+
|
|
25
|
+
A row whose login home is temporarily unavailable at migration time (unmounted volume) is dropped from this machine's authority; the legacy file (kept when tracked; removed when untracked) still lists it, and re-enrollment restores it. Rows are never deleted anywhere else.
|
|
26
|
+
|
|
27
|
+
## 2. Under-block
|
|
28
|
+
|
|
29
|
+
A peer that pulls a `.gitignore` change gains the ignore entry but keeps tracking the file until a later release untracks it. Luna's Studio has already untracked its copy by hand; the laptop's copy is still tracked.
|
|
30
|
+
|
|
31
|
+
## 3. Level-of-abstraction fit
|
|
32
|
+
|
|
33
|
+
Store-level protocol in the authority store (where first-create and update live); row filtering in the pool (which knows the account shape); git/ignore handling in the migrator (the documented path for existing agents).
|
|
34
|
+
|
|
35
|
+
## 4. Signal vs authority compliance
|
|
36
|
+
|
|
37
|
+
No decision authority over agent behaviour; a data-store migration. The migration only ever ADDS the per-machine authority; it deletes a legacy file only when untracked and byte-matching the witness.
|
|
38
|
+
|
|
39
|
+
## 4b. Judgment-point check
|
|
40
|
+
|
|
41
|
+
Not a competing-signals decision: file existence, git tracking and digest equality are facts.
|
|
42
|
+
|
|
43
|
+
## 5. Interactions
|
|
44
|
+
|
|
45
|
+
- `load()` already preferred the authority; `persist()` precedence is corrected to match.
|
|
46
|
+
- Existing `migrateSubscriptionPoolInteractiveReady` still reads the legacy path to find claude homes; it runs on the authority-less pool and is unaffected.
|
|
47
|
+
- Quota polling, swaps and enrollment read the pool; after migration they see only this machine's homes — the intended fix for #2122.
|
|
48
|
+
- E2E: the production update path + server boot on a home with a legacy file serves only this machine's logins over `/subscription-pool`.
|
|
49
|
+
|
|
50
|
+
## 6. External surfaces
|
|
51
|
+
|
|
52
|
+
Migration log lines name published/dropped account ids (never credentials). `.gitignore` gains one entry.
|
|
53
|
+
|
|
54
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
55
|
+
|
|
56
|
+
Machine-local BY DESIGN per the approved spec (login locations live on one disk; `machineId` witness, `foreign-authority` refusal). Account METADATA replication for pool-scope views is the existing WS5.2 projection and is untouched.
|
|
57
|
+
|
|
58
|
+
## 8. Rollback cost
|
|
59
|
+
|
|
60
|
+
Revert the code; the authority directory and witness remain on disk and the previous release ignores them (it reads the legacy file, which was kept when tracked). For an untracked, removed legacy file the prior release would see an unconfigured pool until re-enrollment; the authority directory holds the data.
|
|
61
|
+
|
|
62
|
+
## Conclusion
|
|
63
|
+
|
|
64
|
+
Builds the migration the approved spec already called for, with one safety deviation (never delete a shared copy) that closes the #2122 cross-machine inheritance without risking a peer's data.
|
|
65
|
+
|
|
66
|
+
## Second-pass review (if required)
|
|
67
|
+
|
|
68
|
+
Independent subagent reviewer, 2026-10-06. **Two concerns raised, both fixed:** (1) a crash-recovery rebuild republishes the WHOLE legacy source and the pool then answered `already-machine-local`, so foreign homes would have stayed published under this machine's id — now, while the witness operation is still `legacy-migrate`, the next migration pass re-applies the home filter with an `update()` (test: rebuild → prune → no-op); (2) `isGitTracked` returned `false` on any error, including the source-tree guard refusing `ls-files` on an agent home that is the instar checkout, which would have forced removal — now tri-state, and only git's own "not tracked" answer permits removal (test: no repo → kept). Also noted and fixed: `migrateSubscriptionPoolInteractiveReady` skipped forever once an untracked legacy was removed — it now reads the published authority. Concurred on witness/sibling conformance, `persist()` precedence, deletion safety (digest+size-gated unlink only; recovery never deletes), and the `configHome` filter. The spec's "then remove matching legacy" is deliberately not applied to a tracked copy (recorded above).
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Side-Effects Review — a quiet standby does not connect to the Threadline relay
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `threadline-relay-standby`
|
|
4
|
+
**Date:** `2026-10-06`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `not required (one boolean gate on an opt-in connection, reusing an existing per-machine flag)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
instar#2122: "Both machines connected to the Threadline relay with the same identity and displaced each other. Disabled on the standby for now." The relay admits one connection per agent identity (the agent identity is shared across a multi-machine agent by design), so two machines of one agent knock each other off. `bootstrapThreadline` gains `relayStandby`; `server.ts` passes `!shouldOwnTelegramPoll(config)` — the SAME per-machine flag (`multiMachine.telegramPolling: false`) the lifeline already uses to keep a standby off the Telegram poll. A standby logs `relay connection SUPPRESSED (standby…)`, keeps MCP tools, local discovery and handshake, and never opens the relay socket. CLAUDE.md template + migration gain one awareness line so a standby agent explains "unreachable" correctly.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- Whether this machine opens the relay connection: `relayEnabled && !relayStandby`.
|
|
15
|
+
|
|
16
|
+
## 1. Over-block
|
|
17
|
+
|
|
18
|
+
A standby cannot be reached by peers over the relay; the awake machine answers for the agent (the Threadline Conversation Coherence model already says a conversation lives on one machine). Single-machine agents (flag absent) are unchanged. A standby that becomes awake needs a restart to connect — the same restart `telegramPolling` already requires.
|
|
19
|
+
|
|
20
|
+
## 2. Under-block
|
|
21
|
+
|
|
22
|
+
Lease-following (connect on acquire, disconnect on demote) would remove the restart; this ships the static rule Luna applied by hand, now structural. Two machines both configured as pollers still displace each other, as before.
|
|
23
|
+
|
|
24
|
+
## 3. Level-of-abstraction fit
|
|
25
|
+
|
|
26
|
+
The bootstrap owns the connection decision; the server supplies the machine role from the existing flag.
|
|
27
|
+
|
|
28
|
+
## 4. Signal vs authority compliance
|
|
29
|
+
|
|
30
|
+
No authority over messages; a connection gate.
|
|
31
|
+
|
|
32
|
+
## 4b. Judgment-point check
|
|
33
|
+
|
|
34
|
+
Not a competing-signals decision.
|
|
35
|
+
|
|
36
|
+
## 5. Interactions
|
|
37
|
+
|
|
38
|
+
`shouldOwnTelegramPoll` reads `multiMachine.telegramPolling` only (default true), so the gate is dark for every agent that has not set the standby flag. The listener daemon path (`daemonHandlingRelay`) is downstream of `relayEnabled` and so also stays off on a standby.
|
|
39
|
+
|
|
40
|
+
## 6. External surfaces
|
|
41
|
+
|
|
42
|
+
One boot log line on a standby; one CLAUDE.md bullet.
|
|
43
|
+
|
|
44
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
45
|
+
|
|
46
|
+
This change exists for multi-machine: one relay connection per agent, held by the awake machine. Machine-local by design (a socket is per machine).
|
|
47
|
+
|
|
48
|
+
## 8. Rollback cost
|
|
49
|
+
|
|
50
|
+
Revert; or set `multiMachine.telegramPolling` back to true on the standby.
|
|
51
|
+
|
|
52
|
+
## Conclusion
|
|
53
|
+
|
|
54
|
+
Makes structural what Luna did by hand on the Studio.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Side-Effects Review — a relay send reports the conversation id the peer will reply on
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `threadline-wire-thread-id`
|
|
4
|
+
**Date:** `2026-10-05`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `independent subagent (see below)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
ACT-1304 (reported by Luna/sagemind on Threadline, 2026-10-04). On the relay path of `POST /threadline/relay-send`, a send without an explicit `threadId` let `ThreadlineClient` pick the wire thread (client affinity or a fresh `thread-<ts>-<rand>`), but the route recorded `threadId ?? messageId`. Every downstream record — the canonical outbox, the thread log, the Telegram bridge mirror, the A2A delivery tracker, the reply waiter, and `captureOrigin` (the thread→origin-topic binding that routes a reply back to the session that asked) — was keyed on the `msg-…` id. The peer replied on `thread-…`, which matched none of them, so the reply cold-spawned a new session; at a full session cap that spawn was refused and the queued reply was held indefinitely.
|
|
11
|
+
|
|
12
|
+
`ThreadlineClient.sendAutoWithThread` returns `{ messageId, threadId }` where `threadId` is the id that went on the wire (both the encrypted `send` and plaintext `sendPlaintext` paths record it). The route uses it for every record above. Wire behaviour is unchanged.
|
|
13
|
+
|
|
14
|
+
## Decision-point inventory
|
|
15
|
+
|
|
16
|
+
None changed. Routing of an inbound reply (topic linkage / live inject / resume / spawn) is unchanged; it now receives the correct key.
|
|
17
|
+
|
|
18
|
+
## 1. Over-block
|
|
19
|
+
|
|
20
|
+
Nothing is refused.
|
|
21
|
+
|
|
22
|
+
## 2. Under-block
|
|
23
|
+
|
|
24
|
+
ACT-1304's other parts are not in this change: the spawn drain's give-up alert being held by the origin and duplicate-content gates, and a reported thread id truncated at its last hyphen (no cause located yet). A reply to a send from a session that is NOT topic-bound has no origin binding, so it still takes the spawn path (as before). <!-- tracked: ACT-1304 -->
|
|
25
|
+
|
|
26
|
+
## 3. Level-of-abstraction fit
|
|
27
|
+
|
|
28
|
+
The client is the only place that knows the wire thread; it now reports it. The route stops guessing.
|
|
29
|
+
|
|
30
|
+
## 4. Signal vs authority compliance
|
|
31
|
+
|
|
32
|
+
No authority added.
|
|
33
|
+
|
|
34
|
+
## 4b. Judgment-point check
|
|
35
|
+
|
|
36
|
+
Not a decision point.
|
|
37
|
+
|
|
38
|
+
## 5. Interactions
|
|
39
|
+
|
|
40
|
+
The local-delivery path already used the minted `effectiveThreadId` and is untouched. Threads recorded before this change under `msg-…` ids keep those records; new sends use the wire id. Callers that pass an explicit `threadId` see no difference. Test mocks of the relay client gained `sendAutoWithThread`. Side benefit: the A2A delivery tracker acknowledges by thread id, so relay sends' `stale` flag becomes accurate (a `msg-…` key could never be acked by a reply). The stored-retry path re-sends old records with their recorded `msg-…` thread id, behaving as before.
|
|
41
|
+
|
|
42
|
+
## 6. External surfaces
|
|
43
|
+
|
|
44
|
+
`threadline_send`'s returned `threadId` is now the real conversation id (previously the message id on relay sends). `threadline_history` on that id now finds the thread.
|
|
45
|
+
|
|
46
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
47
|
+
|
|
48
|
+
Machine-local send path; the corrected id flows into the same replicated/mesh records as before (thread log, conversation mesh view), now consistent with the peer's.
|
|
49
|
+
|
|
50
|
+
## 8. Rollback cost
|
|
51
|
+
|
|
52
|
+
Revert; no data migration. Records written with wire ids remain valid.
|
|
53
|
+
|
|
54
|
+
## Conclusion
|
|
55
|
+
|
|
56
|
+
A one-field correctness fix at the source of the mismatch, which lets the existing reply-to-originator path work for relay sends.
|
|
57
|
+
|
|
58
|
+
## Second-pass review (if required)
|
|
59
|
+
|
|
60
|
+
Independent subagent reviewer, 2026-10-05: **Concur with the review.** Both send paths always put a thread id on the wire and the receiver keeps an arriving id (it mints only when none arrives); the anti-hijack isolation fires only for an id owned by a different participant, which a fresh `thread-…` id cannot be. No consumer relies on threadId===messageId; the delivery tracker and reply waiter key on thread id. `lastWireThreadId` is set and read synchronously. Two notes folded into §5. A pre-existing window where a very fast reply could beat the waiter registration (after `await captureOrigin`) is unchanged by this change.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Side-Effects Review — `instar wakeup` talks to the machine that is actually awake
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `wakeup-targets-awake-machine`
|
|
4
|
+
**Date:** `2026-10-06`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `not required (CLI target/URL resolution; the handoff protocol itself is unchanged)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
instar#2122: "`instar wakeup` reported 'Current location: mac-studio' while the laptop held the lease, then failed with `Invalid challenge signature`." Two causes in `wakeup()`:
|
|
11
|
+
|
|
12
|
+
1. It took the awake machine from the registry's ROLE field (`getAwakeMachine`), which lags the live lease and on Luna's Studio still named a removed identity. New `resolveAwakeMachine` asks the local server's `/health` for `multiMachine.syncStatus.leaseHolder` first (an active, non-revoked registry entry), falling back to the registry role.
|
|
13
|
+
2. It sent the handoff challenge to its OWN server (`localhost:<port>/health` → `tunnelUrl || localhost`). The server builds the challenge message with ITSELF as receiver while the client signed for the awake machine's id, so the signature never matched. New `resolveAwakeServerUrl` probes the awake machine's advertised endpoints (tailscale, lan, cloudflare) and last-known URL with `/health` and uses the first that answers; `localhost` is never a candidate.
|
|
14
|
+
|
|
15
|
+
## Decision-point inventory
|
|
16
|
+
|
|
17
|
+
- Which machine is "awake": live lease holder → registry role.
|
|
18
|
+
- Which URL receives the handoff: the awake machine's reachable rope.
|
|
19
|
+
|
|
20
|
+
## 1. Over-block
|
|
21
|
+
|
|
22
|
+
If none of the awake machine's addresses answer, wakeup now says so and points at `--force` — before, it would have contacted the wrong server and failed later with a confusing signature error.
|
|
23
|
+
|
|
24
|
+
## 2. Under-block
|
|
25
|
+
|
|
26
|
+
The handoff protocol, `--force` and the no-awake promotion path are unchanged.
|
|
27
|
+
|
|
28
|
+
## 3. Level-of-abstraction fit
|
|
29
|
+
|
|
30
|
+
Both helpers live in the CLI next to `wakeup()`, exported for tests.
|
|
31
|
+
|
|
32
|
+
## 4. Signal vs authority compliance
|
|
33
|
+
|
|
34
|
+
No authority change; the server's challenge verification is untouched.
|
|
35
|
+
|
|
36
|
+
## 4b. Judgment-point check
|
|
37
|
+
|
|
38
|
+
Not a competing-signals decision.
|
|
39
|
+
|
|
40
|
+
## 5. Interactions
|
|
41
|
+
|
|
42
|
+
Uses `MachineRegistryEntry.endpoints` (multi-transport mesh) and `lastKnownUrl` as the mesh RPC client does. The lease read via `/health` is the same field `instar doctor` shows.
|
|
43
|
+
|
|
44
|
+
## 6. External surfaces
|
|
45
|
+
|
|
46
|
+
CLI output: "Reaching <name> at <redacted url>".
|
|
47
|
+
|
|
48
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
49
|
+
|
|
50
|
+
This change exists for multi-machine; it reads the mesh's own records.
|
|
51
|
+
|
|
52
|
+
## 8. Rollback cost
|
|
53
|
+
|
|
54
|
+
Revert.
|
|
55
|
+
|
|
56
|
+
## Conclusion
|
|
57
|
+
|
|
58
|
+
The handoff request now goes to the machine that can answer it, named from the live lease.
|