@suveren/gateway 0.19.5 → 0.20.1

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 (46) hide show
  1. package/content/integrations/crm.json +1 -1
  2. package/content/integrations/deploy-github.json +1 -1
  3. package/content/integrations/erp.json +1 -1
  4. package/content/integrations/mail.json +1 -1
  5. package/content/integrations/records.json +1 -1
  6. package/dist/control-plane/index.mjs +21 -13
  7. package/dist/mcp-server/{chunk-ZRRZFGRL.mjs → chunk-APIQIEUA.mjs} +39 -27
  8. package/dist/mcp-server/http.mjs +308 -97
  9. package/dist/mcp-server/report-verify-cli.mjs +1 -1
  10. package/dist/ui/assets/index-1s1q-XLL.js +116 -0
  11. package/dist/ui/index.html +1 -1
  12. package/node_modules/@hap/core/README.md +92 -29
  13. package/node_modules/@hap/core/dist/index.d.mts +614 -593
  14. package/node_modules/@hap/core/dist/index.d.ts +614 -593
  15. package/node_modules/@hap/core/dist/index.js +774 -749
  16. package/node_modules/@hap/core/dist/index.mjs +744 -724
  17. package/node_modules/@hap/core/package.json +1 -1
  18. package/node_modules/@hap/core/src/base64url.ts +39 -0
  19. package/node_modules/@hap/core/src/commitment-modes.ts +0 -21
  20. package/node_modules/@hap/core/src/errors.ts +76 -0
  21. package/node_modules/@hap/core/src/frame.ts +47 -178
  22. package/node_modules/@hap/core/src/gatekeeper.ts +219 -600
  23. package/node_modules/@hap/core/src/identity.ts +9 -12
  24. package/node_modules/@hap/core/src/index.ts +8 -4
  25. package/node_modules/@hap/core/src/mandate.ts +177 -166
  26. package/node_modules/@hap/core/src/owner-signature.ts +282 -0
  27. package/node_modules/@hap/core/src/profile.ts +234 -0
  28. package/node_modules/@hap/core/src/ticket.ts +148 -0
  29. package/node_modules/@hap/core/src/types.ts +191 -344
  30. package/node_modules/@hap/core/src/versions.ts +47 -0
  31. package/package.json +2 -2
  32. package/profiles/calendar/0.6.profile.json +191 -0
  33. package/profiles/charge/0.6.profile.json +211 -0
  34. package/profiles/customers/0.9.profile.json +182 -0
  35. package/profiles/delegation/0.3.profile.json +118 -0
  36. package/profiles/deploy/0.11.profile.json +177 -0
  37. package/profiles/email/0.8.profile.json +226 -0
  38. package/profiles/index.json +12 -1
  39. package/profiles/publish/0.6.profile.json +187 -0
  40. package/profiles/purchase/0.7.profile.json +235 -0
  41. package/profiles/records/0.6.profile.json +118 -0
  42. package/profiles/reporting/0.3.profile.json +98 -0
  43. package/profiles/sales/0.4.profile.json +291 -0
  44. package/dist/ui/assets/index-Du68zJhH.js +0 -116
  45. package/node_modules/@hap/core/src/attestation.ts +0 -173
  46. package/node_modules/@hap/core/src/receipt.ts +0 -74
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanagencyp/hap-core",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "description": "Core types, cryptographic primitives, and verification logic for the Human Agency Protocol",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,39 @@
1
+ /**
2
+ * base64url (RFC 4648 §5), strict — no padding, no standard-base64 characters.
3
+ *
4
+ * protocol.md → *Mandate Payload* rule 3: "Signatures MUST be encoded as
5
+ * base64url without padding ... Implementations MUST NOT use standard base64
6
+ * (which differs in `+`/`/` vs `-`/`_` and in padding) — third-party verifiers
7
+ * reading the spec literally will reject standard-base64 signatures."
8
+ *
9
+ * Earlier hap-core verifiers were lenient: they translated `+`/`/` back from
10
+ * `-`/`_` and re-added padding before decoding, so a standard-base64 signature
11
+ * verified exactly as well as a base64url one — silently accepting the wire
12
+ * format the spec forbids. v0.7 closes that: decoding a string that is not
13
+ * strict base64url throws, never silently repairs it.
14
+ */
15
+
16
+ const STRICT_B64URL = /^[A-Za-z0-9_-]*$/;
17
+
18
+ /** Encode bytes as base64url with no padding. */
19
+ export function toBase64Url(bytes: Uint8Array): string {
20
+ return Buffer.from(bytes).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
21
+ }
22
+
23
+ /**
24
+ * Decode a STRICT base64url string (no padding, no `+`/`/`/`=`).
25
+ *
26
+ * @throws Error (no protocol code — the caller knows which artifact it was
27
+ * decoding and assigns the code) when the input carries a `+`, `/`, `=`, or
28
+ * any other byte outside the base64url alphabet.
29
+ */
30
+ export function fromBase64Url(s: string): Uint8Array {
31
+ if (!STRICT_B64URL.test(s)) {
32
+ throw new Error(
33
+ `${JSON.stringify(s)} is not strict base64url — standard base64 ` +
34
+ '("+"/"/"/padding) is retired (protocol.md → Mandate Payload, rule 3).',
35
+ );
36
+ }
37
+ const padding = s.length % 4 === 0 ? '' : '='.repeat(4 - (s.length % 4));
38
+ return new Uint8Array(Buffer.from(s + padding, 'base64'));
39
+ }
@@ -28,24 +28,3 @@ export function allowedCommitmentModes(profile: AgentProfile): readonly Signable
28
28
  export function isCommitmentModeAllowed(profile: AgentProfile, mode: string): boolean {
29
29
  return isSignable(mode) && allowedCommitmentModes(profile).includes(mode);
30
30
  }
