@synoi/gap 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.
Files changed (58) hide show
  1. package/LICENSE +195 -0
  2. package/README.md +223 -0
  3. package/dist/canonicalize.d.ts +19 -0
  4. package/dist/canonicalize.d.ts.map +1 -0
  5. package/dist/canonicalize.js +36 -0
  6. package/dist/canonicalize.js.map +1 -0
  7. package/dist/capabilities.d.ts +605 -0
  8. package/dist/capabilities.d.ts.map +1 -0
  9. package/dist/capabilities.js +53 -0
  10. package/dist/capabilities.js.map +1 -0
  11. package/dist/cdro.d.ts +63 -0
  12. package/dist/cdro.d.ts.map +1 -0
  13. package/dist/cdro.js +16 -0
  14. package/dist/cdro.js.map +1 -0
  15. package/dist/channels.d.ts +107 -0
  16. package/dist/channels.d.ts.map +1 -0
  17. package/dist/channels.js +29 -0
  18. package/dist/channels.js.map +1 -0
  19. package/dist/constants.d.ts +32 -0
  20. package/dist/constants.d.ts.map +1 -0
  21. package/dist/constants.js +36 -0
  22. package/dist/constants.js.map +1 -0
  23. package/dist/index.d.ts +28 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +35 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/oid.d.ts +28 -0
  28. package/dist/oid.d.ts.map +1 -0
  29. package/dist/oid.js +68 -0
  30. package/dist/oid.js.map +1 -0
  31. package/dist/receipts.d.ts +128 -0
  32. package/dist/receipts.d.ts.map +1 -0
  33. package/dist/receipts.js +14 -0
  34. package/dist/receipts.js.map +1 -0
  35. package/dist/revocations.d.ts +65 -0
  36. package/dist/revocations.d.ts.map +1 -0
  37. package/dist/revocations.js +22 -0
  38. package/dist/revocations.js.map +1 -0
  39. package/dist/validate.d.ts +59 -0
  40. package/dist/validate.d.ts.map +1 -0
  41. package/dist/validate.js +835 -0
  42. package/dist/validate.js.map +1 -0
  43. package/dist/workflows.d.ts +186 -0
  44. package/dist/workflows.d.ts.map +1 -0
  45. package/dist/workflows.js +14 -0
  46. package/dist/workflows.js.map +1 -0
  47. package/package.json +55 -0
  48. package/src/canonicalize.ts +38 -0
  49. package/src/capabilities.ts +711 -0
  50. package/src/cdro.ts +92 -0
  51. package/src/channels.ts +183 -0
  52. package/src/constants.ts +46 -0
  53. package/src/index.ts +180 -0
  54. package/src/oid.ts +71 -0
  55. package/src/receipts.ts +169 -0
  56. package/src/revocations.ts +90 -0
  57. package/src/validate.ts +1008 -0
  58. package/src/workflows.ts +241 -0
