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.
- package/README.md +1 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +92 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +16 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/types.d.ts +10 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/A2ARedeliverySentinel.d.ts +82 -0
- package/dist/monitoring/A2ARedeliverySentinel.d.ts.map +1 -0
- package/dist/monitoring/A2ARedeliverySentinel.js +143 -0
- package/dist/monitoring/A2ARedeliverySentinel.js.map +1 -0
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +7 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +2 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +20 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +81 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/A2ADeliveryTracker.d.ts +151 -0
- package/dist/threadline/A2ADeliveryTracker.d.ts.map +1 -0
- package/dist/threadline/A2ADeliveryTracker.js +315 -0
- package/dist/threadline/A2ADeliveryTracker.js.map +1 -0
- package/dist/threadline/ListenerSessionManager.d.ts +15 -0
- package/dist/threadline/ListenerSessionManager.d.ts.map +1 -1
- package/dist/threadline/ListenerSessionManager.js +41 -0
- package/dist/threadline/ListenerSessionManager.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/scaffold/templates.ts +7 -0
- package/upgrades/1.3.386.md +58 -0
- package/upgrades/1.3.387.md +25 -0
- package/upgrades/side-effects/a2a-durable-delivery.md +165 -0
- package/upgrades/side-effects/a2a-redelivery-escalation.md +112 -0
- 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).
|
package/upgrades/1.3.385.md
DELETED
|
@@ -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`.
|