@nelsonlongxiang/dsh-open-a2a-net 0.6.35 → 0.6.37
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 +5 -2
- package/README_zh.md +5 -2
- package/assets/nexusDist/assets/index-C7HagaGy.css +1 -0
- package/assets/nexusDist/assets/index-NkNSeIx6.js +9 -0
- package/assets/nexusDist/index.html +2 -2
- package/cli/a2a.mjs +1 -1
- package/lib/a2a-client.js +3 -3
- package/lib/client.js +63 -63
- package/lib/faces/admin.js +23 -5
- package/lib/faces/canvas-notify.js +8 -6
- package/lib/faces/wire.js +11 -5
- package/lib/index.js +35 -10
- package/lib/origin-claim.js +30 -7
- package/lib/owner-settings.js +38 -1
- package/lib/peer-link-display.js +15 -0
- package/lib/queue-guard.js +3 -3
- package/lib/skill/references/cli.md +4 -0
- package/lib/skill/references/routing.md +4 -0
- package/lib/skill/references/setup.md +4 -0
- package/lib/task-ledger-rows.js +192 -0
- package/lib/task-ledger.js +11 -189
- package/lib/tools/info.js +7 -3
- package/lib/types/a2a-client.d.ts +1 -0
- package/lib/types/faces/admin.d.ts +1 -0
- package/lib/types/faces/canvas-notify.d.ts +3 -1
- package/lib/types/faces/wire.d.ts +1 -0
- package/lib/types/index.d.ts +2 -0
- package/lib/types/origin-claim.d.ts +12 -3
- package/lib/types/owner-settings.d.ts +5 -0
- package/lib/types/peer-link-display.d.ts +13 -0
- package/lib/types/queue-guard.d.ts +3 -2
- package/lib/types/task-ledger-rows.d.ts +341 -0
- package/lib/types/task-ledger.d.ts +6 -284
- package/lib/types/tools/info.d.ts +2 -0
- package/lib/types/types.d.ts +2 -1
- package/nexus-stage/scripts/mockhost.py +14 -6
- package/nexus-stage/src/planning-card.ts +5 -3
- package/nexus-stage/src/planning-member-handles.ts +28 -1
- package/nexus-stage/src/planning-menus.ts +12 -5
- package/nexus-stage/src/planning-view.ts +26 -16
- package/nexus-stage/src/seat-name-editor.ts +31 -0
- package/nexus-stage/src/session-drag.ts +64 -0
- package/nexus-stage/src/wb-directory.ts +2 -0
- package/nexus-stage/src/workbench.css +2 -0
- package/nexus-stage/src/workbench.ts +16 -5
- package/package.json +1 -1
- package/assets/nexusDist/assets/index-Du2E6DJL.js +0 -9
- package/assets/nexusDist/assets/index-z33lHtLw.css +0 -1
package/lib/types/index.d.ts
CHANGED
|
@@ -228,6 +228,8 @@ export interface Config {
|
|
|
228
228
|
* nobody created. Empty list denies every join (default).
|
|
229
229
|
*/
|
|
230
230
|
readonly teamJoinAllowlist: string[];
|
|
231
|
+
/** Human-owned stable display labels keyed by full local session id; never a routing identity. */
|
|
232
|
+
readonly seatNames: Record<string, unknown>;
|
|
231
233
|
/**
|
|
232
234
|
* S3 (phase-2 default-ON, owner ruling 2026-09-01): route dispatch checks
|
|
233
235
|
* caller↔target shared team membership before addressing; set false to
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* face (card, /a2a/direct, /a2a/log, /a2a/canvas-op, /a2a/canvas-changed)
|
|
5
5
|
* runs.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Three canonical forms share the base caller headers
|
|
8
8
|
* (X-A2A-Caller / -Url / -Ts):
|
|
9
9
|
* · bound (v2, §2.1): JSON.stringify([tag, caller, callerUrl, ts,
|
|
10
10
|
* audience, METHOD, path]) in X-A2A-Caller-Sig2, the audience origin in
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* · unbound (v1, legacy): JSON.stringify([caller, callerUrl, ts]) in
|
|
15
15
|
* X-A2A-Caller-Sig. Emitted and accepted only while the unbound window
|
|
16
16
|
* is open (originClaimUnboundWindow); accepted ones are counted.
|
|
17
|
-
*
|
|
17
|
+
* A present Sig3 requires the exact raw body and v3 signature, with no downgrade.
|
|
18
|
+
* Without Sig3, once the window closes a present Sig2 is the only thing verified. While
|
|
18
19
|
* it is open, a Sig2 that misses (audience or signature) falls back to the
|
|
19
20
|
* Sig the envelope also carries — no more than stripping Sig2 would give —
|
|
20
21
|
* and the miss is counted, so a peer dialled by an unlisted name keeps
|
|
@@ -29,12 +30,16 @@ import type { A2aPeerCard } from './types.ts';
|
|
|
29
30
|
export declare const ORIGIN_CLAIM_SKEW_MS: number;
|
|
30
31
|
/** Domain tag of the bound form: a v1 signature can never verify as v2. */
|
|
31
32
|
export declare const ORIGIN_CLAIM_BOUND_TAG = "a2a-origin-claim/2";
|
|
33
|
+
export declare const ORIGIN_CLAIM_BODY_TAG = "a2a-origin-claim/3";
|
|
34
|
+
/** Digest the exact UTF-8 string/bytes passed to transport, never reserialized JSON. */
|
|
35
|
+
export declare const originBodySha256: (body: string | Uint8Array) => string;
|
|
32
36
|
/** Where an outbound request goes: the bound form signs both. */
|
|
33
37
|
export interface OriginClaimTarget {
|
|
34
38
|
readonly url: string;
|
|
35
39
|
readonly method: string;
|
|
40
|
+
readonly body?: string | Uint8Array;
|
|
36
41
|
}
|
|
37
|
-
export type OriginClaimRefusal = 'absent' | 'bad-caller-shape' | 'bad-ts' | 'no-card' | 'bad-sig' | 'not-derivable' | 'wrong-audience' | 'unbound-closed';
|
|
42
|
+
export type OriginClaimRefusal = 'absent' | 'bad-caller-shape' | 'bad-ts' | 'no-card' | 'bad-sig' | 'not-derivable' | 'wrong-audience' | 'unbound-closed' | 'body-mismatch';
|
|
38
43
|
/** Only the canonical origin root can claim that origin's identity. */
|
|
39
44
|
export declare function isRootOriginUrl(value: string): boolean;
|
|
40
45
|
/**
|
|
@@ -59,6 +64,8 @@ export type OriginClaimVerdict = {
|
|
|
59
64
|
export declare function unboundCanonical(caller: string, callerUrl: string, ts: number): string;
|
|
60
65
|
/** The bound (v2) canonical form; method upper-cased, path without query. */
|
|
61
66
|
export declare function boundCanonical(caller: string, callerUrl: string, ts: number, audience: string, method: string, path: string): string;
|
|
67
|
+
/** The body-bound v3 form is domain-separated from v1/v2, with the digest appended. */
|
|
68
|
+
export declare function bodyBoundCanonical(caller: string, callerUrl: string, ts: number, audience: string, method: string, path: string, bodySha256: string): string;
|
|
62
69
|
/** An origin in URL's normal form, or '' when the text is not an http(s) URL. */
|
|
63
70
|
export declare function originOf(url: string): string;
|
|
64
71
|
/** The path a request names, without its query (the bound form signs this). */
|
|
@@ -114,6 +121,8 @@ export declare function verifyOriginClaim(req: IncomingMessage, d: {
|
|
|
114
121
|
/** A loopback arrival is an audience of its own (default); off when originClaimUrl declares a tunnel or proxy. */
|
|
115
122
|
readonly loopbackArrival?: boolean;
|
|
116
123
|
readonly now: number;
|
|
124
|
+
/** Exact received bytes, supplied by the bounded body reader. Missing bytes cannot verify Sig3. */
|
|
125
|
+
readonly rawBody?: Uint8Array;
|
|
117
126
|
/** On the faces that admit CLI identities: the deposit a claim URL names. */
|
|
118
127
|
readonly cliDeposit?: (callerUrl: string) => CliDepositCard | undefined;
|
|
119
128
|
}): Promise<OriginClaimVerdict>;
|
|
@@ -12,7 +12,12 @@ export interface OwnerSettings {
|
|
|
12
12
|
teamJoinAllowlist?: string[];
|
|
13
13
|
teamScopeInfo?: boolean;
|
|
14
14
|
announce?: boolean;
|
|
15
|
+
seatNames?: Record<string, string>;
|
|
15
16
|
}
|
|
17
|
+
/** Shared validation for boot config and owner POST; labels are plain text, never handles or authority. */
|
|
18
|
+
export declare function parseSeatNames(value: unknown): Record<string, string> | undefined;
|
|
19
|
+
/** Boot config degrades entry-by-entry; owner POST remains all-or-nothing. Never echo label contents in warnings. */
|
|
20
|
+
export declare function loadSeatNames(value: unknown, warn: (reason: string) => void): Record<string, string>;
|
|
16
21
|
export declare function applyOwnerSettings(patchYaml: string, changes: OwnerSettings): {
|
|
17
22
|
yaml: string;
|
|
18
23
|
restartNeeded: boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Host-specific badge projection only. Never use this to admit callers or choose CLI homes. */
|
|
2
|
+
import type { A2aPeerCard } from './types.ts';
|
|
3
|
+
export declare function peerLinkDisplay(peerUrl: string, d: {
|
|
4
|
+
readonly card: A2aPeerCard | undefined;
|
|
5
|
+
readonly ownUrl: string;
|
|
6
|
+
readonly seeds: readonly string[];
|
|
7
|
+
readonly refs: readonly string[];
|
|
8
|
+
readonly ownHandles: ReadonlySet<string>;
|
|
9
|
+
readonly cards: readonly A2aPeerCard[];
|
|
10
|
+
}): {
|
|
11
|
+
link: 'linked' | 'stranger';
|
|
12
|
+
verified: boolean;
|
|
13
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* busy session. This counts what the target already owes and refuses a NEW delivery once the
|
|
4
4
|
* count reaches the cap, visibly and before anything is steered or booked.
|
|
5
5
|
*
|
|
6
|
-
* Counted ("queued for T"): rows this host's ledger still holds pending for T that expect a
|
|
6
|
+
* Counted ("queued for T"): unanswered rows this host's ledger still holds pending for T that expect a
|
|
7
7
|
* receipt — inbound rows born at /a2a/direct (addressed to T, or delivered to T), and same-host
|
|
8
8
|
* routes (peer 'local') to T. Never refused: a receipt for a task this host's ledger holds
|
|
9
9
|
* (terminal; refusing one strands that row — a genuine receipt always returns to the host that
|
|
@@ -22,9 +22,10 @@ export interface QueueRow {
|
|
|
22
22
|
readonly status: string;
|
|
23
23
|
readonly direction?: string;
|
|
24
24
|
readonly receiptExpected?: boolean;
|
|
25
|
+
readonly answeredAt?: number;
|
|
25
26
|
readonly deliveredTo?: string;
|
|
26
27
|
}
|
|
27
|
-
/** How many receipt-owing tasks this host holds pending for `team`. */
|
|
28
|
+
/** How many receipt-owing tasks this host holds pending and unanswered for `team`. */
|
|
28
29
|
export declare function queuedFor(rows: readonly QueueRow[], team: string): number;
|
|
29
30
|
/** The refusal text when `team`'s queue is full, else undefined. `cap <= 0` disables the guard. */
|
|
30
31
|
export declare function queueFullRefusal(rows: readonly QueueRow[], team: string, message: string, cap: number, mailbox: boolean, ownTask: (taskId: string) => boolean): string | undefined;
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/** Stateless ledger row types, classifiers and restore projections; moved verbatim for #258. */
|
|
2
|
+
import { type AuthProof, type ChainRole, type SettlementReason, type SettlementVerdict, type TaskAuthorization } from './task-auth.ts';
|
|
3
|
+
import type { ReceiptOutcomeV2 } from './receipt.ts';
|
|
4
|
+
import type { ConservationSnapshot } from './ledger-conservation.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Caller-ended summaries (abandon / cancel) share one isolation law: late
|
|
7
|
+
* receipts never rewrite the verdict — they only stamp lateReceiptAt, and
|
|
8
|
+
* the projected outcome collapses to 'abandoned'.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CALLER_ENDED_PREFIX: RegExp;
|
|
11
|
+
/** Hard cap on the owed book (pending + dead-lettered rows, most recent first). */
|
|
12
|
+
export declare const TASK_CAP = 64;
|
|
13
|
+
/** Hard cap on the settled archive (most recent kept). */
|
|
14
|
+
export declare const ARCHIVE_CAP = 64;
|
|
15
|
+
/** Longest receipt summary kept on a record (multi-line outcomes collapse). */
|
|
16
|
+
export declare const SUMMARY_CAP = 200;
|
|
17
|
+
/**
|
|
18
|
+
* Longest dispatch brief kept per row. A ledger row without its payload is
|
|
19
|
+
* unreadable to supervision — "direct-8f3a1c2b → dsh/ec4ce7f8, 3h old" says
|
|
20
|
+
* nothing about whether to nudge, abandon, or read the receipt. The brief is
|
|
21
|
+
* the first non-empty line of the message, capped; the full body never enters
|
|
22
|
+
* the ledger (it can be arbitrarily large and may quote another session's log).
|
|
23
|
+
*/
|
|
24
|
+
export declare const BRIEF_CAP = 120;
|
|
25
|
+
/** Reduce a dispatch body to one readable line: first non-empty line, collapsed, capped. */
|
|
26
|
+
export declare function briefOf(message: string | undefined): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Age at which an unsettled task auto-dead-letters. A dead row exits the
|
|
29
|
+
* owed view, disarms its async-nudge retry (a cold session cannot consume a
|
|
30
|
+
* re-steer anyway), and still accepts a late receipt from a revived target.
|
|
31
|
+
* Injectable per instance so tests can use short windows; sweeping stays
|
|
32
|
+
* lazy — every read/write entry point sweeps first.
|
|
33
|
+
*/
|
|
34
|
+
export declare const TASK_STALE_TTL_MS: number;
|
|
35
|
+
/**
|
|
36
|
+
* The receipt correlation template now lives in `receipt.ts` (shared codec
|
|
37
|
+
* for header + v2 envelope line). This ledger consumes `parseReceipt` so
|
|
38
|
+
* human summaries and machine projections can never drift apart again.
|
|
39
|
+
*/
|
|
40
|
+
/** Lifecycle of a tracked outbound task inside the owed book. */
|
|
41
|
+
export type TaskStatus = 'pending' | 'dead';
|
|
42
|
+
/** F5 layer-3: why a dead row aged out (stamped by the sweep at flip time). */
|
|
43
|
+
export type DeadReason = 'fire-forget' | 'unconsumed';
|
|
44
|
+
/** gh#31: which archive path ended a row — a receipt, a content echo, or the caller. */
|
|
45
|
+
export type ArchiveEnd = 'receipt' | 'echo' | 'abandoned' | 'cancelled' | 'evicted';
|
|
46
|
+
export declare const ARCHIVE_ENDS: readonly string[];
|
|
47
|
+
/** Summary marker {@link TaskLedger.resolveEcho} writes on echo-settled rows. */
|
|
48
|
+
export declare const ECHO_SUMMARY = "(settled by content echo)";
|
|
49
|
+
/**
|
|
50
|
+
* #114 S3: who really sent one outbound task — the routing session, the
|
|
51
|
+
* agent-less host face, or a native-teams bridge submitter. Before this
|
|
52
|
+
* field every outbound row was attributable only to the host ("home"), so
|
|
53
|
+
* same-host seats could not be told apart.
|
|
54
|
+
*/
|
|
55
|
+
export interface Sender {
|
|
56
|
+
readonly kind: 'session' | 'host' | 'bridge' | 'cli';
|
|
57
|
+
/**
|
|
58
|
+
* A session's `<zone>/<id8>` node team, the host process label, or the
|
|
59
|
+
* bridge submitter. Group senders by `kind:id`, never by `id` alone: a host
|
|
60
|
+
* label set by hand may look like a session handle.
|
|
61
|
+
*/
|
|
62
|
+
readonly id: string;
|
|
63
|
+
/**
|
|
64
|
+
* Host senders only: the label the call borrowed on the wire when it
|
|
65
|
+
* differs from {@link id} (quick dispatch rides a live joined session's
|
|
66
|
+
* team). Display context, never a bucket key.
|
|
67
|
+
*/
|
|
68
|
+
readonly face?: string;
|
|
69
|
+
}
|
|
70
|
+
/** Longest sender id kept; anything longer is malformed, not truncated. */
|
|
71
|
+
export declare const SENDER_ID_CAP = 256;
|
|
72
|
+
/**
|
|
73
|
+
* Restore-side allowlist for one persisted sender: a well-formed value comes
|
|
74
|
+
* back as a fresh object holding only kind, id and (when present) face; anything else is dropped whole (the row
|
|
75
|
+
* itself survives — a bad sender never costs the debt record).
|
|
76
|
+
* @param raw - the persisted value.
|
|
77
|
+
* @returns the sender, or undefined when absent or malformed.
|
|
78
|
+
*/
|
|
79
|
+
export declare function parseSender(raw: unknown): Sender | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Spread helper for restore: `{ sender }` when the persisted value parses and
|
|
82
|
+
* the row is not inbound (an inbound row's originator is its caller, never a
|
|
83
|
+
* sender this host recorded), else nothing.
|
|
84
|
+
*/
|
|
85
|
+
export declare function withSender(raw: unknown, direction: unknown): {
|
|
86
|
+
sender?: Sender;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* #114 S2: `{ auth }` when a persisted INBOUND row carries a record that
|
|
90
|
+
* parses whole (parseAuthorization) and belongs to THIS row — its target is
|
|
91
|
+
* the row's team and its receipt target the row's peer — else nothing: the
|
|
92
|
+
* row survives as a pre-S2 row, never with a partially adopted or borrowed
|
|
93
|
+
* record.
|
|
94
|
+
*/
|
|
95
|
+
export declare function withAuth(raw: unknown, entry: {
|
|
96
|
+
direction?: unknown;
|
|
97
|
+
team?: unknown;
|
|
98
|
+
peer?: unknown;
|
|
99
|
+
}, now: number): {
|
|
100
|
+
auth?: TaskAuthorization;
|
|
101
|
+
};
|
|
102
|
+
/** Routing and answer facts copied unchanged between a row and its archive entry. */
|
|
103
|
+
export declare function routingOf(row: Pick<TaskRecord, 'peerKey' | 'peerHost' | 'deliveredTo' | 'forwarded' | 'answeredAt'>): Pick<TaskRecord, 'peerKey' | 'peerHost' | 'deliveredTo' | 'forwarded' | 'answeredAt'>;
|
|
104
|
+
/** #139: what an outbound row is born with besides its sender. */
|
|
105
|
+
export interface OutboundBirth {
|
|
106
|
+
readonly forwarded?: true;
|
|
107
|
+
/** The local-candidate admission (ingress local); kept only on a row whose peer is 'local'. */
|
|
108
|
+
readonly auth?: TaskAuthorization;
|
|
109
|
+
readonly peerKey?: string;
|
|
110
|
+
readonly peerHost?: string;
|
|
111
|
+
readonly deliveredTo?: string;
|
|
112
|
+
}
|
|
113
|
+
/** The facts of one outbound birth that fit the row (see {@link withRouting} for the same shape on restore). */
|
|
114
|
+
export declare function birthFacts(peer: string, birth: OutboundBirth | undefined): Pick<TaskRecord, 'auth' | 'peerKey' | 'peerHost' | 'deliveredTo' | 'forwarded'>;
|
|
115
|
+
/** Bound on a persisted routing fact; anything longer (or empty, or not a string) is dropped on restore. */
|
|
116
|
+
export declare const ROUTING_FACT_CAP = 256;
|
|
117
|
+
/**
|
|
118
|
+
* #139: restore-side allowlist for the routing facts. Each is kept only in
|
|
119
|
+
* its shape — a non-empty string within the cap — and the peer facts only on
|
|
120
|
+
* a remote outbound row; anything else is dropped (the row survives as one
|
|
121
|
+
* born before the fields, judged by current membership and origin).
|
|
122
|
+
*/
|
|
123
|
+
export declare function withRouting(entry: Record<string, unknown>): Pick<TaskRecord, 'peerKey' | 'peerHost' | 'deliveredTo' | 'forwarded' | 'answeredAt'>;
|
|
124
|
+
/** One tracked outbound task still in the owed book (unsettled). */
|
|
125
|
+
export interface TaskRecord {
|
|
126
|
+
readonly taskId: string;
|
|
127
|
+
readonly team: string;
|
|
128
|
+
readonly peer: string;
|
|
129
|
+
readonly startedAt: number;
|
|
130
|
+
/**
|
|
131
|
+
* Which side of the wire this row serves: 'outbound' (this node sent and
|
|
132
|
+
* awaits a receipt) or 'inbound' (this node received and owes one).
|
|
133
|
+
* Absent on rows persisted before F2' — those are outbound by birth.
|
|
134
|
+
*/
|
|
135
|
+
readonly direction?: 'inbound' | 'outbound';
|
|
136
|
+
/**
|
|
137
|
+
* F5: whether a receipt is actually owed. Absent ⇒ true (back-compat).
|
|
138
|
+
* `false` marks fire-and-forget deliveries — the caller's label could not
|
|
139
|
+
* receive a receipt at dispatch (process-form label), so no debt was born
|
|
140
|
+
* and supervision must not chase this row.
|
|
141
|
+
*/
|
|
142
|
+
readonly receiptExpected?: boolean;
|
|
143
|
+
/** The conversation the delivery steered, for follow-up routes. */
|
|
144
|
+
readonly contextId?: string;
|
|
145
|
+
/**
|
|
146
|
+
* Head of the dispatched message (first non-empty line, {@link BRIEF_CAP}).
|
|
147
|
+
* What the row is ABOUT — the fact that makes the ledger readable without
|
|
148
|
+
* replaying the conversation. Absent on rows persisted before this field.
|
|
149
|
+
*/
|
|
150
|
+
readonly brief?: string;
|
|
151
|
+
/** #114 S3: the real sender of an outbound row (absent on inbound rows and rows persisted before the field). */
|
|
152
|
+
readonly sender?: Sender;
|
|
153
|
+
/** Home host forwarded an online CLI request to a peer. */
|
|
154
|
+
readonly forwarded?: true;
|
|
155
|
+
/**
|
|
156
|
+
* #114 S2: the immutable authorization the row was born with — an inbound
|
|
157
|
+
* row's wire admission, or (#139) an outbound row's local-candidate
|
|
158
|
+
* admission (`ingress: 'local'`). Absent on remote outbound and older rows.
|
|
159
|
+
*/
|
|
160
|
+
readonly auth?: TaskAuthorization;
|
|
161
|
+
/** #139: the public key of the peer card a remote outbound row was dispatched to. */
|
|
162
|
+
readonly peerKey?: string;
|
|
163
|
+
/** #139: that card's session label (the peer host's process label). */
|
|
164
|
+
readonly peerHost?: string;
|
|
165
|
+
/** #139: the member that actually received the task (canvas / bare-zone deliveries). */
|
|
166
|
+
readonly deliveredTo?: string;
|
|
167
|
+
/** Local target has produced an answer; still owed until receipt delivery/settlement, but no longer queued. */
|
|
168
|
+
answeredAt?: number;
|
|
169
|
+
status: TaskStatus;
|
|
170
|
+
/** When the row was swept to dead-letter (absent while pending). */
|
|
171
|
+
deadAt?: number;
|
|
172
|
+
/**
|
|
173
|
+
* F5 layer-3: why the row aged out. 'fire-forget' — a receiptExpected:false
|
|
174
|
+
* row expiring is expected bookkeeping, not a failure; 'unconsumed' — a
|
|
175
|
+
* receipt-owed row never settled, the actionable stale shape. Absent on
|
|
176
|
+
* dead rows persisted before this field (rendered as 'unspecified').
|
|
177
|
+
*/
|
|
178
|
+
reason?: DeadReason;
|
|
179
|
+
}
|
|
180
|
+
/** One settled task kept for audit, with its receipt-correlated outcome. */
|
|
181
|
+
export interface ArchivedRecord {
|
|
182
|
+
readonly taskId: string;
|
|
183
|
+
readonly team: string;
|
|
184
|
+
readonly peer: string;
|
|
185
|
+
readonly startedAt: number;
|
|
186
|
+
readonly contextId?: string;
|
|
187
|
+
readonly resolvedAt: number;
|
|
188
|
+
readonly summary?: string;
|
|
189
|
+
/** Head of the dispatched message that this row settled (see {@link TaskRecord.brief}). */
|
|
190
|
+
readonly brief?: string;
|
|
191
|
+
/** Controlled vocabulary verdict when the settling receipt carried a v2 envelope; forced 'abandoned' for caller-abandoned rows. */
|
|
192
|
+
readonly outcome?: ReceiptOutcomeV2 | 'abandoned';
|
|
193
|
+
/** Remote-turn wall-clock cost, when the settling envelope carried it. */
|
|
194
|
+
readonly elapsedMs?: number;
|
|
195
|
+
/**
|
|
196
|
+
* When a receipt arrived for an explicitly abandoned row ({@link TaskLedger.abandon}).
|
|
197
|
+
* Orphan isolation: the abandonment outcome stays verbatim; the late answer is
|
|
198
|
+
* recorded as metadata only, never promoted to resolution.
|
|
199
|
+
*/
|
|
200
|
+
readonly lateReceiptAt?: number;
|
|
201
|
+
/**
|
|
202
|
+
* gh#31: which archive path ended the row, recorded at archive time. Absent
|
|
203
|
+
* on rows archived before the field — {@link archivedEndedBy} derives it.
|
|
204
|
+
*/
|
|
205
|
+
readonly endedBy?: ArchiveEnd;
|
|
206
|
+
/** gh#31: which side of the wire the row served, copied from the owed row (absent on rows archived before the field). */
|
|
207
|
+
readonly direction?: 'inbound' | 'outbound';
|
|
208
|
+
/**
|
|
209
|
+
* gh#68: copied from the owed row when it awaited no receipt, so a tidied
|
|
210
|
+
* fire-forget expiry is not read as a send we gave up on. Only `false` is
|
|
211
|
+
* ever stored; absent means a receipt was owed (or the row predates the field).
|
|
212
|
+
*/
|
|
213
|
+
readonly receiptExpected?: false;
|
|
214
|
+
/** #114 S3: copied from the owed row (absent on inbound rows and rows archived before the field). */
|
|
215
|
+
readonly sender?: Sender;
|
|
216
|
+
/** Home host forwarded an online CLI request to a peer. */
|
|
217
|
+
readonly forwarded?: true;
|
|
218
|
+
/** #114 S2: carried over from the live row unchanged. */
|
|
219
|
+
readonly auth?: TaskAuthorization;
|
|
220
|
+
/** #139: carried over from the live row unchanged (a late receipt is judged against them). */
|
|
221
|
+
readonly peerKey?: string;
|
|
222
|
+
readonly peerHost?: string;
|
|
223
|
+
readonly deliveredTo?: string;
|
|
224
|
+
/** Local answer time retained as audit metadata, independent of resolution time. */
|
|
225
|
+
readonly answeredAt?: number;
|
|
226
|
+
/** #114 S4: who settled the row (receipt and echo ends), and their chain role. */
|
|
227
|
+
readonly settledBy?: SettledBy;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* How one archived row ended (gh#31). The recorded field wins; legacy rows
|
|
231
|
+
* fall back to the summary markers this ledger writes itself. The fallback
|
|
232
|
+
* reads a receipt-settled row's summary, which is the peer's text, so it is
|
|
233
|
+
* only as exact as the legacy data allows.
|
|
234
|
+
* @param row - the archived record.
|
|
235
|
+
* @returns the archive path that ended it.
|
|
236
|
+
*/
|
|
237
|
+
export declare function archivedEndedBy(row: ArchivedRecord): ArchiveEnd;
|
|
238
|
+
/** Result of an explicit caller-side abandon. Total and never-thrown. */
|
|
239
|
+
export type AbandonOutcome = 'cleared' | 'already-terminal' | 'unknown';
|
|
240
|
+
/** Structured verdict for one {@link TaskLedger.abandon} call (consumer contract: idempotent). */
|
|
241
|
+
export interface AbandonResult {
|
|
242
|
+
readonly outcome: AbandonOutcome;
|
|
243
|
+
readonly taskId: string;
|
|
244
|
+
/** Settle time: fresh abandonment's clock tick, or the already-settled row's original resolvedAt. */
|
|
245
|
+
readonly archivedAt?: number;
|
|
246
|
+
}
|
|
247
|
+
/** Result of a cooperative {@link TaskLedger.cancel}; adds routing facts for the notify half. */
|
|
248
|
+
export interface CancelledResult extends AbandonResult {
|
|
249
|
+
/** Team the ended row addressed — the route layer reaches a live local target through it. */
|
|
250
|
+
readonly team?: string;
|
|
251
|
+
readonly contextId?: string;
|
|
252
|
+
}
|
|
253
|
+
/** Per-instance knobs; defaults come from the exported constants. */
|
|
254
|
+
export interface TaskLedgerOptions {
|
|
255
|
+
/** Stale-TTL override (tests inject short windows); defaults to {@link TASK_STALE_TTL_MS}. */
|
|
256
|
+
staleTtlMs?: number;
|
|
257
|
+
/** Archive capacity override; defaults to {@link ARCHIVE_CAP}. */
|
|
258
|
+
archiveCap?: number;
|
|
259
|
+
/**
|
|
260
|
+
* Clock injection for tests: every sweep/TTL decision reads the time from
|
|
261
|
+
* here. Defaults to Date.now. Injecting a steppable clock makes the stale
|
|
262
|
+
* transition deterministic - no real sleeps to race under load.
|
|
263
|
+
*/
|
|
264
|
+
now?: () => number;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* What one correlated receipt settled — the payload of the
|
|
268
|
+
* `a2a/receipt-resolved` event, emitted for consumers that react to receipt
|
|
269
|
+
* arrivals (P2: native-teams settles its outstanding async submissions from
|
|
270
|
+
* this seam). `outcome` carries the v2 envelope's controlled-vocabulary
|
|
271
|
+
* verdict when the receipt rode one; `late` marks a receipt answering a
|
|
272
|
+
* dead-lettered row (revival) or an abandoned row (arrival recorded) — a
|
|
273
|
+
* healthy row's duplicate receipt refreshes the archive with NO marker.
|
|
274
|
+
*/
|
|
275
|
+
export interface ReceiptResolvedInfo {
|
|
276
|
+
readonly taskId: string;
|
|
277
|
+
/** The team the task was routed to (as the dispatcher recorded it). */
|
|
278
|
+
readonly team: string;
|
|
279
|
+
/** The peer (or `'local'`) the task was dispatched through. */
|
|
280
|
+
readonly peer: string;
|
|
281
|
+
/** v2 envelope outcome, when the receipt rode one. */
|
|
282
|
+
readonly outcome?: string;
|
|
283
|
+
/** One-line outcome summary, when the receipt carried one. */
|
|
284
|
+
readonly summary?: string;
|
|
285
|
+
/** True when the receipt answered a dead-lettered or abandoned row. */
|
|
286
|
+
readonly late?: boolean;
|
|
287
|
+
}
|
|
288
|
+
/** The persisted ledger document: the owed book plus its settled archive. */
|
|
289
|
+
export interface TaskLedgerSnapshot {
|
|
290
|
+
readonly tasks: readonly TaskRecord[];
|
|
291
|
+
readonly archived?: readonly ArchivedRecord[];
|
|
292
|
+
/** #114 S4: the bounded ring of refused / would-be-refused settlements. */
|
|
293
|
+
readonly settlementRefusals?: readonly SettlementRefusalRecord[];
|
|
294
|
+
/** #114 S4: monotonic count of every refusal noted. */
|
|
295
|
+
readonly settlementRefusalsTotal?: number;
|
|
296
|
+
/** gh#154: lifetime conservation counters (persisted; absent = pre-#154 legacy) — the module contract, shared by classifier, counters, and this schema (review P3). */
|
|
297
|
+
readonly conservation?: ConservationSnapshot;
|
|
298
|
+
}
|
|
299
|
+
/** #114 S4: who settled an archived row, and how they stood on its authorization chain. */
|
|
300
|
+
export interface SettledBy {
|
|
301
|
+
readonly caller: string;
|
|
302
|
+
readonly chain_role: ChainRole | 'off-chain';
|
|
303
|
+
}
|
|
304
|
+
/** #114 S4: one settlement the judgement refused (enforce) or would have refused (observe). */
|
|
305
|
+
export interface SettlementRefusalRecord {
|
|
306
|
+
readonly taskId: string;
|
|
307
|
+
readonly at: number;
|
|
308
|
+
readonly settler: {
|
|
309
|
+
readonly caller: string;
|
|
310
|
+
readonly proof: AuthProof;
|
|
311
|
+
readonly ingress: 'wire' | 'local';
|
|
312
|
+
};
|
|
313
|
+
readonly reason: SettlementReason;
|
|
314
|
+
/** The receipt's first line, bounded. */
|
|
315
|
+
readonly head: string;
|
|
316
|
+
/** true: the settlement was refused (enforce); false: recorded only (observe). */
|
|
317
|
+
readonly enforced: boolean;
|
|
318
|
+
/** Repeats of the same (task, settler, reason) folded into this entry, so a flood cannot push older evidence out (review of PR #140 P2-4). */
|
|
319
|
+
readonly count: number;
|
|
320
|
+
}
|
|
321
|
+
/** Bound on ids and callers kept in settlement records; longer values are cut on write and refused on restore. */
|
|
322
|
+
export declare const SETTLEMENT_ID_CAP = 256;
|
|
323
|
+
/** Bound of the refusal ring (design §4.4). */
|
|
324
|
+
export declare const SETTLEMENT_REFUSAL_CAP = 64;
|
|
325
|
+
/**
|
|
326
|
+
* #114 S4: how the ledger asks whether a settler may settle a row. `check`
|
|
327
|
+
* is the pure judgement (task-auth.ts judgeSettlement) bound to this
|
|
328
|
+
* receipt's facts; `enforce` decides whether an off-chain verdict blocks
|
|
329
|
+
* the settlement or is only recorded.
|
|
330
|
+
*/
|
|
331
|
+
export interface SettlementJudge {
|
|
332
|
+
readonly enforce: boolean;
|
|
333
|
+
readonly settler: SettlementRefusalRecord['settler'];
|
|
334
|
+
check(row: Pick<TaskRecord, 'team' | 'peer' | 'direction' | 'auth' | 'peerKey' | 'peerHost' | 'deliveredTo'>, echo: boolean): SettlementVerdict;
|
|
335
|
+
}
|
|
336
|
+
/** Restore-side allowlist for settledBy: whole or nothing. */
|
|
337
|
+
export declare function withSettledBy(raw: unknown): {
|
|
338
|
+
settledBy?: SettledBy;
|
|
339
|
+
};
|
|
340
|
+
/** Restore-side allowlist for one refusal record: whole or nothing. */
|
|
341
|
+
export declare function parseRefusal(raw: unknown): SettlementRefusalRecord | undefined;
|