@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.
- package/LICENSE +195 -0
- package/README.md +223 -0
- package/dist/canonicalize.d.ts +19 -0
- package/dist/canonicalize.d.ts.map +1 -0
- package/dist/canonicalize.js +36 -0
- package/dist/canonicalize.js.map +1 -0
- package/dist/capabilities.d.ts +605 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +53 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/cdro.d.ts +63 -0
- package/dist/cdro.d.ts.map +1 -0
- package/dist/cdro.js +16 -0
- package/dist/cdro.js.map +1 -0
- package/dist/channels.d.ts +107 -0
- package/dist/channels.d.ts.map +1 -0
- package/dist/channels.js +29 -0
- package/dist/channels.js.map +1 -0
- package/dist/constants.d.ts +32 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +36 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/oid.d.ts +28 -0
- package/dist/oid.d.ts.map +1 -0
- package/dist/oid.js +68 -0
- package/dist/oid.js.map +1 -0
- package/dist/receipts.d.ts +128 -0
- package/dist/receipts.d.ts.map +1 -0
- package/dist/receipts.js +14 -0
- package/dist/receipts.js.map +1 -0
- package/dist/revocations.d.ts +65 -0
- package/dist/revocations.d.ts.map +1 -0
- package/dist/revocations.js +22 -0
- package/dist/revocations.js.map +1 -0
- package/dist/validate.d.ts +59 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +835 -0
- package/dist/validate.js.map +1 -0
- package/dist/workflows.d.ts +186 -0
- package/dist/workflows.d.ts.map +1 -0
- package/dist/workflows.js +14 -0
- package/dist/workflows.js.map +1 -0
- package/package.json +55 -0
- package/src/canonicalize.ts +38 -0
- package/src/capabilities.ts +711 -0
- package/src/cdro.ts +92 -0
- package/src/channels.ts +183 -0
- package/src/constants.ts +46 -0
- package/src/index.ts +180 -0
- package/src/oid.ts +71 -0
- package/src/receipts.ts +169 -0
- package/src/revocations.ts +90 -0
- package/src/validate.ts +1008 -0
- 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
|
+
}
|