@happyvertical/smrt-playbooks 0.44.0 → 0.45.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/AGENTS.md +93 -0
- package/dist/index.d.ts +282 -0
- package/dist/index.js +246 -3
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/preflight-types.d.ts +276 -0
- package/dist/preflight-types.js +0 -0
- package/dist/smrt-knowledge.json +127 -6
- package/package.json +6 -6
package/AGENTS.md
CHANGED
|
@@ -117,6 +117,78 @@ what a lower layer disabled. Enforced in `mergePlaybookLayers()` (`enabled &&
|
|
|
117
117
|
layer.enabled`) and rejected at `save()` with a specific message. Plane lists
|
|
118
118
|
narrow the same way.
|
|
119
119
|
|
|
120
|
+
## Preflight (#2590)
|
|
121
|
+
|
|
122
|
+
`preflightPlaybook({ key, plane, principal, resolve, evaluate })` resolves a
|
|
123
|
+
playbook through the **caller's own layer chain**, decomposes it, and returns a
|
|
124
|
+
per-step verdict — `allow` | `deny` | `unknown` — plus an aggregate. It executes
|
|
125
|
+
nothing.
|
|
126
|
+
|
|
127
|
+
**Advisory only. Preflight predicts; it never grants.** Every step re-enforces at
|
|
128
|
+
execution, unconditionally. Without that, a permission revoked mid-playbook would
|
|
129
|
+
leave a cached "allowed" standing — a time-of-check/time-of-use bypass. That
|
|
130
|
+
constraint is also what makes the cache free: a stale `allow` costs a
|
|
131
|
+
correctly-denied step, a stale `deny` costs a briefly hidden capability that
|
|
132
|
+
expires on the TTL, and neither is a security event.
|
|
133
|
+
|
|
134
|
+
The two planes are **not symmetric**, deliberately:
|
|
135
|
+
|
|
136
|
+
| | Server (`createServerStepEvaluator`) | Browser (`createBrowserStepEvaluator`) |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| Layers | `tool-allowlist`, `operation-permission` | `action-exposure`, `public-access`, `field-permissions`, `app-auth` |
|
|
139
|
+
| Source | `PrincipalRun.isToolAllowed` + the operation-permission predicate | `isApiActionEnabled`, `isRoutePublic`, field read-permission slugs |
|
|
140
|
+
| Intent step | `plane` — denied unless the intent declares `server` | `intent-mount` — always `unknown` |
|
|
141
|
+
| App auth | n/a (the predicate *is* the gate) | **`unknown`** — never evaluated |
|
|
142
|
+
|
|
143
|
+
Browser preflight covers the **static layers only**. Generated REST auth is
|
|
144
|
+
`authMiddleware?: (objectName, action) => (req) => Promise<Request | Response>`:
|
|
145
|
+
request-bound, `Response`-returning rather than boolean, and free to consult
|
|
146
|
+
session stores, rate-limit, or audit. It is not a dry-run predicate, so preflight
|
|
147
|
+
**never invokes it**, synthetically or otherwise — the `_preflight` route's
|
|
148
|
+
options in `smrt-core` carry no auth handle at all, only the boolean
|
|
149
|
+
`appAuthConfigured`. An optional `authPredicate` seam can later be added to
|
|
150
|
+
`BrowserPreflightLayerSource` and turn the `app-auth` `unknown` into a real
|
|
151
|
+
verdict **without changing the report contract**.
|
|
152
|
+
|
|
153
|
+
`createBrowserPlaybookPreflight()` (in `rest-preflight.ts`) is the provider wired
|
|
154
|
+
into `APIConfig.playbookPreflight`. Core owns the route and the static-layer
|
|
155
|
+
facts because `ObjectRegistry` is core's; this package owns resolution and the
|
|
156
|
+
verdict vocabulary — so the dependency stays one-way.
|
|
157
|
+
|
|
158
|
+
### Not an oracle
|
|
159
|
+
|
|
160
|
+
Every playbook the caller's chain cannot resolve — unknown key, disabled,
|
|
161
|
+
wrong-plane, unresolvable intent, or an error thrown anywhere in resolution or
|
|
162
|
+
evaluation — returns the single frozen `PLAYBOOK_PREFLIGHT_UNAVAILABLE` value: no
|
|
163
|
+
key echo, no reason, no message, and served by the route with an unconditional
|
|
164
|
+
200. An unknown key and an unauthorized key are byte-identical.
|
|
165
|
+
|
|
166
|
+
Timing is held in the same class from both ends. The unknown-key path pays the
|
|
167
|
+
same override-layer read a resolvable key pays (`equalizeUnknownKeyCost`),
|
|
168
|
+
because `resolvePlaybook()` short-circuits a registry miss before touching the
|
|
169
|
+
database; and unavailable results are cached for unknown and
|
|
170
|
+
registered-but-unavailable keys **alike**, because caching only one of them would
|
|
171
|
+
put every *repeat* probe of the other in a different timing class. Growth from
|
|
172
|
+
probing random keys is bounded where it belongs — the preflight cache is capped
|
|
173
|
+
with expiry-first eviction — not by declining to cache.
|
|
174
|
+
|
|
175
|
+
### Verdict rules worth knowing
|
|
176
|
+
|
|
177
|
+
- `deny` beats `unknown` beats `allow` (`worstVerdict`), and a step's `reason` is
|
|
178
|
+
always the first layer that produced its verdict — reason and verdict can never
|
|
179
|
+
describe different layers.
|
|
180
|
+
- Evaluation never short-circuits: preflight exists to say *which* step of five
|
|
181
|
+
would die.
|
|
182
|
+
- A missing **field** read-permission slug redacts a field, it does not fail the
|
|
183
|
+
step, so `field-permissions` stays `allow` with `reason: 'fields-redacted'` and
|
|
184
|
+
the missing slugs attached. Reporting `deny` would predict a failure that will
|
|
185
|
+
not happen.
|
|
186
|
+
- A non-public route with **no** middleware wired is a real, statically knowable
|
|
187
|
+
`deny` (the generator's fail-closed 401). With one wired it is `unknown`.
|
|
188
|
+
- A tool listing may filter on preflight (`filterPlaybooksByPreflight` in
|
|
189
|
+
`smrt-agents`), but that filter is a listing convenience and **never**
|
|
190
|
+
load-bearing for authorization.
|
|
191
|
+
|
|
120
192
|
## Caching
|
|
121
193
|
|
|
122
194
|
Resolutions are cached per `(key, tenantId, db)` with a TTL. The cache is
|
|
@@ -127,6 +199,27 @@ tenant inherits from it. Use `clearPlaybookCache()` in tests.
|
|
|
127
199
|
A monotonic per-`(db, key)` invalidation generation closes the read-racing-a-
|
|
128
200
|
write window; see the Gotchas entry below before touching `cache.ts`.
|
|
129
201
|
|
|
202
|
+
Preflight results cache separately, per `(principal, key, plane, tenant)`, with a
|
|
203
|
+
shorter TTL and **no invalidation ceremony of their own** — an entry captured
|
|
204
|
+
under an older generation of the playbook cache is dropped on read. `principal`
|
|
205
|
+
is an opaque, caller-scoped partition key: it is never echoed in a report and
|
|
206
|
+
never consulted for authority. Use `clearPlaybookPreflightCache()` in tests.
|
|
207
|
+
|
|
208
|
+
Two partitioning traps, both of which produce a cross-context read rather than a
|
|
209
|
+
stale one:
|
|
210
|
+
|
|
211
|
+
- **The tenant is resolved, not defaulted.** `preflightPlaybook()` applies the
|
|
212
|
+
same `tenantId !== undefined ? tenantId : (getTenantId() ?? null)` fallback
|
|
213
|
+
`loadPlaybookBase()` does. Scoping an omitted tenant to `null` would let two
|
|
214
|
+
ambient `withTenant()` callers with one principal share an entry — and the
|
|
215
|
+
report carries the resolved title and description.
|
|
216
|
+
- **The principal must cover every input the evaluation reads.** The REST
|
|
217
|
+
provider's default folds in `appAuthConfigured` (it decides whether
|
|
218
|
+
`public-access` / `app-auth` are verdicts or `unknown`) and distinguishes an
|
|
219
|
+
*absent* permission set (`perm:unpublished`, field layer `unknown`) from an
|
|
220
|
+
explicitly empty one (known, `allow` with redactions). A custom `principal`
|
|
221
|
+
must do the same.
|
|
222
|
+
|
|
130
223
|
## Gotchas
|
|
131
224
|
|
|
132
225
|
- **`context` carries the tenant scope.** `save()` sets
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,130 @@ import { SmrtCollection } from '@happyvertical/smrt-core';
|
|
|
5
5
|
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
6
6
|
import { SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Static layers a browser-plane step is evaluated against.
|
|
10
|
+
*
|
|
11
|
+
* Everything here is a build-time or configuration fact. The generated
|
|
12
|
+
* `authMiddleware` is deliberately absent: it is request-bound, returns a
|
|
13
|
+
* `Response` rather than a boolean, and may consult session stores, rate-limit,
|
|
14
|
+
* or audit — so preflight never invokes it, synthetically or otherwise, and
|
|
15
|
+
* `appAuthConfigured` is the only thing it is allowed to know about it.
|
|
16
|
+
*
|
|
17
|
+
* An `authPredicate` seam (option 1 in #2590) can later be added as an optional
|
|
18
|
+
* member here, turning the `app-auth` layer's `unknown` into a real verdict
|
|
19
|
+
* without changing the report contract.
|
|
20
|
+
*/
|
|
21
|
+
export declare interface BrowserPreflightLayerSource {
|
|
22
|
+
/** `isApiActionEnabled` for the referenced model operation. */
|
|
23
|
+
isActionExposed(model: string, action: string): boolean;
|
|
24
|
+
/** `isRoutePublic` for the HTTP method the action maps to. */
|
|
25
|
+
isRoutePublic(model: string, action: string): boolean;
|
|
26
|
+
/** Field-level read-permission slugs the step's model declares. */
|
|
27
|
+
requiredFieldPermissions(model: string, action: string): readonly string[];
|
|
28
|
+
/** Whether an app-level auth middleware is wired. Never invoked. */
|
|
29
|
+
appAuthConfigured: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export declare interface BrowserPreflightOptions {
|
|
33
|
+
layers: BrowserPreflightLayerSource;
|
|
34
|
+
/**
|
|
35
|
+
* The caller's published permission slugs, when the host publishes them.
|
|
36
|
+
* `null`/absent makes the field layer report `unknown` rather than guess.
|
|
37
|
+
*/
|
|
38
|
+
permissions?: Iterable<string> | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export declare interface BrowserPreflightProviderOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Resolution options shared by every request — classifier, intent registry,
|
|
44
|
+
* and any host-level runtime override. The per-request database handle and
|
|
45
|
+
* tenant come from the route.
|
|
46
|
+
*/
|
|
47
|
+
resolve?: Omit<ResolvePlaybookOptions, 'plane' | 'db'>;
|
|
48
|
+
/**
|
|
49
|
+
* Derives the cache-partitioning principal identity for a request. Opaque and
|
|
50
|
+
* never echoed; defaults to the caller's permission slugs, which is the only
|
|
51
|
+
* caller-distinguishing input the static layers actually read.
|
|
52
|
+
*/
|
|
53
|
+
principal?: (request: {
|
|
54
|
+
permissions?: Iterable<string>;
|
|
55
|
+
appAuthConfigured: boolean;
|
|
56
|
+
}) => string;
|
|
57
|
+
}
|
|
58
|
+
|
|
8
59
|
export declare function clearPlaybookCache(): void;
|
|
9
60
|
|
|
61
|
+
/** Drops every cached preflight result. Use in tests. */
|
|
62
|
+
export declare function clearPlaybookPreflightCache(): void;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Builds the provider wired into `APIConfig.playbookPreflight`.
|
|
66
|
+
*
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const api = new APIGenerator(manifest, {
|
|
69
|
+
* playbookPreflight: createBrowserPlaybookPreflight(),
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* Every unresolvable key — unknown, disabled, browser-invalid, unresolvable
|
|
74
|
+
* intent — comes back as the single uniform unavailable report, so the endpoint
|
|
75
|
+
* is not an enumeration oracle.
|
|
76
|
+
*/
|
|
77
|
+
export declare function createBrowserPlaybookPreflight(options?: BrowserPreflightProviderOptions): (request: {
|
|
78
|
+
key: string;
|
|
79
|
+
plane: 'browser';
|
|
80
|
+
permissions?: Iterable<string>;
|
|
81
|
+
appAuthConfigured: boolean;
|
|
82
|
+
db?: unknown;
|
|
83
|
+
}) => Promise<PlaybookPreflightReport>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Browser-plane evaluator: the static layers only.
|
|
87
|
+
*
|
|
88
|
+
* - `action-exposure` — `isApiActionEnabled`. A disabled action is a hard,
|
|
89
|
+
* statically knowable deny (the route answers 405).
|
|
90
|
+
* - `public-access` — `isRoutePublic`. A non-public route with **no** app auth
|
|
91
|
+
* middleware wired is a hard deny (the generator's fail-closed 401); with one
|
|
92
|
+
* wired it is `unknown`, because whether that middleware passes is exactly
|
|
93
|
+
* what preflight refuses to guess.
|
|
94
|
+
* - `field-permissions` — the model's declared field read-permission slugs
|
|
95
|
+
* against the caller's published set. Missing slugs redact fields rather than
|
|
96
|
+
* fail the step, so the verdict stays `allow` and carries
|
|
97
|
+
* `reason: 'fields-redacted'` plus the missing slugs; an unpublished
|
|
98
|
+
* permission set reports `unknown`.
|
|
99
|
+
* - `app-auth` — `unknown` whenever a middleware is wired. This is the layer an
|
|
100
|
+
* `authPredicate` seam would later fill in.
|
|
101
|
+
*
|
|
102
|
+
* An intent step never crosses the REST boundary at all, so it is reported
|
|
103
|
+
* `unknown` against `intent-mount`: whether the surface is mounted and what its
|
|
104
|
+
* staged-control review does are runtime facts of the page, not of the build.
|
|
105
|
+
*/
|
|
106
|
+
export declare function createBrowserStepEvaluator(options: BrowserPreflightOptions): PreflightStepEvaluator;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A layer source reading the generated REST surface's own static rules.
|
|
110
|
+
*
|
|
111
|
+
* A model this build does not register resolves to no object name, and every
|
|
112
|
+
* layer then fails closed: an unregistered model has no route, so a step naming
|
|
113
|
+
* one can only die.
|
|
114
|
+
*/
|
|
115
|
+
export declare function createRestPreflightLayerSource(options: {
|
|
116
|
+
appAuthConfigured: boolean;
|
|
117
|
+
}): BrowserPreflightLayerSource;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Server-plane evaluator: the intersection of the persona tool allow-list and
|
|
121
|
+
* the operation-permission predicate, per step, with nothing executed.
|
|
122
|
+
*
|
|
123
|
+
* An intent step is reported against its **declared** plane validity: an intent
|
|
124
|
+
* that does not declare `server` is denied with reason `plane`, and one that
|
|
125
|
+
* does is `unknown`, because server validity rides the #2446 command/ack bridge
|
|
126
|
+
* whose acknowledgement is not statically knowable. This repeats a check
|
|
127
|
+
* resolution already made, deliberately — the same defense-in-depth shape as
|
|
128
|
+
* `assertToolAllowed()` gating both the tool offer and its execution.
|
|
129
|
+
*/
|
|
130
|
+
export declare function createServerStepEvaluator(options: ServerPreflightOptions): PreflightStepEvaluator;
|
|
131
|
+
|
|
10
132
|
/**
|
|
11
133
|
* Registers a code-default playbook. Packages call this at import time so a
|
|
12
134
|
* bundled playbook resolves without any application registration.
|
|
@@ -21,12 +143,26 @@ export declare const FAIL_CLOSED_CLASSIFICATION: CapabilityClassification;
|
|
|
21
143
|
|
|
22
144
|
export declare function getPlaybookCacheTtlMs(): number;
|
|
23
145
|
|
|
146
|
+
/** TTL applied to every cached preflight result. */
|
|
147
|
+
export declare function getPlaybookPreflightCacheTtlMs(): number;
|
|
148
|
+
|
|
24
149
|
export declare function normalizeEditableConfig(editable?: Partial<PlaybookEditableConfig>): PlaybookEditableConfig;
|
|
25
150
|
|
|
26
151
|
/* Excluded from this release type: PACKAGE_VERSION_INITIALIZED */
|
|
27
152
|
|
|
28
153
|
export declare const PLAYBOOK_PLANES: readonly PlaybookPlane[];
|
|
29
154
|
|
|
155
|
+
/**
|
|
156
|
+
* The one response every unresolvable playbook produces.
|
|
157
|
+
*
|
|
158
|
+
* Frozen and shared so an unknown key and an unauthorized key are byte-for-byte
|
|
159
|
+
* identical: same shape, same values, no key echo, no reason code, no message.
|
|
160
|
+
* Preflight answers only for playbooks resolvable through the caller's own
|
|
161
|
+
* layer chain, and says nothing about anything else — including whether it
|
|
162
|
+
* exists.
|
|
163
|
+
*/
|
|
164
|
+
export declare const PLAYBOOK_PREFLIGHT_UNAVAILABLE: PlaybookPreflightUnavailableReport;
|
|
165
|
+
|
|
30
166
|
export declare interface PlaybookAcceptance {
|
|
31
167
|
ok: true;
|
|
32
168
|
plan: PlaybookPlan;
|
|
@@ -289,6 +425,56 @@ export declare interface PlaybookPlanStep {
|
|
|
289
425
|
classificationDeclared: boolean;
|
|
290
426
|
}
|
|
291
427
|
|
|
428
|
+
export declare interface PlaybookPreflightAvailableReport {
|
|
429
|
+
available: true;
|
|
430
|
+
/**
|
|
431
|
+
* Literal `true` on every report, as a type-level reminder: a report is a
|
|
432
|
+
* prediction, never a grant, and every step is authorized again where it
|
|
433
|
+
* executes.
|
|
434
|
+
*/
|
|
435
|
+
advisory: true;
|
|
436
|
+
key: string;
|
|
437
|
+
plane: PlaybookPlane;
|
|
438
|
+
title: string;
|
|
439
|
+
description: string;
|
|
440
|
+
verdict: PreflightVerdict;
|
|
441
|
+
steps: readonly PreflightStepReport[];
|
|
442
|
+
summary: PreflightSummary;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** A preflight result. Advisory only — see the two variants above. */
|
|
446
|
+
export declare type PlaybookPreflightReport = PlaybookPreflightAvailableReport | PlaybookPreflightUnavailableReport;
|
|
447
|
+
|
|
448
|
+
export declare interface PlaybookPreflightRequest {
|
|
449
|
+
key: string;
|
|
450
|
+
plane: PlaybookPlane;
|
|
451
|
+
/**
|
|
452
|
+
* Opaque, caller-scoped principal identity used to partition the cache. It is
|
|
453
|
+
* never echoed in the report and never consulted for authority. The cache key
|
|
454
|
+
* also folds in `resolve.tenantId`, so a principal string need not encode the
|
|
455
|
+
* tenant to stay correct.
|
|
456
|
+
*/
|
|
457
|
+
principal: string;
|
|
458
|
+
/** Options handed to `resolvePlaybook()` — the caller's own layer chain. */
|
|
459
|
+
resolve?: ResolvePlaybookOptions;
|
|
460
|
+
/** Evaluates each resolved step. */
|
|
461
|
+
evaluate: PreflightStepEvaluator;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* The single, uniform answer for every playbook the caller's own layer chain
|
|
466
|
+
* cannot resolve. An unknown key and an unauthorized key produce this exact
|
|
467
|
+
* frozen value, so preflight is not an enumeration oracle: it carries no key,
|
|
468
|
+
* no plane, and no reason to tell the two apart.
|
|
469
|
+
*/
|
|
470
|
+
export declare interface PlaybookPreflightUnavailableReport {
|
|
471
|
+
available: false;
|
|
472
|
+
advisory: true;
|
|
473
|
+
verdict: 'deny';
|
|
474
|
+
steps: readonly [];
|
|
475
|
+
summary: PreflightSummary;
|
|
476
|
+
}
|
|
477
|
+
|
|
292
478
|
/**
|
|
293
479
|
* Global process registry of code-default playbooks, keyed by namespaced key.
|
|
294
480
|
*
|
|
@@ -319,6 +505,87 @@ export declare type PlaybookResolution = PlaybookAcceptance | PlaybookRejection;
|
|
|
319
505
|
/** A playbook step. Exactly two kinds exist in v1; playbooks cannot nest. */
|
|
320
506
|
export declare type PlaybookStep = PlaybookOperationStep | PlaybookIntentStep;
|
|
321
507
|
|
|
508
|
+
/**
|
|
509
|
+
* The authority layers preflight reports on. The two planes are deliberately
|
|
510
|
+
* asymmetric and report different layers:
|
|
511
|
+
*
|
|
512
|
+
* - server plane — `tool-allowlist`, `operation-permission`, and `plane` for an
|
|
513
|
+
* intent step;
|
|
514
|
+
* - browser plane — `action-exposure`, `public-access`, `field-permissions`,
|
|
515
|
+
* `app-auth`, and `intent-mount` for an intent step.
|
|
516
|
+
*/
|
|
517
|
+
export declare type PreflightLayer = 'tool-allowlist' | 'operation-permission' | 'plane' | 'action-exposure' | 'public-access' | 'field-permissions' | 'app-auth' | 'intent-mount';
|
|
518
|
+
|
|
519
|
+
/** One authority layer's verdict for one step. */
|
|
520
|
+
export declare interface PreflightLayerReport {
|
|
521
|
+
layer: PreflightLayer;
|
|
522
|
+
verdict: PreflightVerdict;
|
|
523
|
+
reason: PreflightReason;
|
|
524
|
+
/**
|
|
525
|
+
* Permission slugs this layer found missing, when it knows them. Present only
|
|
526
|
+
* on layers that evaluate a slug set.
|
|
527
|
+
*/
|
|
528
|
+
missingPermissions?: readonly string[];
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Decomposes a resolved plan into per-step verdicts using `evaluate`.
|
|
533
|
+
*
|
|
534
|
+
* Every step is evaluated — evaluation does not short-circuit on the first
|
|
535
|
+
* denial, because the point of preflight is to show the caller *which* step of
|
|
536
|
+
* five would die, not merely that one would.
|
|
537
|
+
*/
|
|
538
|
+
export declare function preflightPlan(plan: PlaybookPlan, evaluate: PreflightStepEvaluator): Promise<PlaybookPreflightAvailableReport>;
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Preflights one playbook for one caller on one plane.
|
|
542
|
+
*
|
|
543
|
+
* Resolves through the **caller's own layer chain** (their tenant, their
|
|
544
|
+
* overrides, their plane), decomposes the plan, and evaluates each step with the
|
|
545
|
+
* plane's evaluator. Any playbook the chain cannot resolve — unknown, disabled,
|
|
546
|
+
* wrong plane, unresolvable intent — returns the single
|
|
547
|
+
* {@link PLAYBOOK_PREFLIGHT_UNAVAILABLE} value.
|
|
548
|
+
*
|
|
549
|
+
* Results are cached per `(principal, key, plane)` under the playbook cache's
|
|
550
|
+
* generation counter; see `preflight-cache.ts` for why that is safe.
|
|
551
|
+
*/
|
|
552
|
+
export declare function preflightPlaybook(request: PlaybookPreflightRequest): Promise<PlaybookPreflightReport>;
|
|
553
|
+
|
|
554
|
+
/** Why a layer reached its verdict. A stable code, never a free-form message. */
|
|
555
|
+
export declare type PreflightReason = 'ok' | 'tool-not-allowed' | 'permission-denied' | 'permission-unknown' | 'plane' | 'action-not-exposed' | 'not-public' | 'auth-required' | 'app-auth-not-evaluated' | 'app-auth-not-configured' | 'fields-redacted' | 'field-permissions-unknown' | 'intent-not-mounted' | 'intent-bridge-not-evaluated' | 'not-evaluated';
|
|
556
|
+
|
|
557
|
+
/** What a step evaluator returns; `preflightPlan` supplies `index` and `kind`. */
|
|
558
|
+
export declare interface PreflightStepEvaluation {
|
|
559
|
+
layers: readonly PreflightLayerReport[];
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** Evaluates one resolved plan step without executing any part of it. */
|
|
563
|
+
export declare type PreflightStepEvaluator = (step: PlaybookPlanStep) => PreflightStepEvaluation | Promise<PreflightStepEvaluation>;
|
|
564
|
+
|
|
565
|
+
/** A step's aggregate verdict plus the per-layer detail behind it. */
|
|
566
|
+
export declare interface PreflightStepReport {
|
|
567
|
+
index: number;
|
|
568
|
+
kind: PlaybookStep['kind'];
|
|
569
|
+
verdict: PreflightVerdict;
|
|
570
|
+
/** Reason of the first layer that produced the step's aggregate verdict. */
|
|
571
|
+
reason: PreflightReason;
|
|
572
|
+
layers: readonly PreflightLayerReport[];
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export declare interface PreflightSummary {
|
|
576
|
+
allow: number;
|
|
577
|
+
deny: number;
|
|
578
|
+
unknown: number;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Verdict for one authority layer, one step, or a whole plan.
|
|
583
|
+
*
|
|
584
|
+
* `unknown` is a first-class answer, never a rounded-down `allow`: a layer that
|
|
585
|
+
* cannot be evaluated without executing something says so.
|
|
586
|
+
*/
|
|
587
|
+
export declare type PreflightVerdict = 'allow' | 'deny' | 'unknown';
|
|
588
|
+
|
|
322
589
|
/**
|
|
323
590
|
* Resolves a playbook to a plan for a caller on a given plane.
|
|
324
591
|
*
|
|
@@ -342,4 +609,19 @@ export declare interface ResolvePlaybookOptions {
|
|
|
342
609
|
intents?: PlaybookIntentResolver;
|
|
343
610
|
}
|
|
344
611
|
|
|
612
|
+
export declare interface ServerPreflightOptions {
|
|
613
|
+
/** `PrincipalRun.isToolAllowed` for the slug that gates this step. */
|
|
614
|
+
isToolAllowed(step: PlaybookOperationStep): boolean;
|
|
615
|
+
/**
|
|
616
|
+
* The operation-permission predicate. Evaluates the catalog gate for the
|
|
617
|
+
* step's `(collection, action)` without performing the operation.
|
|
618
|
+
*/
|
|
619
|
+
checkOperationPermission(step: PlaybookOperationStep): PreflightVerdict | Promise<PreflightVerdict>;
|
|
620
|
+
/** Declared plane validity of a view intent, from the #2588 registry. */
|
|
621
|
+
intentPlanes?(id: string): readonly PlaybookPlane[] | null | undefined;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** Fail-closed combination: `deny` beats `unknown` beats `allow`. */
|
|
625
|
+
export declare function worstVerdict(left: PreflightVerdict, right: PreflightVerdict): PreflightVerdict;
|
|
626
|
+
|
|
345
627
|
export { }
|