@nelsonlongxiang/dsh-open-a2a-net 0.6.30 → 0.6.32

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 (89) hide show
  1. package/README.md +4 -1
  2. package/README_zh.md +3 -0
  3. package/assets/nexusDist/assets/index-D5H8R8de.js +9 -0
  4. package/assets/nexusDist/assets/index-D9GYZyGZ.css +1 -0
  5. package/assets/nexusDist/index.html +2 -2
  6. package/cli/a2a.mjs +445 -57
  7. package/lib/a2a-client.js +9 -4
  8. package/lib/auth-observer.js +254 -0
  9. package/lib/blob-store.js +210 -0
  10. package/lib/card.js +4 -2
  11. package/lib/cli-deposit.js +122 -7
  12. package/lib/client.js +64 -64
  13. package/lib/client.js.map +1 -1
  14. package/lib/faces/admin.js +133 -43
  15. package/lib/faces/canvas-notify.js +12 -3
  16. package/lib/faces/files.js +155 -0
  17. package/lib/faces/nodes.js +171 -0
  18. package/lib/faces/session-log.js +162 -0
  19. package/lib/faces/wire.js +206 -39
  20. package/lib/index.js +809 -1720
  21. package/lib/joined-store.js +23 -0
  22. package/lib/ledger-conservation.js +97 -0
  23. package/lib/legacy/teams-bridge-inbound.js +156 -0
  24. package/lib/node-bind.js +534 -0
  25. package/lib/node-github.js +72 -0
  26. package/lib/node-self.js +453 -0
  27. package/lib/node-store.js +484 -0
  28. package/lib/node-tasks.js +342 -0
  29. package/lib/origin-claim.js +246 -0
  30. package/lib/peer-directory.js +134 -0
  31. package/lib/receipt.js +26 -1
  32. package/lib/skill/SKILL.md +3 -1
  33. package/lib/skill/references/cli.md +82 -3
  34. package/lib/skill/references/collaboration.md +146 -73
  35. package/lib/skill/references/discovery.md +29 -0
  36. package/lib/skill/references/intake.md +107 -0
  37. package/lib/skill/references/router.md +96 -0
  38. package/lib/skill/references/setup.md +2 -1
  39. package/lib/task-auth.js +483 -0
  40. package/lib/task-ledger.js +324 -12
  41. package/lib/team-store.js +24 -1
  42. package/lib/tools/act.js +383 -0
  43. package/lib/tools/files.js +201 -0
  44. package/lib/tools/info.js +684 -0
  45. package/lib/types/a2a-client.d.ts +10 -4
  46. package/lib/types/auth-observer.d.ts +110 -0
  47. package/lib/types/blob-store.d.ts +83 -0
  48. package/lib/types/cli-deposit.d.ts +38 -0
  49. package/lib/types/faces/admin.d.ts +14 -2
  50. package/lib/types/faces/canvas-notify.d.ts +4 -1
  51. package/lib/types/faces/files.d.ts +53 -0
  52. package/lib/types/faces/nodes.d.ts +69 -0
  53. package/lib/types/faces/session-log.d.ts +44 -0
  54. package/lib/types/faces/wire.d.ts +50 -5
  55. package/lib/types/index.d.ts +27 -0
  56. package/lib/types/joined-store.d.ts +9 -0
  57. package/lib/types/ledger-conservation.d.ts +77 -0
  58. package/lib/types/legacy/teams-bridge-inbound.d.ts +94 -0
  59. package/lib/types/node-bind.d.ts +220 -0
  60. package/lib/types/node-github.d.ts +35 -0
  61. package/lib/types/node-self.d.ts +178 -0
  62. package/lib/types/node-store.d.ts +180 -0
  63. package/lib/types/node-tasks.d.ts +121 -0
  64. package/lib/types/origin-claim.d.ts +119 -0
  65. package/lib/types/peer-directory.d.ts +39 -0
  66. package/lib/types/receipt.d.ts +12 -0
  67. package/lib/types/task-auth.d.ts +271 -0
  68. package/lib/types/task-ledger.d.ts +143 -6
  69. package/lib/types/team-store.d.ts +9 -0
  70. package/lib/types/tools/act.d.ts +52 -0
  71. package/lib/types/tools/files.d.ts +38 -0
  72. package/lib/types/tools/info.d.ts +102 -0
  73. package/lib/types/types.d.ts +6 -0
  74. package/nexus-stage/src/canvas-ops.ts +17 -2
  75. package/nexus-stage/src/canvas-wire.ts +27 -19
  76. package/nexus-stage/src/federation.ts +1 -0
  77. package/nexus-stage/src/main.ts +41 -18
  78. package/nexus-stage/src/planning-member-handles.ts +34 -0
  79. package/nexus-stage/src/planning-mirror.ts +47 -3
  80. package/nexus-stage/src/planning-reachability.ts +28 -0
  81. package/nexus-stage/src/planning-types.ts +1 -1
  82. package/nexus-stage/src/planning-view.ts +30 -27
  83. package/nexus-stage/src/planning.css +4 -0
  84. package/nexus-stage/src/wb-directory.ts +13 -0
  85. package/nexus-stage/src/workbench.css +3 -0
  86. package/nexus-stage/src/workbench.ts +5 -1
  87. package/package.json +1 -1
  88. package/assets/nexusDist/assets/index-C_x3iAth.js +0 -9
  89. package/assets/nexusDist/assets/index-Dz8N80tI.css +0 -1