31
-
32
- /**
33
- * Authoring check for a profile's `commitment_modes` declaration — for
34
- * hap-profiles CI, an authoring tool or a test. Like `validateBoundsRequiredFor`
35
- * it is not wired into profile loading; enforcement reads the declaration
36
- * fail-closed through `allowedCommitmentModes`. Empty array = nothing to fix.
37
- */
38
- export function validateCommitmentModes(profile: AgentProfile): string[] {
39
- const declared = (profile as { commitment_modes?: unknown }).commitment_modes;
40
- if (declared === undefined) return [];
41
- if (!Array.isArray(declared)) return ['commitment_modes must be a list of modes.'];
42
- const errors: string[] = [];
43
- if (declared.length === 0) errors.push('commitment_modes is empty — no mandate could ever be signed under this profile.');
44
- const seen = new Set<unknown>();
45
- for (const m of declared) {
46
- if (!isSignable(m)) errors.push(`commitment_modes: "${String(m)}" is not a mode a signer can choose (${SIGNABLE_COMMITMENT_MODES.join(', ')}).`);
47
- if (seen.has(m)) errors.push(`commitment_modes: "${String(m)}" appears twice.`);
48
- seen.add(m);
49
- }
50
- return errors;
51
- }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Canonical v0.7 error codes — protocol.md → *Error Codes*.
3
+ *
4
+ * "Error codes are canonical across the protocol. Implementations MUST emit
5
+ * exactly these codes and MUST NOT alias them under different names." This
6
+ * module is the single list both the Gatekeeper (local checks) and any AS
7
+ * client built on this library read from, so the two cannot drift.
8
+ *
9
+ * v0.7 renamed every code that carried a retired word (attestation → mandate,
10
+ * context → scope, receipt → ticket) — see protocol.md → *Migration from
11
+ * v0.6* → "Error-code renames" for the old→new map. There is no alias: a v0.6
12
+ * code name is simply gone from this list, by design (no backward
13
+ * compatibility — CLAUDE.md "Decided by the owner").
14
+ */
15
+
16
+ /** Codes an Authority Server (or a local Gatekeeper check) emits refusing a mandate request. */
17
+ export const MANDATE_ERROR_CODES = [
18
+ 'BOUNDS_HASH_MISMATCH',
19
+ 'SCOPE_HASH_MISMATCH',
20
+ 'INVALID_SIGNATURE',
21
+ 'OWNER_NOT_COVERED',
22
+ 'TTL_EXPIRED',
23
+ 'PROFILE_NOT_FOUND',
24
+ 'PROFILE_INVALID',
25
+ 'PROFILE_HASH_MISMATCH',
26
+ 'COVERAGE_INSUFFICIENT',
27
+ 'MALFORMED_MANDATE',
28
+ 'VERSION_UNSUPPORTED',
29
+ 'OWNER_NOT_APPROVER',
30
+ 'BOUNDS_INVALID_VALUE',
31
+ 'SCOPE_INVALID_VALUE',
32
+ 'ABOVE_CAP_CONFIG_INVALID',
33
+ 'OWNER_SIGNATURE_REQUIRED',
34
+ 'OWNER_SIGNATURE_INVALID',
35
+ ] as const;
36
+
37
+ /** Codes an Authority Server emits refusing a ticket request. */
38
+ export const TICKET_ERROR_CODES = [
39
+ 'MANDATE_NOT_FOUND',
40
+ 'MANDATE_EXPIRED',
41
+ 'MANDATE_REVOKED',
42
+ 'BOUND_EXCEEDED',
43
+ 'CUMULATIVE_LIMIT_EXCEEDED',
44
+ 'COVERAGE_INSUFFICIENT',
45
+ 'PROFILE_NOT_FOUND',
46
+ 'INVALID_ACTION_TYPE',
47
+ 'APPROVAL_REQUIRED',
48
+ 'PROPOSAL_REQUIRED',
49
+ 'PROPOSAL_NOT_FOUND',
50
+ 'PROPOSAL_NOT_APPROVED',
51
+ 'PROPOSAL_EXPIRED',
52
+ 'PROPOSAL_REJECTED',
53
+ 'PROPOSAL_MISMATCH',
54
+ 'PROPOSAL_MANDATE_MISMATCH',
55
+ 'PROPOSAL_ALREADY_EXECUTED',
56
+ 'MALFORMED_TICKET_REQUEST',
57
+ 'IDEMPOTENCY_KEY_REQUIRED',
58
+ 'IDEMPOTENCY_MISMATCH',
59
+ 'APPROVAL_SIGNATURE_REQUIRED',
60
+ 'APPROVAL_SIGNATURE_INVALID',
61
+ ] as const;
62
+
63
+ /** The full canonical surface — every code a conformant implementation may emit. */
64
+ export type HapErrorCode = (typeof MANDATE_ERROR_CODES)[number] | (typeof TICKET_ERROR_CODES)[number];
65
+
66
+ /**
67
+ * A refusal carrying one of the canonical codes above. Thrown by local
68
+ * (Gatekeeper-side, no-wire-round-trip) checks in this library; an AS client
69
+ * built on hap-core maps a wire `{ code, message }` error onto the same type.
70
+ */
71
+ export class HapError extends Error {
72
+ constructor(public readonly code: HapErrorCode, message: string) {
73
+ super(`${code}: ${message}`);
74
+ this.name = 'HapError';
75
+ }
76
+ }
@@ -1,102 +1,29 @@
1
1
  /**
2
- * Frame Canonicalization for Agent Profiles
2
+ * Bounds & Scope Canonicalization (protocol.md → *Bounds & Scope
3
+ * Canonicalization*).
3
4
  *
4
- * Agent profiles support mixed-type fields (strings and numbers).
5
5
  * Canonical form: `key=value` records joined with LF, keys in the profile's
6
6
  * keyOrder. Values are stringified with String(value) — the shortest
7
7
  * round-trippable form for numbers — then percent-encoded per protocol.md
8
8
  * (`=`, `%`, and every byte outside printable ASCII); a value carrying a raw
9
9
  * LF/CR is refused, never normalized. See `canonicalRecords` below.
10
10
  *
11
- * v0.3: frameSchema
12
- * v0.4: boundsSchema + contextSchema (separate hashes)
11
+ * v0.3's `frameSchema` / `canonicalFrame` / `computeFrameHash` are retired
12
+ * with the rest of v0.3 (CLAUDE.md "Decided by the owner": no backward
13
+ * compatibility). v0.7 renames `contextSchema` → `scopeSchema` and
14
+ * `AgentContextParams` → `AgentScopeParams` (protocol.md → *Migration from
15
+ * v0.6*); the functions below follow suit (`canonicalContext` →
16
+ * `canonicalScope`, `computeContextHash` → `computeScopeHash`,
17
+ * `validateContextParams` → `validateScopeParams`).
13
18
  */
