@affiant/core 0.1.0-alpha.0
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/LICENSE +202 -0
- package/README.md +354 -0
- package/dist/context.d.ts +136 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +30 -0
- package/dist/context.js.map +1 -0
- package/dist/docket/entry.d.ts +421 -0
- package/dist/docket/entry.d.ts.map +1 -0
- package/dist/docket/entry.js +155 -0
- package/dist/docket/entry.js.map +1 -0
- package/dist/docket/expiry.d.ts +82 -0
- package/dist/docket/expiry.d.ts.map +1 -0
- package/dist/docket/expiry.js +106 -0
- package/dist/docket/expiry.js.map +1 -0
- package/dist/docket/memory.d.ts +163 -0
- package/dist/docket/memory.d.ts.map +1 -0
- package/dist/docket/memory.js +528 -0
- package/dist/docket/memory.js.map +1 -0
- package/dist/docket/store.d.ts +387 -0
- package/dist/docket/store.d.ts.map +1 -0
- package/dist/docket/store.js +51 -0
- package/dist/docket/store.js.map +1 -0
- package/dist/errors.d.ts +153 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +164 -0
- package/dist/errors.js.map +1 -0
- package/dist/gate/coverage.d.ts +152 -0
- package/dist/gate/coverage.d.ts.map +1 -0
- package/dist/gate/coverage.js +114 -0
- package/dist/gate/coverage.js.map +1 -0
- package/dist/gate/decide.d.ts +207 -0
- package/dist/gate/decide.d.ts.map +1 -0
- package/dist/gate/decide.js +559 -0
- package/dist/gate/decide.js.map +1 -0
- package/dist/gate/gate.d.ts +212 -0
- package/dist/gate/gate.d.ts.map +1 -0
- package/dist/gate/gate.js +175 -0
- package/dist/gate/gate.js.map +1 -0
- package/dist/gate/pipeline.d.ts +285 -0
- package/dist/gate/pipeline.d.ts.map +1 -0
- package/dist/gate/pipeline.js +515 -0
- package/dist/gate/pipeline.js.map +1 -0
- package/dist/gate/policy.d.ts +272 -0
- package/dist/gate/policy.d.ts.map +1 -0
- package/dist/gate/policy.js +396 -0
- package/dist/gate/policy.js.map +1 -0
- package/dist/gate/wrap.d.ts +107 -0
- package/dist/gate/wrap.d.ts.map +1 -0
- package/dist/gate/wrap.js +164 -0
- package/dist/gate/wrap.js.map +1 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +111 -0
- package/dist/index.js.map +1 -0
- package/dist/model/affidavit.d.ts +354 -0
- package/dist/model/affidavit.d.ts.map +1 -0
- package/dist/model/affidavit.js +417 -0
- package/dist/model/affidavit.js.map +1 -0
- package/dist/model/amendments.d.ts +160 -0
- package/dist/model/amendments.d.ts.map +1 -0
- package/dist/model/amendments.js +183 -0
- package/dist/model/amendments.js.map +1 -0
- package/dist/model/canonical.d.ts +311 -0
- package/dist/model/canonical.d.ts.map +1 -0
- package/dist/model/canonical.js +665 -0
- package/dist/model/canonical.js.map +1 -0
- package/dist/model/money.d.ts +127 -0
- package/dist/model/money.d.ts.map +1 -0
- package/dist/model/money.js +177 -0
- package/dist/model/money.js.map +1 -0
- package/dist/model/provenance.d.ts +315 -0
- package/dist/model/provenance.d.ts.map +1 -0
- package/dist/model/provenance.js +223 -0
- package/dist/model/provenance.js.map +1 -0
- package/dist/ports.d.ts +269 -0
- package/dist/ports.d.ts.map +1 -0
- package/dist/ports.js +34 -0
- package/dist/ports.js.map +1 -0
- package/dist/store-memory.d.ts +21 -0
- package/dist/store-memory.d.ts.map +1 -0
- package/dist/store-memory.js +20 -0
- package/dist/store-memory.js.map +1 -0
- package/dist/telemetry-keys.d.ts +65 -0
- package/dist/telemetry-keys.d.ts.map +1 -0
- package/dist/telemetry-keys.js +72 -0
- package/dist/telemetry-keys.js.map +1 -0
- package/dist/telemetry.d.ts +77 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +43 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/testing.d.ts +574 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +1291 -0
- package/dist/testing.js.map +1 -0
- package/package.json +75 -0
- package/telemetry-keys.json +92 -0
package/dist/errors.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error the gate throws, and the closed set of codes it throws with.
|
|
3
|
+
*
|
|
4
|
+
* **Rules served: CV-1** (hard-fail at wire-up; there is no disable switch),
|
|
5
|
+
* **AZ-4** (a requirement the implementation does not run is refused, never
|
|
6
|
+
* silently degraded).
|
|
7
|
+
*
|
|
8
|
+
* CV-1 in one sentence: *a misconfiguration the framework can detect fails at
|
|
9
|
+
* wire-up with a stated error, and no option turns the gate off for a tool it
|
|
10
|
+
* covers.* Every such failure is an {@link AffiantError} carrying one
|
|
11
|
+
* {@link ErrorCode} — a machine-readable reason a host can branch on and a
|
|
12
|
+
* conformance fixture can assert, rather than a message string that drifts.
|
|
13
|
+
*
|
|
14
|
+
* The codes are a closed union on purpose. A refusal that cannot be named here is
|
|
15
|
+
* a refusal no fixture can pin, and a host that cannot distinguish "you wired this
|
|
16
|
+
* wrong" from "the reviewer rejected it" will paper over the first.
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Every reason the gate refuses, keyed by itself.
|
|
22
|
+
*
|
|
23
|
+
* The key and the value are the same string because the string *is* the API: it is
|
|
24
|
+
* what a fixture asserts and what a host branches on, so there is exactly one
|
|
25
|
+
* spelling to remember. Read a code as `ErrorCode["substance-refused"]`, or write
|
|
26
|
+
* the literal — both type-check.
|
|
27
|
+
*
|
|
28
|
+
* Three codes are marked **provisional**: they are named by the v0.1 design but the
|
|
29
|
+
* protocol rulebook does not yet carry an `ErrorCode` registry. When it does, those
|
|
30
|
+
* three are the ones that may be renamed to match it; the other seven describe
|
|
31
|
+
* behaviour the rulebook already fixes.
|
|
32
|
+
*/
|
|
33
|
+
export const ErrorCode = {
|
|
34
|
+
/**
|
|
35
|
+
* A requirement this implementation recognises but does not run — a `MultiParty`
|
|
36
|
+
* approval, a `ReferralRequired` referral — reached the pipeline. The entry is
|
|
37
|
+
* filed `pending` and marked blocked; every decision on it is refused. Never
|
|
38
|
+
* degraded to a weaker requirement (AZ-4).
|
|
39
|
+
*
|
|
40
|
+
* **Provisional** until the protocol's `ErrorCode` registry lands.
|
|
41
|
+
*/
|
|
42
|
+
"requirement-not-implemented": "requirement-not-implemented",
|
|
43
|
+
/**
|
|
44
|
+
* A tool the gate must cover cannot be intercepted — it is write-capable with no
|
|
45
|
+
* `execute` to replace, it is executed by the model provider, or it is a hosted
|
|
46
|
+
* MCP tool. Raised at wire-up (CV-4, CV-1), or carried on a proposal from a tool
|
|
47
|
+
* the host explicitly declared uncovered.
|
|
48
|
+
*
|
|
49
|
+
* **Provisional** until the protocol's `ErrorCode` registry lands.
|
|
50
|
+
*/
|
|
51
|
+
"coverage-refused": "coverage-refused",
|
|
52
|
+
/**
|
|
53
|
+
* A proposal reached the substance gate with nothing to swear to: no field
|
|
54
|
+
* carrying provenance other than `Empty`, or a non-empty value sitting under
|
|
55
|
+
* `Empty` provenance. Refused before anything is filed (GT-3).
|
|
56
|
+
*/
|
|
57
|
+
"substance-refused": "substance-refused",
|
|
58
|
+
/**
|
|
59
|
+
* A decision was refused on identity grounds: the context carried no resolved
|
|
60
|
+
* principal, the entry belongs to another tenant, or the host's authorization
|
|
61
|
+
* port said no. Refused before the store is touched (AZ-2).
|
|
62
|
+
*/
|
|
63
|
+
"decision-unauthorized": "decision-unauthorized",
|
|
64
|
+
/** A decision was made on an entry that is no longer `pending` (DK-1). */
|
|
65
|
+
"decision-not-pending": "decision-not-pending",
|
|
66
|
+
/**
|
|
67
|
+
* A decision was made on an entry that has passed its expiry. The entry reads
|
|
68
|
+
* `expired` whether or not the host's sweep has run; the decision's amendments
|
|
69
|
+
* are preserved on the row for a resubmission (DK-1).
|
|
70
|
+
*/
|
|
71
|
+
"decision-expired": "decision-expired",
|
|
72
|
+
/**
|
|
73
|
+
* Two decisions raced for the same entry and this one lost the compare-and-set.
|
|
74
|
+
* A transition is applied once or not at all, never twice (DK-1).
|
|
75
|
+
*/
|
|
76
|
+
"decision-lost-race": "decision-lost-race",
|
|
77
|
+
/**
|
|
78
|
+
* The gate was built wrong in a way it can detect: no store, no authorization
|
|
79
|
+
* port, a policy declaring a risk threshold with no scorer to compare against.
|
|
80
|
+
* Thrown from `createGate`, not on the first request (CV-1).
|
|
81
|
+
*/
|
|
82
|
+
"wireup-invalid": "wireup-invalid",
|
|
83
|
+
/** No entry with that id is visible in the given scope (DK-1). */
|
|
84
|
+
"entry-not-found": "entry-not-found",
|
|
85
|
+
/**
|
|
86
|
+
* An execution outcome was reported against a row that already carries one. The
|
|
87
|
+
* first report stands and the row is untouched: a decision, once recorded, is
|
|
88
|
+
* never edited in place, and an approved-but-failed write must stay
|
|
89
|
+
* distinguishable from an approved-and-committed one (DK-4, DK-1).
|
|
90
|
+
*
|
|
91
|
+
* A host that retries a write reports **once**, when it knows the outcome (AZ-5:
|
|
92
|
+
* an outbox is a retry of an already-attested write, not a second authorization
|
|
93
|
+
* path, and not a second fact about what happened).
|
|
94
|
+
*
|
|
95
|
+
* **Provisional** until the protocol's `ErrorCode` registry lands.
|
|
96
|
+
*/
|
|
97
|
+
"execution-already-recorded": "execution-already-recorded",
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Every {@link ErrorCode} value, in registry order. Pinned as data so a runtime
|
|
101
|
+
* check and a fixture can use the same list the type does.
|
|
102
|
+
*
|
|
103
|
+
* **The order only ever grows at the end.** A code is added by appending it, never
|
|
104
|
+
* by inserting one among the codes that already shipped: the list is what a host's
|
|
105
|
+
* exhaustiveness check and a parity manifest read, and a reordering would look like
|
|
106
|
+
* a rename to both.
|
|
107
|
+
*/
|
|
108
|
+
export const ERROR_CODES = [
|
|
109
|
+
"requirement-not-implemented",
|
|
110
|
+
"coverage-refused",
|
|
111
|
+
"substance-refused",
|
|
112
|
+
"decision-unauthorized",
|
|
113
|
+
"decision-not-pending",
|
|
114
|
+
"decision-expired",
|
|
115
|
+
"decision-lost-race",
|
|
116
|
+
"wireup-invalid",
|
|
117
|
+
"entry-not-found",
|
|
118
|
+
"execution-already-recorded",
|
|
119
|
+
];
|
|
120
|
+
/** Whether `value` is one of the codes in {@link ErrorCode}. */
|
|
121
|
+
export function isErrorCode(value) {
|
|
122
|
+
return typeof value === "string" && ERROR_CODES.includes(value);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The error every refusal in this package throws.
|
|
126
|
+
*
|
|
127
|
+
* The `code` is the contract; the `message` is for a human reading a log. A host
|
|
128
|
+
* that branches on the message is doing it wrong, and a fixture that asserts on
|
|
129
|
+
* the message is asserting on prose.
|
|
130
|
+
*/
|
|
131
|
+
export class AffiantError extends Error {
|
|
132
|
+
/** Why the gate refused. */
|
|
133
|
+
code;
|
|
134
|
+
/** Structured context for the refusal. `{}` when the throwing site supplied none. */
|
|
135
|
+
details;
|
|
136
|
+
/**
|
|
137
|
+
* @param code Why the gate refused.
|
|
138
|
+
* @param message A human-readable explanation. Defaults to the code itself.
|
|
139
|
+
* @param details Structured context for the refusal.
|
|
140
|
+
*/
|
|
141
|
+
constructor(code, message, details) {
|
|
142
|
+
super(message ?? code);
|
|
143
|
+
this.name = "AffiantError";
|
|
144
|
+
this.code = code;
|
|
145
|
+
this.details = details ?? {};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Whether `value` is an {@link AffiantError}.
|
|
150
|
+
*
|
|
151
|
+
* `instanceof` first, then a structural check: a host can end up with two copies of
|
|
152
|
+
* this package in one process (a bundler, two versions in a dependency tree), and a
|
|
153
|
+
* `catch` that spans that boundary still has to give a true answer. The structural
|
|
154
|
+
* arm is deliberately narrow — an `Error` named `AffiantError` carrying a `code`
|
|
155
|
+
* from {@link ERROR_CODES}.
|
|
156
|
+
*/
|
|
157
|
+
export function isAffiantError(value) {
|
|
158
|
+
if (value instanceof AffiantError)
|
|
159
|
+
return true;
|
|
160
|
+
return (value instanceof Error &&
|
|
161
|
+
value.name === "AffiantError" &&
|
|
162
|
+
isErrorCode(value.code));
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB;;;;;;;OAOG;IACH,6BAA6B,EAAE,6BAA6B;IAC5D;;;;;;;OAOG;IACH,kBAAkB,EAAE,kBAAkB;IACtC;;;;OAIG;IACH,mBAAmB,EAAE,mBAAmB;IACxC;;;;OAIG;IACH,uBAAuB,EAAE,uBAAuB;IAChD,0EAA0E;IAC1E,sBAAsB,EAAE,sBAAsB;IAC9C;;;;OAIG;IACH,kBAAkB,EAAE,kBAAkB;IACtC;;;OAGG;IACH,oBAAoB,EAAE,oBAAoB;IAC1C;;;;OAIG;IACH,gBAAgB,EAAE,gBAAgB;IAClC,kEAAkE;IAClE,iBAAiB,EAAE,iBAAiB;IACpC;;;;;;;;;;;OAWG;IACH,4BAA4B,EAAE,4BAA4B;CAClD,CAAC;AAKX;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,6BAA6B;IAC7B,kBAAkB;IAClB,mBAAmB;IACnB,uBAAuB;IACvB,sBAAsB;IACtB,kBAAkB;IAClB,oBAAoB;IACpB,gBAAgB;IAChB,iBAAiB;IACjB,4BAA4B;CACW,CAAC;AAE1C,gEAAgE;AAChE,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,WAAiC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzF,CAAC;AAcD;;;;;;GAMG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,4BAA4B;IACnB,IAAI,CAAY;IACzB,qFAAqF;IAC5E,OAAO,CAAsB;IAEtC;;;;OAIG;IACH,YAAY,IAAe,EAAE,OAAgB,EAAE,OAA6B;QAC1E,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,EAAE,CAAC;IAC/B,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,YAAY,YAAY;QAAE,OAAO,IAAI,CAAC;IAC/C,OAAO,CACL,KAAK,YAAY,KAAK;QACtB,KAAK,CAAC,IAAI,KAAK,cAAc;QAC7B,WAAW,CAAE,KAAqC,CAAC,IAAI,CAAC,CACzD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage: which tools the gate can actually intercept, and what happens to the
|
|
3
|
+
* ones it cannot.
|
|
4
|
+
*
|
|
5
|
+
* **Rules served: CV-4** (a write-capable tool in an uncovered category is refused at
|
|
6
|
+
* wire-up, or — where wire-up cannot see it — marked `blocked` with code
|
|
7
|
+
* `coverage-refused` on the Docket; it is never silently allowed to write),
|
|
8
|
+
* **CV-1** (the refusal is at wire-up and there is no option that turns the gate off
|
|
9
|
+
* for a tool it covers), **AZ-4** (the blocked marker and its codes).
|
|
10
|
+
*
|
|
11
|
+
* ## The three categories
|
|
12
|
+
*
|
|
13
|
+
* A tool is *uncovered* when there is nothing for the gate to replace:
|
|
14
|
+
*
|
|
15
|
+
* - `"no-execute"` — the tool declares no `execute`. There is no function to stand in
|
|
16
|
+
* front of; whatever writes, writes somewhere this package cannot see.
|
|
17
|
+
* - `"provider-executed"` — the model provider runs the tool on its own side. The
|
|
18
|
+
* call never reaches this process.
|
|
19
|
+
* - `"hosted-mcp"` — the tool is a hosted MCP server's, and the write happens
|
|
20
|
+
* server-side, past the boundary an adapter can bind at.
|
|
21
|
+
*
|
|
22
|
+
* The first one that matches is the category reported; a tool with no `execute` that
|
|
23
|
+
* is *also* provider-executed reads as `"no-execute"`, because that is the fact a
|
|
24
|
+
* host can check without knowing anything about the provider.
|
|
25
|
+
*
|
|
26
|
+
* ## Why a declaration, and why it does not make the tool safe
|
|
27
|
+
*
|
|
28
|
+
* A host that knows it cannot cover a tool may declare it. The declaration does not
|
|
29
|
+
* grant the tool anything: it converts a **wire-up refusal** into a **Docket record**
|
|
30
|
+
* — every proposal from that tool is filed `pending` with `blocked: { code:
|
|
31
|
+
* "coverage-refused", category, toolName }`, which AZ-4 says can never be decided and
|
|
32
|
+
* never executes. So the choice a host makes is not "refuse or allow"; it is "refuse
|
|
33
|
+
* at start-up, or refuse per proposal with the proposal on the record". The second is
|
|
34
|
+
* the more useful of the two when the host wants a person to see what an uncovered
|
|
35
|
+
* tool tried to do.
|
|
36
|
+
*
|
|
37
|
+
* What no declaration can do is cover a tool that opens its own connection and writes
|
|
38
|
+
* inside its body. GT-6 states that boundary as an honest limit rather than a rule an
|
|
39
|
+
* implementation can enforce.
|
|
40
|
+
*
|
|
41
|
+
* @packageDocumentation
|
|
42
|
+
*/
|
|
43
|
+
import type { TurnContext } from "../context.js";
|
|
44
|
+
import type { BlockedMarker } from "../docket/entry.js";
|
|
45
|
+
import type { FieldSchema, Operation } from "../ports.js";
|
|
46
|
+
/**
|
|
47
|
+
* A tool as the host declares it, in the shape the gate needs to decide two things:
|
|
48
|
+
* whether it can be intercepted, and how its arguments become a proposal.
|
|
49
|
+
*
|
|
50
|
+
* The type parameters default to `never` and `unknown`, which makes the bare
|
|
51
|
+
* `ToolDefinition` a supertype of every concrete one — so
|
|
52
|
+
* {@link assessCoverage} and {@link declareUncovered} take any tool without a cast.
|
|
53
|
+
*
|
|
54
|
+
* `operation` is what a **write-capable** tool must supply: the function that turns a
|
|
55
|
+
* call's arguments into the {@link Operation} the Affidavit is sworn over. It is the
|
|
56
|
+
* host's declaration of what the tool is about to write, and it is required at
|
|
57
|
+
* wire-up rather than inferred, because nothing else in this package can know that
|
|
58
|
+
* `{ id, status }` is an update of a `Ticket` and not a search over one.
|
|
59
|
+
*
|
|
60
|
+
* `execute` takes the turn context as its second argument. A read tool is called
|
|
61
|
+
* with the same explicit context the gate was given (GT-2); nothing is ambient, at
|
|
62
|
+
* any depth of the call path. A write tool's `execute` is **never called by the
|
|
63
|
+
* gate** (GT-6) — see `wrap.ts`.
|
|
64
|
+
*/
|
|
65
|
+
export interface ToolDefinition<TArgs = never, TResult = unknown> {
|
|
66
|
+
/** The tool's name, as the model sees it and as a Docket row records it. */
|
|
67
|
+
readonly name: string;
|
|
68
|
+
/** What the tool does, in one line. */
|
|
69
|
+
readonly description: string;
|
|
70
|
+
/** The fields a proposal from this tool can carry, for the inference step. */
|
|
71
|
+
readonly inputSchema: FieldSchema;
|
|
72
|
+
/** The host's implementation. Called for a read tool; never for a write tool (GT-6). */
|
|
73
|
+
readonly execute?: (args: TArgs, ctx: TurnContext) => Promise<TResult> | TResult;
|
|
74
|
+
/** Whether a call to this tool would write. */
|
|
75
|
+
readonly writeCapable: boolean;
|
|
76
|
+
/** Who runs it. `"provider"` means the call never reaches this process. */
|
|
77
|
+
readonly executedBy?: "host" | "provider";
|
|
78
|
+
/** Whether the tool is a hosted MCP server's, writing server-side. */
|
|
79
|
+
readonly hostedMcp?: boolean;
|
|
80
|
+
/** How a call's arguments declare the write being proposed. Required for a write tool. */
|
|
81
|
+
readonly operation?: (args: TArgs) => Operation;
|
|
82
|
+
/** The host's own verb for the operation, e.g. `"WriteUpdate"`, carried onto the card. */
|
|
83
|
+
readonly operationLabel?: string;
|
|
84
|
+
}
|
|
85
|
+
/** Why a tool cannot be intercepted (CV-4). */
|
|
86
|
+
export type UncoveredCategory = "no-execute" | "provider-executed" | "hosted-mcp";
|
|
87
|
+
/** Every {@link UncoveredCategory}, in the order {@link assessCoverage} checks them. */
|
|
88
|
+
export declare const UNCOVERED_CATEGORIES: readonly ["no-execute", "provider-executed", "hosted-mcp"];
|
|
89
|
+
/** Whether `value` is one of the categories in {@link UNCOVERED_CATEGORIES}. */
|
|
90
|
+
export declare function isUncoveredCategory(value: unknown): value is UncoveredCategory;
|
|
91
|
+
/** What {@link assessCoverage} found. */
|
|
92
|
+
export type CoverageAssessment = {
|
|
93
|
+
readonly covered: true;
|
|
94
|
+
} | {
|
|
95
|
+
readonly covered: false;
|
|
96
|
+
readonly category: UncoveredCategory;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Whether the gate can intercept `tool`, and if not, which category it falls in.
|
|
100
|
+
*
|
|
101
|
+
* This asks only about **interception**, not about whether the tool matters: a read
|
|
102
|
+
* tool with no `execute` is reported uncovered too, because it is. What turns an
|
|
103
|
+
* uncovered assessment into a refusal is CV-4's other half —
|
|
104
|
+
* {@link ToolDefinition.writeCapable} — and that check lives in `wrap.ts`, where the
|
|
105
|
+
* refusal is raised.
|
|
106
|
+
*/
|
|
107
|
+
export declare function assessCoverage(tool: ToolDefinition): CoverageAssessment;
|
|
108
|
+
/** One tool a host has declared it cannot cover. */
|
|
109
|
+
export interface UncoveredDeclaration {
|
|
110
|
+
/** The tool's name — the key a later proposal is matched by. */
|
|
111
|
+
readonly toolName: string;
|
|
112
|
+
/** Why it cannot be covered. */
|
|
113
|
+
readonly category: UncoveredCategory;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The set of uncovered declarations a gate holds.
|
|
117
|
+
*
|
|
118
|
+
* One registry per gate, created by `createGate` and closed over by the pipeline —
|
|
119
|
+
* never a module-level map. Two gates in one process (two tenants' wirings, a test
|
|
120
|
+
* and the thing it tests) share nothing, which is the same reason GT-2 gives for the
|
|
121
|
+
* turn context: state that is reachable without being passed is state two callers
|
|
122
|
+
* can collide in.
|
|
123
|
+
*/
|
|
124
|
+
export interface CoverageRegistry {
|
|
125
|
+
/**
|
|
126
|
+
* Record that `toolName` cannot be covered.
|
|
127
|
+
*
|
|
128
|
+
* @throws RangeError if the tool was already declared under a different category —
|
|
129
|
+
* a host contradicting itself about what a tool is, which is a programming
|
|
130
|
+
* error rather than a refusal.
|
|
131
|
+
*/
|
|
132
|
+
declare(toolName: string, category: UncoveredCategory): void;
|
|
133
|
+
/** The category `toolName` was declared under, or `null` if it was not declared. */
|
|
134
|
+
lookup(toolName: string): UncoveredCategory | null;
|
|
135
|
+
/** Every declaration, in the order they were made. */
|
|
136
|
+
declarations(): readonly UncoveredDeclaration[];
|
|
137
|
+
}
|
|
138
|
+
/** A fresh, empty {@link CoverageRegistry}. */
|
|
139
|
+
export declare function createCoverageRegistry(): CoverageRegistry;
|
|
140
|
+
/**
|
|
141
|
+
* Declare `tool` uncovered in `registry`, so that a later proposal from it is filed
|
|
142
|
+
* `blocked` rather than refused at wire-up (CV-4).
|
|
143
|
+
*
|
|
144
|
+
* The first parameter is the registry rather than the gate: `gate.ts` builds the
|
|
145
|
+
* registry and hands it to both `wrap` and the pipeline, and a function that took the
|
|
146
|
+
* gate would make this module import the module that imports it. `Gate.declareUncovered`
|
|
147
|
+
* is the surface a host calls; this is what it calls.
|
|
148
|
+
*/
|
|
149
|
+
export declare function declareUncovered(registry: CoverageRegistry, tool: Pick<ToolDefinition, "name">, category: UncoveredCategory): void;
|
|
150
|
+
/** The AZ-4 marker a proposal from a declared-uncovered tool is filed with (CV-4). */
|
|
151
|
+
export declare function coverageRefusedMarker(toolName: string, category: UncoveredCategory): BlockedMarker;
|
|
152
|
+
//# sourceMappingURL=coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/gate/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAM1D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,cAAc,CAAC,KAAK,GAAG,KAAK,EAAE,OAAO,GAAG,OAAO;IAC9D,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,wFAAwF;IACxF,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACjF,+CAA+C;IAC/C,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC1C,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,0FAA0F;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC;IAChD,0FAA0F;IAC1F,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAMD,+CAA+C;AAC/C,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,mBAAmB,GAAG,YAAY,CAAC;AAElF,wFAAwF;AACxF,eAAO,MAAM,oBAAoB,4DAIgB,CAAC;AAElD,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAE9E;AAED,yCAAyC;AACzC,MAAM,MAAM,kBAAkB,GAC5B;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAEjG;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,kBAAkB,CAKvE;AAMD,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;CACtC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC7D,oFAAoF;IACpF,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAAC;IACnD,sDAAsD;IACtD,YAAY,IAAI,SAAS,oBAAoB,EAAE,CAAC;CACjD;AAED,+CAA+C;AAC/C,wBAAgB,sBAAsB,IAAI,gBAAgB,CA6BzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,EAClC,QAAQ,EAAE,iBAAiB,GAC1B,IAAI,CAEN;AAED,sFAAsF;AACtF,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,iBAAiB,GAC1B,aAAa,CAEf"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage: which tools the gate can actually intercept, and what happens to the
|
|
3
|
+
* ones it cannot.
|
|
4
|
+
*
|
|
5
|
+
* **Rules served: CV-4** (a write-capable tool in an uncovered category is refused at
|
|
6
|
+
* wire-up, or — where wire-up cannot see it — marked `blocked` with code
|
|
7
|
+
* `coverage-refused` on the Docket; it is never silently allowed to write),
|
|
8
|
+
* **CV-1** (the refusal is at wire-up and there is no option that turns the gate off
|
|
9
|
+
* for a tool it covers), **AZ-4** (the blocked marker and its codes).
|
|
10
|
+
*
|
|
11
|
+
* ## The three categories
|
|
12
|
+
*
|
|
13
|
+
* A tool is *uncovered* when there is nothing for the gate to replace:
|
|
14
|
+
*
|
|
15
|
+
* - `"no-execute"` — the tool declares no `execute`. There is no function to stand in
|
|
16
|
+
* front of; whatever writes, writes somewhere this package cannot see.
|
|
17
|
+
* - `"provider-executed"` — the model provider runs the tool on its own side. The
|
|
18
|
+
* call never reaches this process.
|
|
19
|
+
* - `"hosted-mcp"` — the tool is a hosted MCP server's, and the write happens
|
|
20
|
+
* server-side, past the boundary an adapter can bind at.
|
|
21
|
+
*
|
|
22
|
+
* The first one that matches is the category reported; a tool with no `execute` that
|
|
23
|
+
* is *also* provider-executed reads as `"no-execute"`, because that is the fact a
|
|
24
|
+
* host can check without knowing anything about the provider.
|
|
25
|
+
*
|
|
26
|
+
* ## Why a declaration, and why it does not make the tool safe
|
|
27
|
+
*
|
|
28
|
+
* A host that knows it cannot cover a tool may declare it. The declaration does not
|
|
29
|
+
* grant the tool anything: it converts a **wire-up refusal** into a **Docket record**
|
|
30
|
+
* — every proposal from that tool is filed `pending` with `blocked: { code:
|
|
31
|
+
* "coverage-refused", category, toolName }`, which AZ-4 says can never be decided and
|
|
32
|
+
* never executes. So the choice a host makes is not "refuse or allow"; it is "refuse
|
|
33
|
+
* at start-up, or refuse per proposal with the proposal on the record". The second is
|
|
34
|
+
* the more useful of the two when the host wants a person to see what an uncovered
|
|
35
|
+
* tool tried to do.
|
|
36
|
+
*
|
|
37
|
+
* What no declaration can do is cover a tool that opens its own connection and writes
|
|
38
|
+
* inside its body. GT-6 states that boundary as an honest limit rather than a rule an
|
|
39
|
+
* implementation can enforce.
|
|
40
|
+
*
|
|
41
|
+
* @packageDocumentation
|
|
42
|
+
*/
|
|
43
|
+
/** Every {@link UncoveredCategory}, in the order {@link assessCoverage} checks them. */
|
|
44
|
+
export const UNCOVERED_CATEGORIES = [
|
|
45
|
+
"no-execute",
|
|
46
|
+
"provider-executed",
|
|
47
|
+
"hosted-mcp",
|
|
48
|
+
];
|
|
49
|
+
/** Whether `value` is one of the categories in {@link UNCOVERED_CATEGORIES}. */
|
|
50
|
+
export function isUncoveredCategory(value) {
|
|
51
|
+
return typeof value === "string" && UNCOVERED_CATEGORIES.includes(value);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether the gate can intercept `tool`, and if not, which category it falls in.
|
|
55
|
+
*
|
|
56
|
+
* This asks only about **interception**, not about whether the tool matters: a read
|
|
57
|
+
* tool with no `execute` is reported uncovered too, because it is. What turns an
|
|
58
|
+
* uncovered assessment into a refusal is CV-4's other half —
|
|
59
|
+
* {@link ToolDefinition.writeCapable} — and that check lives in `wrap.ts`, where the
|
|
60
|
+
* refusal is raised.
|
|
61
|
+
*/
|
|
62
|
+
export function assessCoverage(tool) {
|
|
63
|
+
if (typeof tool.execute !== "function")
|
|
64
|
+
return { covered: false, category: "no-execute" };
|
|
65
|
+
if (tool.executedBy === "provider")
|
|
66
|
+
return { covered: false, category: "provider-executed" };
|
|
67
|
+
if (tool.hostedMcp === true)
|
|
68
|
+
return { covered: false, category: "hosted-mcp" };
|
|
69
|
+
return { covered: true };
|
|
70
|
+
}
|
|
71
|
+
/** A fresh, empty {@link CoverageRegistry}. */
|
|
72
|
+
export function createCoverageRegistry() {
|
|
73
|
+
const declared = new Map();
|
|
74
|
+
return {
|
|
75
|
+
declare(toolName, category) {
|
|
76
|
+
if (toolName.trim() === "") {
|
|
77
|
+
throw new RangeError("CV-4: an uncovered declaration names a tool; the name was blank");
|
|
78
|
+
}
|
|
79
|
+
if (!isUncoveredCategory(category)) {
|
|
80
|
+
throw new RangeError(`CV-4: ${JSON.stringify(category)} is not an uncovered category; the three are ` +
|
|
81
|
+
`${UNCOVERED_CATEGORIES.join(", ")}`);
|
|
82
|
+
}
|
|
83
|
+
const existing = declared.get(toolName);
|
|
84
|
+
if (existing !== undefined && existing !== category) {
|
|
85
|
+
throw new RangeError(`CV-4: tool ${JSON.stringify(toolName)} is already declared uncovered as ` +
|
|
86
|
+
`${JSON.stringify(existing)}; it cannot also be ${JSON.stringify(category)}`);
|
|
87
|
+
}
|
|
88
|
+
declared.set(toolName, category);
|
|
89
|
+
},
|
|
90
|
+
lookup(toolName) {
|
|
91
|
+
return declared.get(toolName) ?? null;
|
|
92
|
+
},
|
|
93
|
+
declarations() {
|
|
94
|
+
return [...declared].map(([toolName, category]) => ({ toolName, category }));
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Declare `tool` uncovered in `registry`, so that a later proposal from it is filed
|
|
100
|
+
* `blocked` rather than refused at wire-up (CV-4).
|
|
101
|
+
*
|
|
102
|
+
* The first parameter is the registry rather than the gate: `gate.ts` builds the
|
|
103
|
+
* registry and hands it to both `wrap` and the pipeline, and a function that took the
|
|
104
|
+
* gate would make this module import the module that imports it. `Gate.declareUncovered`
|
|
105
|
+
* is the surface a host calls; this is what it calls.
|
|
106
|
+
*/
|
|
107
|
+
export function declareUncovered(registry, tool, category) {
|
|
108
|
+
registry.declare(tool.name, category);
|
|
109
|
+
}
|
|
110
|
+
/** The AZ-4 marker a proposal from a declared-uncovered tool is filed with (CV-4). */
|
|
111
|
+
export function coverageRefusedMarker(toolName, category) {
|
|
112
|
+
return { code: "coverage-refused", category, toolName };
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/gate/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAyDH,wFAAwF;AACxF,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,YAAY;IACZ,mBAAmB;IACnB,YAAY;CACmC,CAAC;AAElD,gFAAgF;AAChF,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,oBAA0C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClG,CAAC;AAMD;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,IAAoB;IACjD,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,UAAU;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;IAC1F,IAAI,IAAI,CAAC,UAAU,KAAK,UAAU;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAC7F,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;IAC/E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAsCD,+CAA+C;AAC/C,MAAM,UAAU,sBAAsB;IACpC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA6B,CAAC;IACtD,OAAO;QACL,OAAO,CAAC,QAAgB,EAAE,QAA2B;YACnD,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBAC3B,MAAM,IAAI,UAAU,CAAC,iEAAiE,CAAC,CAAC;YAC1F,CAAC;YACD,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,UAAU,CAClB,SAAS,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,+CAA+C;oBAC9E,GAAG,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACvC,CAAC;YACJ,CAAC;YACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACxC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACpD,MAAM,IAAI,UAAU,CAClB,cAAc,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,oCAAoC;oBACxE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,uBAAuB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAC/E,CAAC;YACJ,CAAC;YACD,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACnC,CAAC;QACD,MAAM,CAAC,QAAgB;YACrB,OAAO,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC;QACxC,CAAC;QACD,YAAY;YACV,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC/E,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAA0B,EAC1B,IAAkC,EAClC,QAA2B;IAE3B,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACxC,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,qBAAqB,CACnC,QAAgB,EAChB,QAA2B;IAE3B,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decisions: who may make one, what it attests to, what it does to the row, and how
|
|
3
|
+
* a refused one is resubmitted.
|
|
4
|
+
*
|
|
5
|
+
* **Rules served: AZ-1** (every decided row carries an attestation record naming who
|
|
6
|
+
* agreed, when, and to which entry), **AZ-2** (tenant-scoped, fail-closed decision
|
|
7
|
+
* authorization, checked by the framework before any transition), **AZ-3** (what
|
|
8
|
+
* identity may attest what — a machine caller may never attest `member`), **AZ-4** (a
|
|
9
|
+
* blocked entry refuses every decision), **AZ-5** (the Docket is the sole record of
|
|
10
|
+
* approval authority: the only way to `executed` is a report against an approved,
|
|
11
|
+
* attested row), **AZ-6** (nothing here is conditional on a port, a model or a
|
|
12
|
+
* transport being available, so there is no degraded path that skips a check),
|
|
13
|
+
* **AZ-7** (the gate never performs the write — there is no executor port and no
|
|
14
|
+
* method that calls one), **DK-1** (the guarded compare-and-set, expiry as state with
|
|
15
|
+
* the late decision's amendments preserved, the execution outcome, resubmission
|
|
16
|
+
* lineage), **DK-2** (`null` clears, absent leaves untouched), **DK-5** (rehydration
|
|
17
|
+
* order), **AF-4** (an accepted amendment recomputes the three numbers), **PV-2** (an
|
|
18
|
+
* amended field's tag carries a `reviewer-act` binding naming the decision).
|
|
19
|
+
*
|
|
20
|
+
* ## The order the checks run in, and why it is this order
|
|
21
|
+
*
|
|
22
|
+
* 1. **No resolved principal → refused, before the store is touched.** AZ-2's
|
|
23
|
+
* "fail closed, never *identity unknown, allow*" is not only about the answer; it
|
|
24
|
+
* is about not doing any work on an entry for a caller who has not been
|
|
25
|
+
* identified. A read that happened before the refusal is a read an attacker can
|
|
26
|
+
* time.
|
|
27
|
+
* 2. **The entry is fetched inside the caller's tenant, and the row that comes back
|
|
28
|
+
* is checked against it.** A row in another tenant is reported as
|
|
29
|
+
* `entry-not-found`, exactly as an id that never existed — never "not authorized
|
|
30
|
+
* for that tenant", which would be an oracle for guessing ids. The scope goes to
|
|
31
|
+
* the store *and* the row's own `tenantId` is compared here, because AZ-2 makes
|
|
32
|
+
* this the framework's check: a host store with a scope bug would otherwise fail
|
|
33
|
+
* open with nothing above it noticing.
|
|
34
|
+
* 3. **The host's authorization port.** Its answer is the host's own — role,
|
|
35
|
+
* ownership, separation of duties. A port that throws is a refusal.
|
|
36
|
+
* 4. **What the entry reads *now*.** Expiry is state (DK-1): a row past its deadline
|
|
37
|
+
* reads `expired` whether or not the host's sweep has run, and a decision on it is
|
|
38
|
+
* refused with the amendments it carried preserved for a resubmission — provided
|
|
39
|
+
* the caller could have attested at all, because those values come back as a
|
|
40
|
+
* person's act on the resubmission (PV-3).
|
|
41
|
+
* 5. **The attestation.** Built from the principal, and *only* from the principal:
|
|
42
|
+
* there is no parameter through which a caller can name whose signature this is.
|
|
43
|
+
* 6. **The transition**, a guarded compare-and-set that is applied once or not at
|
|
44
|
+
* all.
|
|
45
|
+
*
|
|
46
|
+
* ## Why there is no `execute`
|
|
47
|
+
*
|
|
48
|
+
* AZ-7 says no package in an implementation writes to a host's store, and the gate
|
|
49
|
+
* has no executor port to call. The path to `execution: "executed"` is
|
|
50
|
+
* {@link markExecuted} — the host's own executor telling the Docket what happened.
|
|
51
|
+
* That is also why a `service` principal is admitted there and refused as a decider
|
|
52
|
+
* here: reporting an outcome is a statement of fact about work the host did, and a
|
|
53
|
+
* decision is an act of authority a machine may not perform on a person's behalf
|
|
54
|
+
* (AZ-3).
|
|
55
|
+
*
|
|
56
|
+
* @packageDocumentation
|
|
57
|
+
*/
|
|
58
|
+
import type { Principal, TurnContext } from "../context.js";
|
|
59
|
+
import type { Attestor, DocketEntry, ExecutionOutcome } from "../docket/entry.js";
|
|
60
|
+
import type { SessionStore } from "../docket/store.js";
|
|
61
|
+
import type { AmendmentMap } from "../model/amendments.js";
|
|
62
|
+
import type { AuthorizationPort } from "../ports.js";
|
|
63
|
+
import type { FiledEntry, PipelineDeps } from "./pipeline.js";
|
|
64
|
+
/**
|
|
65
|
+
* What a reviewer decided.
|
|
66
|
+
*
|
|
67
|
+
* Amending is not a third kind: it is an approval carrying an
|
|
68
|
+
* {@link AmendmentMap} (DK-2). Modelling it as a separate verb would let an
|
|
69
|
+
* implementation record an amendment that approved nothing, and a reviewer who
|
|
70
|
+
* corrects a field has, by correcting it, said what they are prepared to approve.
|
|
71
|
+
*
|
|
72
|
+
* A rejection requires a reason and an approval does not, for the asymmetric reason
|
|
73
|
+
* that a person who declines a machine's proposal is the only one who knows why,
|
|
74
|
+
* whereas an approval's reason is the Affidavit.
|
|
75
|
+
*/
|
|
76
|
+
export type Decision = {
|
|
77
|
+
/** Approve the write, as proposed or as amended. */
|
|
78
|
+
readonly kind: "approve";
|
|
79
|
+
/** The reviewer's corrections. `null` under a key clears the field; an absent key leaves it untouched (DK-2). */
|
|
80
|
+
readonly amendments?: AmendmentMap;
|
|
81
|
+
/** Why, for the record. Optional: the Affidavit is the reason. */
|
|
82
|
+
readonly reason?: string;
|
|
83
|
+
} | {
|
|
84
|
+
/** Refuse the write. */
|
|
85
|
+
readonly kind: "reject";
|
|
86
|
+
/** Why. Required — a refusal nobody explained teaches the host nothing. */
|
|
87
|
+
readonly reason: string;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* What a host's executor reports back: the write happened, or it did not.
|
|
91
|
+
*
|
|
92
|
+
* `"unexecuted"` is excluded because it is the state a row is *filed* in, never a
|
|
93
|
+
* report — an executor that has nothing to say says nothing.
|
|
94
|
+
*/
|
|
95
|
+
export type ExecutionReport = Exclude<ExecutionOutcome, "unexecuted">;
|
|
96
|
+
/** Everything the decision path needs from the host, assembled once by `createGate`. */
|
|
97
|
+
export interface DecideDeps extends PipelineDeps {
|
|
98
|
+
/** Who may decide an entry (AZ-2). The gate has already refused the cases this port must not be asked about. */
|
|
99
|
+
readonly authorization: AuthorizationPort;
|
|
100
|
+
/** The rehydration surface (DK-5), or `null` when the host supplied none. */
|
|
101
|
+
readonly sessions: SessionStore | null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Approve, amend or reject the entry `entryId` names, as `ctx.principal` (DK-1,
|
|
105
|
+
* AZ-1, AZ-2, AZ-3).
|
|
106
|
+
*
|
|
107
|
+
* @returns The row as it stands after the transition.
|
|
108
|
+
* @throws AffiantError `"decision-unauthorized"` when the context carries no
|
|
109
|
+
* resolved principal, when the host's authorization port says no, or when a
|
|
110
|
+
* machine caller with nothing to relay tries to attest (AZ-2, AZ-3);
|
|
111
|
+
* `"entry-not-found"` when no entry with that id is visible in the caller's
|
|
112
|
+
* tenant; `"decision-expired"` when the entry has passed its deadline — the
|
|
113
|
+
* decision's amendments are preserved on the row first (DK-1);
|
|
114
|
+
* `"decision-not-pending"` when it has already been decided or is blocked
|
|
115
|
+
* (AZ-4); `"decision-lost-race"` when a competing decision won the guarded
|
|
116
|
+
* compare-and-set.
|
|
117
|
+
* @throws RangeError when an amendment map holds `undefined` under a key, or names a
|
|
118
|
+
* field the Affidavit does not propose. Neither is a refusal the gate hands
|
|
119
|
+
* back to a caller: they are caller programming errors, and an `AffiantError`
|
|
120
|
+
* code would invite a host to catch and continue.
|
|
121
|
+
*/
|
|
122
|
+
export declare function decide(entryId: string, decision: Decision, ctx: TurnContext, deps: DecideDeps): Promise<DocketEntry>;
|
|
123
|
+
/**
|
|
124
|
+
* Record what the host's executor did with an approved write (DK-1, AZ-5, AZ-7).
|
|
125
|
+
*
|
|
126
|
+
* `status` stays `approved` — the approval happened and is not undone by a failed
|
|
127
|
+
* write; only `execution` and `executionDetail` move. This is the **only** public
|
|
128
|
+
* path to `execution: "executed"`, and the gate never calls an executor to reach it:
|
|
129
|
+
* a host reads its approved-and-unexecuted rows (DK-5), does the write itself, and
|
|
130
|
+
* says what happened.
|
|
131
|
+
*
|
|
132
|
+
* A `service` principal is admitted here and refused as a decider (AZ-3). The
|
|
133
|
+
* asymmetry is the point: reporting an outcome is a statement of fact about work the
|
|
134
|
+
* host performed, which a machine is the right party to make, while a decision is an
|
|
135
|
+
* act of authority that a machine may never make in a person's name. The tenant check
|
|
136
|
+
* and the host's authorization port still apply, so "which service may report on this
|
|
137
|
+
* entry" is still the host's answer and not an open door.
|
|
138
|
+
*
|
|
139
|
+
* **Reported once.** The execution transition is a guarded compare-and-set out of
|
|
140
|
+
* `"unexecuted"`, like every other transition on a row (DK-1): a second report is
|
|
141
|
+
* refused and the first stands. Overwriting would let an approved-and-committed row
|
|
142
|
+
* later read `failed` — an edit in place of a recorded fact (DK-4), and the loss of
|
|
143
|
+
* exactly the distinction DK-1 requires the row to keep. So a host that retries a
|
|
144
|
+
* write reports **once**, when it knows the outcome; the retries are the host's
|
|
145
|
+
* business (AZ-5), the outcome is the Docket's.
|
|
146
|
+
*
|
|
147
|
+
* @throws AffiantError `"decision-unauthorized"`, `"entry-not-found"`,
|
|
148
|
+
* `"decision-not-pending"` when the row is not `approved`, or
|
|
149
|
+
* `"execution-already-recorded"` when it already carries an outcome.
|
|
150
|
+
*/
|
|
151
|
+
export declare function markExecuted(entryId: string, outcome: ExecutionReport, detail: string | null, ctx: TurnContext, deps: DecideDeps): Promise<DocketEntry>;
|
|
152
|
+
/**
|
|
153
|
+
* File the expired entry `entryId` names again, as a **new** entry whose lineage
|
|
154
|
+
* points back at it (DK-1).
|
|
155
|
+
*
|
|
156
|
+
* A resubmission is never a reopening. The superseded entry keeps its terminal state
|
|
157
|
+
* and gains a successor link, so the history reads forward (DK-4) and nothing that
|
|
158
|
+
* was once decided is quietly edited. The new proposal carries the same operation and
|
|
159
|
+
* the same sworn fields, with the amendments the refused late decision left on the
|
|
160
|
+
* row prefilled as values — each prefilled field tagged `UserStated` with a
|
|
161
|
+
* `reviewer-act` binding naming the superseded entry (PV-2), so a reader of the new
|
|
162
|
+
* card can see which values came from a person's earlier correction rather than from
|
|
163
|
+
* the machine.
|
|
164
|
+
*
|
|
165
|
+
* The whole pipeline runs again: the policy chain gets another say, the deadline is
|
|
166
|
+
* stamped afresh from that verdict (GT-4), and the substance gate applies (GT-3). A
|
|
167
|
+
* resubmission is a new proposal, not a replay of an old approval.
|
|
168
|
+
*
|
|
169
|
+
* @throws AffiantError `"decision-unauthorized"`, `"entry-not-found"`, or
|
|
170
|
+
* `"decision-not-pending"` when the entry does not read `expired`.
|
|
171
|
+
* @throws RangeError when the preserved amendment map names a field the Affidavit
|
|
172
|
+
* does not propose.
|
|
173
|
+
*/
|
|
174
|
+
export declare function resubmit(entryId: string, ctx: TurnContext, deps: DecideDeps): Promise<FiledEntry>;
|
|
175
|
+
/** A human-verified session's attestation. The only kind a `member` principal makes. */
|
|
176
|
+
export type MemberAttestation = Extract<Attestor, {
|
|
177
|
+
kind: "member";
|
|
178
|
+
}>;
|
|
179
|
+
/** A person's decision carried by a relay. The strongest kind a `service` principal can make. */
|
|
180
|
+
export type RelayAttestation = Extract<Attestor, {
|
|
181
|
+
kind: "member-via-relay";
|
|
182
|
+
}>;
|
|
183
|
+
/**
|
|
184
|
+
* The strongest attestation `principal` can honestly make, or `null` when it can
|
|
185
|
+
* make none (AZ-1, AZ-3).
|
|
186
|
+
*
|
|
187
|
+
* A `member` principal attests `member`. A `service` principal carrying both a relay
|
|
188
|
+
* assertion and the person it speaks for attests `member-via-relay`, naming both — and
|
|
189
|
+
* that is the *only* thing it can attest. A `service` principal with nothing to relay
|
|
190
|
+
* attests nothing: it is a machine acting on its own behalf, and a machine cannot
|
|
191
|
+
* agree to a write in a person's name.
|
|
192
|
+
*
|
|
193
|
+
* **AZ-3 is in the overloads, not only in the body.** The signature for a `service`
|
|
194
|
+
* principal cannot return a `member` attestation — a compiler rejects the shortcut
|
|
195
|
+
* before a reviewer has to notice it — and this is the only function in the package
|
|
196
|
+
* that builds an attestation for a decision, so there is no second path to check.
|
|
197
|
+
* `standing-order` is not reachable from here at all: a policy verdict writes that
|
|
198
|
+
* one, in the same operation as the filing (AZ-1, GT-1 step 9).
|
|
199
|
+
*/
|
|
200
|
+
export declare function attestorOf(principal: Extract<Principal, {
|
|
201
|
+
kind: "member";
|
|
202
|
+
}>): MemberAttestation;
|
|
203
|
+
export declare function attestorOf(principal: Extract<Principal, {
|
|
204
|
+
kind: "service";
|
|
205
|
+
}>): RelayAttestation | null;
|
|
206
|
+
export declare function attestorOf(principal: Principal): Attestor | null;
|
|
207
|
+
//# sourceMappingURL=decide.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decide.d.ts","sourceRoot":"","sources":["../../src/gate/decide.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,KAAK,EAAe,QAAQ,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE/F,OAAO,KAAK,EAAS,YAAY,EAAmB,MAAM,oBAAoB,CAAC;AAG/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAI3D,OAAO,KAAK,EAAE,iBAAiB,EAAa,MAAM,aAAa,CAAC;AAEhE,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAmC,MAAM,eAAe,CAAC;AAO/F;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,QAAQ,GAChB;IACE,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,iHAAiH;IACjH,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC;IACnC,kEAAkE;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,GACD;IACE,wBAAwB;IACxB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAEN;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC;AAEtE,wFAAwF;AACxF,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC9C,gHAAgH;IAChH,QAAQ,CAAC,aAAa,EAAE,iBAAiB,CAAC;IAC1C,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;CACxC;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,MAAM,CAC1B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,WAAW,CAAC,CAuItB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,WAAW,CAAC,CAgDtB;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,UAAU,CAAC,CA0BrB;AA6MD,wFAAwF;AACxF,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,QAAQ,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC;AAEtE,iGAAiG;AACjG,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,QAAQ,EAAE;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,CAAC,CAAC;AAE/E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,GAAG,iBAAiB,CAAC;AACjG,wBAAgB,UAAU,CACxB,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,GACjD,gBAAgB,GAAG,IAAI,CAAC;AAC3B,wBAAgB,UAAU,CAAC,SAAS,EAAE,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC"}
|