@punica/editor 1.15.1 → 1.17.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/dist/index.bundle.esm.js +2 -2
- package/dist/index.bundle.esm.js.map +1 -1
- package/dist/index.bundle.umd.js +2 -2
- package/dist/index.bundle.umd.js.map +1 -1
- package/package.json +1 -1
- package/types/punica.module.flow.engine.d.ts +9 -0
- package/types/punica.module.kernel.ai.d.ts +17 -1
- package/types/punica.module.kernel.policy.d.ts +278 -12
package/package.json
CHANGED
|
@@ -116,6 +116,15 @@ declare module 'punica' {
|
|
|
116
116
|
risk: string;
|
|
117
117
|
approval: string;
|
|
118
118
|
reason?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Argument binding carried from the gate — see
|
|
121
|
+
* `kernel.PolicyRequest.argsBinding`. Without it the approval
|
|
122
|
+
* recorded when this pending resolves covers every argument
|
|
123
|
+
* tuple for the step's capability, not just this step's.
|
|
124
|
+
*/
|
|
125
|
+
argsBinding?: string;
|
|
126
|
+
/** Redacted view of the bound arguments, for the approval UI. */
|
|
127
|
+
argsPreview?: unknown;
|
|
119
128
|
workspaceId?: string;
|
|
120
129
|
timestampMs: number;
|
|
121
130
|
}
|
|
@@ -46,6 +46,20 @@ declare module 'punica' {
|
|
|
46
46
|
risk: string;
|
|
47
47
|
approval: string;
|
|
48
48
|
reason?: string;
|
|
49
|
+
/**
|
|
50
|
+
* The argument binding the gate computed for this call — see
|
|
51
|
+
* `kernel.PolicyRequest.argsBinding`. Carried across the round
|
|
52
|
+
* trip so `approvePending` grants for the arguments the user was
|
|
53
|
+
* shown rather than for the capability as a whole.
|
|
54
|
+
*/
|
|
55
|
+
argsBinding?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Redacted view of those arguments, so an approval prompt can
|
|
58
|
+
* show what is being bound. A user asked to allow
|
|
59
|
+
* `mcp.aws.deleteObject` without seeing the bucket is consenting
|
|
60
|
+
* to a name, not to an action.
|
|
61
|
+
*/
|
|
62
|
+
argsPreview?: unknown;
|
|
49
63
|
correlationId?: string;
|
|
50
64
|
/**
|
|
51
65
|
* Trace id of the invocation that hit the gate (an agent run id
|
|
@@ -509,7 +523,9 @@ declare module 'punica' {
|
|
|
509
523
|
* history rather than fail a run.
|
|
510
524
|
*/
|
|
511
525
|
export interface ToolUsageStore {
|
|
512
|
-
read(): Promise<
|
|
526
|
+
read(): Promise<
|
|
527
|
+
Record<string, { calls: number; ok: number; last: number }>
|
|
528
|
+
>;
|
|
513
529
|
record(capabilityId: string, ok: boolean, atMs: number): Promise<void>;
|
|
514
530
|
forget(): Promise<void>;
|
|
515
531
|
}
|
|
@@ -22,6 +22,27 @@ declare module 'punica' {
|
|
|
22
22
|
* Optional human-readable reason shown in UI prompts.
|
|
23
23
|
*/
|
|
24
24
|
reason?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Which arguments this decision is about — a digest computed by
|
|
27
|
+
* `computeArgsBinding` from the capability's REDACTED input, or the
|
|
28
|
+
* literal `'any'` for a grant deliberately made argument-blind
|
|
29
|
+
* (an unattended-client pre-authorisation).
|
|
30
|
+
*
|
|
31
|
+
* Absent means the request has no argument dimension (`llm.remote`,
|
|
32
|
+
* a flow step). Absence keys exactly as it did before bindings
|
|
33
|
+
* existed, so those kinds re-prompt nothing.
|
|
34
|
+
*
|
|
35
|
+
* The field is what stops one "always" grant on
|
|
36
|
+
* `mcp.aws.deleteObject` from authorising every bucket.
|
|
37
|
+
*/
|
|
38
|
+
argsBinding?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Redacted, possibly truncated view of the bound arguments.
|
|
41
|
+
* Carried so an approval prompt can show what is being bound and
|
|
42
|
+
* an evidence package can read it back — a digest alone means the
|
|
43
|
+
* user consented to a hash they never saw.
|
|
44
|
+
*/
|
|
45
|
+
argsPreview?: unknown;
|
|
25
46
|
}
|
|
26
47
|
|
|
27
48
|
/**
|
|
@@ -123,6 +144,15 @@ declare module 'punica' {
|
|
|
123
144
|
* record + chain state by this id.
|
|
124
145
|
*/
|
|
125
146
|
chainRef?: string;
|
|
147
|
+
/**
|
|
148
|
+
* The arguments this grant covers. See `PolicyRequest.argsBinding`.
|
|
149
|
+
* Persisted so a grant matches only the arguments it was given
|
|
150
|
+
* for, and so a reader can distinguish a bucket-specific approval
|
|
151
|
+
* from an `'any'` blanket one.
|
|
152
|
+
*/
|
|
153
|
+
argsBinding?: string;
|
|
154
|
+
/** Redacted view of the bound arguments, for display and audit. */
|
|
155
|
+
argsPreview?: unknown;
|
|
126
156
|
/**
|
|
127
157
|
* Time-bounded approvals — Sub-step 3.F. Epoch ms after which
|
|
128
158
|
* the approval is considered expired. `hasApproval` returns
|
|
@@ -145,6 +175,12 @@ declare module 'punica' {
|
|
|
145
175
|
requiresApproval: boolean;
|
|
146
176
|
suggestedScope?: ApprovalScope;
|
|
147
177
|
reason?: string;
|
|
178
|
+
/**
|
|
179
|
+
* Set when a rule from a policy template decided this. Absent
|
|
180
|
+
* means the decision came from the capability's own declaration,
|
|
181
|
+
* which is the state of every install with no `.punica/policy.yaml`.
|
|
182
|
+
*/
|
|
183
|
+
policyRule?: PolicyRuleRef;
|
|
148
184
|
}
|
|
149
185
|
|
|
150
186
|
export interface PolicyApi {
|
|
@@ -153,7 +189,32 @@ declare module 'punica' {
|
|
|
153
189
|
|
|
154
190
|
evaluate(
|
|
155
191
|
req: PolicyRequest,
|
|
156
|
-
ctx?: {
|
|
192
|
+
ctx?: {
|
|
193
|
+
workspaceId?: string;
|
|
194
|
+
/**
|
|
195
|
+
* The REDACTED arguments, for rule conditions to test. The
|
|
196
|
+
* same value `argsBinding` is computed from, so a condition
|
|
197
|
+
* can never read a secret and the decision, the binding and
|
|
198
|
+
* the audit record's `input` all agree.
|
|
199
|
+
*
|
|
200
|
+
* Used transiently for matching and never persisted — only the
|
|
201
|
+
* digest and the capped preview are.
|
|
202
|
+
*/
|
|
203
|
+
args?: unknown;
|
|
204
|
+
/**
|
|
205
|
+
* Input paths the redactor has already replaced. A condition
|
|
206
|
+
* on one of them never matches — comparing a bucket name
|
|
207
|
+
* against `'[REDACTED]'` would be governance in appearance
|
|
208
|
+
* only.
|
|
209
|
+
*/
|
|
210
|
+
redactedPaths?: ReadonlyArray<ReadonlyArray<string>>;
|
|
211
|
+
/**
|
|
212
|
+
* Every string in `args` was replaced, not selected paths —
|
|
213
|
+
* what the redactor does for a `secretBearing` capability. No
|
|
214
|
+
* condition can mean anything against that, so none apply.
|
|
215
|
+
*/
|
|
216
|
+
argsWhollyRedacted?: boolean;
|
|
217
|
+
}
|
|
157
218
|
): PolicyDecision;
|
|
158
219
|
|
|
159
220
|
requestApproval(
|
|
@@ -191,8 +252,17 @@ declare module 'punica' {
|
|
|
191
252
|
}
|
|
192
253
|
): ApprovalRecord;
|
|
193
254
|
|
|
255
|
+
/**
|
|
256
|
+
* `argsBinding` must be supplied when revoking a bound grant —
|
|
257
|
+
* omitting it targets the unbound key and silently revokes
|
|
258
|
+
* nothing, which is how a consumed "approve once" would turn into
|
|
259
|
+
* a permanent allow.
|
|
260
|
+
*/
|
|
194
261
|
revokeApproval(
|
|
195
|
-
rec: Pick<
|
|
262
|
+
rec: Pick<
|
|
263
|
+
ApprovalRecord,
|
|
264
|
+
'kind' | 'id' | 'scope' | 'workspaceId' | 'argsBinding'
|
|
265
|
+
>
|
|
196
266
|
): boolean;
|
|
197
267
|
|
|
198
268
|
/**
|
|
@@ -239,31 +309,150 @@ declare module 'punica' {
|
|
|
239
309
|
}
|
|
240
310
|
|
|
241
311
|
/**
|
|
242
|
-
*
|
|
312
|
+
* What a matching rule does to the decision.
|
|
313
|
+
*
|
|
314
|
+
* - `deny` — the call does not run, and no approval can make it
|
|
315
|
+
* run. Terminal by design: a rule that a prompt could
|
|
316
|
+
* click past is a suggestion, not a policy.
|
|
317
|
+
* - `require` — the call needs approval even if the capability
|
|
318
|
+
* declares `approval: none`.
|
|
319
|
+
* - `allow` — the call runs without a prompt. Attributed by name
|
|
320
|
+
* in the decision and in the durable audit record, so
|
|
321
|
+
* a later reader can tell "a rule allowed this" from
|
|
322
|
+
* "a human approved this".
|
|
323
|
+
*/
|
|
324
|
+
export type PolicyRuleEffect = 'allow' | 'deny' | 'require';
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Comparison operators a rule condition may use.
|
|
328
|
+
*
|
|
329
|
+
* A deliberately closed set. Every entry has an obvious equivalent
|
|
330
|
+
* in the gateway policy schemas this vocabulary is meant to be
|
|
331
|
+
* exported into (K4) — regex is absent for exactly that reason: it
|
|
332
|
+
* cannot cross into a foreign engine without either changing meaning
|
|
333
|
+
* or carrying a ReDoS hazard with it.
|
|
334
|
+
*/
|
|
335
|
+
export type PolicyConditionOp =
|
|
336
|
+
| 'eq'
|
|
337
|
+
| 'neq'
|
|
338
|
+
| 'in'
|
|
339
|
+
| 'notIn'
|
|
340
|
+
| 'startsWith'
|
|
341
|
+
| 'endsWith'
|
|
342
|
+
| 'glob'
|
|
343
|
+
| 'exists'
|
|
344
|
+
| 'absent'
|
|
345
|
+
| 'lt'
|
|
346
|
+
| 'lte'
|
|
347
|
+
| 'gt'
|
|
348
|
+
| 'gte';
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* One test against one argument path.
|
|
352
|
+
*
|
|
353
|
+
* `path` is dotted, with `*` for every element of an array
|
|
354
|
+
* (`bucket`, `target.path`, `items.*.id`) — the same grammar as the
|
|
355
|
+
* `policyKey` annotation, so there is one path syntax to learn.
|
|
356
|
+
*
|
|
357
|
+
* The value tested is the REDACTED argument, the same one the
|
|
358
|
+
* binding is computed from. A condition on a `sensitive` field
|
|
359
|
+
* therefore cannot match anything meaningful and never matches at
|
|
360
|
+
* all; the capability linter reports the declaration side of that
|
|
361
|
+
* contradiction.
|
|
362
|
+
*/
|
|
363
|
+
export interface PolicyCondition {
|
|
364
|
+
path: string;
|
|
365
|
+
op: PolicyConditionOp;
|
|
366
|
+
/** Not read by `exists` / `absent`. */
|
|
367
|
+
value?: unknown;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* One rule. `kind`/`id` select which requests it applies to; `when`
|
|
372
|
+
* selects which argument tuples.
|
|
373
|
+
*/
|
|
374
|
+
export interface PolicyRule {
|
|
375
|
+
/** Request kind, e.g. `capability`. */
|
|
376
|
+
kind: string;
|
|
377
|
+
/** Exact id, or a trailing-`*` prefix glob. Absent matches every id of the kind. */
|
|
378
|
+
id?: string;
|
|
379
|
+
effect: PolicyRuleEffect;
|
|
380
|
+
/** ANDed. Absent or empty matches every argument tuple. */
|
|
381
|
+
when?: PolicyCondition[];
|
|
382
|
+
/** Which approval `effect: 'require'` demands. Default `step`. */
|
|
383
|
+
approval?: 'none' | 'plan' | 'step';
|
|
384
|
+
/** Shown to the user on a denial and written into the audit record. */
|
|
385
|
+
reason?: string;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Which rule decided, carried on the decision and into the audit record. */
|
|
389
|
+
export interface PolicyRuleRef {
|
|
390
|
+
/** Name of the template the rule came from. */
|
|
391
|
+
template: string;
|
|
392
|
+
/** Index within that template's `rules`. */
|
|
393
|
+
ruleIndex: number;
|
|
394
|
+
effect: PolicyRuleEffect;
|
|
395
|
+
reason?: string;
|
|
396
|
+
/** Digest of the template's rules — which policy text was in force. */
|
|
397
|
+
digest?: string;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** A rule that matched, plus the rule itself for the caller to read. */
|
|
401
|
+
export interface PolicyRuleMatch {
|
|
402
|
+
ref: PolicyRuleRef;
|
|
403
|
+
rule: PolicyRule;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** One problem found while validating rule input. */
|
|
407
|
+
export interface PolicyRuleProblem {
|
|
408
|
+
/** Dotted path into the parsed input, e.g. `templates.0.rules.2.op`. */
|
|
409
|
+
path: string;
|
|
410
|
+
message: string;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* A named, workspace-scoped bundle of rules.
|
|
415
|
+
*
|
|
416
|
+
* Until 1.17.0 this type existed but nothing read it: `evaluate()`
|
|
417
|
+
* never consulted the template store, `#persist()` never saved it,
|
|
418
|
+
* and its `condition?: string` field was marked "Future" and read by
|
|
419
|
+
* nobody. The string expression is gone — a structured matcher is
|
|
420
|
+
* the half that can be translated into another engine's schema,
|
|
421
|
+
* which is the entire purpose of exporting policy at all.
|
|
243
422
|
*/
|
|
244
423
|
export interface PolicyTemplate {
|
|
245
424
|
name: string;
|
|
246
425
|
description?: string;
|
|
247
426
|
workspaceId?: string;
|
|
248
|
-
rules:
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
427
|
+
rules: PolicyRule[];
|
|
428
|
+
/**
|
|
429
|
+
* Where the template came from. `file` templates are owned by
|
|
430
|
+
* `.punica/policy.yaml` and are removed when they leave it, so
|
|
431
|
+
* deleting the file cannot leave stale rules enforcing from
|
|
432
|
+
* persisted state. Absent is treated as `api`.
|
|
433
|
+
*/
|
|
434
|
+
source?: 'file' | 'api';
|
|
435
|
+
/** Digest of `rules`, set by whoever loaded them. */
|
|
436
|
+
digest?: string;
|
|
255
437
|
createdAtMs?: number;
|
|
256
438
|
updatedAtMs?: number;
|
|
257
439
|
}
|
|
258
440
|
|
|
259
441
|
/**
|
|
260
|
-
* Exported policy state
|
|
442
|
+
* Exported policy state.
|
|
443
|
+
*
|
|
444
|
+
* v3 adds `templates`, so an export answers "what was in force"
|
|
445
|
+
* rather than only "what was approved" — the question an evidence
|
|
446
|
+
* package has to answer. `importState` accepts v2 as well: a v2 blob
|
|
447
|
+
* simply carries no rules.
|
|
261
448
|
*/
|
|
262
449
|
export interface PolicyExport {
|
|
263
|
-
version: 2;
|
|
450
|
+
version: 2 | 3;
|
|
264
451
|
exportedAt: number;
|
|
265
452
|
mode: PolicyMode;
|
|
266
453
|
approvals: ApprovalRecord[];
|
|
454
|
+
/** Present in v3. Absent in a v2 blob. */
|
|
455
|
+
templates?: PolicyTemplate[];
|
|
267
456
|
}
|
|
268
457
|
|
|
269
458
|
/**
|
|
@@ -308,8 +497,85 @@ declare module 'punica' {
|
|
|
308
497
|
isRevoked(jti: string): boolean;
|
|
309
498
|
}
|
|
310
499
|
|
|
500
|
+
/** What `computeArgsBinding` returns. */
|
|
501
|
+
export interface ArgsBinding {
|
|
502
|
+
/** Digest of the bound arguments. */
|
|
503
|
+
binding: string;
|
|
504
|
+
/** Redacted, possibly truncated view of what was bound. */
|
|
505
|
+
preview: unknown;
|
|
506
|
+
}
|
|
507
|
+
|
|
311
508
|
export namespace Policy {
|
|
312
509
|
const manager: PolicyApi;
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Literal binding meaning "any arguments" — a grant deliberately
|
|
513
|
+
* made argument-blind, as opposed to a request that has no
|
|
514
|
+
* argument dimension at all (absent binding).
|
|
515
|
+
*/
|
|
516
|
+
const ARGS_BINDING_ANY: 'any';
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Digest the arguments a decision is about.
|
|
520
|
+
*
|
|
521
|
+
* `source` must be the REDACTED input, so no secret value reaches
|
|
522
|
+
* the digest. `policyKeyPaths` narrows the binding to the declared
|
|
523
|
+
* fields, which is what makes an `always` grant practical; empty
|
|
524
|
+
* binds the whole argument tuple.
|
|
525
|
+
*
|
|
526
|
+
* An extension that pre-records a grant must call this rather than
|
|
527
|
+
* hashing locally — otherwise its key and the gate's key drift and
|
|
528
|
+
* the grant never matches. Throws when Web Crypto is unavailable
|
|
529
|
+
* rather than falling back to a weaker hash.
|
|
530
|
+
*/
|
|
531
|
+
function computeArgsBinding(
|
|
532
|
+
source: unknown,
|
|
533
|
+
options?: { policyKeyPaths?: ReadonlyArray<ReadonlyArray<string>> }
|
|
534
|
+
): Promise<ArgsBinding>;
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* SHA-256 over the canonical JSON of `value`, 128 bits of hex.
|
|
538
|
+
* Used for the rule-set digest that says which policy text was in
|
|
539
|
+
* force; shares its implementation with `computeArgsBinding` so
|
|
540
|
+
* the two cannot drift.
|
|
541
|
+
*/
|
|
542
|
+
function canonicalDigest(value: unknown): Promise<string>;
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Which rule applies to a request, if any.
|
|
546
|
+
*
|
|
547
|
+
* Precedence is `deny` > `require` > `allow`, independent of the
|
|
548
|
+
* order rules were written in — order-dependent precedence does
|
|
549
|
+
* not survive translation into another policy engine.
|
|
550
|
+
*
|
|
551
|
+
* `args` must be the REDACTED arguments. Exported so a policy
|
|
552
|
+
* exporter reads the same matcher the gate enforces instead of
|
|
553
|
+
* reimplementing it.
|
|
554
|
+
*/
|
|
555
|
+
function matchRules(
|
|
556
|
+
templates: readonly PolicyTemplate[],
|
|
557
|
+
req: { kind: string; id: string },
|
|
558
|
+
args: unknown,
|
|
559
|
+
options?: {
|
|
560
|
+
/** Paths the redactor has already replaced; conditions on them never match. */
|
|
561
|
+
sensitivePaths?: ReadonlyArray<ReadonlyArray<string>>;
|
|
562
|
+
/** The whole tuple was replaced (a `secretBearing` capability); no condition applies. */
|
|
563
|
+
argsWhollyRedacted?: boolean;
|
|
564
|
+
}
|
|
565
|
+
): PolicyRuleMatch | null;
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Normalise and check rule input, e.g. parsed
|
|
569
|
+
* `.punica/policy.yaml`.
|
|
570
|
+
*
|
|
571
|
+
* An invalid rule is dropped and reported while its siblings load;
|
|
572
|
+
* a file-level shape error yields zero templates and a problem.
|
|
573
|
+
* Never returns a partial set that reads as complete.
|
|
574
|
+
*/
|
|
575
|
+
function validatePolicyTemplates(raw: unknown): {
|
|
576
|
+
templates: PolicyTemplate[];
|
|
577
|
+
problems: PolicyRuleProblem[];
|
|
578
|
+
};
|
|
313
579
|
}
|
|
314
580
|
}
|
|
315
581
|
}
|