14
19
 
15
20
  import { createHash } from 'crypto';
16
- import type { AgentFrameParams, AgentBoundsParams, AgentContextParams, AgentProfile, BoundType } from './types';
21
+ import type { AgentBoundsParams, AgentScopeParams, AgentProfile } from './types';
17
22
 
18
- // ─── v0.3 Frame Functions ─────────────────────────────────────────────────────
23
+ // ─── Bounds Validation ────────────────────────────────────────────────────────
19
24
 
20
25
  /**
21
- * Validates frame parameters against the profile's frame schema.
22
- */
23
- export function validateFrameParams(
24
- params: AgentFrameParams,
25
- profile: AgentProfile
26
- ): { valid: boolean; errors: string[] } {
27
- const errors: string[] = [];
28
-
29
- if (!profile.frameSchema) {
30
- return { valid: false, errors: ['Profile does not have a frameSchema'] };
31
- }
32
-
33
- // Check all required fields are present
34
- for (const [fieldName, fieldDef] of Object.entries(profile.frameSchema.fields)) {
35
- if (fieldDef.required && !(fieldName in params)) {
36
- errors.push(`Missing required field: ${fieldName}`);
37
- }
38
- }
39
-
40
- // Validate each provided field
41
- for (const [field, value] of Object.entries(params)) {
42
- const fieldDef = profile.frameSchema.fields[field];
43
- if (!fieldDef) {
44
- errors.push(`Unknown field "${field}" not defined in profile ${profile.id}`);
45
- continue;
46
- }
47
-
48
- // Type check
49
- if (fieldDef.type === 'number' && typeof value !== 'number') {
50
- errors.push(`Field "${field}" must be a number, got ${typeof value}`);
51
- }
52
- if (fieldDef.type === 'string' && typeof value !== 'string') {
53
- errors.push(`Field "${field}" must be a string, got ${typeof value}`);
54
- }
55
- }
56
-
57
- return { valid: errors.length === 0, errors };
58
- }
59
-
60
- /**
61
- * Builds the canonical frame string from parameters.
62
- * All values are converted to strings. Keys are ordered per profile's keyOrder.
63
- *
64
- * @throws Error if any field fails validation
65
- */
66
- export function canonicalFrame(params: AgentFrameParams, profile: AgentProfile): string {
67
- const validation = validateFrameParams(params, profile);
68
- if (!validation.valid) {
69
- throw new Error(`Invalid frame parameters: ${validation.errors.join('; ')}`);
70
- }
71
-
72
- const lines = profile.frameSchema!.keyOrder.map(
73
- (key) => `${key}=${String(params[key])}`
74
- );
75
-
76
- return lines.join('\n');
77
- }
78
-
79
- /**
80
- * Computes the frame hash from a canonical frame string.
81
- *
82
- * @returns Hash in format "sha256:<64 hex chars>"
83
- */
84
- export function frameHash(canonicalFrameString: string): string {
85
- const hash = createHash('sha256').update(canonicalFrameString, 'utf8').digest('hex');
86
- return `sha256:${hash}`;
87
- }
88
-
89
- /**
90
- * Convenience: builds canonical frame and computes hash in one step.
91
- */
92
- export function computeFrameHash(params: AgentFrameParams, profile: AgentProfile): string {
93
- return frameHash(canonicalFrame(params, profile));
94
- }
95
-
96
- // ─── v0.4 Bounds Functions ────────────────────────────────────────────────────
97
-
98
- /**
99
- * Validates bounds parameters against the profile's boundsSchema (v0.4).
26
+ * Validates bounds parameters against the profile's boundsSchema.
100
27
  */