@@ -0,0 +1,271 @@
1
+ /** The admission basis (closed set, design §2.2). */
2
+ export type Via = 'host-face' | 'joined-session' | 'canvas-member' | 'peer-org' | 'cli-deposit' | 'open';
3
+ /** The exact rule that admitted a caller (closed set, design §2.2). */
4
+ export type AdmissionRule = 'scope-off' | 'host-face' | 'local-exempt' | 'roster-shared' | 'canvas-shared' | 'remote-ref-samezone' | 'remote-ref-foreign' | 'peer-org' | 'node-fact' | 'cli-deposit';
5
+ /** Every rule's basis — the mapping the authorization record stores beside the rule. */
6
+ export declare const VIA_OF: Readonly<Record<AdmissionRule, Via>>;
7
+ /**
8
+ * One admission decision. `basis` lists every shared team that held; `rule`
9
+ * is the first branch of the decision that hit (deterministic, so a record
10
+ * built from it is reproducible).
11
+ */
12
+ export type Admission = {
13
+ readonly ok: true;
14
+ readonly via: Via;
15
+ readonly rule: AdmissionRule;
16
+ readonly basis: readonly string[];
17
+ } | {
18
+ readonly ok: false;
19
+ readonly refusal: string;
20
+ };
21
+ /** The refusal text of a decision, or undefined when admitted — the shape the faces have always read. */
22
+ export declare const refusalOf: (admission: Admission) => string | undefined;
23
+ /** One cached peer card's published canvas org (gh#71). */
24
+ export interface PeerOrgEntry {
25
+ readonly team?: unknown;
26
+ readonly members?: unknown;
27
+ }
28
+ /**
29
+ * This host's records, read at decision time. Every field the admission
30
+ * rules consult and nothing else — the caller's self-description never
31
+ * appears here (gh#7 N3, V5).
32
+ */
33
+ export interface AdmissionRecords {
34
+ readonly zone: string;
35
+ readonly teamScopeRouting: boolean;
36
+ id8(id: string): string;
37
+ canvasTeamOf(name: string): string;
38
+ memberships(): readonly {
39
+ readonly session: string;
40
+ readonly teams: readonly string[];
41
+ }[];
42
+ joined(): readonly string[];
43
+ canvasNames(): readonly string[];
44
+ /** Canvas names the session sits in. */
45
+ canvasTeamsOf(sessionId: string): readonly string[];
46
+ canvasMembersOf(name: string): readonly string[];
47
+ canvasRemoteMembersOf(name: string): readonly string[];
48
+ /** The canvasOrg lists of the warm peer-card cache. */
49
+ peerOrgs(): Iterable<readonly PeerOrgEntry[]>;
50
+ liveNodeIds(): readonly string[];
51
+ cliDeposited(handle: string): boolean;
52
+ }
53
+ /**
54
+ * W1/W2 shared-team admission core: a same-zone zone/id8 caller passes when
55
+ * it holds a team this host's records also hold. The caller's basis is its
56
+ * roster declarations UNION its canvas memberships (W2②); a handle that
57
+ * resolves to no local session is a cross-host member whose basis is this
58
+ * host's remote member refs (#43) and the warm cache's two-way canvasOrg
59
+ * (gh#71 ②). F5: with a target, the intersection is at target level.
60
+ */
61
+ export declare function sameZoneAdmission(r: AdmissionRecords, caller: string, targetTeam?: string): Admission;
62
+ /**
63
+ * W1: the joined-face gate for the LOCAL dispatch candidate — only a
64
+ * zone/id8 label of THIS zone is judged (the shared-team core); every other
65
+ * label is outside this gate.
66
+ */
67
+ export declare function joinedFaceAdmission(r: AdmissionRecords, callerSession: string | undefined, targetTeam?: string): Admission | undefined;
68
+ /**
69
+ * #139: the admission an in-process dispatch is recorded under (design §2.4):
70
+ * the joined face's shared-team verdict, or `local-exempt` for the callers
71
+ * dispatchLocalCandidate does not gate (the initiator face, a foreign-zone
72
+ * label, an agent-less call). A refused joined face never dispatches.
73
+ */
74
+ export declare function localCandidateAdmission(r: AdmissionRecords, callerSession: string | undefined, targetTeam?: string): Admission;
75
+ /** Inbound admission for the peer faces (/a2a/direct, /a2a/log): caller_session is the contract. */
76
+ export declare function callerAdmission(r: AdmissionRecords, caller: string, targetTeam?: string): Admission;
77
+ /**
78
+ * The inputs of the allowlist version (design §2.3): only operator-controlled
79
+ * records the admission rules really read. The peer-card canvasOrg is left
80
+ * out on purpose — it is the remote's self-description and churns with the
81
+ * cache; a peer-org record carries its org entry in `basis` instead.
82
+ */
83
+ export interface AllowlistInputs {
84
+ readonly zone: string;
85
+ readonly scope: readonly [teamScopeRouting: boolean, originClaimWindow: boolean];
86
+ readonly joined: readonly string[];
87
+ readonly roster: readonly (readonly [handle: string, team: string])[];
88
+ readonly canvas: readonly (readonly [team: string, members: readonly string[]])[];
89
+ readonly cli: readonly string[];
90
+ readonly links: readonly string[];
91
+ }
92
+ /**
93
+ * The canonical JSON the version hashes: fixed key order, arrays sorted and
94
+ * deduplicated, no whitespace. Strings sort by UTF-16 code unit (JavaScript's
95
+ * default `sort`) and are NOT Unicode-normalized: another implementation must
96
+ * do the same to reproduce a version.
97
+ */
98
+ export declare function canonicalAllowlist(inputs: AllowlistInputs): string;
99
+ /** `al1:` + the first 16 hex of sha256 over the canonical inputs. */
100
+ export declare function allowlistVersion(inputs: AllowlistInputs): string;
101
+ /**
102
+ * How strongly the caller's identity was proven at birth:
103
+ * - `origin-claim`: a bound, audience-correct envelope verified on a root-origin card for this caller.
104
+ * - `claim-mismatch`: bound proof names another caller, or root-origin derivation failed.
105
+ * - `legacy-window`: no envelope, admitted inside the OriginClaim window.
106
+ * - `unbound`: a v1 signature proves only key possession, not origin.
107
+ * - `cli-identity`: a deposited CLI key proves the local CLI handle, not market issuer origin.
108
+ * - `in-process`: a local delivery inside this host (S4).
109
+ * - `none`: no identity evidence at all.
110
+ */
111
+ export type AuthProof = 'origin-claim' | 'claim-mismatch' | 'legacy-window' | 'unbound' | 'cli-identity' | 'in-process' | 'none';
112
+ /**
113
+ * Frozen on an inbound ledger row when it is born, archived with it, never
114
+ * mutated: the answer to "on what authority did this host take this task".
115
+ */
116
+ export interface TaskAuthorization {
117
+ readonly v: 1;
118
+ /** The caller_session the admission rules judged. */
119
+ readonly caller: string;
120
+ readonly proof: AuthProof;
121
+ /** Signed claim identity/origin retained for audit even when proof is low-trust unbound or CLI; its presence alone is not trust. */
122
+ readonly claim?: {
123
+ readonly caller: string;
124
+ readonly origin: string;
125
+ };
126
+ readonly ingress: 'wire' | 'local';
127
+ readonly via: Via;
128
+ readonly admission_rule: AdmissionRule;
129
+ /** Every shared team that held (cache-owed ones as `peer-org:<team>`). */
130
+ readonly basis: readonly string[];
131
+ readonly allowlist_version: string;
132
+ /** The team the task was delivered to. */
133
+ readonly target: string;
134
+ /** Where the receipt is owed. */
135
+ readonly receipt_target: string;
136
+ readonly received_at: number;
137
+ /** Audit only: the target was running another turn when the task arrived (never used to attribute turns). */
138
+ readonly target_busy?: true;
139
+ }
140
+ /** Bounds a persisted record must respect; anything outside them is malformed, not truncated. */
141
+ export declare const AUTH_STRING_CAP = 256;
142
+ export declare const AUTH_BASIS_CAP = 64;
143
+ /** A record born more than this far in the future is a forgery or a broken clock, not a birth. */
144
+ export declare const AUTH_CLOCK_SKEW_MS: number;
145
+ /** Does a verified claim's signed zone cover the judged caller (the same name, or `<zone>/…`)? */
146
+ export declare const claimCovers: (caller: string, claimCaller: string) => boolean;
147
+ /** The facts of one delivery that the admission decision does not carry. */
148
+ export interface AuthorizationFacts {
149
+ readonly caller: string;
150
+ readonly proof: AuthProof;
151
+ readonly claim?: {
152
+ readonly caller: string;
153
+ readonly origin: string;
154
+ };
155
+ readonly ingress: 'wire' | 'local';
156
+ readonly allowlistVersion: string;
157
+ readonly target: string;
158
+ readonly receiptTarget: string;
159
+ readonly receivedAt: number;
160
+ readonly targetBusy?: boolean;
161
+ }
162
+ /** The one constructor of a record: from an admitted decision and the delivery's facts. Frozen. */
163
+ export declare function buildAuthorization(admission: Extract<Admission, {
164
+ ok: true;
165
+ }>, facts: AuthorizationFacts): TaskAuthorization;
166
+ /**
167
+ * #114 S5: the canonical JSON of one authorization record — the single
168
+ * implementation `authRef` hashes. Fixed key order (the {@link
169
+ * TaskAuthorization} declaration order), absent optional fields omitted,
170
+ * `basis` sorted and deduplicated (it is a set: the order admission found its
171
+ * teams in is not part of the authority), no whitespace. Persisted key order
172
+ * never matters, so a restored record yields the same ref as its original.
173
+ */
174
+ export declare function canonicalAuthorization(auth: TaskAuthorization): string;
175
+ /**
176
+ * #114 S5: the auto-receipt envelope's `authRef` — the first 16 hex of
177
+ * sha256 over {@link canonicalAuthorization}. Pins which inbound record a
178
+ * host-synthesized receipt answers; recorded for audit, judged by S4.
179
+ */
180
+ export declare function authorizationRef(auth: TaskAuthorization): string;
181
+ /**
182
+ * Restore-side allowlist for one persisted record (design §2.5): all of it or
183
+ * none of it — a record with any field outside its closed set or bounds, or
184
+ * any combination admission could never have produced, is dropped whole (the
185
+ * row is kept, as a pre-S2 row), never partially adopted. Later slices (S4
186
+ * settlement) trust what survives, so a corrupt or hand-edited tasks.json
187
+ * must not be able to plant a plausible record.
188
+ * @param now - the restore clock, for the future-birth bound.
189
+ */
190
+ export declare function parseAuthorization(raw: unknown, now?: number): TaskAuthorization | undefined;
191
+ /** `off`: no judgement. `observe` (default): judge and record, settle as before. `enforce`: an off-chain settlement is refused. */
192
+ export type SettlementMode = 'off' | 'observe' | 'enforce';
193
+ /** How a settler stands on the task's authorization chain. */
194
+ export type ChainRole = 'target' | 'target-member' | 'host-face' | 'compat' | 'legacy';
195
+ /** Why a settlement is (or would be) refused — closed set (design §4.4). */
196
+ export type SettlementReason = 'settler-off-chain' | 'settler-unverified' | 'on-behalf-mismatch' | 'echo-off-chain';
197
+ /** Who carried the receipt to this host (design §4.1). */
198
+ export interface Settler {
199
+ /** The caller_session of the message that carries the receipt. */
200
+ readonly caller: string;
201
+ readonly proof: AuthProof;
202
+ /** The origin of a VERIFIED OriginClaim's card URL (claim-mismatch included), absent without one. */
203
+ readonly claimOrigin?: string;
204
+ /** The public key of that claim's verified card (#139): the host identity, whatever address spelling signed it. */
205
+ readonly claimKey?: string;
206
+ readonly ingress: 'wire' | 'local';
207
+ }
208
+ /** The ledger row a receipt correlates, as far as the judgement reads it. */
209
+ export interface SettlementRow {
210
+ readonly team: string;
211
+ readonly peer: string;
212
+ readonly direction?: 'inbound' | 'outbound';
213
+ readonly auth?: TaskAuthorization;
214
+ /** #139: the public key of the peer card the row was dispatched to (remote outbound rows). */
215
+ readonly peerKey?: string;
216
+ /** #139: that card's session label, the peer host's process label. */
217
+ readonly peerHost?: string;
218
+ /** #139: the member that actually received the task (canvas and bare-zone deliveries). */
219
+ readonly deliveredTo?: string;
220
+ }
221
+ /** Facts from outside the row. */
222
+ export interface SettlementFacts {
223
+ /** This host's process label: the caller its own auto-receipts carry. */
224
+ readonly hostLabel: string;
225
+ /** A canvas team's members (session ids and handles), undefined for any other team. */
226
+ membersOf(team: string): readonly string[] | undefined;
227
+ /** The receipt envelope's onBehalfOf (S5), when present. */
228
+ readonly onBehalfOf?: string;
229
+ /** The row was matched by a content echo, not a receipt header (weak evidence). */
230
+ readonly echo?: boolean;
231
+ }
232
+ /** #139 (design §4.4): what the wire response tells a sender whose receipt enforce refused. */
233
+ export interface SettlementRefusal {
234
+ readonly status: 'refused';
235
+ readonly reason: SettlementReason;
236
+ readonly task_id: string;
237
+ }
238
+ export type SettlementVerdict = {
239
+ readonly ok: true;
240
+ readonly role: ChainRole;
241
+ } | {
242
+ readonly ok: false;
243
+ readonly reason: SettlementReason;
244
+ };
245
+ /**
246
+ * Is this settler on the task's authorization chain? Pure. The chain:
247
+ * - `target`: the settler IS the team the task went to (an inbound row: its
248
+ * record's target). Where that target lives decides how it may speak: a
249
+ * LOCAL row's target (an inbound row, or an outbound row delivered on this
250
+ * host) lives here, so it settles in-process only; a REMOTE row's target
251
+ * settles over the wire with an OriginClaim that covers it AND comes from
252
+ * the row's peer origin (a zone name alone is self-signable — review of
253
+ * PR #140 P1-1/P1-3);
254
+ * - `target-member`: the team is a canvas and the settler the member that
255
+ * received the task (`deliveredTo`, #139) — one of its current members for
256
+ * a row born before the field — under the same where-it-lives rule;
257
+ * - `host-face`: the target's host settling on its behalf — an envelope
258
+ * `onBehalfOf` naming the target (a member, or for a bare zone team one of
259
+ * that zone's faces — the one that received it once `deliveredTo` is
260
+ * known), carried locally by THIS host's process label, or across hosts by
261
+ * the peer's process label (its card's session once recorded) with a
262
+ * verified claim from the row's peer: its origin, or its card key (#139);
263
+ * - `compat`: an older host's auto-receipt (no onBehalfOf, its process label —
264
+ * never a session face) with a verified claim from the row's peer origin
265
+ * (design §6);
266
+ * - `legacy`: an inbound row born before S2 (no record) — allowed, counted.
267
+ * A remote target or member without a covering claim is `settler-unverified`;
268
+ * an onBehalfOf for someone else is `on-behalf-mismatch`; anything else is
269
+ * off-chain (`echo-off-chain` for an echo match).
270
+ */
271
+ export declare function judgeSettlement(row: SettlementRow, settler: Settler, facts: SettlementFacts): SettlementVerdict;
@@ -18,7 +18,9 @@
18
18
  * illusion: settlements vanished while debts piled up).
