instar 1.3.42 → 1.3.44
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/server.d.ts.map +1 -1
- package/dist/commands/server.js +292 -3
- package/dist/commands/server.js.map +1 -1
- package/dist/core/HandoffReceiver.d.ts +56 -0
- package/dist/core/HandoffReceiver.d.ts.map +1 -0
- package/dist/core/HandoffReceiver.js +103 -0
- package/dist/core/HandoffReceiver.js.map +1 -0
- package/dist/core/HandoffWireTransport.d.ts +61 -0
- package/dist/core/HandoffWireTransport.d.ts.map +1 -0
- package/dist/core/HandoffWireTransport.js +114 -0
- package/dist/core/HandoffWireTransport.js.map +1 -0
- package/dist/core/HttpLeaseTransport.d.ts +68 -0
- package/dist/core/HttpLeaseTransport.d.ts.map +1 -0
- package/dist/core/HttpLeaseTransport.js +97 -0
- package/dist/core/HttpLeaseTransport.js.map +1 -0
- package/dist/core/HttpLiveTailTransport.d.ts +88 -0
- package/dist/core/HttpLiveTailTransport.d.ts.map +1 -0
- package/dist/core/HttpLiveTailTransport.js +95 -0
- package/dist/core/HttpLiveTailTransport.js.map +1 -0
- package/dist/core/LeaseCoordinator.d.ts +14 -0
- package/dist/core/LeaseCoordinator.d.ts.map +1 -1
- package/dist/core/LeaseCoordinator.js +38 -0
- package/dist/core/LeaseCoordinator.js.map +1 -1
- package/dist/core/LiveTailSource.d.ts +65 -0
- package/dist/core/LiveTailSource.d.ts.map +1 -0
- package/dist/core/LiveTailSource.js +87 -0
- package/dist/core/LiveTailSource.js.map +1 -0
- package/dist/core/MultiMachineCoordinator.d.ts +14 -0
- package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
- package/dist/core/MultiMachineCoordinator.js +22 -0
- package/dist/core/MultiMachineCoordinator.js.map +1 -1
- package/dist/core/ReplyMarkerTransport.d.ts +58 -0
- package/dist/core/ReplyMarkerTransport.d.ts.map +1 -0
- package/dist/core/ReplyMarkerTransport.js +66 -0
- package/dist/core/ReplyMarkerTransport.js.map +1 -0
- package/dist/core/SafeGitExecutor.d.ts.map +1 -1
- package/dist/core/SafeGitExecutor.js +21 -2
- package/dist/core/SafeGitExecutor.js.map +1 -1
- package/dist/core/featureRolloutScan.d.ts.map +1 -1
- package/dist/core/featureRolloutScan.js +5 -4
- package/dist/core/featureRolloutScan.js.map +1 -1
- package/dist/core/handoffReceiverWiring.d.ts +57 -0
- package/dist/core/handoffReceiverWiring.d.ts.map +1 -0
- package/dist/core/handoffReceiverWiring.js +65 -0
- package/dist/core/handoffReceiverWiring.js.map +1 -0
- package/dist/core/handoffSentinelBootWiring.d.ts +53 -0
- package/dist/core/handoffSentinelBootWiring.d.ts.map +1 -0
- package/dist/core/handoffSentinelBootWiring.js +52 -0
- package/dist/core/handoffSentinelBootWiring.js.map +1 -0
- package/dist/core/handoffSentinelWiring.d.ts +60 -0
- package/dist/core/handoffSentinelWiring.d.ts.map +1 -0
- package/dist/core/handoffSentinelWiring.js +59 -0
- package/dist/core/handoffSentinelWiring.js.map +1 -0
- package/dist/core/seamlessnessConfig.d.ts +8 -0
- package/dist/core/seamlessnessConfig.d.ts.map +1 -1
- package/dist/core/seamlessnessConfig.js +1 -0
- package/dist/core/seamlessnessConfig.js.map +1 -1
- package/dist/core/types.d.ts +6 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/messaging/TelegramAdapter.d.ts +6 -0
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +8 -0
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/messaging/ingressDedup.d.ts +55 -0
- package/dist/messaging/ingressDedup.d.ts.map +1 -0
- package/dist/messaging/ingressDedup.js +69 -0
- package/dist/messaging/ingressDedup.js.map +1 -0
- package/dist/messaging/stuckMessageRecovery.d.ts +46 -0
- package/dist/messaging/stuckMessageRecovery.d.ts.map +1 -0
- package/dist/messaging/stuckMessageRecovery.js +52 -0
- package/dist/messaging/stuckMessageRecovery.js.map +1 -0
- package/dist/monitoring/releaseReadinessWiring.d.ts.map +1 -1
- package/dist/monitoring/releaseReadinessWiring.js +4 -2
- package/dist/monitoring/releaseReadinessWiring.js.map +1 -1
- package/dist/server/AgentServer.d.ts +64 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +24 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/handoffInitiateRoutes.d.ts +40 -0
- package/dist/server/handoffInitiateRoutes.d.ts.map +1 -0
- package/dist/server/handoffInitiateRoutes.js +57 -0
- package/dist/server/handoffInitiateRoutes.js.map +1 -0
- package/dist/server/machineRoutes.d.ts +47 -0
- package/dist/server/machineRoutes.d.ts.map +1 -1
- package/dist/server/machineRoutes.js +138 -0
- package/dist/server/machineRoutes.js.map +1 -1
- package/dist/server/routes.d.ts +20 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +65 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +49 -49
- package/upgrades/1.3.41.md +9 -5
- package/upgrades/1.3.43.md +21 -0
- package/upgrades/1.3.44.md +72 -0
- package/upgrades/side-effects/cross-machine-seamlessness-consent-acquire.md +52 -0
- package/upgrades/side-effects/cross-machine-seamlessness-exactly-once-ingress.md +73 -0
- package/upgrades/side-effects/cross-machine-seamlessness-handoff-ack-yield.md +82 -0
- package/upgrades/side-effects/cross-machine-seamlessness-handoff-begin.md +48 -0
- package/upgrades/side-effects/cross-machine-seamlessness-handoff-boot-wiring.md +68 -0
- package/upgrades/side-effects/cross-machine-seamlessness-handoff-receiver-wiring.md +52 -0
- package/upgrades/side-effects/cross-machine-seamlessness-handoff-receiver.md +46 -0
- package/upgrades/side-effects/cross-machine-seamlessness-handoff-sentinel-wiring.md +47 -0
- package/upgrades/side-effects/cross-machine-seamlessness-lease-wire.md +46 -0
- package/upgrades/side-effects/cross-machine-seamlessness-livetail-source.md +50 -0
- package/upgrades/side-effects/cross-machine-seamlessness-livetail-streaming.md +50 -0
- package/upgrades/side-effects/cross-machine-seamlessness-livetail-wire.md +65 -0
- package/upgrades/side-effects/cross-machine-seamlessness-noloss-recovery.md +61 -0
- package/upgrades/side-effects/cross-machine-seamlessness-reply-marker.md +65 -0
- package/upgrades/side-effects/source-tree-guard-readsync-bypass.md +34 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Side-Effects Review — Cross-Machine Seamlessness: no-loss stuck-message recovery (D-noloss)
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/CROSS-MACHINE-SEAMLESSNESS-SPEC.md §8 G3a ("Stuck-`processing` recovery")
|
|
4
|
+
|
|
5
|
+
The complement to the inbound dedup gate. With dedup alone, a crash AFTER claiming an
|
|
6
|
+
inbound event but BEFORE the reply commits would lose the reply (Telegram won't redeliver
|
|
7
|
+
once polled). This adds the spec's explicit "re-run from the stored input by the current
|
|
8
|
+
lease holder" mechanism: a `processing` entry stuck past `maxProcessingMs` is re-routed from
|
|
9
|
+
its `inputSnapshot`, producing the lost reply. Together with the dedup gate this makes the
|
|
10
|
+
flag's guarantee genuinely *exactly-once* (no-loss AND no-duplicate). Still flag-gated dark
|
|
11
|
+
(`multiMachine.exactlyOnceIngress`, default false).
|
|
12
|
+
|
|
13
|
+
## What changed
|
|
14
|
+
- `src/messaging/stuckMessageRecovery.ts` — NEW. `recoverStuckMessages(deps)`: lease-gated
|
|
15
|
+
(a standby never injects); `reclaimStuck(maxProcessingMs)` → for each entry with a stored
|
|
16
|
+
input and `attempts < maxReplayAttempts` (default 3), re-claim under the current epoch and
|
|
17
|
+
call the injected `reinject(topic, dedupeKey, text)`. Pure orchestration over injected deps.
|
|
18
|
+
- `src/commands/server.ts` — after `server.start()`, ONLY when the ledger is wired (flag on)
|
|
19
|
+
AND telegram is present: builds `reinject` (sets the per-topic current-inbound key, then
|
|
20
|
+
routes the stored text via the same `telegram.onTopicMessage` path a fresh forward uses),
|
|
21
|
+
runs recovery at boot, and on a cadence (`max(30s, maxProcessingMs)`), lease-gated.
|
|
22
|
+
|
|
23
|
+
## Over-block / under-block
|
|
24
|
+
- OVER-re-run (the risk = re-answering a message the agent legitimately left unanswered, or a
|
|
25
|
+
persistently-failing turn looping): bounded by (1) only entries stuck past `maxProcessingMs`
|
|
26
|
+
(a genuinely timed-out turn, not one in flight), (2) `attempts < maxReplayAttempts` cap —
|
|
27
|
+
after the cap the entry is left alone, never re-run again, (3) lease-gate (only the awake
|
|
28
|
+
machine), (4) flag-off default. Proven both sides in stuck-message-recovery.test.ts.
|
|
29
|
+
- UNDER-recover: an entry with no `inputSnapshot` can't be replayed (nothing captured) → skipped
|
|
30
|
+
honestly rather than guessed. Inputs ARE captured by the dedup gate's `decideIngress(input:text)`.
|
|
31
|
+
- The known single-duplicate residual (spec §3 / §8 G3a impossibility floor): a crash AFTER the
|
|
32
|
+
reply physically sent but BEFORE `reply_committed` persisted → re-run produces one duplicate.
|
|
33
|
+
This is the documented Two-Generals floor, bounded to one, not a defect.
|
|
34
|
+
|
|
35
|
+
## Signal vs authority
|
|
36
|
+
- recoverStuckMessages decides which entries to re-run; the re-injection authority is the
|
|
37
|
+
existing onTopicMessage routing. The module holds no Telegram/server coupling (injected deps).
|
|
38
|
+
|
|
39
|
+
## Interactions
|
|
40
|
+
- Re-injection sets `currentInboundByTopic[topic]` BEFORE routing so the eventual reply commits
|
|
41
|
+
the correct entry (same coupling the inbound gate uses) — closing the loop: re-run → reply →
|
|
42
|
+
commit → not re-run again.
|
|
43
|
+
- Telegram-only for v1 (the `--no-telegram` sessionManager path is a tracked refinement; the
|
|
44
|
+
block is guarded on `telegram` present).
|
|
45
|
+
- Cadenced re-scan is `unref()`'d (never holds the process open) and lease-gated (a standby's
|
|
46
|
+
timer no-ops).
|
|
47
|
+
|
|
48
|
+
## Rollback cost
|
|
49
|
+
- Low. Flag default-off ⇒ the whole block is skipped (messageLedger undefined). Revert = drop the
|
|
50
|
+
post-start block + the module. No persisted schema beyond the opt-in ledger SQLite.
|
|
51
|
+
|
|
52
|
+
## Tests
|
|
53
|
+
- `tests/unit/stuck-message-recovery.test.ts` (7): re-runs a stuck entry from stored input;
|
|
54
|
+
standby no-op; already-replied not re-run; in-flight (within window) not re-run; attempts-cap
|
|
55
|
+
gives up (no storm); no-input skipped; + boot wiring-integrity (server.ts calls it after
|
|
56
|
+
server.start(), gated on the ledger + coordinator lease).
|
|
57
|
+
|
|
58
|
+
## NOT in this increment (tracked, topic-13481)
|
|
59
|
+
- Behavioral boot-recovery e2e (needs a live Telegram adapter) — exercised by the live flag-flip
|
|
60
|
+
test-as-self, which is the gate before defaulting the flag on. Cross-machine marker (D-xmachine)
|
|
61
|
+
+ CONTINUATION resume (D3) remain.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Side-Effects Review — Cross-Machine Seamlessness: reply-marker propagation (D-xmachine)
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/CROSS-MACHINE-SEAMLESSNESS-SPEC.md §8 G3a ("dual-medium reply_committed marker")
|
|
4
|
+
|
|
5
|
+
The cross-machine half of exactly-once. The same-machine dedup gate + no-loss recovery handle
|
|
6
|
+
one machine; this closes the window where, AFTER a handoff/failover, a provider redelivery of a
|
|
7
|
+
message the OLD holder already answered arrives at the NEW holder — whose ledger wouldn't know,
|
|
8
|
+
so it would re-answer. The holder now propagates a `reply_committed` marker to standby peers on
|
|
9
|
+
each commit; the standby applies it via `applyRemoteReplyMarker`, so the new holder's dedup gate
|
|
10
|
+
drops the redelivery. Still flag-gated dark (`multiMachine.exactlyOnceIngress`, default false).
|
|
11
|
+
|
|
12
|
+
## What changed
|
|
13
|
+
- `src/core/ReplyMarkerTransport.ts` — NEW. `broadcast(marker)` signs + POSTs
|
|
14
|
+
{dedupeKey, platform, replyIdempotencyKey, epoch, topic} to each standby peer's
|
|
15
|
+
`/api/message-marker` over the authenticated machine channel. NO encryption (the marker
|
|
16
|
+
carries no conversation content — authentication only). No peers → reachable no-op. Never
|
|
17
|
+
throws (best-effort; the dedup gate + provider redelivery are the backstop).
|
|
18
|
+
- `src/server/machineRoutes.ts` — `POST /api/message-marker` (machineAuth) + `onReplyMarker`
|
|
19
|
+
ctx callback. Validates the marker shape; 503 when no receiver (exactly-once off).
|
|
20
|
+
- `src/server/AgentServer.ts` — `replyMarkerTransport?` + `onReplyMarker?` options → routeCtx /
|
|
21
|
+
machineRoutes.
|
|
22
|
+
- `src/server/routes.ts` — RouteContext.replyMarkerTransport; the outbound commit at
|
|
23
|
+
`/telegram/reply/:topicId` reads the just-committed `replyIdempotencyKey` from the ledger and
|
|
24
|
+
fires `replyMarkerTransport.broadcast(...)` (fire-and-forget, after the local commit).
|
|
25
|
+
- `src/commands/server.ts` — constructs `ReplyMarkerTransport` (peer resolver = registry
|
|
26
|
+
machines with a lastKnownUrl) ONLY when `exactlyOnceIngress` + coordinator.enabled; wires
|
|
27
|
+
`onReplyMarker` → `messageLedger.applyRemoteReplyMarker`.
|
|
28
|
+
|
|
29
|
+
## Over-block / under-block
|
|
30
|
+
- The marker NEVER over-marks a fresh event: `applyRemoteReplyMarker` only sets reply_committed
|
|
31
|
+
on an entry not already acted-on, and the dedupeKey is the same provider-stable id both
|
|
32
|
+
machines compute — so a marker for event X can only dedup event X. The deterministic
|
|
33
|
+
`replyIdempotencyKey` (hash(dedupeKey+index)) is identical on both machines.
|
|
34
|
+
- UNDER-propagate (a lost marker): best-effort — if the marker doesn't reach the standby, the
|
|
35
|
+
provider's at-least-once redelivery + the standby's own ledger (once it catches up) still
|
|
36
|
+
dedup; the residual is the documented single-duplicate Two-Generals floor, not a new failure.
|
|
37
|
+
- Signed channel: `/api/message-marker` requires machineAuth (unsigned → 401, proven in the e2e);
|
|
38
|
+
the marker carries no secrets so no encryption is needed.
|
|
39
|
+
|
|
40
|
+
## Signal vs authority
|
|
41
|
+
- The transport propagates; the receiving ledger's `applyRemoteReplyMarker` is the authority on
|
|
42
|
+
its own state (only promotes a not-yet-acted entry). The dedup gate remains the single drop authority.
|
|
43
|
+
|
|
44
|
+
## Interactions
|
|
45
|
+
- Fires AFTER the local `commitInboundReply` (the local guarantee never depends on propagation).
|
|
46
|
+
- Skipped for proxy/system sends (same guard as the commit) — a proxy message isn't a user reply.
|
|
47
|
+
- Peer resolver mirrors the live-tail/handoff resolvers (registry machines with lastKnownUrl,
|
|
48
|
+
excludes self/revoked). Solo agent → no peers → no-op.
|
|
49
|
+
|
|
50
|
+
## Rollback cost
|
|
51
|
+
- Low. Flag default-off ⇒ transport not constructed, route 503s, no propagation. Revert = drop
|
|
52
|
+
the transport + route + the broadcast call. No persisted schema.
|
|
53
|
+
|
|
54
|
+
## Tests
|
|
55
|
+
- `tests/unit/reply-marker-transport.test.ts` (3): no-peers no-op; signs + POSTs to each peer;
|
|
56
|
+
resolves false (never throws) when all peers reject.
|
|
57
|
+
- `tests/e2e/reply-marker-cross-machine-e2e.test.ts` (2): a PEER-signed marker over real HTTP +
|
|
58
|
+
machine-auth applies to the receiver's ledger, then a redelivery of that message is DEDUPED
|
|
59
|
+
(already-replied) — the cross-machine guarantee end-to-end; + unsigned marker rejected (401).
|
|
60
|
+
|
|
61
|
+
## Completion status
|
|
62
|
+
- With D-dedup + D-noloss + D-xmachine, the exactly-once guarantee is COMPLETE in all dimensions
|
|
63
|
+
(no-dup same+cross machine, no-loss on crash), all DARK behind the flag. Remaining before the
|
|
64
|
+
flag flips: D3 (CONTINUATION resume verify) + the over-Telegram two-machine test-as-self (the
|
|
65
|
+
live acceptance bar, needs the operator's machines).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Side-Effects Review — SourceTreeGuard read-tier bypass (extend to readSync)
|
|
2
|
+
|
|
3
|
+
**Driver:** dogfood follow-up to PR #450 (source-tree-guard-data-pull). The narrow `sourceTreeReadOk` opt was added to `execSync` + `spawn` to let `git fetch` work on the agent's own instar checkout. After deploying v1.3.38 to Echo, the next layer of the same gap surfaced: `readSync` also runs source-tree-checks ("defense-in-depth"), and the canonical-ref scan path uses readonly verbs (`rev-parse`, `ls-tree`, `show`, `log`, `merge-base`, `remote -v`) that all go through `readSync` → all blocked on the source tree.
|
|
4
|
+
|
|
5
|
+
## What changed
|
|
6
|
+
|
|
7
|
+
- **`src/core/SafeGitExecutor.ts`** —
|
|
8
|
+
- `readSync` now honors `opts.sourceTreeReadOk` the same way `execSync` / `spawn` do: when the opt is true AND the verb is in `SOURCE_TREE_READ_TIER_VERBS`, skip `runSourceTreeChecks`; audit the bypass with the `sourceTreeReadOk-bypass` reason.
|
|
9
|
+
- `SOURCE_TREE_READ_TIER_VERBS` extended from `['fetch']` to `['fetch', 'rev-parse', 'ls-tree', 'show', 'log', 'cat-file', 'merge-base', 'remote']`. Still a small closed enumeration; still requires a spec edit to grow. Every verb is read-tier: none mutate the working tree or committed refs.
|
|
10
|
+
- **`src/monitoring/releaseReadinessWiring.ts`** — every `SafeGitExecutor.run` callsite passes `sourceTreeReadOk: true`: `resolveCanonicalRemote` (`remote -v`), `fetchCanonical` (`rev-parse FETCH_HEAD` — fetch already had it), `oldestUnreleasedCommit` (`log`), `isAncestor` (`merge-base --is-ancestor`).
|
|
11
|
+
- **`src/core/featureRolloutScan.ts`** — every `SafeGitExecutor.run` callsite in the canonical scan path passes `sourceTreeReadOk: true`: `rev-parse FETCH_HEAD`, `ls-tree` (spec + trace enumeration), `show` (spec + trace blob reads). `fetch` already had it.
|
|
12
|
+
|
|
13
|
+
## Side-effects analysis
|
|
14
|
+
|
|
15
|
+
**Why this is safe.** Every verb in the (still-small, still-closed) `SOURCE_TREE_READ_TIER_VERBS` set is read-tier: it does NOT modify the working tree, any committed ref, or any source file. `fetch` writes only to `FETCH_HEAD` + objects (transient). `rev-parse`, `ls-tree`, `show`, `log`, `cat-file`, `merge-base`, `remote -v/-get-url` are pure-read. The bypass is opt-in per-call, audit-logged, and only effective inside the closed verb allowlist.
|
|
16
|
+
|
|
17
|
+
**Why the previous fix wasn't enough.** PR-450 only added the bypass to `execSync` + `spawn`, on the assumption that readonly verbs went through `readSync` which "didn't run source-tree checks." That was wrong: `readSync` runs them as defense-in-depth (catches repo-local aliases that could rebind a "read-only" verb). Every readonly verb on the agent's source tree was therefore still being blocked. This PR closes the gap at the right layer.
|
|
18
|
+
|
|
19
|
+
**Reach.** Two real-code callsites touched (the two new ones added by the spec: Layer B wiring + Layer C scanner). Every existing `SafeGitExecutor.readSync` caller that does NOT pass `sourceTreeReadOk: true` is byte-identically guarded by the prior behavior.
|
|
20
|
+
|
|
21
|
+
**Rollback.** Reverting this PR re-blocks the canonical-ref readSync calls on source trees. Layer B's fail-loud path keeps signaling on the failure; Layer C's local-scan fallback keeps working. No broken state.
|
|
22
|
+
|
|
23
|
+
## Testing
|
|
24
|
+
|
|
25
|
+
`tests/unit/SafeGitExecutor-sourceTreeReadOk.test.ts` (7 tests, all green):
|
|
26
|
+
- default: fetch on non-source-tree works
|
|
27
|
+
- default: fetch on source-tree blocked
|
|
28
|
+
- `sourceTreeReadOk: true` + fetch on source-tree passes
|
|
29
|
+
- `sourceTreeReadOk: true` does NOT bypass non-allowlist verbs (e.g. `add`)
|
|
30
|
+
- `SOURCE_TREE_READ_TIER_VERBS` is a closed read-tier set — no destructive write verbs (commit/push/reset/checkout/rebase/merge/clean/rm/branch/tag); size ≤ 10
|
|
31
|
+
- **readSync path also honors `sourceTreeReadOk` — `rev-parse FETCH_HEAD` on a source tree passes**
|
|
32
|
+
- **readSync path WITHOUT `sourceTreeReadOk` — `rev-parse FETCH_HEAD` on a source tree STILL blocked**
|
|
33
|
+
|
|
34
|
+
Lint clean.
|