instar 1.3.1321 → 1.3.1323
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 +20 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +27 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- 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/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +13 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +3 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +3 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/middleware.d.ts +13 -0
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +21 -0
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/routes.d.ts +2 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +233 -71
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/A2ADeliveryTracker.d.ts +59 -2
- package/dist/threadline/A2ADeliveryTracker.d.ts.map +1 -1
- package/dist/threadline/A2ADeliveryTracker.js +193 -21
- package/dist/threadline/A2ADeliveryTracker.js.map +1 -1
- package/dist/threadline/ListenerSessionManager.d.ts.map +1 -1
- package/dist/threadline/ListenerSessionManager.js +7 -0
- package/dist/threadline/ListenerSessionManager.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 +33 -3
- package/dist/threadline/ThreadlineBootstrap.js.map +1 -1
- package/dist/threadline/ThreadlineMCPServer.d.ts +8 -0
- package/dist/threadline/ThreadlineMCPServer.d.ts.map +1 -1
- package/dist/threadline/ThreadlineMCPServer.js +8 -0
- package/dist/threadline/ThreadlineMCPServer.js.map +1 -1
- package/dist/threadline/client/RelayClient.d.ts.map +1 -1
- package/dist/threadline/client/RelayClient.js +15 -0
- package/dist/threadline/client/RelayClient.js.map +1 -1
- package/dist/threadline/client/ThreadlineClient.d.ts +49 -0
- package/dist/threadline/client/ThreadlineClient.d.ts.map +1 -1
- package/dist/threadline/client/ThreadlineClient.js +169 -0
- package/dist/threadline/client/ThreadlineClient.js.map +1 -1
- package/dist/threadline/honestDeliveryWiring.d.ts +55 -0
- package/dist/threadline/honestDeliveryWiring.d.ts.map +1 -0
- package/dist/threadline/honestDeliveryWiring.js +142 -0
- package/dist/threadline/honestDeliveryWiring.js.map +1 -0
- package/dist/threadline/mcp-http-client.d.ts.map +1 -1
- package/dist/threadline/mcp-http-client.js +12 -2
- package/dist/threadline/mcp-http-client.js.map +1 -1
- package/dist/threadline/relayConnectionObserver.d.ts.map +1 -1
- package/dist/threadline/relayConnectionObserver.js +10 -3
- package/dist/threadline/relayConnectionObserver.js.map +1 -1
- package/dist/threadline/relayVerdict.d.ts +49 -0
- package/dist/threadline/relayVerdict.d.ts.map +1 -0
- package/dist/threadline/relayVerdict.js +70 -0
- package/dist/threadline/relayVerdict.js.map +1 -0
- 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 +13 -0
- package/upgrades/1.3.1322.md +29 -0
- package/upgrades/1.3.1323.md +31 -0
- package/upgrades/a2a-honest-delivery-outcomes.eli16.md +7 -0
- package/upgrades/relay-displaced-reclaim.eli16.md +12 -0
- package/upgrades/side-effects/a2a-honest-delivery-outcomes.md +46 -0
- package/upgrades/side-effects/relay-displaced-reclaim.md +24 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The relay already answers every message — delivered, held because the peer is offline, refused, or expired — and the sender discarded all of it, so every relay send read "submitted to relay; acceptance unconfirmed" forever (two sends to an agent that had been off the relay for 40 hours, 2026-10-06). Spec: `docs/specs/a2a-honest-delivery-outcomes.md` (converged, 8 rounds; approved).
|
|
9
|
+
|
|
10
|
+
- `ThreadlineClient` runs one verdict dispatcher: a 60 s recent-verdict cache (a fast relay answer is never lost), bounded waiters, `awaitRelayAck`, the relay's new `delivery_expired` frame, and the socket-level ban signal (never turned into a per-message verdict).
|
|
11
|
+
- `src/threadline/relayVerdict.ts` maps the relay's refusal prose to fixed codes (`queue-full`, `rate-limited`, `routing-refused`, `banned`, `unmapped`); relay prose never reaches an agent.
|
|
12
|
+
- `A2ADeliveryTracker` records relay verdicts (six additive columns, migrated in place) with a new non-terminal `unconfirmed` state; silence is never failure. A refusal or a corroborated expiry is `failed`. `stale` now also covers unconfirmed rows and provably-expired messages.
|
|
13
|
+
- A 15-minute silence sweep relabels verdict-less relay rows to `unconfirmed` (never `failed`), with brakes and a metadata-only audit log; it raises no operator notice.
|
|
14
|
+
- `/threadline/relay-send` waits ≤3 s for the verdict and answers `relayStatus`; a refusal is HTTP 502 with `retryLater`, releases the reply claim, and leaves the refused reply re-drivable.
|
|
15
|
+
- `GET /threadline/peers/health?scope=pool` (token-checked) merges every machine's view; counters ride the authenticated branch of `/health`.
|
|
16
|
+
- CLAUDE.md template + migration: "What `relayStatus` means (honest delivery)".
|
|
17
|
+
|
|
18
|
+
## What to Tell Your User
|
|
19
|
+
|
|
20
|
+
When I send a message to another agent, I now know what happened to it: it reached them, they're offline and the relay is holding it, or it was refused. Before, every message just said "sent, unconfirmed" — even to an agent that had been offline for two days.
|
|
21
|
+
|
|
22
|
+
## Summary of New Capabilities
|
|
23
|
+
|
|
24
|
+
- `threadline_send` returns `relayStatus` (`delivered` / `queued` / `rejected` / `unconfirmed`), `relayReasonCode`, `retryLater`, `banSuspected`.
|
|
25
|
+
- Peer health shows `failedCount`, `unconfirmedCount`, `lastRelayStatus`; `?scope=pool` for every machine.
|
|
26
|
+
|
|
27
|
+
## Evidence
|
|
28
|
+
|
|
29
|
+
- `tests/unit/a2a-honest-delivery.test.ts` (35 checks): every transition-table cell, the reason bridge, the sweep, the stale signal surviving it, the settled-reply rule, the dispatcher (cache race, waiter cap, ban hint).
|
|
30
|
+
- `tests/integration/threadline/relay-send-honest-delivery.test.ts`: real ThreadlineClient + real RelayServer — live peer → delivered, offline → queued, full queue → 502 rejected with claim released, MCP rendering, pool-scope token check.
|
|
31
|
+
- `tests/e2e/threadline/honest-delivery-alive.test.ts`: production bootstrap + the exact server wiring — queued → relay expiry → failed and stale; counters only on authed `/health`; peer acks never forwarded; acks arrive in send order.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Honest delivery for agent-to-agent messages (ELI16)
|
|
2
|
+
|
|
3
|
+
When this agent sends a message to another agent, it goes through a shared relay. The relay always answers: the message reached the other agent, it's being held because the other agent is offline, it was refused, or it expired after waiting too long. Until now the agent's side ignored every one of those answers, so every message it sent said "sent, unconfirmed" forever — including two messages on 2026-10-06 to an agent that had been offline for 40 hours.
|
|
4
|
+
|
|
5
|
+
Now the agent listens. A send answers with what really happened. A refused message comes back as a clear failure with a hint about whether trying again later might work. A message the relay held and then threw away is recorded as never received, and the health view for that peer shows it has gone quiet.
|
|
6
|
+
|
|
7
|
+
The important rule: no answer is not the same as failure. If the relay says nothing, the message is marked "unknown", and a later answer or a reply from the other agent settles it. Nothing here resends anything or sends any alerts yet; the next step (backup routes) builds on this honest signal.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# A machine bumped off the agent relay takes it back (ELI16)
|
|
2
|
+
|
|
3
|
+
Agents talk to each other through a shared relay, and the relay allows one connection per agent identity. When a second connection with the same identity arrives, the first one is kicked off ("displaced").
|
|
4
|
+
|
|
5
|
+
Before this change, a displaced connection gave up for good, and its health report still said it would recover. That is what happened to Luna: her Studio briefly connected with her identity, her laptop was kicked off, and for 40 hours no agent could reach her, with nothing raising an alarm.
|
|
6
|
+
|
|
7
|
+
Now:
|
|
8
|
+
- The health report says "displaced" honestly instead of "will recover".
|
|
9
|
+
- The server raises a degradation alert, so the problem shows up.
|
|
10
|
+
- After a 15-minute pause, the machine takes its connection back. Only the machine that is supposed to own the relay ever holds a connection (a standby never connects), so taking it back is the right outcome. The pause keeps two misconfigured machines from fighting over it every few seconds.
|
|
11
|
+
- Two other ways a connection could stall forever are closed: a rejected login that left the socket hanging, and a failed first connection that the server stopped tracking.
|
|
12
|
+
- Agents learn, through their instructions, to check their own relay before assuming a quiet peer is ignoring them.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Side-effects review: a2a-honest-delivery-outcomes
|
|
2
|
+
|
|
3
|
+
Spec: docs/specs/a2a-honest-delivery-outcomes.md (converged 8 rounds, approved 2026-10-06).
|
|
4
|
+
|
|
5
|
+
## Change summary
|
|
6
|
+
Sender consumes the relay's per-message verdicts (ack delivered/queued/rejected, delivery_expired, socket-level banned) via one dispatcher on ThreadlineClient; A2ADeliveryTracker records them (six additive columns + `unconfirmed` state); a 15-min silence sweep relabels verdict-less relay rows to `unconfirmed`; `/threadline/relay-send` waits ≤3 s and answers `relayStatus` (502 on refusal); pool-scope peer health (token-checked); counters on authed `/health`; `hasCanonicalReplyFor` treats a lone `relay-rejected` outbox entry as unsettled; CLAUDE.md awareness section + migration.
|
|
7
|
+
|
|
8
|
+
## 1. Over-block
|
|
9
|
+
No new gate. The only refusal surface is a relay-refused send answering 502 instead of 200 — the relay already refused it; the 200 was the bug. A slow relay adds ≤3 s to a send (route latency only; delivery is unchanged). Waiter cap (1 000) resolves excess waits to `unconfirmed` — reporting only.
|
|
10
|
+
|
|
11
|
+
## 2. Under-block
|
|
12
|
+
Verdicts the relay never sends (relay restart drops its in-memory queue; flush/expiry while the sender is disconnected; a late verdict landing on a different machine of this agent) end as `unconfirmed`, not `failed` — honest unknown, documented in the spec. A sender ban without a messageId cannot be attributed per message (hint only). Unsigned/unacked `delivered` rows stay pending until a reply (Layer B of the parent spec is the exit).
|
|
13
|
+
|
|
14
|
+
## 3. Level of abstraction
|
|
15
|
+
Verdicts are consumed at the one client that receives them (ThreadlineClient) and recorded by the existing tracker; the route only reports. The sweep and wiring live in one module server.ts calls, so tests run production wiring.
|
|
16
|
+
|
|
17
|
+
## 4. Signal vs authority
|
|
18
|
+
Pure recording/reporting. No path blocks, reroutes or resends a message. The 502 reports a refusal the relay already made. Relay prose is mapped to fixed codes and never reaches an agent. The sweep only relabels to a non-terminal state.
|
|
19
|
+
|
|
20
|
+
## 5. Interactions
|
|
21
|
+
- A2ARedeliverySentinel (off by default): `findOverdue`/`markAttempt`/`markEscalated` now include `unconfirmed` so swept rows stay reachable.
|
|
22
|
+
- Reply claims: refusal no longer calls `retainReplyClaimFailure`; the existing ≥400 finish handler releases the claim. Claim release on 2xx moved after the verdict wait (closes a double-reply window).
|
|
23
|
+
- Outbox: written once with the final outcome (append-only, HMAC); `hasCanonicalReplyFor` ANY-settled predicate, legacy entries unchanged (settled).
|
|
24
|
+
- Canonical thread log: the outbound leg is recorded before the verdict; a refused leg reads `diverged` until the next exchange (stated limit).
|
|
25
|
+
- `/health` auth check extracted to `bearerMatches` — byte-identical behaviour (verified by existing /health tests).
|
|
26
|
+
- Existing relay-send test stubs extended with the verdict API; one assertion updated to the new honest wording.
|
|
27
|
+
|
|
28
|
+
## 6. External surfaces
|
|
29
|
+
`threadline_send` result gains `relayStatus`, `relayReasonCode`, `retryLater`, `banSuspected`; refusal is now `success:false` / HTTP 502. `GET /threadline/peers/health` gains `failedCount`, `unconfirmedCount`, `lastRelayStatus`, `instarVersion` (accepted small unauthenticated disclosure — the version is public on npm) and a token-checked `?scope=pool`. Authenticated `/health` gains `threadline.relayVerdicts`. New audit file `logs/a2a-delivery-verdicts.jsonl` (ids + fingerprint, no bodies; 10 MB + one rotation). Depends on relay timing only through the bounded 3 s wait.
|
|
30
|
+
|
|
31
|
+
## 7. Multi-machine posture
|
|
32
|
+
Unified (default). Each machine's tracker holds the rows for the sends it made (only the relay-owning machine can send); `?scope=pool` merges every machine's rows (machine-tagged, dark peer → `pool.failed`, `mixedVersion` flag). A late verdict landing on another machine is ignored there and the row falls to `unconfirmed` — honest. No user-facing notice, no state that strands on topic transfer, no generated URL.
|
|
33
|
+
|
|
34
|
+
## 8. Rollback cost
|
|
35
|
+
Revert the release. Older code ignores the added columns, indexes and meta table; `unconfirmed` rows go inert under older code (omitted from its state lists). One-line reverse migration restores them: `UPDATE a2a_delivery SET state='awaiting-ack' WHERE state='unconfirmed'`. No agent-state repair beyond that; the CLAUDE.md section is advisory text.
|
|
36
|
+
|
|
37
|
+
## Deviations from the spec text (recorded)
|
|
38
|
+
- Reason mapping: wording that matches no known prefix is `unmapped` (retryability unknown, `retryLater: null`) rather than the spec's catch-all `routing-refused`. The safer direction — a transient refusal is never misread as permanent — and consistent with the spec's own unmapped row. Three extra prefixes the relay's router really emits (`Recipient not connected`, `Sender fingerprint mismatch`, `Envelope too large`) are mapped to `routing-refused`.
|
|
39
|
+
|
|
40
|
+
## Second-pass review (round 1)
|
|
41
|
+
Concern raised: (1) `waitForReply:true` ran the verdict wait BEFORE registering the reply waiter (a reply landing in that window was missed); (2) the reason-mapping deviation was unrecorded; (3) `expired` accepted a frame with no `recipientId`.
|
|
42
|
+
|
|
43
|
+
Resolution: (1) the reply waiter is now registered immediately after the send, before any await, and runs concurrently with the verdict wait; a refusal cancels only this request's own waiter (identity-checked), never a retry's. Pinned by `tests/integration/threadline/relay-send-reply-wait-concurrency.test.ts` (reply during the verdict wait is captured; a retry's waiter survives a refusal). (2) recorded above. (3) `expired` now requires `recipientId === peer_fp`; a frame without one is ignored and counted (unit test added).
|
|
44
|
+
|
|
45
|
+
## Second-pass review (round 2)
|
|
46
|
+
Concur with the review.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Side-effects review: relay-displaced-reclaim
|
|
2
|
+
|
|
3
|
+
## Change
|
|
4
|
+
RelayClient displaced/auth_error state handling; observer keeps terminal events; ThreadlineBootstrap reports a degradation on displacement and reclaims the relay after a pause (default 15 min); a failed first connect keeps the client retrying; ThreadlineClient.reconnectRelay(); CLAUDE.md template + idempotent migration section.
|
|
5
|
+
|
|
6
|
+
## Over-block / under-block
|
|
7
|
+
No gate added. The reclaim can only reconnect a machine that already held a relay client, i.e. a configured relay owner (relayEnabled and not relayStandby, and no listener daemon owning the relay).
|
|
8
|
+
|
|
9
|
+
## Self-action bound
|
|
10
|
+
Reclaim is a self-triggered reconnect. Bounded: at most one pending timer per process, one reclaim per displacement, minimum spacing of the configured pause (15 min default). If two machines are both misconfigured as owners they trade the connection at most once per pause each, and every displacement raises a degradation, so the misconfiguration is visible rather than silent. `relayRearmAfterDisplacedMs: 0` restores the pre-fix behaviour.
|
|
11
|
+
|
|
12
|
+
## Interactions
|
|
13
|
+
- Standby (`relayStandby`) machines never create a relay client, so they never reclaim (unchanged, instar#2122).
|
|
14
|
+
- Listener-daemon mode: the server does not hold the relay client, so no reclaim (unchanged).
|
|
15
|
+
- Keeping the client after a failed first connect: routes that check `relayClient` now see a client in `disconnected` state instead of undefined; sends while disconnected fail visibly as before, and the client reconnects when the relay returns.
|
|
16
|
+
|
|
17
|
+
## Signal vs authority
|
|
18
|
+
The degradation report is a signal only. The reclaim reconnects this agent's own connection; it takes no action on peers.
|
|
19
|
+
|
|
20
|
+
## External surfaces
|
|
21
|
+
`/threadline/health` relay.state reports `displaced` accurately. Agent CLAUDE.md gains one section (template + migration, content-sniffed).
|
|
22
|
+
|
|
23
|
+
## Rollback
|
|
24
|
+
Revert the commit, or set `relayRearmAfterDisplacedMs: 0` to disable the reclaim only.
|