101
28
  export function validateBoundsParams(
102
29
  params: AgentBoundsParams,
@@ -135,25 +62,27 @@ export function validateBoundsParams(
135
62
  return { valid: errors.length === 0, errors };
136
63
  }
137
64
 
65
+ // ─── Scope Validation ─────────────────────────────────────────────────────────
66
+
138
67
  /**
139
- * Validates context parameters against the profile's contextSchema (v0.4).
68
+ * Validates scope parameters against the profile's scopeSchema.
140
69
  */
141
- export function validateContextParams(
142
- params: AgentContextParams,
70
+ export function validateScopeParams(
71
+ params: AgentScopeParams,
143
72
  profile: AgentProfile
144
73
  ): { valid: boolean; errors: string[] } {
145
74
  const errors: string[] = [];
146
75
 
147
- if (!profile.contextSchema) {
148
- // No contextSchema is valid — empty context
76
+ if (!profile.scopeSchema) {
77
+ // No scopeSchema is valid — empty scope
149
78
  if (Object.keys(params).length > 0) {
150
- errors.push('Profile does not have a contextSchema but context params were provided');
79
+ errors.push('Profile does not have a scopeSchema but scope params were provided');
151
80
  }
152
81
  return { valid: errors.length === 0, errors };
153
82
  }
154
83
 
155
84
  // Check all required fields are present
156
- for (const [fieldName, fieldDef] of Object.entries(profile.contextSchema.fields)) {
85
+ for (const [fieldName, fieldDef] of Object.entries(profile.scopeSchema.fields)) {
157
86
  if (fieldDef.required && !(fieldName in params)) {
158
87
  errors.push(`Missing required field: ${fieldName}`);
159
88
  }
@@ -161,9 +90,9 @@ export function validateContextParams(
161
90
 
162
91
  // Validate each provided field
163
92
  for (const [field, value] of Object.entries(params)) {
164
- const fieldDef = profile.contextSchema.fields[field];
93
+ const fieldDef = profile.scopeSchema.fields[field];
165
94
  if (!fieldDef) {
166
- errors.push(`Unknown field "${field}" not defined in contextSchema of profile ${profile.id}`);
95
+ errors.push(`Unknown field "${field}" not defined in scopeSchema of profile ${profile.id}`);
167
96
  continue;
168
97
  }
169
98
 
@@ -179,69 +108,7 @@ export function validateContextParams(
179
108
  return { valid: errors.length === 0, errors };
180
109
  }
181
110
 
182
- /**
183
- * Authoring-time check for `per_transaction` bound `requiredFor` (v0.8+):
184
- * entries must name a real `action_type` from the profile's declared
185
- * `boundsSchema.actionTypes` registry, and the field is only meaningful on
186
- * `per_transaction` bounds (see `BoundType`'s doc comment for why the other
187
- * kinds don't carry it).
188
- *
189
- * This is NOT wired into `registerProfile` or any enforcement path — nothing
190
- * in hap-core validates profile JSON at load time today (the pre-existing
191
- * `requiredFor` on a context/scope constraint has the same gap), so adding
192
- * an automatic refusal here would be new behaviour beyond this change's
193
- * scope. Call it explicitly from an authoring tool, a hap-profiles CI check,
194
- * or a test, to catch a `requiredFor` entry that can never match (misspelled
195
- * action type) or is attached to a bound kind that silently ignores it.
196
- *
197
- * Returns an empty array when the profile has no boundsSchema or nothing to
198
- * check — never throws.
199
- */
200
- export function validateBoundsRequiredFor(profile: AgentProfile): string[] {
201
- const errors: string[] = [];
202
- const boundsSchema = profile.boundsSchema;
203
- if (!boundsSchema) return errors;
204
-
205
- const actionTypes = boundsSchema.actionTypes ? [...boundsSchema.actionTypes] : undefined;
206
-
207
- for (const [fieldName, fieldDef] of Object.entries(boundsSchema.fields)) {
208
- const bt = fieldDef.boundType as (BoundType & { requiredFor?: unknown }) | undefined;
209
- if (!bt) continue;
210
- const requiredFor = (bt as { requiredFor?: unknown }).requiredFor;
211
- if (requiredFor === undefined) continue;
212
-
213
- if (!Array.isArray(requiredFor) || !requiredFor.every((v) => typeof v === 'string')) {
214
- errors.push(`Bound "${fieldName}": requiredFor must be a string array.`);
215
- continue;
216
- }
217
-
218
- if (bt.kind !== 'per_transaction') {
219
- errors.push(
220
- `Bound "${fieldName}": requiredFor is only meaningful on a per_transaction bound ` +
221
- `(found on kind "${bt.kind}") — every enforcement point ignores it there. Remove it.`,
222
- );
223
- continue;
224
- }
225
-
226
- if (actionTypes) {
227
- for (const actionType of requiredFor) {
228
- if (!actionTypes.includes(actionType)) {
229
- errors.push(
230
- `Bound "${fieldName}": requiredFor names action type "${actionType}", which is not ` +
231
- `in this profile's boundsSchema.actionTypes registry [${actionTypes.join(', ')}]. ` +
232
- `It can never match a real execution and the requirement is dead.`,
233
- );
234
- }
235
- }
236
- }
237
- // No actionTypes registry declared: membership cannot be checked (same
238
- // tolerance the AS receipt route applies to pre-registry profiles).
239
- }
240
-
241
- return errors;
242
- }
243
-
244
- // ─── Value Encoding (normative, v0.5+) ───────────────────────────────────────
111
+ // ─── Value Encoding (normative, protocol.md → *Bounds & Scope Canonicalization*) ─
245
112
 
246
113
  /**
247
114
  * Thrown when a value cannot be canonicalized at all — currently only for raw
@@ -255,10 +122,10 @@ export function validateBoundsRequiredFor(profile: AgentProfile): string[] {
255
122
  * does not faithfully represent the input."
256
123
  */
257
124
  export class CanonicalValueError extends Error {
258
- readonly code: 'BOUNDS_INVALID_VALUE' | 'CONTEXT_INVALID_VALUE';
125
+ readonly code: 'BOUNDS_INVALID_VALUE' | 'SCOPE_INVALID_VALUE';
259
126
  readonly field: string;
260
127
 
261
- constructor(code: 'BOUNDS_INVALID_VALUE' | 'CONTEXT_INVALID_VALUE', field: string, message: string) {
128
+ constructor(code: 'BOUNDS_INVALID_VALUE' | 'SCOPE_INVALID_VALUE', field: string, message: string) {
262
129
  super(message);
263
130
  this.name = 'CanonicalValueError';
264
131
  this.code = code;
@@ -275,7 +142,9 @@ export class CanonicalValueError extends Error {
275
142
  * - every byte outside printable ASCII 0x20–0x7E
276
143
  *
277
144
  * LF and CR are deliberately NOT in this list: they are refused upstream, so
278
- * encoding them is unreachable (v0.7 removed the spec's contradiction here).
145
+ * encoding them is unreachable (v0.7 removed the spec's own contradiction here
146
+ * — earlier versions listed them in the percent-encode rule too, which read
147
+ * as permission to encode what the line above already refuses).
279
148
  *
280
149
  * This runs at canonicalization time only. Stored values keep the human's
281
150
  * original bytes.
@@ -294,7 +163,7 @@ function percentEncodeCanonicalValue(raw: string): string {
294
163
  }
295
164
 
296
165
  /**
297
- * The one place `key=value` records are built for bounds and context.
166
+ * The one place `key=value` records are built for bounds and scope.
298
167
  *
299
168
  * Rules applied here (all normative, protocol.md → *Bounds & Scope
300
169
  * Canonicalization*):
@@ -306,7 +175,7 @@ function percentEncodeCanonicalValue(raw: string): string {
306
175
  * - a key with no value is OMITTED entirely — it emits no record
307
176
  *
308
177
  * On the omission rule: required keys are guaranteed present by the caller's
309
- * validation (`validateBoundsParams` / `validateContextParams` reject a missing
178
+ * validation (`validateBoundsParams` / `validateScopeParams` reject a missing
310
179
  * required field), so "explicit inclusion of all required keys" still holds.
311
180
  * What remains are *optional* keys the human never set. Rendering those as the
312
181
  * literal string "undefined" — the pre-fix behaviour — hashed a JavaScript
@@ -319,7 +188,7 @@ function percentEncodeCanonicalValue(raw: string): string {
319
188
  function canonicalRecords(
320
189
  params: Record<string, string | number | undefined>,
321
190
  keyOrder: string[],
322
- code: 'BOUNDS_INVALID_VALUE' | 'CONTEXT_INVALID_VALUE',
191
+ code: 'BOUNDS_INVALID_VALUE' | 'SCOPE_INVALID_VALUE',
323
192
  ): string {
324
193
  const lines: string[] = [];
325
194
 
@@ -361,30 +230,30 @@ export function canonicalBounds(params: AgentBoundsParams, profile: AgentProfile
361
230
  }
362
231
 
363
232
  /**
364
- * Builds the canonical context string from parameters.
365
- * Keys are ordered per profile's contextSchema.keyOrder; values are encoded per
233
+ * Builds the canonical scope string from parameters.
234
+ * Keys are ordered per profile's scopeSchema.keyOrder; values are encoded per
366
235
  * protocol.md → *Value encoding* (see `canonicalRecords`).
367
- * For empty context (no contextSchema or no fields), returns "".
236
+ * For empty scope (no scopeSchema or no fields), returns "".
368
237
  *
369
238
  * @throws Error if any field fails validation
370
- * @throws CanonicalValueError (code CONTEXT_INVALID_VALUE) if a value carries a raw LF/CR
239
+ * @throws CanonicalValueError (code SCOPE_INVALID_VALUE) if a value carries a raw LF/CR
371
240
  */
372
- export function canonicalContext(params: AgentContextParams, profile: AgentProfile): string {
373
- // No contextSchema or no fields → empty context
374
- if (!profile.contextSchema || Object.keys(profile.contextSchema.fields).length === 0) {
241
+ export function canonicalScope(params: AgentScopeParams, profile: AgentProfile): string {
242
+ // No scopeSchema or no fields → empty scope
243
+ if (!profile.scopeSchema || Object.keys(profile.scopeSchema.fields).length === 0) {
375
244
  return '';
376
245
  }
377
246
 
378
- const validation = validateContextParams(params, profile);
247
+ const validation = validateScopeParams(params, profile);
379
248
  if (!validation.valid) {
380
- throw new Error(`Invalid context parameters: ${validation.errors.join('; ')}`);
249
+ throw new Error(`Invalid scope parameters: ${validation.errors.join('; ')}`);
381
250
  }
382
251
 
383
- return canonicalRecords(params, profile.contextSchema.keyOrder, 'CONTEXT_INVALID_VALUE');
252
+ return canonicalRecords(params, profile.scopeSchema.keyOrder, 'SCOPE_INVALID_VALUE');
384
253
  }
385
254
 
386
255
  /**
387
- * Computes the bounds hash from bounds parameters (v0.4).
256
+ * Computes the bounds hash from bounds parameters.
388
257
  *
389
258
  * @returns Hash in format "sha256:<64 hex chars>"
390
259
  */
@@ -395,14 +264,14 @@ export function computeBoundsHash(params: AgentBoundsParams, profile: AgentProfi
395
264
  }
396
265
 
397
266
  /**
398
- * Computes the context hash from context parameters (v0.4).
399
- * For empty context {}, returns the sha256 of "":
267
+ * Computes the scope hash from scope parameters.
268
+ * For empty scope {}, returns the sha256 of "":
400
269
  * "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
401
270
  *
402
271
  * @returns Hash in format "sha256:<64 hex chars>"
403
272
  */
404
- export function computeContextHash(params: AgentContextParams, profile: AgentProfile): string {
405
- const canonical = canonicalContext(params, profile);
273
+ export function computeScopeHash(params: AgentScopeParams, profile: AgentProfile): string {
274
+ const canonical = canonicalScope(params, profile);
406
275
  const hash = createHash('sha256').update(canonical, 'utf8').digest('hex');
407
276
  return `sha256:${hash}`;
408
277
  }