@nelsonlongxiang/dsh-open-a2a-net 0.6.31 → 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.
- package/README.md +4 -1
- package/README_zh.md +3 -0
- package/assets/nexusDist/assets/index-D5H8R8de.js +9 -0
- package/assets/nexusDist/assets/index-D9GYZyGZ.css +1 -0
- package/assets/nexusDist/index.html +2 -2
- package/cli/a2a.mjs +445 -57
- package/lib/a2a-client.js +9 -4
- package/lib/auth-observer.js +254 -0
- package/lib/blob-store.js +210 -0
- package/lib/card.js +4 -2
- package/lib/cli-deposit.js +122 -7
- package/lib/client.js +64 -64
- package/lib/client.js.map +1 -1
- package/lib/faces/admin.js +133 -43
- package/lib/faces/canvas-notify.js +12 -3
- package/lib/faces/files.js +155 -0
- package/lib/faces/nodes.js +171 -0
- package/lib/faces/session-log.js +162 -0
- package/lib/faces/wire.js +206 -39
- package/lib/index.js +809 -1720
- package/lib/joined-store.js +23 -0
- package/lib/ledger-conservation.js +97 -0
- package/lib/legacy/teams-bridge-inbound.js +156 -0
- package/lib/node-bind.js +534 -0
- package/lib/node-github.js +72 -0
- package/lib/node-self.js +453 -0
- package/lib/node-store.js +484 -0
- package/lib/node-tasks.js +342 -0
- package/lib/origin-claim.js +246 -0
- package/lib/peer-directory.js +134 -0
- package/lib/receipt.js +26 -1
- package/lib/skill/SKILL.md +3 -1
- package/lib/skill/references/cli.md +82 -3
- package/lib/skill/references/collaboration.md +126 -74
- package/lib/skill/references/discovery.md +29 -0
- package/lib/skill/references/intake.md +107 -0
- package/lib/skill/references/router.md +96 -0
- package/lib/skill/references/setup.md +2 -1
- package/lib/task-auth.js +483 -0
- package/lib/task-ledger.js +324 -12
- package/lib/team-store.js +24 -1
- package/lib/tools/act.js +383 -0
- package/lib/tools/files.js +201 -0
- package/lib/tools/info.js +684 -0
- package/lib/types/a2a-client.d.ts +10 -4
- package/lib/types/auth-observer.d.ts +110 -0
- package/lib/types/blob-store.d.ts +83 -0
- package/lib/types/cli-deposit.d.ts +38 -0
- package/lib/types/faces/admin.d.ts +14 -2
- package/lib/types/faces/canvas-notify.d.ts +4 -1
- package/lib/types/faces/files.d.ts +53 -0
- package/lib/types/faces/nodes.d.ts +69 -0
- package/lib/types/faces/session-log.d.ts +44 -0
- package/lib/types/faces/wire.d.ts +50 -5
- package/lib/types/index.d.ts +27 -0
- package/lib/types/joined-store.d.ts +9 -0
- package/lib/types/ledger-conservation.d.ts +77 -0
- package/lib/types/legacy/teams-bridge-inbound.d.ts +94 -0
- package/lib/types/node-bind.d.ts +220 -0
- package/lib/types/node-github.d.ts +35 -0
- package/lib/types/node-self.d.ts +178 -0
- package/lib/types/node-store.d.ts +180 -0
- package/lib/types/node-tasks.d.ts +121 -0
- package/lib/types/origin-claim.d.ts +119 -0
- package/lib/types/peer-directory.d.ts +39 -0
- package/lib/types/receipt.d.ts +12 -0
- package/lib/types/task-auth.d.ts +271 -0
- package/lib/types/task-ledger.d.ts +143 -6
- package/lib/types/team-store.d.ts +9 -0
- package/lib/types/tools/act.d.ts +52 -0
- package/lib/types/tools/files.d.ts +38 -0
- package/lib/types/tools/info.d.ts +102 -0
- package/lib/types/types.d.ts +6 -0
- package/nexus-stage/src/canvas-ops.ts +1 -1
- package/nexus-stage/src/federation.ts +1 -0
- package/nexus-stage/src/main.ts +41 -18
- package/nexus-stage/src/planning-member-handles.ts +34 -0
- package/nexus-stage/src/planning-mirror.ts +47 -3
- package/nexus-stage/src/planning-reachability.ts +28 -0
- package/nexus-stage/src/planning-types.ts +1 -1
- package/nexus-stage/src/planning-view.ts +29 -26
- package/nexus-stage/src/planning.css +4 -0
- package/nexus-stage/src/wb-directory.ts +13 -0
- package/nexus-stage/src/workbench.css +3 -0
- package/nexus-stage/src/workbench.ts +5 -1
- package/package.json +1 -1
- package/assets/nexusDist/assets/index-Dz8N80tI.css +0 -1
- package/assets/nexusDist/assets/index-jm-cFAhB.js +0 -9
|
@@ -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
|
-
/**
|
|
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 {};
|