@@ -0,0 +1,711 @@
1
+ /**
2
+ * capabilities.ts -- Declaration / Grant / Invocation shapes.
3
+ *
4
+ * These mirror the GAP gateway reference implementation wire types.
5
+ * The trio is the GAP operational core:
6
+ *
7
+ * - CapabilityDeclaration: an actor announces "I can do X under conditions Y".
8
+ * - CapabilityGrant: an operator says "actor A may invoke X within scope S until time T".
9
+ * - CapabilityInvocation: an actor performs "I am invoking X, here are my args + the grant I'm using".
10
+ *
11
+ * Every gate check (grant exists + not revoked + not expired + scope matches
12
+ * + preconditions hold) happens on invocation. The decision becomes a
13
+ * GapDecisionReceipt.
14
+ */
15
+
16
+ import type { GapCdroEnvelope } from './cdro.js'
17
+
18
+ // -- Actor taxonomy -----------------------------------------------------------
19
+
20
+ export type GapActorType =
21
+ | 'skill'
22
+ | 'service'
23
+ | 'device'
24
+ | 'agent'
25
+ | 'mcp_server'
26
+ | 'gateway_subsystem'
27
+ | 'human_user'
28
+
29
+ // -- Item 1: Agent Delegation Chain ------------------------------------------
30
+
31
+ /**
32
+ * [DESIGN] One hop in a gap:orchestration_chain. Each step signs over the
33
+ * prior step receipt OID plus the canonical invocation body. Signing keys for
34
+ * each hop MUST be declared at grant issuance.
35
+ */
36
+ export interface DelegationStep {
37
+ /** Zero-based index of this hop in the chain. */
38
+ step_index: number
39
+ /** Actor OID performing the delegation. */
40
+ delegator_actor_oid: string
41
+ /** Actor OID receiving delegated authority. */
42
+ delegatee_actor_oid: string
43
+ /** OID of the grant that authorizes this delegation hop. */
44
+ grant_oid: string
45
+ /** OID of the receipt from the prior hop (absent for step_index 0). */
46
+ prior_receipt_oid?: string
47
+ /** Unix epoch ms when delegation was issued. */
48
+ delegated_at_ms: number
49
+ /** Signature over canonical(prior_receipt_oid + invocation_body), base64url. */
50
+ step_signature: string
51
+ /** Algorithm used for step_signature, e.g. 'Ed25519' or 'ML-DSA-65'. */
52
+ step_signature_alg: string
53
+ }
54
+
55
+ /**
56
+ * [DESIGN] Body of a gap:orchestration_chain CDRO. Consolidates all delegation
57
+ * hops into one envelope. The gateway MUST verify each step's signature before
58
+ * allowing the terminal invocation.
59
+ *
60
+ * Max hops: 10. Gateway returns HTTP 400 with error 'delegation_depth_exceeded'
61
+ * when steps.length > 10.
62
+ */
63
+ export interface OrchestrationChainBody {
64
+ /** Actor OID that initiated the chain. */
65
+ root_actor_oid: string
66
+ /** Ordered array of delegation steps, max 10. */
67
+ steps: DelegationStep[]
68
+ /** Capability name being delegated through the chain. */
69
+ capability_name: string
70
+ /** OID of the terminal invocation CDRO this chain authorizes. */
71
+ final_invocation_oid: string
72
+ }
73
+
74
+ // -- Item 2: MCP Tool-Call Governance ----------------------------------------
75
+
76
+ /**
77
+ * [DESIGN] Context attached to an invocation when the capability originated
78
+ * from an MCP tools/list response. Capability names for MCP tools follow the
79
+ * pattern mcp.<server_id>.<tool_name>.
80
+ *
81
+ * The gateway MUST reject any auto-generated capability name that starts with
82
+ * 'gap:' or matches any normative capability name, to prevent namespace
83
+ * pollution from attacker-controlled tools/list responses.
84
+ */
85
+ export interface McpToolCallContext {
86
+ /** Stable identifier for the MCP server. */
87
+ server_id: string
88
+ /** Name of the tool as returned by tools/list. */
89
+ tool_name: string
90
+ /** Optional SHA-256 hash of the tool's JSON schema, for drift detection. */
91
+ tool_schema_hash?: string
92
+ }
93
+
94
+ // -- Item 3: Token Budget Governance -----------------------------------------
95
+
96
+ /**
97
+ * [DESIGN] Args for the 'token_budget' precondition kind. Evaluation timing:
98
+ * post_invoke (settled after execution). Any cost figures MUST carry [MODELED]
99
+ * tag until a conformance vector exists.
100
+ */
101
+ export interface TokenBudgetArgs {
102
+ /**
103
+ * Model ID pattern using shell-glob syntax, e.g. 'anthropic/claude-*'.
104
+ * Matched against the model identifier on the receipt's token_consumption.
105
+ */
106
+ model_scope: string
107
+ /** Maximum input tokens permitted within window_seconds. */
108
+ max_input_tokens?: number
109
+ /** Maximum output tokens permitted within window_seconds. */
110
+ max_output_tokens?: number
111
+ /** Maximum cost in USD permitted within window_seconds. [MODELED] */
112
+ max_cost_usd?: number
113
+ /** Rolling window length in seconds. */
114
+ window_seconds: number
115
+ }
116
+
117
+ // -- Item 4: Consent Version Chain -------------------------------------------
118
+
119
+ /**
120
+ * [DESIGN] Body of a gap:consent_record CDRO. Forms an append-only chain via
121
+ * prior_consent_oid. The precondition kind 'consent_current' evaluates whether
122
+ * the actor's most recent consent record has consented: true.
123
+ *
124
+ * MUST: the gateway MUST NOT use the idempotency cache for consent_current
125
+ * evaluation. Withdrawal (consented: false) MUST take effect within 5 seconds
126
+ * across all replicas.
127
+ *
128
+ * This single primitive subsumes hiring consent, learner consent, and clinical
129
+ * consent. The context field carries sector-specific detail.
130
+ */
131
+ export interface ConsentRecordBody {
132
+ /** Actor OID whose consent this record captures. */
133
+ actor_oid: string
134
+ /** Tenant scope of the consent. */
135
+ tenant_id: string
136
+ /**
137
+ * Free-form context string identifying the consent subject, e.g.
138
+ * 'hiring.background_check', 'clinical.data_sharing', 'learner.analytics'.
139
+ */
140
+ context: string
141
+ /** True = consent granted; false = consent withdrawn. */
142
+ consented: boolean
143
+ /** OID of the prior consent record for this actor + context, forming the chain. */
144
+ prior_consent_oid?: string
145
+ /** Unix epoch ms when consent was recorded. */
146
+ consented_at_ms: number
147
+ /** Optional expiry; gateway MUST treat expired records as consented: false. */
148
+ expires_at_ms?: number
149
+ /** SHA-256 hash of the consent disclosure text shown to the actor. */
150
+ consent_text_hash?: string
151
+ }
152
+
153
+ // -- Item 5: Identity Binding ------------------------------------------------
154
+
155
+ /**
156
+ * [DESIGN] Normative credential_kind values for IdentityBinding. The binding
157
+ * ties an actor_oid to a real-world credential with a hardware-backed signature.
158
+ */
159
+ export type CredentialKind =
160
+ | 'piv_cac'
161
+ | 'x509'
162
+ | 'fido2'
163
+ | 'tpm_attestation'
164
+ | 'oidc_sub'
165
+ | 'spiffe_svid'
166
+ | 'wallet_address'
167
+ | 'professional_license'
168
+
169
+ /**
170
+ * [DESIGN] Ties an actor_oid to a real-world credential. The canonical binding
171
+ * payload (domain-separated) is:
172
+ * "gap-identity-binding-v1" + ":" + actor_oid + ":" + tenant_id + ":" + credential_identifier
173
+ *
174
+ * The binding_signature is a credential holder's signature over that payload.
175
+ */
176
+ export interface IdentityBinding {
177
+ /** Normative credential kind. */
178
+ credential_kind: CredentialKind
179
+ /** Stable identifier within the credential_kind namespace (e.g. certificate serial, SPIFFE SVID URI). */
180
+ credential_identifier: string
181
+ /** Signature over the domain-separated canonical payload, base64url. */
182
+ binding_signature: string
183
+ /** Algorithm used for binding_signature, e.g. 'Ed25519', 'ES256', 'RS256'. */
184
+ binding_alg: string
185
+ /** Unix epoch ms when binding was established. */
186
+ bound_at_ms: number
187
+ /** Issuer identifier (CA DN, OIDC issuer URL, etc.) -- optional but RECOMMENDED. */
188
+ issuer?: string
189
+ /** Unix epoch ms when binding expires. Absent = no expiry. */
190
+ expires_at_ms?: number
191
+ }
192
+
193
+ // -- Item 7: Signed PIP Response ---------------------------------------------
194
+
195
+ /**
196
+ * [DESIGN] Args for the 'external_pip' precondition kind. The gateway POSTs
197
+ * invocation args to the endpoint and evaluates the boolean `allowed` response.
198
+ * When pip_response_oid is set, the referenced gap:pip_response CDRO is
199
+ * ENFORCING; the gateway MUST verify its signature before using it as the sole
200
+ * basis for an allow decision. Unsigned reads without pip_response_oid are
201
+ * ADVISORY only.
202
+ */
203
+ export interface ExternalPipArgs {
204
+ /** URL of the external Policy Information Point. */
205
+ endpoint_url: string
206
+ /** Cache TTL in seconds for the PIP response, keyed by (tenant, capability, args-hash). */
207
+ cache_ttl_seconds: number
208
+ /** Invocation arg keys sent to the PIP as subject context. */
209
+ subject_fields: string[]
210
+ /**
211
+ * [DESIGN] OID of a gap:pip_response CDRO. When present, the gateway MUST
212
+ * verify the CDRO signature before treating the response as ENFORCING.
213
+ * Absent = response is ADVISORY.
214
+ */
215
+ pip_response_oid?: string
216
+ }
217
+
218
+ /**
219
+ * [DESIGN] Body of a gap:pip_response CDRO. Emitted by an external PIP and
220
+ * re-signed by the gateway. Distinction:
221
+ * - Unsigned external reads: ADVISORY (influence decision; cannot be sole basis for allow).
222
+ * - Signed gap:pip_response: ENFORCING (gateway may use as sole basis for allow/deny).
223
+ *
224
+ * The gateway MUST verify the CDRO signature before treating the response as
225
+ * enforcing.
226
+ */
227
+ export interface PipResponseBody {
228
+ /** URL of the external PIP endpoint that produced this response. */
229
+ pip_endpoint: string
230
+ /** SHA-256 hash of the canonical request args sent to the PIP. */
231
+ request_args_hash: string
232
+ /** SHA-256 hash of the raw response body received from the PIP. */
233
+ response_body_hash: string
234
+ /** Optional human-readable summary of the PIP response (not authoritative). */
235
+ response_summary?: string
236
+ /** Unix epoch ms when the PIP was queried. */
237
+ evaluated_at_ms: number
238
+ /** How long (ms) this response may be cached by the gateway. */
239
+ cache_ttl_ms: number
240
+ /** Optional signature from the PIP itself over the response body, base64url. */
241
+ pip_signature?: string
242
+ /** Algorithm used for pip_signature. */
243
+ pip_signature_alg?: string
244
+ }
245
+
246
+ // -- Predicates + capability shape --------------------------------------------
247
+
248
+ export interface CapabilityPredicate {
249
+ kind: string
250
+ args: Record<string, unknown>
251
+ }
252
+
253
+ export interface Capability {
254
+ /** Dotted-taxonomy capability name, e.g. `skill.create`, `gap.discovery.query`. */
255
+ capability: string
256
+ /** Capability-level scope constraints (free-form per-capability). */
257
+ scope?: Record<string, unknown>
258
+ /** Preconditions evaluated at invocation gate. */
259
+ preconditions?: CapabilityPredicate[]
260
+ /** Safety classification: A (low) / B (medium) / C (high). */
261
+ safety_class?: 'A' | 'B' | 'C'
262
+ /** True if invocation can change physical-world state (HA, IoT, etc.). */
263
+ physical_safety?: boolean
264
+ /**
265
+ * When true, the gateway MUST attach a cryptographic signature to every
266
+ * decision receipt for this capability, regardless of the server's default
267
+ * conformance tier. When false, the gateway SHOULD omit the signature even
268
+ * on an L4 server (useful for high-frequency trivial actions where signing
269
+ * cost outweighs the benefit). When absent, the gateway applies its
270
+ * configured default signing policy.
271
+ *
272
+ * The operator may override this on the grant via
273
+ * GrantedCapabilityScope.require_signed_receipt.
274
+ */
275
+ require_signed_receipt?: boolean
276
+ /**
277
+ * Array of invocation arg key strings whose values contain PII, PHI, or NPI
278
+ * requiring tokenization before storage. The gateway MUST replace each listed
279
+ * key's value with a keyed HMAC token (one-way, using a per-tenant key)
280
+ * before constructing the invocation CDRO and receipt body. The original
281
+ * value is used for capability execution by the adapter but MUST NOT be
282
+ * stored in any CDRO.
283
+ *
284
+ * Required for capabilities with privacy_classification 'phi' or whose name
285
+ * matches medical.* or financial.*.
286
+ */
287
+ pii_args?: string[]
288
+ /**
289
+ * C17: When true, the gateway routes receipts for this capability to a
290
+ * privilege-isolated store, suppresses them from the standard GET /receipts
291
+ * list endpoint, requires an explicit attorney-assertion header on fetch by
292
+ * OID, and excludes the receipt from automated compliance exports.
293
+ * Controls access routing, not deletion.
294
+ */
295
+ privilege_protected?: boolean
296
+ }
297
+
298
+ // -- Declaration --------------------------------------------------------------
299
+
300
+ export interface CapabilityDeclarationBody {
301
+ actor_type: GapActorType
302
+ actor_id: string
303
+ actor_name: string
304
+ actor_version: string
305
+ source_url?: string
306
+ parent_oid?: string
307
+ capabilities: Capability[]
308
+ /**
309
+ * C15: Ephemeral actor lifecycle (GAP spec Phase 1 -- Ephemeral Actors).
310
+ *
311
+ * `persistent` (default) -- standard supersession uniqueness rules apply.
312
+ * `ephemeral` -- declaration is exempt from supersession uniqueness; each
313
+ * invocation gets a fresh OID valid for its session only.
314
+ */
315
+ actor_lifecycle?: 'persistent' | 'ephemeral'
316
+ /**
317
+ * C15: UUID or job ID distinguishing this instance from others with the same
318
+ * actor_id. When present, two declarations with the same actor_id but
319
+ * different actor_instance_id MUST NOT be treated as superseding each other.
320
+ */
321
+ actor_instance_id?: string
322
+ /**
323
+ * C15: For ephemeral actors: when this session ends (Unix epoch ms). The
324
+ * gateway MUST auto-revoke all grants scoped to this actor_instance_id at
325
+ * this time.
326
+ */
327
+ session_expires_at_ms?: number
328
+ declared_limits?: {
329
+ max_invocations_per_minute?: number
330
+ max_concurrent_invocations?: number
331
+ max_payload_bytes?: number
332
+ requires_network?: boolean
333
+ requires_filesystem_read?: string[]
334
+ requires_filesystem_write?: string[]
335
+ }
336
+ human_summary?: string
337
+ privacy_classification?: 'public' | 'restricted' | 'sensitive' | 'phi' | 'pii' | 'financial' | 'privileged'
338
+ /**
339
+ * Item 5 [DESIGN]: Real-world credential binding for this actor. Ties the
340
+ * actor_oid to a verifiable credential using a hardware-backed signature.
341
+ * See IdentityBinding for the canonical payload and domain-separation prefix.
342
+ */
343
+ identity_binding?: IdentityBinding
344
+ /**
345
+ * Item 6 [DESIGN]: Compartment label for this declaration.
346
+ * Values: 'UNCLASS', 'CUI', or a reverse-domain operator label
347
+ * (e.g. 'com.acme.project-alpha').
348
+ */
349
+ compartment?: string
350
+ }
351
+
352
+ export type CapabilityDeclaration = GapCdroEnvelope<CapabilityDeclarationBody>
353
+
354
+ // -- Grant --------------------------------------------------------------------
355
+
356
+ export interface GrantedCapabilityScope {
357
+ capability: string
358
+ /**
359
+ * OID of the specific capability declaration envelope this scope is pinned to.
360
+ * REQUIRED for grants covering safety_class='C' or physical_safety=true
361
+ * capabilities. The gateway evaluates those grants against this pinned
362
+ * declaration OID rather than the actor's current active declaration,
363
+ * preventing declaration supersession attacks.
364
+ */
365
+ capability_declaration_oid?: string
366
+ scope_narrowing?: Record<string, unknown>
367
+ additional_preconditions?: CapabilityPredicate[]
368
+ /**
369
+ * M-4: Operator override for receipt signing on this specific scope. When
370
+ * set, takes precedence over the capability declaration's
371
+ * require_signed_receipt. Allows a compliance deployment to require signed
372
+ * receipts for every action regardless of what the actor declared.
373
+ */
374
+ require_signed_receipt?: boolean
375
+ }
376
+
377
+ export interface CapabilityGrantBody {
378
+ grantee: {
379
+ actor_type: GapActorType
380
+ actor_oid: string
381
+ actor_session_id?: string
382
+ }
383
+ capability_scopes: GrantedCapabilityScope[]
384
+ granted_at_ms: number
385
+ expires_at_ms: number | null
386
+ limits?: {
387
+ max_invocations_per_minute?: number
388
+ max_invocations_total?: number
389
+ max_payload_bytes?: number
390
+ /**
391
+ * Rolling-window aggregate constraints. Each entry specifies an invocation
392
+ * args key to sum across invocations and a ceiling within a rolling time
393
+ * window. Example: { key: 'amount_usd', max: 10000, window_seconds: 3600 }
394
+ * denies invocations that would push the rolling sum above $10,000/hr.
395
+ * Enables financial controls that a per-invocation cap cannot provide.
396
+ */
397
+ aggregate_limits?: Array<{
398
+ /** Invocation args key to aggregate (must be a numeric value). */
399
+ key: string
400
+ /** Maximum allowed sum within the rolling window. */
401
+ max: number
402
+ /** Length of the rolling window in seconds. */
403
+ window_seconds: number
404
+ }>
405
+ /**
406
+ * Named pool identifier for cross-grant aggregate limit groups. All grants
407
+ * with the same aggregate_limit_group share rolling aggregate counters
408
+ * defined in the tenant's pool configuration. The gateway MUST maintain
409
+ * atomic counters per pool and MUST deny any invocation from any grant in
410
+ * the pool that would exceed the pool ceiling.
411
+ */
412
+ aggregate_limit_group?: string
413
+ }
414
+ granted_by: string
415
+ reason?: string
416
+ evidence_oids?: string[]
417
+ revocation_level_override?: 1 | 2 | 3
418
+ /**
419
+ * Operator override for receipt signing on a per-scope basis. When set,
420
+ * takes precedence over the capability declaration's require_signed_receipt.
421
+ * Allows an operator to require signing for a capability the actor declared
422
+ * as unsigned (e.g. a compliance deployment that needs signed receipts for
423
+ * every action), or to suppress signing for a high-frequency capability
424
+ * the actor flagged as requiring it.
425
+ */
426
+ require_signed_receipt?: boolean
427
+
428
+ /**
429
+ * OID of the parent grant when this grant was delegated from another actor.
430
+ * When present the gateway MUST verify the parent grant covers all
431
+ * capability_scopes in this grant before accepting it. Enables verifiable
432
+ * delegation chains (orchestrator -> sub-agent). max_delegation_depth caps
433
+ * how many hops this grant may be further delegated.
434
+ */
435
+ parent_grant_oid?: string
436
+ /**
437
+ * Maximum number of additional delegation hops permitted below this grant.
438
+ * When absent from a grant covering any capability with physical_safety=true,
439
+ * the gateway MUST treat it as 0 (no sub-delegation). A gateway-enforced hard
440
+ * cap of 10 applies regardless of the value set here.
441
+ */
442
+ max_delegation_depth?: number
443
+ /**
444
+ * For safety_class C without physical_safety: override for the default
445
+ * timestamp validation window in seconds. Gateway applies its default if absent.
446
+ */
447
+ timestamp_window_seconds?: number
448
+ /**
449
+ * Additional seconds beyond grant expiry during which offline provisional
450
+ * receipts are accepted at reconciliation. Defaults to 0.
451
+ */
452
+ offline_grace_seconds?: number
453
+ /**
454
+ * Maximum duration any device may use this grant without syncing to the
455
+ * gateway. After this window, further invocations MUST be denied until
456
+ * the device reconnects.
457
+ */
458
+ max_grant_offline_ttl_ms?: number
459
+ /**
460
+ * Maximum acceptable age of a revocation bundle for this grant. Devices
461
+ * MUST deny physical_safety/class C invocations if the bundle is older
462
+ * than this value.
463
+ */
464
+ max_revocation_bundle_age_ms?: number
465
+
466
+ // -- Break-glass fields ------------------------------------------------------
467
+
468
+ /**
469
+ * When true, marks this grant as a break-glass grant. A break-glass grant
470
+ * pre-authorizes a defined set of emergency capabilities with an
471
+ * offline-verifiable signed token, for use when the gateway is unreachable
472
+ * and immediate action is required for safety or clinical reasons.
473
+ */
474
+ break_glass?: boolean
475
+ /**
476
+ * TTL of the break-glass token in milliseconds from issuance.
477
+ * RECOMMENDED: 4 hours (14_400_000 ms). Required when break_glass is true.
478
+ */
479
+ break_glass_ttl_ms?: number
480
+ /**
481
+ * Maximum invocations allowed under this token before it is exhausted.
482
+ * Defaults to 1 for safety_class C.
483
+ */
484
+ break_glass_max_invocations?: number
485
+ /**
486
+ * When true, the invoker MUST supply a break_glass_reason string in
487
+ * invocation args when activating break-glass operation.
488
+ */
489
+ break_glass_requires_reason?: boolean
490
+ /**
491
+ * Item 6 [DESIGN]: Compartment label for this grant. At invocation time,
492
+ * if the grant carries a compartment the invocation compartment MUST exactly
493
+ * match. Cross-compartment access requires a bridge grant issued through a
494
+ * TPI-gated HITL workflow.
495
+ */
496
+ compartment?: string
497
+ }
498
+
499
+ export type CapabilityGrant = GapCdroEnvelope<CapabilityGrantBody>
500
+
501
+ // -- Invocation ---------------------------------------------------------------
502
+
503
+ export interface CapabilityInvocationBody {
504
+ caller: {
505
+ actor_type: GapActorType
506
+ actor_oid: string
507
+ actor_session_id?: string
508
+ grant_oid: string
509
+ }
510
+ capability: string
511
+ /**
512
+ * OID of the capability's declaration envelope. Optional routing hint --
513
+ * the gateway resolves it from the grant if omitted. When provided, the
514
+ * gateway MAY use it to skip the declaration lookup.
515
+ */
516
+ capability_declaration_oid?: string
517
+ args: Record<string, unknown>
518
+ workflow_context?: {
519
+ workflow_instance_oid: string
520
+ stage_id: string
521
+ }
522
+ sla_hint?: {
523
+ max_latency_ms?: number
524
+ deferrable?: boolean
525
+ }
526
+ idempotency_key?: string
527
+ /**
528
+ * Server-stamped. Clients SHOULD omit this field; the gateway sets it to
529
+ * the time the invocation was received. If provided by the client, the
530
+ * value is accepted as-is but the gateway may override it.
531
+ */
532
+ invoked_at_ms?: number
533
+ /**
534
+ * Unix epoch ms when the action originally occurred in the caller's reference
535
+ * frame (game-world time, clinical queue time, SCADA scan cycle). Populated
536
+ * by the client; not used for replay prevention. Stored in receipt for audit.
537
+ */
538
+ client_event_ms?: number
539
+ /**
540
+ * Unix epoch ms when the invocation was enqueued for submission (e.g. at
541
+ * reconnect after an offline period). Optional; aids debugging of delivery
542
+ * latency.
543
+ */
544
+ queued_at_ms?: number
545
+ /**
546
+ * Item 1 [DESIGN]: Delegation chain steps, when this invocation was reached
547
+ * through a multi-hop orchestration chain. Max 10 steps; gateway returns
548
+ * HTTP 400 'delegation_depth_exceeded' when length exceeds 10.
549
+ */
550
+ delegation_chain?: DelegationStep[]
551
+ /**
552
+ * Item 2 [DESIGN]: Raw MCP tool-call context when the capability was
553
+ * auto-generated from an MCP tools/list response. The gateway validates
554
+ * server_id is non-empty and tool_name does not start with 'gap:'.
555
+ */
556
+ mcp_tool_call?: McpToolCallContext
557
+ /**
558
+ * Item 6 [DESIGN]: Compartment label on this invocation. MUST exactly match
559
+ * the grant's compartment when the grant carries one. The gateway MUST return
560
+ * HTTP 404 (not HTTP 403) when the invoking actor's compartment level is
561
+ * insufficient to know a resource exists.
562
+ */
563
+ compartment?: string
564
+ }
565
+
566
+ export type CapabilityInvocation = GapCdroEnvelope<CapabilityInvocationBody>
567
+
568
+ // -- Key distribution ---------------------------------------------------------
569
+
570
+ export interface KeyEntry {
571
+ key_id: string
572
+ public_key_base64: string
573
+ algorithm: 'Ed25519' | 'ML-DSA-65' | 'Ed25519+ML-DSA-65'
574
+ valid_from_ms: number
575
+ expires_at_ms: number
576
+ }
577
+
578
+ export interface KeyringExportBody {
579
+ keys: KeyEntry[]
580
+ exported_at_ms: number
581
+ expires_at_ms: number
582
+ }
583
+
584
+ // -- Offline Execution Profile ------------------------------------------------
585
+
586
+ export interface OfflinePolicy {
587
+ max_offline_duration_ms: number
588
+ max_offline_invocations: number
589
+ offline_capability_filter?: string[]
590
+ offline_allowed?: boolean
591
+ }
592
+
593
+ export interface OfflineBundleBody {
594
+ grant: CapabilityGrant
595
+ declaration: CapabilityDeclaration
596
+ keyring: KeyringExportBody
597
+ revocation_snapshot: RevocationSnapshotBody
598
+ offline_policy: OfflinePolicy
599
+ }
600
+
601
+ // -- Revocation bundle --------------------------------------------------------
602
+
603
+ export interface RevocationEntry {
604
+ grant_oid: string
605
+ effective_at_ms: number
606
+ kind: 'immediate' | 'scheduled' | 'provisional_block'
607
+ }
608
+
609
+ export interface RevocationSnapshotBody {
610
+ revocations: RevocationEntry[]
611
+ snapshot_at_ms: number
612
+ expires_at_ms: number
613
+ tenant_id: string
614
+ }
615
+
616
+ // -- Capability pattern matching ----------------------------------------------
617
+
618
+ // -- Break-glass --------------------------------------------------------------
619
+
620
+ export interface BreakGlassTokenBody {
621
+ grant_oid: string
622
+ actor_oid: string
623
+ valid_from_ms: number
624
+ expires_at_ms: number
625
+ permitted_capabilities: string[]
626
+ max_invocations: number
627
+ }
628
+
629
+ export interface LocalOverrideCredentialBody {
630
+ grant_oid: string
631
+ actor_oid: string
632
+ expires_at_ms: number
633
+ single_use: true
634
+ override_reason_required: boolean
635
+ }
636
+
637
+ // -- C16: GDPR erasure event --------------------------------------------------
638
+
639
+ /**
640
+ * C16: Body of a gap:erasure_event CDRO. Replaces the body of the targeted
641
+ * receipt with a fixed erasure sentinel while preserving envelope metadata.
642
+ * The erasure event OID anchors to the original CDRO OID and is itself a
643
+ * signed CDRO, making it non-repudiable. Verifiers MUST treat an erasure event
644
+ * as authoritative over the prior OID body.
645
+ */
646
+ export interface ErasureEventBody {
647
+ /** OID of the CDRO being erased. */
648
+ target_oid: string
649
+ /** Reason code for the erasure. */
650
+ erasure_reason: 'gdpr_article_17' | 'ccpa' | 'operator_policy'
651
+ /** Unix epoch ms of erasure. */
652
+ erased_at_ms: number
653
+ /** Actor OID issuing the erasure. */
654
+ erased_by: string
655
+ /** Array of field paths erased from the target CDRO body. */
656
+ fields_erased: string[]
657
+ }
658
+
659
+ // -- C12: Self-Sovereign Credential (LCA root) --------------------------------
660
+
661
+ /**
662
+ * C12: Body of a gap:lca_root CDRO. Bootstraps a Local Credential Authority
663
+ * for deployments without external connectivity or without a SynOI-operated
664
+ * token issuer. The LCA root is signed by the local root key; actor credentials
665
+ * are then issued by the LCA and verified using the locally-held LCA public key.
666
+ */
667
+ export interface LcaRootBody {
668
+ root_public_key_base64: string
669
+ algorithm: 'ML-DSA-65' | 'Ed25519'
670
+ tenant_id: string
671
+ valid_from_ms: number
672
+ expires_at_ms: number
673
+ }
674
+
675
+ // -- Capability pattern matching ----------------------------------------------
676
+
677
+ /**
678
+ * Match a capability `target` against a `pattern`.
679
+ *
680
+ * Rules (matching the GAP gateway reference implementation):
681
+ * - Exact string match returns true.
682
+ * - A pattern ending in `*` matches any target with the prefix before `*`.
683
+ * - Everything else returns false.
684
+ *
685
+ * Examples:
686
+ * capabilityMatches('skill.create', 'skill.create') // true
687
+ * capabilityMatches('skill.*', 'skill.create') // true
688
+ * capabilityMatches('skill.*', 'skill.update') // true
689
+ * capabilityMatches('skill.create', 'skill.update') // false
690
+ * capabilityMatches('*', 'anything') // true
691
+ */
692
+ export function capabilityMatches(pattern: string, target: string): boolean {
693
+ if (pattern === target) return true
694
+ if (pattern === '*') return true // match-all
695
+ // M-8: Two wildcard levels.
696
+ // 'prefix.**' matches all descendants recursively (the prefix itself OR any
697
+ // path under it). Must be checked before '.*' because '.**' ends with '.*'.
698
+ if (pattern.endsWith('.**')) {
699
+ const prefix = pattern.slice(0, -3) // strip '.**'
700
+ return target === prefix || target.startsWith(prefix + '.')
701
+ }
702
+ // Segment-boundary wildcard: 'skill.*' matches 'skill.create' (direct
703
+ // children only -- single path segment). A non-boundary pattern like
704
+ // 'admin.us*' must NOT prefix-match 'admin.users.delete' (privilege-
705
+ // escalation footgun). The '*' must follow a '.'.
706
+ if (pattern.endsWith('.*')) {
707
+ const prefix = pattern.slice(0, -1) // keep the trailing '.', e.g. 'skill.'
708
+ return target.startsWith(prefix)
709
+ }
710
+ return false
711
+ }