19
19
  * @module @nelsonlongxiang/dsh-open-a2a-net/task-ledger
20
20
  */
21
+ import { type AuthProof, type ChainRole, type SettlementReason, type SettlementVerdict, type TaskAuthorization } from './task-auth.ts';
21
22
  import { type ReceiptOutcomeV2 } from './receipt.ts';
23
+ import { type ConservationSnapshot, type ConservationVerdict } from './ledger-conservation.ts';
22
24
  /** Hard cap on the owed book (pending + dead-lettered rows, most recent first). */
23
25
  export declare const TASK_CAP = 64;
24
26
  /** Hard cap on the settled archive (most recent kept). */
@@ -54,6 +56,45 @@ export type TaskStatus = 'pending' | 'dead';
54
56
  export type DeadReason = 'fire-forget' | 'unconsumed';
55
57
  /** gh#31: which archive path ended a row — a receipt, a content echo, or the caller. */
56
58
  export type ArchiveEnd = 'receipt' | 'echo' | 'abandoned' | 'cancelled' | 'evicted';
59
+ /**
60
+ * #114 S3: who really sent one outbound task — the routing session, the
61
+ * agent-less host face, or a native-teams bridge submitter. Before this
62
+ * field every outbound row was attributable only to the host ("home"), so
63
+ * same-host seats could not be told apart.
64
+ */
65
+ export interface Sender {
66
+ readonly kind: 'session' | 'host' | 'bridge';
67
+ /**
68
+ * A session's `<zone>/<id8>` node team, the host process label, or the
69
+ * bridge submitter. Group senders by `kind:id`, never by `id` alone: a host
70
+ * label set by hand may look like a session handle.
71
+ */
72
+ readonly id: string;
73
+ /**
74
+ * Host senders only: the label the call borrowed on the wire when it
75
+ * differs from {@link id} (quick dispatch rides a live joined session's
76
+ * team). Display context, never a bucket key.
77
+ */
78
+ readonly face?: string;
79
+ }
80
+ /** Longest sender id kept; anything longer is malformed, not truncated. */
81
+ export declare const SENDER_ID_CAP = 256;
82
+ /**
83
+ * Restore-side allowlist for one persisted sender: a well-formed value comes
84
+ * back as a fresh object holding only kind, id and (when present) face; anything else is dropped whole (the row
85
+ * itself survives — a bad sender never costs the debt record).
86
+ * @param raw - the persisted value.
87
+ * @returns the sender, or undefined when absent or malformed.
88
+ */
89
+ export declare function parseSender(raw: unknown): Sender | undefined;
90
+ /** #139: what an outbound row is born with besides its sender. */
91
+ export interface OutboundBirth {
92
+ /** The local-candidate admission (ingress local); kept only on a row whose peer is 'local'. */
93
+ readonly auth?: TaskAuthorization;
94
+ readonly peerKey?: string;
95
+ readonly peerHost?: string;
96
+ readonly deliveredTo?: string;
97
+ }
57
98
  /** One tracked outbound task still in the owed book (unsettled). */
