instar 1.3.385 → 1.3.387

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 (41) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +92 -1
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  6. package/dist/core/PostUpdateMigrator.js +16 -0
  7. package/dist/core/PostUpdateMigrator.js.map +1 -1
  8. package/dist/core/types.d.ts +10 -0
  9. package/dist/core/types.d.ts.map +1 -1
  10. package/dist/core/types.js.map +1 -1
  11. package/dist/monitoring/A2ARedeliverySentinel.d.ts +82 -0
  12. package/dist/monitoring/A2ARedeliverySentinel.d.ts.map +1 -0
  13. package/dist/monitoring/A2ARedeliverySentinel.js +143 -0
  14. package/dist/monitoring/A2ARedeliverySentinel.js.map +1 -0
  15. package/dist/scaffold/templates.d.ts.map +1 -1
  16. package/dist/scaffold/templates.js +7 -0
  17. package/dist/scaffold/templates.js.map +1 -1
  18. package/dist/server/AgentServer.d.ts +2 -0
  19. package/dist/server/AgentServer.d.ts.map +1 -1
  20. package/dist/server/AgentServer.js +20 -0
  21. package/dist/server/AgentServer.js.map +1 -1
  22. package/dist/server/routes.d.ts +3 -0
  23. package/dist/server/routes.d.ts.map +1 -1
  24. package/dist/server/routes.js +81 -0
  25. package/dist/server/routes.js.map +1 -1
  26. package/dist/threadline/A2ADeliveryTracker.d.ts +151 -0
  27. package/dist/threadline/A2ADeliveryTracker.d.ts.map +1 -0
  28. package/dist/threadline/A2ADeliveryTracker.js +315 -0
  29. package/dist/threadline/A2ADeliveryTracker.js.map +1 -0
  30. package/dist/threadline/ListenerSessionManager.d.ts +15 -0
  31. package/dist/threadline/ListenerSessionManager.d.ts.map +1 -1
  32. package/dist/threadline/ListenerSessionManager.js +41 -0
  33. package/dist/threadline/ListenerSessionManager.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/data/builtin-manifest.json +64 -64
  36. package/src/scaffold/templates.ts +7 -0
  37. package/upgrades/1.3.386.md +58 -0
  38. package/upgrades/1.3.387.md +25 -0
  39. package/upgrades/side-effects/a2a-durable-delivery.md +165 -0
  40. package/upgrades/side-effects/a2a-redelivery-escalation.md +112 -0
  41. package/upgrades/1.3.385.md +0 -43
