@outcrawl/sdk 0.1.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/README.md +198 -0
- package/dist/index.js +2493 -0
- package/dist/types/_deps/core/agent-alias.d.ts +219 -0
- package/dist/types/_deps/core/brand.d.ts +62 -0
- package/dist/types/_deps/core/certificate.d.ts +100 -0
- package/dist/types/_deps/core/cron.d.ts +116 -0
- package/dist/types/_deps/core/errors.d.ts +340 -0
- package/dist/types/_deps/core/index.d.ts +17 -0
- package/dist/types/_deps/core/money.d.ts +108 -0
- package/dist/types/_deps/core/registry.d.ts +466 -0
- package/dist/types/_deps/core/rules.d.ts +237 -0
- package/dist/types/_deps/core/secrets.d.ts +278 -0
- package/dist/types/_deps/core/types.d.ts +1677 -0
- package/dist/types/_deps/integrations/connector.d.ts +153 -0
- package/dist/types/_deps/replay/events.d.ts +1004 -0
- package/dist/types/agent.d.ts +313 -0
- package/dist/types/availability.d.ts +86 -0
- package/dist/types/browser.d.ts +107 -0
- package/dist/types/client.d.ts +128 -0
- package/dist/types/hands.d.ts +259 -0
- package/dist/types/index.d.ts +111 -0
- package/dist/types/integrations.d.ts +63 -0
- package/dist/types/monitors.d.ts +24 -0
- package/dist/types/page.d.ts +96 -0
- package/dist/types/profiles.d.ts +26 -0
- package/dist/types/result.d.ts +90 -0
- package/dist/types/rules.d.ts +41 -0
- package/dist/types/scrape.d.ts +68 -0
- package/dist/types/secrets.d.ts +36 -0
- package/dist/types/sessions.d.ts +124 -0
- package/dist/types/transport.d.ts +250 -0
- package/package.json +70 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rules: natural language in, enforceable gates out.
|
|
3
|
+
*
|
|
4
|
+
* A customer forbids things in their own words — "never spend over £200",
|
|
5
|
+
* "only use amazon.co.uk", "ask me before you buy anything" — and this file
|
|
6
|
+
* decides, per rule, whether we can actually STOP it or can only ask a model
|
|
7
|
+
* nicely. That split is the product:
|
|
8
|
+
*
|
|
9
|
+
* ENFORCED a gate `@outcrawl/runtime` evaluates before a dispatch reaches
|
|
10
|
+
* the page. A tripped gate refuses the action. It is not advice.
|
|
11
|
+
* GUIDANCE the sentence, rendered into the prompt, where a model under
|
|
12
|
+
* pressure may weigh it against everything else it was told.
|
|
13
|
+
*
|
|
14
|
+
* **A rule the customer believes is enforced but which is only advice is the
|
|
15
|
+
* worst possible outcome here**, so the class of every rule is returned to them
|
|
16
|
+
* on the same call that sets it, with the gate's canonical parameters beside
|
|
17
|
+
* it. Nothing about the classification is implicit and nothing is a surprise
|
|
18
|
+
* discovered from a bill.
|
|
19
|
+
*
|
|
20
|
+
* ── WHY A PARSER AND NOT A MODEL ─────────────────────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* Extracting the gate with a model at `rules.set` time is the obvious approach
|
|
23
|
+
* and it is the wrong one, for three reasons that are all about the same thing:
|
|
24
|
+
*
|
|
25
|
+
* 1. REPRODUCIBILITY. A gate that decides whether a £200 payment goes through
|
|
26
|
+
* has to return the same verdict for the same sentence every time. A model
|
|
27
|
+
* can reclassify ENFORCED to GUIDANCE between two `rules.get` calls, on the
|
|
28
|
+
* same text, and the customer would have no signal that their protection had
|
|
29
|
+
* evaporated. That is precisely the failure this file exists to prevent.
|
|
30
|
+
* 2. NO SECOND STEERING SURFACE. Rule text is customer-authored and a model
|
|
31
|
+
* extractor is one more place a sentence can talk our behaviour into
|
|
32
|
+
* something. A parser has none.
|
|
33
|
+
* 3. MEASURABLE COVERAGE. Every phrasing claimed here is a row in
|
|
34
|
+
* `core/test/rules.test.ts`, and so is every phrasing deliberately refused.
|
|
35
|
+
* A model extractor's coverage cannot be stated per release.
|
|
36
|
+
*
|
|
37
|
+
* ── HOW A MIS-EXTRACTION IS MADE VISIBLE ─────────────────────────────────────
|
|
38
|
+
*
|
|
39
|
+
* The parser is TOTAL: every rule gets a class and nothing throws. There are
|
|
40
|
+
* three outcomes, and the third is the whole anti-silent-failure device.
|
|
41
|
+
*
|
|
42
|
+
* ENFORCED, gate present — the canonical parameters go back to the
|
|
43
|
+
* customer, so an extraction that read the wrong
|
|
44
|
+
* number, the wrong currency or the wrong host is
|
|
45
|
+
* READABLE rather than discovered in production.
|
|
46
|
+
* GUIDANCE, no warning — nothing enforceable in the sentence. Correct
|
|
47
|
+
* and quiet: "be polite" is guidance and there is
|
|
48
|
+
* nothing to warn about.
|
|
49
|
+
* GUIDANCE, `unenforceable` — the sentence LOOKS enforceable and did not
|
|
50
|
+
* compile. A money amount with no currency, a
|
|
51
|
+
* host with no allow/deny sense, a limit that is
|
|
52
|
+
* a floor rather than a ceiling, or two
|
|
53
|
+
* independent constraints in one sentence.
|
|
54
|
+
*
|
|
55
|
+
* The two failure modes therefore land in opposite, both-visible directions. An
|
|
56
|
+
* over-eager extraction that would block legitimate work is visible because the
|
|
57
|
+
* parameters are on screen. An extraction that silently failed to bind is
|
|
58
|
+
* visible because {@link CompiledRule.unenforceable} says so, in the response to
|
|
59
|
+
* the very call that accepted the rule.
|
|
60
|
+
*
|
|
61
|
+
* ── ONE GATE PER RULE ────────────────────────────────────────────────────────
|
|
62
|
+
*
|
|
63
|
+
* A rule compiles to at most one gate. Two independent constraints in one
|
|
64
|
+
* sentence — "never spend over £200 on ebay.com" — is a CONJUNCTION, and
|
|
65
|
+
* neither gate alone is the rule the customer wrote: a bare ceiling would apply
|
|
66
|
+
* it to every site, a bare host deny would forbid a £5 order. So that rule is
|
|
67
|
+
* refused with a warning telling them to split it, which is honest, rather than
|
|
68
|
+
* enforced approximately, which is not. See {@link compileRule}.
|
|
69
|
+
*/
|
|
70
|
+
import type { ValidationResult } from './types.js';
|
|
71
|
+
export declare const RULE_CLASSES: readonly ["enforced", "guidance"];
|
|
72
|
+
export type RuleClass = (typeof RULE_CLASSES)[number];
|
|
73
|
+
/**
|
|
74
|
+
* Currencies a ceiling can be stated in.
|
|
75
|
+
*
|
|
76
|
+
* Short on purpose. A ceiling is only enforceable against an amount read off a
|
|
77
|
+
* page, and there is no FX rate anywhere in this system, so a currency we
|
|
78
|
+
* cannot recognise in BOTH places buys nothing. `$` is read as `USD`; it is
|
|
79
|
+
* also CAD and AUD and a customer who means those writes the code.
|
|
80
|
+
*/
|
|
81
|
+
export declare const RULE_CURRENCIES: readonly ["GBP", "USD", "EUR", "JPY"];
|
|
82
|
+
export type RuleCurrency = (typeof RULE_CURRENCIES)[number];
|
|
83
|
+
/**
|
|
84
|
+
* The classes of irreversible act a confirmation rule can name.
|
|
85
|
+
*
|
|
86
|
+
* These are `IrreversibleKind` from `@outcrawl/runtime` minus `suspected`,
|
|
87
|
+
* which is a verdict the classifier produces and never something a customer
|
|
88
|
+
* asks for by name. Declared here because core cannot import the runtime — the
|
|
89
|
+
* dependency runs the other way — and `runtime/src/rules.ts` is checked against
|
|
90
|
+
* this list at compile time.
|
|
91
|
+
*/
|
|
92
|
+
export declare const RULE_IRREVERSIBLE_CLASSES: readonly ["payment", "order", "message", "account", "handoff"];
|
|
93
|
+
export type RuleIrreversibleClass = (typeof RULE_IRREVERSIBLE_CLASSES)[number];
|
|
94
|
+
/**
|
|
95
|
+
* What a compiled rule can actually stop.
|
|
96
|
+
*
|
|
97
|
+
* Three arms, and they are the three the owner scoped: a spend ceiling, a
|
|
98
|
+
* domain allow/deny, and a confirmation requirement on irreversible acts.
|
|
99
|
+
* Everything else a customer can write is guidance. A discriminated union
|
|
100
|
+
* rather than an options bag, so a fourth class added later is a compile error
|
|
101
|
+
* at every evaluation site until somebody handles it.
|
|
102
|
+
*/
|
|
103
|
+
export type RuleGate = {
|
|
104
|
+
readonly kind: 'spend_ceiling';
|
|
105
|
+
readonly currency: RuleCurrency;
|
|
106
|
+
/** Exact decimal, two places. Never a float; this number gates money. */
|
|
107
|
+
readonly amount: string;
|
|
108
|
+
} | {
|
|
109
|
+
readonly kind: 'domain';
|
|
110
|
+
readonly mode: 'allow' | 'deny';
|
|
111
|
+
/** Registrable hosts, lowercased. A host matches itself and its subdomains. */
|
|
112
|
+
readonly hosts: readonly string[];
|
|
113
|
+
} | {
|
|
114
|
+
readonly kind: 'confirm_irreversible';
|
|
115
|
+
readonly classes: readonly RuleIrreversibleClass[];
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* One customer rule, classified.
|
|
119
|
+
*
|
|
120
|
+
* `gate` and `unenforceable` are REQUIRED AND NULLABLE, on the doctrine
|
|
121
|
+
* `registry.ts` states at length for `mcpTool`: "we decided this rule has no
|
|
122
|
+
* gate" and "nobody said" must not be the same bytes. A guidance rule writes
|
|
123
|
+
* `gate: null` and a rule nobody classified does not compile.
|
|
124
|
+
*/
|
|
125
|
+
export interface CompiledRule {
|
|
126
|
+
/**
|
|
127
|
+
* The customer's sentence, VERBATIM. Never a normalised or reworded form:
|
|
128
|
+
* this is what they typed, it is what `rules.get` shows them, and a rule they
|
|
129
|
+
* cannot recognise is a rule they cannot audit.
|
|
130
|
+
*/
|
|
131
|
+
readonly text: string;
|
|
132
|
+
readonly class: RuleClass;
|
|
133
|
+
/** The gate, or `null` for a guidance rule. */
|
|
134
|
+
readonly gate: RuleGate | null;
|
|
135
|
+
/**
|
|
136
|
+
* Why a rule that LOOKED enforceable is only guidance, or `null`.
|
|
137
|
+
*
|
|
138
|
+
* Present only on guidance rules, and its presence is the signal a customer
|
|
139
|
+
* acts on: it means we suspect they think this is enforced and it is not.
|
|
140
|
+
*/
|
|
141
|
+
readonly unenforceable: string | null;
|
|
142
|
+
}
|
|
143
|
+
export type RuleSet = readonly CompiledRule[];
|
|
144
|
+
/**
|
|
145
|
+
* Ceilings on the rule LIST, not on any one gate.
|
|
146
|
+
*
|
|
147
|
+
* Rules are rendered into every prompt of every step of the run, so an
|
|
148
|
+
* unbounded list is an unbounded per-step token cost the customer did not
|
|
149
|
+
* choose to pay. Thirty-two rules of 240 characters is about 2k tokens, which
|
|
150
|
+
* is affordable against a page.
|
|
151
|
+
*/
|
|
152
|
+
export declare const MAX_RULES = 32;
|
|
153
|
+
export declare const MAX_RULE_LENGTH = 240;
|
|
154
|
+
/** One monetary amount, currency resolved and value exact to two places. */
|
|
155
|
+
export interface RuleAmount {
|
|
156
|
+
readonly currency: RuleCurrency;
|
|
157
|
+
readonly amount: string;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Every currency-bearing amount in the text, in order.
|
|
161
|
+
*
|
|
162
|
+
* Exported because `runtime/src/rules.ts` reads amounts off a PAGE with the
|
|
163
|
+
* same reader. One reader and not two: a rule's "£200" and a page's "£200.00"
|
|
164
|
+
* have to parse to the same number or the comparison is theatre, and two
|
|
165
|
+
* implementations of that is how they come to differ by a factor of a hundred.
|
|
166
|
+
*/
|
|
167
|
+
export declare function readAmounts(text: string): readonly RuleAmount[];
|
|
168
|
+
/**
|
|
169
|
+
* One rule, classified.
|
|
170
|
+
*
|
|
171
|
+
* ── PRECEDENCE, AND IT IS NOT ARBITRARY ──────────────────────────────────────
|
|
172
|
+
*
|
|
173
|
+
* A sentence can trip more than one extractor and only one gate may come out,
|
|
174
|
+
* so the order below is a claim about which reading is COMPLETE:
|
|
175
|
+
*
|
|
176
|
+
* domain over confirmation. "never buy from ebay.com" is fully enforced by
|
|
177
|
+
* refusing ebay.com; adding a confirmation gate on `order` would forbid
|
|
178
|
+
* ordering everywhere, which the customer did not say.
|
|
179
|
+
*
|
|
180
|
+
* ceiling over confirmation. "don't pay more than £200" is a quantified rule
|
|
181
|
+
* and the number is the whole point of it; a confirmation gate on `payment`
|
|
182
|
+
* would refuse a £5 payment the rule permits.
|
|
183
|
+
*
|
|
184
|
+
* ceiling AND domain is a CONJUNCTION and neither gate is the rule. "never
|
|
185
|
+
* spend over £200 on ebay.com" is refused with a warning to split it, because
|
|
186
|
+
* the ceiling alone over-enforces (every site) and the deny alone
|
|
187
|
+
* under-enforces (a £5 order is allowed by the sentence and blocked by the
|
|
188
|
+
* gate). We do not have a scoping algebra and inventing an approximate one
|
|
189
|
+
* here is exactly the "believes it is enforced" defect.
|
|
190
|
+
*/
|
|
191
|
+
export declare function compileRule(text: string): CompiledRule;
|
|
192
|
+
/** The whole list, in the order the customer wrote it. Order is theirs, not ours. */
|
|
193
|
+
export declare function compileRules(texts: readonly string[]): RuleSet;
|
|
194
|
+
export declare function enforcedRules(rules: RuleSet): readonly CompiledRule[];
|
|
195
|
+
export declare function guidanceRules(rules: RuleSet): readonly CompiledRule[];
|
|
196
|
+
/**
|
|
197
|
+
* The canonical parameters of a gate, as one line of English.
|
|
198
|
+
*
|
|
199
|
+
* This string is the customer's ONLY view of what we extracted, so it says the
|
|
200
|
+
* number, the currency and the hosts rather than the class name: "spend ceiling
|
|
201
|
+
* GBP 200.00" is auditable and "spend_ceiling" is not.
|
|
202
|
+
*/
|
|
203
|
+
export declare function describeGate(gate: RuleGate): string;
|
|
204
|
+
/**
|
|
205
|
+
* The wire form: an array of non-empty strings.
|
|
206
|
+
*
|
|
207
|
+
* `[]` is VALID and is not the same as absent. Absent on a run means "use the
|
|
208
|
+
* workspace default"; `[]` means "this run runs under no rules", which is a
|
|
209
|
+
* thing a customer is entitled to say out loud and which cannot be spelled if
|
|
210
|
+
* an empty array is rejected.
|
|
211
|
+
*/
|
|
212
|
+
export declare function validateRules(input: unknown): ValidationResult<readonly string[]>;
|
|
213
|
+
/**
|
|
214
|
+
* What `rules.get` and `rules.set` answer.
|
|
215
|
+
*
|
|
216
|
+
* Both routes answer the SAME body, and that is deliberate rather than tidy:
|
|
217
|
+
* the split has to be visible in the response to the call that SET the rules,
|
|
218
|
+
* not only on a later read somebody might not make. A customer who PUTs four
|
|
219
|
+
* rules and gets back "three enforced, one guidance and here is why" has been
|
|
220
|
+
* told at the only moment they were going to look.
|
|
221
|
+
*/
|
|
222
|
+
export interface RulesView {
|
|
223
|
+
readonly rules: readonly RulesViewRule[];
|
|
224
|
+
/** Counts, so a caller can assert the split without walking the list. */
|
|
225
|
+
readonly enforced: number;
|
|
226
|
+
readonly guidance: number;
|
|
227
|
+
}
|
|
228
|
+
export interface RulesViewRule {
|
|
229
|
+
readonly text: string;
|
|
230
|
+
readonly class: RuleClass;
|
|
231
|
+
/** What the gate does, in English. `null` for a guidance rule. */
|
|
232
|
+
readonly enforcement: string | null;
|
|
233
|
+
/** The gate's own parameters, for a caller that wants to assert on them. */
|
|
234
|
+
readonly gate: RuleGate | null;
|
|
235
|
+
readonly unenforceable: string | null;
|
|
236
|
+
}
|
|
237
|
+
export declare function rulesView(rules: RuleSet): RulesView;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The secrets store: handles, sealing, and the concealment that makes a handle
|
|
3
|
+
* the only representation a model ever holds.
|
|
4
|
+
*
|
|
5
|
+
* ## The invariant, stated once, where the fourth person will find it
|
|
6
|
+
*
|
|
7
|
+
* **A payload is addressed by HANDLE and never by value.** A secret value, an
|
|
8
|
+
* uploaded file body and a connector credential are named, never quoted; the
|
|
9
|
+
* value is resolved BELOW the model, at the moment the action reaches the thing
|
|
10
|
+
* that consumes it, and the model is never shown it. Three slices arrived at
|
|
11
|
+
* this rule independently on 2026-09-08 and it is one rule with several
|
|
12
|
+
* enforcement points, not a local convention at each of them.
|
|
13
|
+
*
|
|
14
|
+
* The corollary that decides where a payload may travel:
|
|
15
|
+
*
|
|
16
|
+
* A credential reaches the least-trusted tier ONLY when the thing on that
|
|
17
|
+
* tier is the only thing that can consume it. A form fill qualifies — the
|
|
18
|
+
* value must become keystrokes in a renderer, and the renderer is on a fleet
|
|
19
|
+
* Mac. An outbound HTTP bearer does not: the request can originate at the
|
|
20
|
+
* edge, where the credential already lives.
|
|
21
|
+
*
|
|
22
|
+
* ## Why this file has both halves of the substitution
|
|
23
|
+
*
|
|
24
|
+
* Substitution one way only is a leak, and it is not a subtle one. The AX tree
|
|
25
|
+
* is the model's view of the page and `snapshot.ts` serialises `node.value`
|
|
26
|
+
* verbatim into the prompt, so a card number typed into a text field is read
|
|
27
|
+
* back into the next prompt one step later — the model would learn the value it
|
|
28
|
+
* was never allowed to see, from the page, with nobody having made a mistake.
|
|
29
|
+
* So {@link SecretConcealer} does both: {@link SecretConcealer.reveal} on the
|
|
30
|
+
* way out to the page, {@link SecretConcealer.conceal} on everything coming
|
|
31
|
+
* back, and the concealed form is the SAME placeholder the model wrote. That
|
|
32
|
+
* last detail is what keeps `runtime/src/verify.ts` working unchanged: it
|
|
33
|
+
* compares what was typed against what reads back, and placeholder-in /
|
|
34
|
+
* placeholder-out compares equal.
|
|
35
|
+
*
|
|
36
|
+
* ## No route returns a value
|
|
37
|
+
*
|
|
38
|
+
* There is no read route for a secret value anywhere in the public API, and
|
|
39
|
+
* that absence is the property that makes the store worth having. The only
|
|
40
|
+
* reveal is `POST /internal/secret-reveal`, signed with `TICKET_SECRET`,
|
|
41
|
+
* scoped to one live run and to the handles that run's submit named.
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* What kind of thing this credential is, which decides what {@link Secret.hint}
|
|
45
|
+
* can honestly say and how {@link SecretConcealer} has to match it on a page.
|
|
46
|
+
*
|
|
47
|
+
* Two members, not a taxonomy. `card` is the one kind whose value the PAGE
|
|
48
|
+
* reformats — a checkout field turns `4111111111111111` into
|
|
49
|
+
* `4111 1111 1111 1111` — so exact-substring concealment would miss it and the
|
|
50
|
+
* number would reach the prompt. `text` covers an environment variable, a
|
|
51
|
+
* password, an API token: values a page echoes back byte for byte. A third
|
|
52
|
+
* member would have to name a third matching rule to be worth spelling.
|
|
53
|
+
*/
|
|
54
|
+
export declare const SECRET_KINDS: readonly ["card", "text"];
|
|
55
|
+
export type SecretKind = (typeof SECRET_KINDS)[number];
|
|
56
|
+
export declare function isSecretKind(value: unknown): value is SecretKind;
|
|
57
|
+
/**
|
|
58
|
+
* The name a run addresses a secret by.
|
|
59
|
+
*
|
|
60
|
+
* Customer-chosen and stable, because the whole point is that a submit can say
|
|
61
|
+
* `secrets: ['card_virtual_burner']` and mean one of several cards. Lowercase,
|
|
62
|
+
* because a handle that differs only in case from another is a handle a human
|
|
63
|
+
* grants by mistake.
|
|
64
|
+
*/
|
|
65
|
+
export declare const SECRET_HANDLE_PATTERN: RegExp;
|
|
66
|
+
/**
|
|
67
|
+
* The placeholder a model writes, and the only form of a secret that exists
|
|
68
|
+
* above the CDP boundary.
|
|
69
|
+
*
|
|
70
|
+
* Global, so `reveal` can substitute several in one `fill` — a card form is
|
|
71
|
+
* often number, expiry and CVC, and a motor program chains them.
|
|
72
|
+
*/
|
|
73
|
+
export declare const SECRET_PLACEHOLDER_PATTERN: RegExp;
|
|
74
|
+
export declare function secretPlaceholder(handle: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* Shortest value that may be stored, and it is a concealment constraint rather
|
|
77
|
+
* than a password policy.
|
|
78
|
+
*
|
|
79
|
+
* `conceal` replaces every occurrence of the value in the page's text with the
|
|
80
|
+
* placeholder. A three-character value occurs on an ordinary page by accident,
|
|
81
|
+
* so concealing it would corrupt the tree the model reads — turning the word
|
|
82
|
+
* "the" into `{{secret:x}}` across a checkout page is a worse failure than the
|
|
83
|
+
* one this file prevents. Refusing the value at CREATE is the only place the
|
|
84
|
+
* refusal can be acted on by the person who chose it. Cards are twelve digits
|
|
85
|
+
* or more, so this binds only on `text`.
|
|
86
|
+
*/
|
|
87
|
+
export declare const MIN_SECRET_VALUE_CHARS = 6;
|
|
88
|
+
/** Longest value stored. A megabyte in a form field is not a credential. */
|
|
89
|
+
export declare const MAX_SECRET_VALUE_CHARS = 4096;
|
|
90
|
+
/**
|
|
91
|
+
* A secret as every route returns it. **There is no `value` field, on purpose,
|
|
92
|
+
* and adding one is the defect this whole slice exists to prevent.**
|
|
93
|
+
*/
|
|
94
|
+
export interface Secret {
|
|
95
|
+
readonly id: string;
|
|
96
|
+
readonly handle: string;
|
|
97
|
+
readonly kind: SecretKind;
|
|
98
|
+
/**
|
|
99
|
+
* What a human needs to tell two stored cards apart, and nothing more.
|
|
100
|
+
*
|
|
101
|
+
* `'•••• 4242'` for a card; `null` for text, where any window onto the value
|
|
102
|
+
* is a window onto the value. Required-and-nullable rather than optional: "we
|
|
103
|
+
* decided this kind has no safe hint" and "nobody filled it in" must not be
|
|
104
|
+
* the same bytes, which is the rule `mcpTool` in `registry.ts` argues at
|
|
105
|
+
* length.
|
|
106
|
+
*/
|
|
107
|
+
readonly hint: string | null;
|
|
108
|
+
readonly createdAt: string;
|
|
109
|
+
}
|
|
110
|
+
/** What `secrets.create` accepts. `value` is write-only, everywhere, forever. */
|
|
111
|
+
export interface SecretCreateRequest {
|
|
112
|
+
readonly handle: string;
|
|
113
|
+
readonly kind: SecretKind;
|
|
114
|
+
readonly value: string;
|
|
115
|
+
}
|
|
116
|
+
export interface SecretQuery {
|
|
117
|
+
/** Glob over the handle, like `ProfileQuery.label`. */
|
|
118
|
+
readonly handle?: string;
|
|
119
|
+
readonly limit?: number;
|
|
120
|
+
readonly cursor?: string;
|
|
121
|
+
}
|
|
122
|
+
/** Why a value was refused before anything was written. */
|
|
123
|
+
export type SecretRefusal = 'bad-handle' | 'bad-kind' | 'too-short' | 'too-long' | 'not-a-card' | 'unknown-handle' | 'not-granted';
|
|
124
|
+
export declare class SecretRejectedError extends Error {
|
|
125
|
+
readonly refusal: SecretRefusal;
|
|
126
|
+
constructor(refusal: SecretRefusal, message: string);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check a create request and answer the hint the row will carry.
|
|
130
|
+
*
|
|
131
|
+
* Throws rather than returning a result union because every caller is a trust
|
|
132
|
+
* boundary that has to refuse: the API handler turns this into a 400 naming the
|
|
133
|
+
* field, and there is no caller for whom a bad handle is a recoverable state.
|
|
134
|
+
*/
|
|
135
|
+
export declare function validateSecretCreate(request: SecretCreateRequest): {
|
|
136
|
+
readonly hint: string | null;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Import the 32-byte account-wide key.
|
|
140
|
+
*
|
|
141
|
+
* `crypto.subtle` and not `node:crypto`, and that is the one decision in this
|
|
142
|
+
* half: the only process that decrypts is the Cloudflare Worker, which has no
|
|
143
|
+
* `node:crypto` cipher, and the tests run under node. WebCrypto is the single
|
|
144
|
+
* implementation both can execute, so there is no second code path holding the
|
|
145
|
+
* key and no "works locally" gap in the one function whose failure is silent
|
|
146
|
+
* plaintext.
|
|
147
|
+
*
|
|
148
|
+
* The key is base64 or hex, 32 bytes decoded. A short key is refused loudly
|
|
149
|
+
* here rather than producing a key WebCrypto would reject later with a message
|
|
150
|
+
* naming neither the variable nor the length.
|
|
151
|
+
*/
|
|
152
|
+
export declare function importSecretKey(material: string): Promise<CryptoKey>;
|
|
153
|
+
/**
|
|
154
|
+
* Seal a value. `accountId` and `handle` are the additional authenticated
|
|
155
|
+
* data, which is what stops a ciphertext being MOVED: a row copied to another
|
|
156
|
+
* account, or renamed to another handle, fails to open rather than decrypting
|
|
157
|
+
* to somebody else's card. Encryption alone would not catch either.
|
|
158
|
+
*/
|
|
159
|
+
export declare function sealSecret(key: CryptoKey, scope: {
|
|
160
|
+
readonly accountId: string;
|
|
161
|
+
readonly handle: string;
|
|
162
|
+
}, value: string): Promise<string>;
|
|
163
|
+
/**
|
|
164
|
+
* Open a sealed value, or throw.
|
|
165
|
+
*
|
|
166
|
+
* Never returns a partial or a placeholder on failure. A decrypt that fails is
|
|
167
|
+
* a key rotation nobody finished, a row moved between accounts, or a tampered
|
|
168
|
+
* ciphertext; answering with an empty string would type nothing into a payment
|
|
169
|
+
* form and report success.
|
|
170
|
+
*/
|
|
171
|
+
export declare function openSecret(key: CryptoKey, scope: {
|
|
172
|
+
readonly accountId: string;
|
|
173
|
+
readonly handle: string;
|
|
174
|
+
}, sealed: string): Promise<string>;
|
|
175
|
+
/**
|
|
176
|
+
* The pair of operations a store needs, over a key it never has to import
|
|
177
|
+
* itself.
|
|
178
|
+
*
|
|
179
|
+
* Exists so that the encryption key travels as ONE object with two methods
|
|
180
|
+
* rather than as key material a caller has to remember to import, and so that
|
|
181
|
+
* a deployment with no `SECRETS_KEY` is a typed `null` at the composition root
|
|
182
|
+
* instead of a route that answers 500 the first time somebody stores a card.
|
|
183
|
+
*/
|
|
184
|
+
export interface SecretCipher {
|
|
185
|
+
seal(scope: {
|
|
186
|
+
readonly accountId: string;
|
|
187
|
+
readonly handle: string;
|
|
188
|
+
}, value: string): Promise<string>;
|
|
189
|
+
open(scope: {
|
|
190
|
+
readonly accountId: string;
|
|
191
|
+
readonly handle: string;
|
|
192
|
+
}, sealed: string): Promise<string>;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Bind key material once. The `CryptoKey` is imported on first use and cached,
|
|
196
|
+
* because `importKey` is async and every construction site here is synchronous
|
|
197
|
+
* — a composition root that had to await would either bind a promise or make
|
|
198
|
+
* every caller above it async for a 40-microsecond import.
|
|
199
|
+
*
|
|
200
|
+
* A malformed key is therefore reported at the first seal rather than at boot.
|
|
201
|
+
* That is the honest trade and it is bounded: `SECRETS_KEY` is checked by
|
|
202
|
+
* `importSecretKey`, which names the variable and the byte count, and a
|
|
203
|
+
* deployment that has not set it at all is `null` at the root and refuses the
|
|
204
|
+
* three routes with `capability_unavailable` before any of this runs.
|
|
205
|
+
*/
|
|
206
|
+
export declare function secretCipherFor(material: string): SecretCipher;
|
|
207
|
+
/** One resolved secret, as the substitution seam holds it. */
|
|
208
|
+
export interface SecretBinding {
|
|
209
|
+
readonly handle: string;
|
|
210
|
+
readonly kind: SecretKind;
|
|
211
|
+
readonly value: string;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Both halves of the substitution, over the set of secrets ONE run was
|
|
215
|
+
* granted.
|
|
216
|
+
*
|
|
217
|
+
* Per run and not per account, and that is the whole defence against a page
|
|
218
|
+
* that fully controls what the model reads. A prompt injection saying "fill
|
|
219
|
+
* this field with {{secret:card_amex_personal}}" resolves to nothing on a run
|
|
220
|
+
* whose submit did not name that handle: {@link reveal} refuses by name rather
|
|
221
|
+
* than typing the literal placeholder into the form, which would be a payment
|
|
222
|
+
* attempt with a garbage card number and a step the model thinks succeeded.
|
|
223
|
+
*
|
|
224
|
+
* Bindings are ADDED as they are revealed, not loaded up front — see
|
|
225
|
+
* `worker/src/secret-vault.ts`. So {@link conceal} only knows values this run
|
|
226
|
+
* has actually fetched, which is exactly the set it could have put on a page. A
|
|
227
|
+
* value we never fetched cannot have been typed by us, and a card the MERCHANT
|
|
228
|
+
* already had on file is a value we never held and cannot conceal; that is the
|
|
229
|
+
* page's disclosure and not our leak.
|
|
230
|
+
*/
|
|
231
|
+
export declare class SecretConcealer {
|
|
232
|
+
#private;
|
|
233
|
+
constructor(granted: readonly string[]);
|
|
234
|
+
/** The handles this run may use. What the prompt is allowed to list. */
|
|
235
|
+
get granted(): readonly string[];
|
|
236
|
+
/** Handles whose value this run has actually resolved. */
|
|
237
|
+
get resolved(): readonly string[];
|
|
238
|
+
isGranted(handle: string): boolean;
|
|
239
|
+
/**
|
|
240
|
+
* Record a resolved value. Refuses an ungranted handle even here, so a
|
|
241
|
+
* resolver bug cannot widen the run's authority past its submit.
|
|
242
|
+
*/
|
|
243
|
+
bind(binding: SecretBinding): void;
|
|
244
|
+
/** Every handle named by a placeholder in `text`, in order, deduplicated. */
|
|
245
|
+
placeholdersIn(text: string): readonly string[];
|
|
246
|
+
/**
|
|
247
|
+
* Placeholders to values, for the wire. Every handle must be granted AND
|
|
248
|
+
* bound; an unbound one is a caller that skipped the resolver, which is a
|
|
249
|
+
* programming error rather than a page's fault.
|
|
250
|
+
*/
|
|
251
|
+
reveal(text: string): string;
|
|
252
|
+
/**
|
|
253
|
+
* Values to placeholders, for everything coming back: the AX tree, a step
|
|
254
|
+
* error, a log line, a replay event.
|
|
255
|
+
*
|
|
256
|
+
* Returns the input unchanged when nothing is bound, which is every run that
|
|
257
|
+
* uses no secrets — one `length` check and no allocation on the hot path.
|
|
258
|
+
*/
|
|
259
|
+
conceal(text: string): string;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The one sentence the model is told about handles, in the grammar block.
|
|
263
|
+
*
|
|
264
|
+
* One sentence and not three. Three near-identical explanations of one rule is
|
|
265
|
+
* how a model learns the rule has exceptions, so the secret, the uploaded file
|
|
266
|
+
* and the connector credential are described here together, once, and the other
|
|
267
|
+
* two seams point at this constant rather than restating it.
|
|
268
|
+
*/
|
|
269
|
+
export declare const HANDLE_GRAMMAR: string;
|
|
270
|
+
/**
|
|
271
|
+
* The grammar line for the handles one run holds, or `null` when it holds none.
|
|
272
|
+
*
|
|
273
|
+
* `null` and not an empty string: a run with no secrets pays zero prompt tokens
|
|
274
|
+
* for a facility it does not have, and — more to the point — is never told that
|
|
275
|
+
* secrets exist. A page cannot talk a model into using a mechanism it was not
|
|
276
|
+
* shown.
|
|
277
|
+
*/
|
|
278
|
+
export declare function secretGrammarFor(granted: readonly string[]): string | null;
|