58
99
  export interface TaskRecord {
59
100
  readonly taskId: string;
@@ -81,6 +122,20 @@ export interface TaskRecord {
81
122
  * replaying the conversation. Absent on rows persisted before this field.
82
123
  */
83
124
  readonly brief?: string;
125
+ /** #114 S3: the real sender of an outbound row (absent on inbound rows and rows persisted before the field). */
126
+ readonly sender?: Sender;
127
+ /**
128
+ * #114 S2: the immutable authorization the row was born with — an inbound
129
+ * row's wire admission, or (#139) an outbound row's local-candidate
130
+ * admission (`ingress: 'local'`). Absent on remote outbound and older rows.
131
+ */
132
+ readonly auth?: TaskAuthorization;
133
+ /** #139: the public key of the peer card a remote outbound row was dispatched to. */
134
+ readonly peerKey?: string;
135
+ /** #139: that card's session label (the peer host's process label). */
136
+ readonly peerHost?: string;
137
+ /** #139: the member that actually received the task (canvas / bare-zone deliveries). */
138
+ readonly deliveredTo?: string;
84
139
  status: TaskStatus;
85
140
  /** When the row was swept to dead-letter (absent while pending). */
86
141
  deadAt?: number;
@@ -126,6 +181,16 @@ export interface ArchivedRecord {
126
181
  * ever stored; absent means a receipt was owed (or the row predates the field).
127
182
  */
128
183
  readonly receiptExpected?: false;
184
+ /** #114 S3: copied from the owed row (absent on inbound rows and rows archived before the field). */
185
+ readonly sender?: Sender;
186
+ /** #114 S2: carried over from the live row unchanged. */
187
+ readonly auth?: TaskAuthorization;
188
+ /** #139: carried over from the live row unchanged (a late receipt is judged against them). */
189
+ readonly peerKey?: string;
190
+ readonly peerHost?: string;
191
+ readonly deliveredTo?: string;
192
+ /** #114 S4: who settled the row (receipt and echo ends), and their chain role. */
193
+ readonly settledBy?: SettledBy;
129
194
  }
130
195
  /**
131
196
  * How one archived row ended (gh#31). The recorded field wins; legacy rows
@@ -190,6 +255,47 @@ export interface ReceiptResolvedInfo {
190
255
  export interface TaskLedgerSnapshot {
191
256
  readonly tasks: readonly TaskRecord[];
192
257
  readonly archived?: readonly ArchivedRecord[];
258
+ /** #114 S4: the bounded ring of refused / would-be-refused settlements. */
259
+ readonly settlementRefusals?: readonly SettlementRefusalRecord[];
260
+ /** #114 S4: monotonic count of every refusal noted. */
261
+ readonly settlementRefusalsTotal?: number;
262
+ /** gh#154: lifetime conservation counters (persisted; absent = pre-#154 legacy) — the module contract, shared by classifier, counters, and this schema (review P3). */
263
+ readonly conservation?: ConservationSnapshot;
264
+ }
265
+ /** #114 S4: who settled an archived row, and how they stood on its authorization chain. */
266
+ export interface SettledBy {
267
+ readonly caller: string;
268
+ readonly chain_role: ChainRole | 'off-chain';
269
+ }
270
+ /** #114 S4: one settlement the judgement refused (enforce) or would have refused (observe). */
271
+ export interface SettlementRefusalRecord {
272
+ readonly taskId: string;
273
+ readonly at: number;
274
+ readonly settler: {
275
+ readonly caller: string;
276
+ readonly proof: AuthProof;
277
+ readonly ingress: 'wire' | 'local';
278
+ };
279
+ readonly reason: SettlementReason;
280
+ /** The receipt's first line, bounded. */
281
+ readonly head: string;
282
+ /** true: the settlement was refused (enforce); false: recorded only (observe). */
283
+ readonly enforced: boolean;
284
+ /** 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). */
285
+ readonly count: number;
286
+ }
287
+ /** Bound of the refusal ring (design §4.4). */
288
+ export declare const SETTLEMENT_REFUSAL_CAP = 64;
289
+ /**
290
+ * #114 S4: how the ledger asks whether a settler may settle a row. `check`
291
+ * is the pure judgement (task-auth.ts judgeSettlement) bound to this
292
+ * receipt's facts; `enforce` decides whether an off-chain verdict blocks
293
+ * the settlement or is only recorded.
294
+ */
295
+ export interface SettlementJudge {
296
+ readonly enforce: boolean;
297
+ readonly settler: SettlementRefusalRecord['settler'];
298
+ check(row: Pick<TaskRecord, 'team' | 'peer' | 'direction' | 'auth' | 'peerKey' | 'peerHost' | 'deliveredTo'>, echo: boolean): SettlementVerdict;
193
299
  }
194
300
  /**
195
301
  * Bounded outbound-task ledger with whole-file persistence. Pure of any
@@ -203,6 +309,12 @@ export declare class TaskLedger {
203
309
  private tasks;
204
310
  /** Settled rows, most recently resolved first; bounded by the archive cap. */
205
311
  private archived;
312
+ /** #114 S4: refused / would-be-refused settlements, newest first. */
313
+ private refusals;
314
+ /** #114 S4: every refusal ever noted (monotonic; survives the ring's bound). */
315
+ private refusalsTotal;
316
+ /** gh#154: lifetime conservation counters (persisted; never reset by cap trims or restarts) and the durable damage marker — the machinery lives in src/ledger-conservation.ts (R-mod); the ledger's seams delegate. */
317
+ private readonly counters;
206
318
  private readonly staleTtlMs;
207
319
  private readonly archiveCap;
208
320
  /** Injectable clock (option `now`). */
@@ -244,8 +356,8 @@ export declare class TaskLedger {
244
356
  * via context_id follow-ups (the dispatch reply already says so).
245
357
  * Idempotent by task id like {@link track}.
246
358
  */
247
- trackReceiptless(taskId: string, team: string, peer: string, contextId?: string, brief?: string): void;
248
- track(taskId: string, team: string, peer: string, contextId?: string, brief?: string): void;
359
+ trackReceiptless(taskId: string, team: string, peer: string, contextId?: string, brief?: string, sender?: Sender, birth?: OutboundBirth): void;
360
+ track(taskId: string, team: string, peer: string, contextId?: string, brief?: string, sender?: Sender, birth?: OutboundBirth): void;
249
361
  /**
250
362
  * F2' (receipt-settlement): remember one INBOUND delivery owed a receipt —
251
363
  * this node is the settlement debtor here: the caller waits for a receipt
@@ -259,7 +371,7 @@ export declare class TaskLedger {
259
371
  * @param from - the caller's receipt target (label or callback address).
260
372
  * @param contextId - the delivery's conversation id, kept for follow-up routes (empty omits it).
261
373
  */
262
- trackInbound(taskId: string, team: string, from: string, contextId?: string, receiptExpected?: boolean, brief?: string): void;
374
+ trackInbound(taskId: string, team: string, from: string, contextId?: string, receiptExpected?: boolean, brief?: string, auth?: TaskAuthorization, deliveredTo?: string): void;
263
375
  /**
264
376
  * v0.5.23 (async-stall): whether a tracked task still owes its receipt.
265
377
  * The async nudge consults this — a resolved or dead-lettered task
@@ -304,10 +416,12 @@ export declare class TaskLedger {
304
416
  * record with the latest outcome. A dead-lettered row revives through this
305
417
  * same path when a revived target finally answers.
306
418
  * @param message - the inbound or relayed message text.
419
+ * @param only - gh#141: settle the named row only when it passes this test;
420
+ * with a test, a row that fails it or is already archived correlates nothing.
307
421
  * @returns what the receipt settled, or `undefined` when the message
308
422
  * correlated nothing.
309
423
  */
310
- resolveFromMessage(message: string): ReceiptResolvedInfo | undefined;
424
+ resolveFromMessage(message: string, judge?: SettlementJudge, only?: (record: TaskRecord) => boolean): ReceiptResolvedInfo | undefined;
311
425
  /**
312
426
  * F5 (settlement integrity): settle pending rows by CONTENT ECHO — an
313
427
  * inbound message that references `task <id>` of a pending row counts as
@@ -320,9 +434,32 @@ export declare class TaskLedger {
320
434
  * @param message - the inbound message text.
321
435
  * @returns the task ids this message settled (usually none or one).
322
436
  */
323
- resolveEcho(message: string): string[];
324
- /** Move one settled record to the archive head, trimming at the cap. */
437
+ resolveEcho(message: string, judge?: SettlementJudge): string[];
438
+ /** #114 S4: the owed row for a task id, if any (read-only; for the settlement gate). */
439
+ find(taskId: string): TaskRecord | undefined;
440
+ /** #114 S4: refused / would-be-refused settlements, newest first. */
441
+ settlementRefusals(): readonly SettlementRefusalRecord[];
442
+ /** #114 S4: every refusal ever noted, including those folded or pushed out of the ring. */
443
+ settlementRefusalsTotal(): number;
444
+ /**
445
+ * Record one refused / would-be-refused settlement in the bounded ring
446
+ * (persisted by the caller). A repeat of the same (task, settler, reason)
447
+ * folds into its entry and moves it to the head, so one row's flood cannot
448
+ * push other evidence out.
449
+ */
450
+ private noteRefusal;
451
+ /**
452
+ * Move one settled record to the archive head, trimming at the cap.
453
+ * gh#154: every arrival and every cap-trimmed row is counted — the trim
454
+ * itself stays exactly as it was, only visible now.
455
+ */
325
456
  private archiveSettled;
457
+ /**
458
+ * gh#154 conservation verdict — duplicates first, then the count identity
459
+ * (a duplicated row can still make the counts balance; the Harness²
460
+ * assert_identity lesson). The counters module answers the count half.
461
+ */
462
+ conservation(): ConservationVerdict;
326
463
  /**
327
464
  * gh#86 (chief order direct-4f596524): when the owed book exceeds
328
465
  * TASK_CAP, the overflow rows go to the ARCHIVE as evicted - visible
@@ -30,6 +30,7 @@ export declare class TeamMembershipStore {
30
30
  /** Entries in first-declaration order; teams in join order. */
31
31
  private entries;
32
32
  private readonly path;
33
+ private readonly listeners;
33
34
  /**
34
35
  * @param path - the snapshot file; an empty path keeps the store in memory.
35
36
  */
@@ -52,4 +53,12 @@ export declare class TeamMembershipStore {
52
53
  remove(session: string, team: string): void;
53
54
  /** Drop every declaration of one session (a left or archived node). */
54
55
  dropSession(session: string): void;
56
+ /**
57
+ * #114 S6: observe changes — the listener runs after every write that
58
+ * changed the store (never on restore or reload). A throwing listener
59
+ * cannot break the write.
60
+ * @returns the unsubscribe function.
61
+ */
62
+ onChange(listener: () => void): () => void;
63
+ private notify;
55
64
  }
@@ -0,0 +1,52 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { Agent } from '@deepseek-ai/dsh-agent';
3
+ import type { Config } from '../index.ts';
4
+ import type { ExposureStore } from '../exposure-store.ts';
5
+ import type { JoinedSessions } from '../joined-store.ts';
6
+ import type { TeamMembershipStore } from '../team-store.ts';
7
+ type JsonValue = null | boolean | number | string | JsonValue[] | {
8
+ [key: string]: JsonValue;
9
+ };
10
+ /** Everything the action-plane tools capture from apply(). */
11
+ export interface ActToolsDeps {
12
+ readonly ctx: Context;
13
+ readonly config: Config;
14
+ readonly logger: ReturnType<Context['logger']>;
15
+ readonly ownerAllowlist: readonly string[];
16
+ readonly sessionNodes: Map<string, Agent>;
17
+ readonly joinedSessions: JoinedSessions;
18
+ readonly teamMemberships: TeamMembershipStore;
19
+ readonly exposureStore: ExposureStore;
20
+ readonly reportRing: {
21
+ at: number;
22
+ level: 'info' | 'warning' | 'error';
23
+ message: string;
24
+ session: string;
25
+ task?: string;
26
+ }[];
27
+ readonly recordTeamAudit: (action: 'join' | 'leave' | 'archive-prune' | 'agent-less', team: string, id: string, ok: boolean, reason?: string) => void;
28
+ readonly a2aJoinGateRefusal: (exec: {
29
+ agent?: {
30
+ id: unknown;
31
+ };
32
+ } | undefined) => void;
33
+ readonly id8: (id: string) => string;
34
+ readonly routeTrunk: (args: {
35
+ team: string;
36
+ message: string;
37
+ context_id?: string;
38
+ async?: boolean;
39
+ }, agent: Agent | undefined, signal: AbortSignal, hostCaller?: string) => Promise<Record<string, JsonValue>>;
40
+ }
41
+ /**
42
+ * Build the five registrars. Invoke each at the tool's original registration
43
+ * position — the registry order the model sees must not change.
44
+ */
45
+ export declare function createActTools(deps: ActToolsDeps): {
46
+ registerTeamJoin: () => void;
47
+ registerTeamLeave: () => void;
48
+ registerExposure: () => void;
49
+ registerReport: () => void;
50
+ registerRoute: () => void;
51
+ };
52
+ export {};
@@ -0,0 +1,38 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { Agent } from '@deepseek-ai/dsh-agent';
3
+ import type { Config } from '../index.ts';
4
+ import { type BlobStore } from '../blob-store.ts';
5
+ import { type OriginClaimTarget } from '../origin-claim.ts';
6
+ type JsonValue = null | boolean | number | string | JsonValue[] | {
7
+ [key: string]: JsonValue;
8
+ };
9
+ /** Files one send may carry (one message; the FloodGuard per-transfer figure). */
10
+ export declare const FILE_SEND_MAX = 16;
11
+ /** The inbox directory under a session's workspace. */
12
+ export declare const FILE_INBOX_DIR = "a2a-inbox";
13
+ export interface FileToolsDeps {
14
+ readonly ctx: Context;
15
+ readonly config: Pick<Config, 'apiKey' | 'team'>;
16
+ readonly blobStore: BlobStore;
17
+ readonly canvasMembersOf: (name: string) => readonly string[];
18
+ /** This host's own card URL (live: it is known only once the web server settled). */
19
+ readonly ownCardUrl: () => string;
20
+ readonly sessionTeamOf: (agent: Agent) => string;
21
+ readonly a2aJoinGateRefusal: (exec: {
22
+ agent?: {
23
+ id: unknown;
24
+ };
25
+ } | undefined) => void;
26
+ readonly routeTrunk: (args: {
27
+ team: string;
28
+ message: string;
29
+ async?: boolean;
30
+ }, agent: Agent | undefined, signal: AbortSignal) => Promise<Record<string, JsonValue>>;
31
+ readonly peerFetch: typeof globalThis.fetch;
32
+ readonly originClaimHeaders: (target: OriginClaimTarget) => Record<string, string>;
33
+ }
34
+ export declare function createFileTools(d: FileToolsDeps): {
35
+ registerFileSend: () => void;
36
+ registerFileFetch: () => void;
37
+ };
38
+ export {};