@@ -0,0 +1,165 @@
1
+ # Side-Effects Review — A2A Durable Delivery (peer-health + delivery lifecycle)
2
+
3
+ **Version / slug:** `a2a-durable-delivery`
4
+ **Date:** `2026-06-06`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `two parallel adversarial reviewers (multi-agent convergence) — see below`
7
+
8
+ ## Summary of the change
9
+
10
+ Adds the durable spine of "agent-to-agent communications never just die out"
11
+ (A2A-DURABLE-DELIVERY-SPEC.md, issue #939, CMT-1143). New SQLite component
12
+ `src/threadline/A2ADeliveryTracker.ts` records every outbound A2A message's
13
+ delivery lifecycle (`awaiting-ack → acked | escalated | failed`) and a per-peer
14
+ inbound-liveness clock, and composes a `peerHealth()` read ("is my channel to
15
+ <peer> alive?"). Wired into the existing relay-send paths (`recordSent` at both
16
+ canonical-outbox callsites in `routes.ts`) and the relay accept point
17
+ (`recordInboundFrom` + implicit-ack-via-reply `recordAckByThread` at
18
+ `routes.ts` ~15729). Two read-only routes `GET /threadline/peers/health` and
19
+ `GET /threadline/peers/:fp/health`. `AgentServer` self-constructs the tracker
20
+ from `stateDir` when not injected, so the routes are alive on every entry path.
21
+ Files: `src/threadline/A2ADeliveryTracker.ts`, `src/server/routes.ts`,
22
+ `src/server/AgentServer.ts`, `src/commands/server.ts`,
23
+ `src/scaffold/templates.ts`, `src/core/PostUpdateMigrator.ts`, + 3 test files.
24
+
25
+ ## Decision-point inventory
26
+
27
+ - `A2ADeliveryTracker recording calls` — add — pure RECORDING side-effects on
28
+ send/accept; they never gate, delay, or alter a message. No decision authority.
29
+ - `GET /threadline/peers/health[/:fp]` — add — read-only observability; returns
30
+ computed health, never acts.
31
+
32
+ No block/allow surface is introduced.
33
+
34
+ ---
35
+
36
+ ## 1. Over-block
37
+
38
+ No block/allow surface — over-block not applicable. The tracker records; it
39
+ cannot reject a send or an inbound message. `recordSent` is wrapped in try/catch
40
+ at both callsites (a tracker failure logs and is swallowed — the send already
41
+ happened above the recording call).
42
+
43
+ ---
44
+
45
+ ## 2. Under-block
46
+
47
+ No block/allow surface — under-block not applicable. Coverage note for the
48
+ liveness signal: implicit-ack-via-reply only acks messages on threads that
49
+ receive a reply; a genuine fire-and-forget message with no reply stays
50
+ `awaiting-ack` and (correctly) shows as pending/stale. That is the intended
51
+ signal, and the explicit `a2a-ack` control message (PR2) closes the no-reply
52
+ case. No silence is hidden.
53
+
54
+ ---
55
+
56
+ ## 3. Level-of-abstraction fit
57
+
58
+ Correct layer. This is a **signal producer** (a durable detector of "what have I
59
+ sent / heard / had acknowledged") plus a read-only view. It does NOT re-implement
60
+ the canonical outbox (which already audits sends) — it adds the lifecycle the
61
+ outbox lacks, alongside it. It reuses the proven SQLite substrate pattern
62
+ (`MessageProcessingLedger`: WAL, `busy_timeout`, `registerSqliteHandle`,
63
+ in-memory test ctor) rather than inventing a new store. The escalation that will
64
+ consume `findOverdue` (PR2) is the authority layer and is intentionally NOT in
65
+ this PR.
66
+
67
+ ---
68
+
69
+ ## 4. Signal vs authority compliance
70
+
71
+ - [x] No — this change has no block/allow surface (and the future escalation
72
+ consumer in PR2 is an aggregated attention item, not a blocking authority).
73
+
74
+ The tracker is brittle-free recording; it owns no authority. The peer-health
75
+ routes are read-only. Nothing here can wrongly block a message.
76
+
77
+ ---
78
+
79
+ ## 5. Interactions
80
+
81
+ - **Shadowing:** none. `recordSent` runs AFTER the actual send + after
82
+ `captureOrigin`; `recordInboundFrom`/`recordAckByThread` run AFTER
83
+ `messageRouter.relay(...)` returns `accepted` and after the existing accept
84
+ log — it cannot pre-empt or shadow delivery/dedup logic.
85
+ - **Double-fire:** the tracker may be constructed in BOTH `commands/server.ts`
86
+ (production injection) and `AgentServer` (fallback). Guarded: AgentServer only
87
+ self-builds when `!options.a2aDeliveryTracker`, so production opens ONE handle.
88
+ `recordSent` is `INSERT OR IGNORE` on `message_id` — a retry of the same id
89
+ never double-inserts and never resurrects an acked row.
90
+ - **Races:** SQLite WAL + `busy_timeout=5000` (same as MessageProcessingLedger);
91
+ recording calls are independent rows, no shared mutable state with concurrent
92
+ code.
93
+ - **Feedback loops:** none. Recording an ack/inbound does not emit a message.
94
+
95
+ ---
96
+
97
+ ## 6. External surfaces
98
+
99
+ - **Other agents on this machine:** none — per-agent-id DB file
100
+ (`state/a2a-delivery.<agentId>.sqlite`), isolated.
101
+ - **Install base:** new read-only routes + two CLAUDE.md template/migration
102
+ sections (Agent Awareness Standard — `generateClaudeMd` + `migrateClaudeMd`,
103
+ content-sniffed, idempotent). Existing agents gain the awareness on update.
104
+ - **External systems:** none. No Telegram/Slack/GitHub/Cloudflare surface change.
105
+ - **Persistent state:** a new SQLite file, schema self-initializing on first
106
+ access (no PostUpdateMigrator DB step). Append/update only.
107
+ - **Timing:** none introduced (no new timers in this PR; the redelivery sweep is
108
+ PR2).
109
+
110
+ ---
111
+
112
+ ## 7. Rollback cost
113
+
114
+ Pure additive code change. Back-out = revert the commit and ship a patch. The new
115
+ SQLite file is orphaned harmlessly if the code is reverted (no other component
116
+ reads it). No user-visible regression during the rollback window — the routes
117
+ simply 503 again if the tracker is absent, and sends/receives are unchanged
118
+ (recording-only). No agent-state repair needed; the CLAUDE.md sections are
119
+ content-sniffed so a re-run is a no-op.
120
+
121
+ ---
122
+
123
+ ## Conclusion
124
+
125
+ The review found no block/allow surface and no authority — this is recording-only
126
+ infrastructure plus a read-only health view, riding the proven SQLite substrate
127
+ and sitting alongside (not replacing) the canonical outbox. The one interaction
128
+ worth noting (double-construction) is guarded by the inject-or-self-build pattern
129
+ and `INSERT OR IGNORE` idempotency. Clear to ship as PR1; the redelivery +
130
+ escalation authority + explicit `a2a-ack` control message are intentionally
131
+ scoped to PR2.
132
+
133
+ ## Second-pass review (multi-agent convergence, 2026-06-06)
134
+
135
+ **Reviewers:** two parallel adversarial agents — (A) correctness/idempotency/concurrency, (B) integration/spec-fidelity/conventions.
136
+ **Independent read: CONCERN → resolved.** Both independently caught a load-bearing bug the green test suite hid:
137
+
138
+ - **[CRITICAL/HIGH] Implicit-ack non-functional in production.** Outbound rows were keyed by the peer's FINGERPRINT, but the inbound accept point keyed the ack by `from.agent` — a display NAME on the local transport — and was wired ONLY on the same-machine path, never on the cross-machine relay-ingest path (the actual Echo↔Dawn case). The ack never fired; messages would sit awaiting-ack and go stale even after the peer replied. Tests passed only because they used one identifier for both sides. **Resolution:** `recordAckByThread` now keys on `threadId` ALONE (robust to the identity-format asymmetry); the cross-machine relay-ingest accept point in `server.ts` records with the real `senderFingerprint`; the local path resolves the thread-owner fingerprint for liveness. Added a unit asymmetry regression test + a real `POST /messages/relay-agent` round-trip wiring-integrity test asserting awaiting-ack → acked.
139
+ - **[MAJOR] Weak message-id silent-drop.** `INSERT OR IGNORE` is idempotent only if ids are unique; plaintext/local transports use a weak `msg-<ms>-<4char>` id. **Resolution:** `recordSent` detects an ignored insert whose existing row is a genuinely different (peer, thread) and logs it loudly instead of silently dropping. (Root id-generator hardening noted as a follow-up; out of PR1 scope.)
140
+ - **[MINOR] `close()` violated the SqliteRegistry unregister contract.** **Resolution:** capture + call the unregister fn in `close()`.
141
+ - **[MINOR] inbound `peerName` always null.** **Resolution:** pass the sender name as the label.
142
+ - **[LOW] Spec over-claims** (Tier-3 kill-receiver scenario; "escalation sentinel default ON"). **Resolution:** corrected — both are PR2; the wiring-integrity test the spec promised now exists.
143
+
144
+ After folding all findings the design converged: read surface + lifecycle were sound; the fix was identifier reconciliation + wiring the cross-machine path + the missing wiring-integrity test. 31 tests green across all tiers; tsc clean.
145
+
146
+ ### Post-CI hardening (the suite is the authority)
147
+
148
+ CI then caught three guard-allowlist failures that a targeted local run had skipped (each lives in a shard a subset run doesn't reach) — all mine, all fixed:
149
+ - `SqliteRegistry-wiring`: the new SQLite store registered in `LONG_LIVED_STORES`.
150
+ - `feature-delivery-completeness`: the new `migrateClaudeMd` section registered in `legacyMigratorSections` (READ-surface class, like `/codex/usage`).
151
+ - `no-silent-fallbacks`: the recording-only catches (`recordSent`/`recordInboundFrom`/`recordAckByThread`) and the tracker-init catches are intentional (A2A tracking must never break the send/accept it observes; a tracker-open failure must never 503 the server) — annotated with in-brace `@silent-fallback-ok` + justification so the gate counts zero new silent swallows (count 460 ≤ 461 baseline). Lesson recorded: any new SQLite store / `migrateClaudeMd` section / defensive catch must run these completeness guards locally before push.
152
+
153
+ ## Evidence pointers
154
+
155
+ - Tier 1: `tests/unit/A2ADeliveryTracker.test.ts` — 20 tests (lifecycle,
156
+ idempotency, overdue/stale gates, peer-health composition, thread-keyed ack +
157
+ the identity-asymmetry regression).
158
+ - Tier 2: `tests/integration/threadline-peer-health-route.test.ts` — 6 tests
159
+ (full HTTP pipeline, staleAfterMs param, 503-when-absent).
160
+ - Tier 2 wiring-integrity: `tests/integration/a2a-delivery-wiring.test.ts` — 2
161
+ tests (real POST /messages/relay-agent round-trip flips awaiting-ack → acked;
162
+ inbound-liveness bumped).
163
+ - Tier 3: `tests/e2e/a2a-peer-health-alive.test.ts` — 3 tests (real AgentServer
164
+ boot, routes 200-not-503, DB file created on prod init path).
165
+ - All 31 green; `tsc --noEmit` clean; pre-commit lints clean.
@@ -0,0 +1,112 @@
1
+ # Side-Effects Review — A2A Redelivery + Dark-Peer Escalation Sentinel (PR2)
2
+
3
+ **Version / slug:** `a2a-redelivery-escalation`
4
+ **Date:** `2026-06-06`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `self-review (modeled 1:1 on the reviewed CollaborationRedriveEngine; redeliver path covered by a real-outbox wiring-integrity test)`
7
+
8
+ ## Summary of the change
9
+
10
+ Adds the active-recovery layer of "communications never just die out"
11
+ (A2A-DURABLE-DELIVERY-SPEC §4, issue #939, CMT-1143, PR2 — building on PR1's
12
+ `A2ADeliveryTracker`, shipped in v1.3.386). `src/monitoring/A2ARedeliverySentinel.ts`
13
+ sweeps the tracker's `findOverdue` work-list on a cadence: re-sends each overdue
14
+ (unacknowledged) message under the attempt cap with exponential backoff, and once
15
+ attempts are exhausted raises ONE aggregated attention item per dark peer. Wired in
16
+ `server.ts` (mirroring the `CollaborationRedriveEngine` block) with a `redeliver` fn
17
+ that recovers the message body from the canonical outbox (new
18
+ `ListenerSessionManager.readCanonicalOutboxEntry`) and re-emits via the relay client,
19
+ and `raiseAttention` → `TelegramAdapter.createAttentionItem`. New config
20
+ `monitoring.a2aRedelivery` (ships OFF). Files: `src/monitoring/A2ARedeliverySentinel.ts`,
21
+ `src/threadline/ListenerSessionManager.ts`, `src/commands/server.ts`, `src/core/types.ts`,
22
+ + 2 test files + docs.
23
+
24
+ Scope note: this PR ships §4 (redelivery + escalation sentinel). The explicit
25
+ `a2a-ack` control message (spec §3 "Layer B") remains a follow-on — the implicit
26
+ ack-via-reply (PR1) plus this sentinel's redelivery+escalation already cover the
27
+ no-reply case, so the explicit control message is a lower-priority optimization. <!-- tracked: #939 -->
28
+
29
+ ## Decision-point inventory
30
+
31
+ - `A2ARedeliverySentinel` — add — a SIGNAL CONSUMER: it re-sends (via the relay) and
32
+ escalates (via the Attention queue). It holds NO blocking authority — never gates a
33
+ send or receive. Escalate-once is structural (markEscalated removes a row from
34
+ `findOverdue`).
35
+ - `readCanonicalOutboxEntry` — add — pure read helper (no decision surface).
36
+
37
+ ## 1. Over-block
38
+
39
+ No block/allow surface — over-block not applicable. The sentinel only re-attempts and
40
+ escalates; it cannot reject anything.
41
+
42
+ ## 2. Under-block
43
+
44
+ No block/allow surface. Coverage note: redelivery counts an attempt whether or not the
45
+ transport "accepted" (an accepted send still isn't an ack), so the retry clock always
46
+ advances toward escalation — a perpetually-accepted-but-never-acked message still
47
+ escalates rather than retrying forever.
48
+
49
+ ## 3. Level-of-abstraction fit
50
+
51
+ Correct. A signal CONSUMER of the PR1 tracker's `findOverdue`, feeding existing
52
+ surfaces (relay client, Attention queue) — it re-implements neither. Modeled on the
53
+ reviewed `CollaborationRedriveEngine` (same injected-deps, per-tick caps,
54
+ setInterval+unref, ship-OFF shape). The message body is recovered from the existing
55
+ canonical outbox (not duplicated into the tracker).
56
+
57
+ ## 4. Signal vs authority compliance
58
+
59
+ - [x] No — this change produces re-sends + an aggregated signal (attention item); it
60
+ holds no block/allow authority. Brittle-free: all logic is deterministic state
61
+ transitions over the durable tracker.
62
+
63
+ ## 5. Interactions
64
+
65
+ - **Shadowing:** none. The sentinel runs on its own interval, independent of the
66
+ send/receive paths.
67
+ - **Double-fire:** escalate-once is structural (`markEscalated` → row leaves
68
+ `findOverdue`); a redelivered message stays `awaiting-ack` and is naturally re-acked
69
+ by the peer's reply (PR1 implicit-ack) or re-swept. `maxRedrivesPerTick` caps load on
70
+ a degraded relay. The implicit-ack-via-reply (PR1) and this sentinel cooperate: a
71
+ reply acks-by-thread and removes the message from `findOverdue`, stopping redelivery.
72
+ - **Races:** the tracker is SQLite (WAL); the sentinel reads/writes rows independently.
73
+ The outbox reader is read-only.
74
+ - **Feedback loops:** none — a re-send does not itself create a new tracked message
75
+ (recordSent is only called on the original relay-send path, not here).
76
+
77
+ ## 6. External surfaces
78
+
79
+ - **Other agents:** a redelivery re-sends a previously-sent message to the SAME peer on
80
+ the SAME thread — idempotent at the peer (same messageId via relay). No new message
81
+ content is invented.
82
+ - **Install base:** new `monitoring.a2aRedelivery` config (OFF by default → no behavior
83
+ change for existing agents until enabled). New site/README docs.
84
+ - **Persistent state:** none new (uses PR1's SQLite tracker + the existing canonical
85
+ outbox file). No PostUpdateMigrator step.
86
+ - **Attention queue:** ONE aggregated item per dark peer per escalation (P17-compliant);
87
+ never one-per-message.
88
+
89
+ ## 7. Rollback cost
90
+
91
+ Pure additive. Ships OFF (`monitoring.a2aRedelivery.enabled` absent/false → sentinel
92
+ not armed). Back-out = revert the commit + patch. No persistent-state cleanup; no
93
+ user-visible regression (the sentinel is inert until explicitly enabled).
94
+
95
+ ## Conclusion
96
+
97
+ A signal-consuming recovery sentinel built on PR1's durable tracker, modeled 1:1 on the
98
+ already-reviewed `CollaborationRedriveEngine`, ship-OFF, with the redeliver wiring
99
+ (outbox-body recovery → relay re-send) proven by a real-outbox wiring-integrity test and
100
+ escalate-once guaranteed structurally. The explicit `a2a-ack` control message is the
101
+ only remaining piece of the spec, intentionally deferred as a tracked follow-on.
102
+
103
+ ## Evidence pointers
104
+
105
+ - Tier 1: `tests/unit/A2ARedeliverySentinel.test.ts` — 8 tests (disabled no-op,
106
+ redelivery under cap, escalate-once at cap, per-peer aggregation P17, per-tick cap,
107
+ swallowed transport error, escalate-only mode).
108
+ - Tier 2 wiring-integrity: `tests/integration/a2a-redelivery-wiring.test.ts` — 6 tests
109
+ (`readCanonicalOutboxEntry` found/absent/newest-wins/malformed-skip; the redeliver
110
+ path recovers a real outbox body and re-sends; body-missing → escalate, no fabrication).
111
+ - All 14 green; `tsc --noEmit` clean; no-silent-fallbacks 461≤461; docs-coverage green
112
+ on merge-state (class 134/537, route 104/574).
@@ -1,43 +0,0 @@
1
- # Upgrade Guide — vNEXT
2
-
3
- <!-- assembled-by: assemble-next-md -->
4
- <!-- bump: patch -->
5
-
6
- ## What Changed
7
-
8
- Quota-aware placement is meant to route work away from a rate-limited machine.
9
- But each machine only ever knew its OWN quota: a peer's quota state rides the
10
- session-status response (the peer's own capacity already includes it), yet the
11
- receive side parsed it away — the exact same narrowing bug that previously
12
- dropped the commitments advert (#931). So the router could avoid placing onto a
13
- rate-limited LOCAL machine but never onto a rate-limited PEER — which is the
14
- original EXO failure (the laptop placing work onto a rate-limited Mini).
15
-
16
- The peer-capacity parse + the PeerPresencePuller now carry `quotaState` through
17
- to the pool registry. Absent from an older peer = treated as not blocked
18
- (fail-open, unchanged).
19
-
20
- ## What to Tell Your User
21
-
22
- If one of your machines hits its usage limit, the other machine now actually
23
- knows and stops sending new work to it — instead of finding out the hard way.
24
-
25
- - audience: agent-only
26
- - maturity: stable
27
-
28
- ## Summary of New Capabilities
29
-
30
- - `PeerCapacity.quotaState` carried from the session-status response through
31
- `fetchPeerCapacity` and `PeerPresencePuller` into the pool registry.
32
- - `GET /pool` now shows a peer's `quotaState`, not just the local machine's.
33
- - Pairs with finding A1 (the collector that produces the data now runs on
34
- lifeline-driven agents).
35
-
36
- ## Evidence
37
-
38
- - `tests/unit/peer-presence-puller.test.ts` (+2): a peer quotaState propagates
39
- into the recorded heartbeat; an old peer omitting it records no quotaState
40
- (fail-open). 13/13 in file incl. the existing wiring suite.
41
- - Live (pre-fix, v1.3.384): laptop `/pool` shows its own `quotaState:{blocked:false}`
42
- but the Mini's stays `null` after 5+ minutes despite the Mini's `/quota`
43
- reading `status:ok`.