@suveren/gateway 0.6.2 → 0.6.4

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 (36) hide show
  1. package/content/integrations/calendar.json +6 -3
  2. package/content/integrations/crm.json +6 -3
  3. package/content/integrations/deploy-github.json +5 -1
  4. package/content/integrations/gmail.json +16 -6
  5. package/content/integrations/linkedin.json +75 -16
  6. package/content/integrations/records.json +76 -14
  7. package/dist/mcp-server/http.mjs +215 -28
  8. package/dist/ui/assets/index-DIcuh0I4.css +1 -0
  9. package/dist/ui/assets/index-Dl5isaOV.js +105 -0
  10. package/dist/ui/index.html +2 -2
  11. package/dist/ui/mockups/audit-and-header.html +192 -0
  12. package/node_modules/@hap/core/dist/index.d.mts +139 -6
  13. package/node_modules/@hap/core/dist/index.d.ts +139 -6
  14. package/node_modules/@hap/core/dist/index.js +93 -0
  15. package/node_modules/@hap/core/dist/index.mjs +88 -0
  16. package/node_modules/@hap/core/package.json +1 -1
  17. package/node_modules/@hap/core/src/content-binding.ts +168 -0
  18. package/node_modules/@hap/core/src/types.ts +85 -5
  19. package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +8 -0
  20. package/node_modules/jose/dist/webapi/jwe/general/encrypt.js +1 -1
  21. package/node_modules/jose/dist/webapi/jwks/local.js +3 -3
  22. package/node_modules/jose/dist/webapi/jwks/remote.js +1 -1
  23. package/node_modules/jose/dist/webapi/key/generate_key_pair.js +3 -3
  24. package/node_modules/jose/dist/webapi/key/generate_secret.js +2 -2
  25. package/node_modules/jose/dist/webapi/lib/asn1.js +3 -3
  26. package/node_modules/jose/dist/webapi/lib/jwe_algorithms.js +6 -15
  27. package/node_modules/jose/dist/webapi/lib/jws_algorithms.js +2 -5
  28. package/node_modules/jose/dist/webapi/lib/key.js +1 -1
  29. package/node_modules/jose/dist/webapi/lib/key_algorithm.js +8 -8
  30. package/node_modules/jose/package.json +1 -1
  31. package/package.json +2 -2
  32. package/profiles/deploy/0.7.profile.json +2 -1
  33. package/profiles/email/0.5.profile.json +200 -0
  34. package/profiles/index.json +1 -0
  35. package/dist/ui/assets/index-DoZvxHLN.js +0 -105
  36. package/dist/ui/assets/index-JHaCddDE.css +0 -1
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Suveren</title>
7
- <script type="module" crossorigin src="/assets/index-DoZvxHLN.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-JHaCddDE.css">
7
+ <script type="module" crossorigin src="/assets/index-Dl5isaOV.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-DIcuh0I4.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -0,0 +1,192 @@
1
+ <!doctype html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Mockup — header + receipts</title>
7
+ <link rel="stylesheet" href="/src/styles/design-system.css">
8
+ <style>
9
+ /* Tokens mirrored so the mockup renders standalone if the stylesheet 404s. */
10
+ :root[data-theme="dark"]{
11
+ --text-primary:#fff; --text-secondary:#fff; --text-tertiary:#fff; --text-muted:#888;
12
+ --bg-main:#0a0a0a; --bg-elevated:#1a1a1a; --bg-input:#141414;
13
+ --accent:#888; --accent-subtle:#1f1f1f; --accent-hover:#bbb;
14
+ --border:#2a2a2a; --danger:#dc2626; --success:#16a34a; --warning:#d97706;
15
+ }
16
+ body{background:var(--bg-main);color:var(--text-primary);font-family:Inter,-apple-system,system-ui,sans-serif;margin:0;padding:0 0 4rem}
17
+ .wrap{max-width:920px;margin:0 auto;padding:0 1.5rem}
18
+ h2.section{font-size:.8rem;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);
19
+ margin:2.5rem 0 .75rem;font-weight:600}
20
+ .note{font-size:.82rem;color:var(--text-muted);line-height:1.6;margin:0 0 1rem;max-width:70ch}
21
+ .note strong{color:var(--text-secondary)}
22
+
23
+ /* ── top nav ── */
24
+ .top-nav{background:rgba(10,10,10,.92);border-bottom:1px solid var(--border);
25
+ backdrop-filter:blur(8px);position:sticky;top:0;z-index:10}
26
+ .top-nav-inner{max-width:100%;padding:0 1.5rem;height:3.5rem;display:flex;align-items:center;gap:.6rem}
27
+ .logo{font-weight:700;font-size:1.05rem;letter-spacing:-.02em}
28
+ .version-badge{font-size:.7rem;padding:.15rem .45rem;border:1px solid var(--border);
29
+ border-radius:5px;color:var(--text-muted);letter-spacing:.02em}
30
+ .ver{font-size:.72rem;color:var(--text-muted);font-variant-numeric:tabular-nums;
31
+ padding:.15rem .4rem;border-radius:5px;background:var(--accent-subtle)}
32
+ .ver.stale{color:var(--warning);border:1px solid rgba(217,119,6,.35);background:transparent}
33
+ .nav-spacer{flex:1}
34
+ .user-chip{display:flex;align-items:center;gap:.4rem;font-size:.85rem;color:var(--text-secondary)}
35
+ .icon-btn{background:none;border:1px solid var(--border);color:var(--text-secondary);
36
+ border-radius:6px;padding:.28rem .5rem;cursor:pointer;font-size:.85rem}
37
+
38
+ /* ── receipt card ── */
39
+ .card{background:var(--bg-elevated);border:1px solid var(--border);border-radius:10px;
40
+ padding:.85rem 1rem;margin-bottom:.6rem}
41
+ .r-top{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}
42
+ .r-what{font-size:.95rem;font-weight:600;letter-spacing:-.01em}
43
+ .r-time{margin-left:auto;font-size:.75rem;color:var(--text-muted);white-space:nowrap}
44
+ .r-sub{font-size:.82rem;color:var(--text-muted);margin-top:.2rem}
45
+ .r-sub b{color:var(--text-secondary);font-weight:500}
46
+ .badge{font-size:.68rem;padding:.12rem .4rem;border-radius:4px;border:1px solid var(--border);
47
+ color:var(--text-muted);white-space:nowrap}
48
+ .badge.mode{border-color:rgba(136,136,136,.4)}
49
+ .badge.bound{color:var(--success);border-color:rgba(22,163,74,.4)}
50
+ .r-foot{display:flex;align-items:center;gap:.75rem;margin-top:.55rem;
51
+ padding-top:.55rem;border-top:1px solid var(--border);flex-wrap:wrap}
52
+ .r-link{font-size:.78rem;color:var(--text-secondary);text-decoration:none;
53
+ border-bottom:1px solid var(--border)}
54
+ .r-link:hover{color:var(--accent-hover);border-color:var(--accent-hover)}
55
+ .r-id{font-family:'SF Mono',Monaco,monospace;font-size:.7rem;color:var(--text-muted)}
56
+ details.more{margin-top:.5rem}
57
+ details.more summary{cursor:pointer;font-size:.75rem;color:var(--text-muted);list-style:none}
58
+ details.more summary::-webkit-details-marker{display:none}
59
+ details.more summary::before{content:'▸ ';}
60
+ details.more[open] summary::before{content:'▾ ';}
61
+ .kv{font-size:.75rem;color:var(--text-muted);margin-top:.4rem;line-height:1.7;
62
+ font-family:'SF Mono',Monaco,monospace;word-break:break-all}
63
+ .old{opacity:.75}
64
+ .old .mono{font-family:'SF Mono',Monaco,monospace;font-size:.7rem;color:var(--text-muted);word-break:break-all}
65
+ </style>
66
+ </head>
67
+ <body>
68
+
69
+ <!-- ══ HEADER ══ -->
70
+ <nav class="top-nav">
71
+ <div class="top-nav-inner">
72
+ <span class="logo">Suveren</span>
73
+ <span class="version-badge">Local Gateway</span>
74
+ <span class="ver" title="Running gateway version">v0.6.3</span>
75
+ <span class="nav-spacer"></span>
76
+ <span class="user-chip"><strong>Andreas</strong></span>
77
+ <button class="icon-btn">☾</button>
78
+ <button class="icon-btn">Logout</button>
79
+ </div>
80
+ </nav>
81
+
82
+ <div class="wrap">
83
+
84
+ <h2 class="section">1 · Header</h2>
85
+ <p class="note">
86
+ The group/domain pair is gone — it was duplicated verbatim in the sidebar's <b>Active context</b>,
87
+ and the raw UUID was the least readable thing on the page. The version badge moves up beside
88
+ <strong>Local Gateway</strong> and now shows while signed <em>in</em> (today it only renders when
89
+ signed out, which is backwards — you need it when you're working, not on the login screen).
90
+ <br><br>
91
+ Bonus: the update checker already knows when a newer gateway exists, so the badge can carry that
92
+ for free — <span class="ver stale">v0.6.3 → 0.6.4</span> instead of a silent stale install.
93
+ </p>
94
+
95
+ <h2 class="section">2 · Receipts — now</h2>
96
+ <div class="card old">
97
+ <div style="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap">
98
+ <span class="badge">Email</span>
99
+ <span class="mono">github.com/humanagencyprotocol/hap-profiles/email@0.5</span>
100
+ <span class="badge">gmail__send_message</span>
101
+ <span class="r-time">3 Aug, 13:50</span>
102
+ </div>
103
+ <div class="kv">action_type=send · recipient_count=2 · allowed_recipients=andreas@sublin.app,andreasschadauer@googlemail.com · allowed_domains=googlemail.com,sublin.app</div>
104
+ <div style="display:flex;gap:1rem;font-size:.75rem;color:var(--text-muted);margin-top:.4rem">
105
+ <span>Daily: 2 calls, $0</span><span>Monthly: 2 calls, $0</span>
106
+ </div>
107
+ <div class="mono" style="margin-top:.4rem">sha256:3efa65406502c45973935a4a6433fe7af97dc6f862891b6105e481361239ba33</div>
108
+ </div>
109
+ <p class="note">
110
+ Three of the four most prominent things are machine identifiers. The profile URL is the widest
111
+ element on the row, the same on every card, and tells you nothing. Nowhere does it say
112
+ <strong>an email went to these two people</strong> — and there's no way to reach the receipt.
113
+ </p>
114
+
115
+ <h2 class="section">3 · Receipts — proposed</h2>
116
+
117
+ <div class="card">
118
+ <div class="r-top">
119
+ <span class="r-what">Email sent</span>
120
+ <span class="badge mode">review</span>
121
+ <span class="badge bound">content bound</span>
122
+ <span class="r-time">today, 13:50</span>
123
+ </div>
124
+ <div class="r-sub">to <b>andreas@sublin.app</b>, <b>andreasschadauer@googlemail.com</b></div>
125
+ <div class="r-foot">
126
+ <a class="r-link" href="#">View receipt ↗</a>
127
+ <span class="r-id">14415e75</span>
128
+ <span class="badge">email@0.5</span>
129
+ </div>
130
+ <details class="more">
131
+ <summary>Technical</summary>
132
+ <div class="kv">
133
+ action · gmail__send_message<br>
134
+ profile · github.com/humanagencyprotocol/hap-profiles/email@0.5<br>
135
+ context · recipient_count=2 · allowed_domains=googlemail.com,sublin.app<br>
136
+ usage · daily 2 calls · monthly 2 calls<br>
137
+ authorization · sha256:3efa6540…1239ba33
138
+ </div>
139
+ </details>
140
+ </div>
141
+
142
+ <div class="card">
143
+ <div class="r-top">
144
+ <span class="r-what">Record created</span>
145
+ <span class="badge mode">automatic</span>
146
+ <span class="badge bound">content bound</span>
147
+ <span class="r-time">today, 11:02</span>
148
+ </div>
149
+ <div class="r-sub"><b>Q3 Strategy</b> · note</div>
150
+ <div class="r-foot">
151
+ <a class="r-link" href="#">View receipt ↗</a>
152
+ <span class="r-id">8c21b40e</span>
153
+ <span class="badge">records@0.4</span>
154
+ </div>
155
+ </div>
156
+
157
+ <div class="card">
158
+ <div class="r-top">
159
+ <span class="r-what">Deployment released</span>
160
+ <span class="badge mode">review</span>
161
+ <span class="r-time">2 Aug, 16:20</span>
162
+ </div>
163
+ <div class="r-sub">to <b>production</b> · humanagencyprotocol/hap-protocol</div>
164
+ <div class="r-foot">
165
+ <a class="r-link" href="#">View receipt ↗</a>
166
+ <span class="r-id">c9f01a77</span>
167
+ <span class="badge">deploy@0.7</span>
168
+ </div>
169
+ </div>
170
+
171
+ <p class="note">
172
+ <strong>What changed.</strong> The headline is the <em>action in plain words</em>, derived from
173
+ the profile plus action type — not the tool name. The one line under it is the thing you'd
174
+ actually want to check: who it went to, what was written, where it deployed. Machine identifiers
175
+ move into a collapsed <em>Technical</em> block, and the profile version survives as a small badge
176
+ because after today it tells you whether recipients were bound.
177
+ <br><br>
178
+ <strong>View receipt</strong> links to <code>{spUrl}/r/{id}</code> — the AS already publishes
179
+ <code>spUrl</code> on <code>/health</code>, so nothing new is needed. <strong>content bound</strong>
180
+ appears when the receipt carries a content hash, which is the difference between "this happened"
181
+ and "this exact content was approved".
182
+ </p>
183
+
184
+ <p class="note">
185
+ <strong>One thing I'd flag:</strong> "Email sent" for a <code>create_draft</code> call would be a
186
+ lie. The plain-language label has to come from the manifest's <code>action_type</code>, not be
187
+ guessed from the profile — otherwise this trades an ugly card for a misleading one.
188
+ </p>
189
+
190
+ </div>
191
+ </body>
192
+ </html>
@@ -267,22 +267,63 @@ interface ProfileBoundsField {
267
267
  * backward compatibility). The gateway computes the hash; the SP only ever
268
268
  * receives the hash, never the content, so HAP's privacy-minimal design holds.
269
269
  *
270
- * The profile declares only the *policy* — whether to bind and how to
271
- * canonicalize. It does NOT name the tool field: that is tool-specific and is
272
- * resolved at runtime (the same content-field resolver the footer uses for
273
- * `kind:"text"`; the whole record payload for `kind:"jcs"`).
270
+ * At `version:"1"` the profile declares only the *policy* — whether to bind and
271
+ * how to canonicalize. It does NOT name the tool field: that is tool-specific
272
+ * and is resolved at runtime (the same content-field resolver the footer uses
273
+ * for `kind:"text"`; the whole record payload for `kind:"jcs"`).
274
+ *
275
+ * At `version:"2"` the profile additionally declares WHICH fields are bound (see
276
+ * {@link ContentBinding.fields}). Neither v1 mode is the general case: `text`
277
+ * binds one field and leaves everything beside it unbound, while `jcs` over the
278
+ * whole payload is checkable only by a party that already knows the whole
279
+ * payload — an email recipient holds the body, the subject and their own
280
+ * address, but not `bcc`. The general case is a declared subset, chosen so the
281
+ * intended verifier can reproduce it.
274
282
  */
275
283
  interface ContentBinding {
276
284
  /** Canonicalization version. A verifier MUST pin the version named here. */
277
285
  version: string;
278
286
  /**
279
- * - 'jcs' → structured writes: RFC 8785 JCS over the record payload.
287
+ * - 'jcs' → structured writes: RFC 8785 JCS over the record payload
288
+ * (v1) or over the object built from {@link fields} (v2).
280
289
  * - 'text' → free text: NFC + LF + trailing-whitespace strip (see
281
290
  * canonicalizeText), auto-detected content field.
282
291
  */
283
292
  kind: 'jcs' | 'text';
284
293
  /** text only: hash the content BEFORE any appended Suveren footer. */
285
294
  pre_footer?: boolean;
295
+ /**
296
+ * v2 only — the tool-argument keys this binding covers, and the complete
297
+ * statement of what a verifier must reproduce. The Gatekeeper builds an
298
+ * object from exactly these keys and canonicalizes it by `kind`.
299
+ *
300
+ * Adding or removing an entry changes every resulting hash, so it is a
301
+ * BREAKING profile change requiring a version bump, never a silent edit.
302
+ *
303
+ * Choose the subset by one rule: bind everything the approving human is
304
+ * shown, and nothing the intended verifier cannot see.
305
+ */
306
+ fields?: string[];
307
+ /**
308
+ * v2 only — the subset of {@link fields} whose absence is a fault rather than
309
+ * a fact. An absent OPTIONAL field is omitted from the hashed object (an
310
+ * email legitimately has no `cc`); an absent REQUIRED field means the call is
311
+ * not the call this profile thinks it is, and MUST refuse rather than hash a
312
+ * partial object that reads exactly like a complete one.
313
+ *
314
+ * MUST be a subset of `fields`. Absent → every field is optional, and only a
315
+ * wholly empty selection refuses.
316
+ */
317
+ required_fields?: string[];
318
+ /**
319
+ * v2 only — the action types this binding covers, using the same vocabulary
320
+ * as {@link ProfileBoundsField.appliesTo}. A profile gates more than its
321
+ * content-bearing calls: `email` also gates deletes, which carry an id and no
322
+ * content, and applying a field binding to those would refuse them.
323
+ *
324
+ * Absent → the binding applies to every gated action under the profile.
325
+ */
326
+ appliesTo?: string[];
286
327
  }
287
328
  /**
288
329
  * Context field definition within a v0.4 profile.
@@ -360,6 +401,42 @@ interface AgentProfile {
360
401
  name?: string;
361
402
  version: string;
362
403
  description: string;
404
+ /**
405
+ * One line on what this version changed and why it matters to the person
406
+ * granting authority — written for them, not for a changelog.
407
+ *
408
+ * A grant pins the profile version it was signed against, so authorities
409
+ * issued before a newer version keep their old terms indefinitely and
410
+ * nothing prompts an upgrade. A version number alone does not motivate one:
411
+ * "email@0.4 → 0.5" says nothing, while "binds recipients, not only the
412
+ * message body" says what the older grant is not protecting.
413
+ *
414
+ * Belongs on the profile because the profile is what changed; a UI cannot
415
+ * know why 0.5 exists. Absent → surfaces show the version alone.
416
+ */
417
+ whatsNew?: string;
418
+ /**
419
+ * Whether receipts under this profile may be looked up BY THEIR CONTENT — a
420
+ * verifier holding the content supplies its hash and learns which receipts
421
+ * bind it, without needing a receipt id.
422
+ *
423
+ * OFF unless declared, and that default is the point. The lookup is a
424
+ * confirmation oracle: given a guess at the content it says whether that
425
+ * content was authorized. Where the bound content has low entropy this is
426
+ * disclosure, not verification — guessing a message body is hopeless,
427
+ * guessing `production` takes a second. It is the same enumeration hazard
428
+ * recorded for per-field commitments, arriving from the other direction.
429
+ *
430
+ * Enable only when the bound content is unguessable enough that producing it
431
+ * is equivalent to already having it: prose, an artifact URL, a whole record
432
+ * payload. Never for a binding over a short value drawn from a small set.
433
+ *
434
+ * Why it must exist at all: most consequential actions cannot carry their
435
+ * receipt id. A released build was built before the receipt existed, a
436
+ * content-addressed artifact would change identity if the id were added, and
437
+ * a forwarded message has usually lost the footer that carried it.
438
+ */
439
+ receipt_lookup?: boolean;
363
440
  /**
364
441
  * v0.3 frame schema (deprecated, kept for backward compat).
365
442
  * Used when boundsSchema is not present.
@@ -596,6 +673,12 @@ declare function canonicalize(value: unknown): string;
596
673
  * (Unicode NFC, LF line endings, trailing per-line whitespace stripped,
597
674
  * trailing blank lines removed), taken pre-footer when `pre_footer` is set.
598
675
  *
676
+ * At `version:"2"` the profile also declares WHICH tool arguments are bound
677
+ * ({@link selectBoundFields}), so a receipt can commit to an email's recipients
678
+ * and not only its prose — while still omitting what the intended verifier
679
+ * cannot see. Every string entering the hashed object is canonicalized by the
680
+ * same `text` rule, so a delivered copy with CRLF endings still reproduces it.
681
+ *
599
682
  * Both Node and the browser produce byte-identical output: JCS relies only on
600
683
  * environment-independent primitives, and the text rule uses String.normalize +
601
684
  * plain string ops. The SHA-256 is computed with Node `crypto` here (the same
@@ -626,6 +709,56 @@ declare function contentCanonicalBytes(kind: ContentBinding['kind'], content: Re
626
709
  * @param content the record payload (jcs) or the resolved text field (text)
627
710
  */
628
711
  declare function computeContentHash(binding: ContentBinding, content: Record<string, unknown> | string): string;
712
+ /** Why a field binding refused. Every case is fail-closed by design. */
713
+ type ContentBindingErrorCode =
714
+ /** The profile declares version:"2" with no usable `fields` list. */
715
+ 'NO_FIELDS_DECLARED'
716
+ /** `required_fields` names something absent from `fields`. */
717
+ | 'REQUIRED_FIELD_NOT_DECLARED'
718
+ /** A required field was absent or empty at call time. */
719
+ | 'MISSING_REQUIRED_FIELD'
720
+ /** No declared field carried a value — the hash would commit to nothing. */
721
+ | 'EMPTY_BINDING';
722
+ /**
723
+ * A field binding could not be computed. ALWAYS a refusal, never a downgrade:
724
+ * the alternative is a receipt that verifies while proving less than it appears
725
+ * to, which is the failure content binding exists to prevent.
726
+ */
727
+ declare class ContentBindingError extends Error {
728
+ readonly code: ContentBindingErrorCode;
729
+ /** The offending field, when the code names one. */
730
+ readonly field?: string;
731
+ constructor(code: ContentBindingErrorCode, message: string, field?: string);
732
+ }
733
+ /** True when this binding selects a declared subset (v2) rather than v1's implicit scope. */
734
+ declare function isFieldBinding(binding: ContentBinding): boolean;
735
+ /**
736
+ * Whether a field binding covers this action type, per the profile's `appliesTo`.
737
+ *
738
+ * Read STRICTLY — an undeclared action type is NOT covered. This differs from
739
+ * how bounds read the same key (there, an unknown action type enforces the
740
+ * bound, because an extra limit is safe). Here the two directions are not
741
+ * symmetric: applying a field binding to a call that carries no content refuses
742
+ * a legitimate action, so an unknown action type must fall outside rather than
743
+ * inside. Callers are expected to warn on the undeclared case — it is a
744
+ * manifest bug either way.
745
+ */
746
+ declare function bindingAppliesTo(binding: ContentBinding, actionType: string | undefined): boolean;
747
+ /**
748
+ * Build the object a v2 binding hashes: exactly the declared `fields` that
749
+ * carry a value, canonicalized. Exported so a verifier can construct the same
750
+ * object from what they hold and see it before hashing.
751
+ *
752
+ * Throws {@link ContentBindingError} rather than returning a partial result —
753
+ * see that class for why refusing is the only safe outcome.
754
+ */
755
+ declare function selectBoundFields(binding: ContentBinding, args: Record<string, unknown>): Record<string, unknown>;
756
+ /**
757
+ * Compute a v2 field-binding hash from raw tool arguments: select the declared
758
+ * subset, then hash it by the declared `kind`. Convenience over
759
+ * {@link selectBoundFields} + {@link computeContentHash} for the common path.
760
+ */
761
+ declare function computeFieldsContentHash(binding: ContentBinding, args: Record<string, unknown>): string;
629
762
 
630
763
  /**
631
764
  * Intent hashing + intent-disclosure binding (HAP v0.5).
@@ -917,4 +1050,4 @@ declare function listProfiles(): string[];
917
1050
  declare function getAllProfiles(): AgentProfile[];
918
1051
  declare function clearProfiles(): void;
919
1052
 
920
- export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, type ContentBinding, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ResolvedDomain, type Subject, type SubjectValidation, attestationId, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, deriveIdentityLine, encodeAttestationBlob, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isV4Attestation, listProfiles, registerProfile, validateBoundsParams, validateContextParams, validateFrameParams, validateSubject, verify, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash };
1053
+ export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ResolvedDomain, type Subject, type SubjectValidation, attestationId, bindingAppliesTo, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFieldsContentHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, deriveIdentityLine, encodeAttestationBlob, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isFieldBinding, isV4Attestation, listProfiles, registerProfile, selectBoundFields, validateBoundsParams, validateContextParams, validateFrameParams, validateSubject, verify, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash };
@@ -267,22 +267,63 @@ interface ProfileBoundsField {
267
267
  * backward compatibility). The gateway computes the hash; the SP only ever
268
268
  * receives the hash, never the content, so HAP's privacy-minimal design holds.
269
269
  *
270
- * The profile declares only the *policy* — whether to bind and how to
271
- * canonicalize. It does NOT name the tool field: that is tool-specific and is
272
- * resolved at runtime (the same content-field resolver the footer uses for
273
- * `kind:"text"`; the whole record payload for `kind:"jcs"`).
270
+ * At `version:"1"` the profile declares only the *policy* — whether to bind and
271
+ * how to canonicalize. It does NOT name the tool field: that is tool-specific
272
+ * and is resolved at runtime (the same content-field resolver the footer uses
273
+ * for `kind:"text"`; the whole record payload for `kind:"jcs"`).
274
+ *
275
+ * At `version:"2"` the profile additionally declares WHICH fields are bound (see
276
+ * {@link ContentBinding.fields}). Neither v1 mode is the general case: `text`
277
+ * binds one field and leaves everything beside it unbound, while `jcs` over the
278
+ * whole payload is checkable only by a party that already knows the whole
279
+ * payload — an email recipient holds the body, the subject and their own
280
+ * address, but not `bcc`. The general case is a declared subset, chosen so the
281
+ * intended verifier can reproduce it.
274
282
  */
275
283
  interface ContentBinding {
276
284
  /** Canonicalization version. A verifier MUST pin the version named here. */
277
285
  version: string;
278
286
  /**
279
- * - 'jcs' → structured writes: RFC 8785 JCS over the record payload.
287
+ * - 'jcs' → structured writes: RFC 8785 JCS over the record payload
288
+ * (v1) or over the object built from {@link fields} (v2).
280
289
  * - 'text' → free text: NFC + LF + trailing-whitespace strip (see
281
290
  * canonicalizeText), auto-detected content field.
282
291
  */
283
292
  kind: 'jcs' | 'text';
284
293
  /** text only: hash the content BEFORE any appended Suveren footer. */
285
294
  pre_footer?: boolean;
295
+ /**
296
+ * v2 only — the tool-argument keys this binding covers, and the complete
297
+ * statement of what a verifier must reproduce. The Gatekeeper builds an
298
+ * object from exactly these keys and canonicalizes it by `kind`.
299
+ *
300
+ * Adding or removing an entry changes every resulting hash, so it is a
301
+ * BREAKING profile change requiring a version bump, never a silent edit.
302
+ *
303
+ * Choose the subset by one rule: bind everything the approving human is
304
+ * shown, and nothing the intended verifier cannot see.
305
+ */
306
+ fields?: string[];
307
+ /**
308
+ * v2 only — the subset of {@link fields} whose absence is a fault rather than
309
+ * a fact. An absent OPTIONAL field is omitted from the hashed object (an
310
+ * email legitimately has no `cc`); an absent REQUIRED field means the call is
311
+ * not the call this profile thinks it is, and MUST refuse rather than hash a
312
+ * partial object that reads exactly like a complete one.
313
+ *
314
+ * MUST be a subset of `fields`. Absent → every field is optional, and only a
315
+ * wholly empty selection refuses.
316
+ */
317
+ required_fields?: string[];
318
+ /**
319
+ * v2 only — the action types this binding covers, using the same vocabulary
320
+ * as {@link ProfileBoundsField.appliesTo}. A profile gates more than its
321
+ * content-bearing calls: `email` also gates deletes, which carry an id and no
322
+ * content, and applying a field binding to those would refuse them.
323
+ *
324
+ * Absent → the binding applies to every gated action under the profile.
325
+ */
326
+ appliesTo?: string[];
286
327
  }
287
328
  /**
288
329
  * Context field definition within a v0.4 profile.
@@ -360,6 +401,42 @@ interface AgentProfile {
360
401
  name?: string;
361
402
  version: string;
362
403
  description: string;
404
+ /**
405
+ * One line on what this version changed and why it matters to the person
406
+ * granting authority — written for them, not for a changelog.
407
+ *
408
+ * A grant pins the profile version it was signed against, so authorities
409
+ * issued before a newer version keep their old terms indefinitely and
410
+ * nothing prompts an upgrade. A version number alone does not motivate one:
411
+ * "email@0.4 → 0.5" says nothing, while "binds recipients, not only the
412
+ * message body" says what the older grant is not protecting.
413
+ *
414
+ * Belongs on the profile because the profile is what changed; a UI cannot
415
+ * know why 0.5 exists. Absent → surfaces show the version alone.
416
+ */
417
+ whatsNew?: string;
418
+ /**
419
+ * Whether receipts under this profile may be looked up BY THEIR CONTENT — a
420
+ * verifier holding the content supplies its hash and learns which receipts
421
+ * bind it, without needing a receipt id.
422
+ *
423
+ * OFF unless declared, and that default is the point. The lookup is a
424
+ * confirmation oracle: given a guess at the content it says whether that
425
+ * content was authorized. Where the bound content has low entropy this is
426
+ * disclosure, not verification — guessing a message body is hopeless,
427
+ * guessing `production` takes a second. It is the same enumeration hazard
428
+ * recorded for per-field commitments, arriving from the other direction.
429
+ *
430
+ * Enable only when the bound content is unguessable enough that producing it
431
+ * is equivalent to already having it: prose, an artifact URL, a whole record
432
+ * payload. Never for a binding over a short value drawn from a small set.
433
+ *
434
+ * Why it must exist at all: most consequential actions cannot carry their
435
+ * receipt id. A released build was built before the receipt existed, a
436
+ * content-addressed artifact would change identity if the id were added, and
437
+ * a forwarded message has usually lost the footer that carried it.
438
+ */
439
+ receipt_lookup?: boolean;
363
440
  /**
364
441
  * v0.3 frame schema (deprecated, kept for backward compat).
365
442
  * Used when boundsSchema is not present.
@@ -596,6 +673,12 @@ declare function canonicalize(value: unknown): string;
596
673
  * (Unicode NFC, LF line endings, trailing per-line whitespace stripped,
597
674
  * trailing blank lines removed), taken pre-footer when `pre_footer` is set.
598
675
  *
676
+ * At `version:"2"` the profile also declares WHICH tool arguments are bound
677
+ * ({@link selectBoundFields}), so a receipt can commit to an email's recipients
678
+ * and not only its prose — while still omitting what the intended verifier
679
+ * cannot see. Every string entering the hashed object is canonicalized by the
680
+ * same `text` rule, so a delivered copy with CRLF endings still reproduces it.
681
+ *
599
682
  * Both Node and the browser produce byte-identical output: JCS relies only on
600
683
  * environment-independent primitives, and the text rule uses String.normalize +
601
684
  * plain string ops. The SHA-256 is computed with Node `crypto` here (the same
@@ -626,6 +709,56 @@ declare function contentCanonicalBytes(kind: ContentBinding['kind'], content: Re
626
709
  * @param content the record payload (jcs) or the resolved text field (text)
627
710
  */
628
711
  declare function computeContentHash(binding: ContentBinding, content: Record<string, unknown> | string): string;
712
+ /** Why a field binding refused. Every case is fail-closed by design. */
713
+ type ContentBindingErrorCode =
714
+ /** The profile declares version:"2" with no usable `fields` list. */
715
+ 'NO_FIELDS_DECLARED'
716
+ /** `required_fields` names something absent from `fields`. */
717
+ | 'REQUIRED_FIELD_NOT_DECLARED'
718
+ /** A required field was absent or empty at call time. */
719
+ | 'MISSING_REQUIRED_FIELD'
720
+ /** No declared field carried a value — the hash would commit to nothing. */
721
+ | 'EMPTY_BINDING';
722
+ /**
723
+ * A field binding could not be computed. ALWAYS a refusal, never a downgrade:
724
+ * the alternative is a receipt that verifies while proving less than it appears
725
+ * to, which is the failure content binding exists to prevent.
726
+ */
727
+ declare class ContentBindingError extends Error {
728
+ readonly code: ContentBindingErrorCode;
729
+ /** The offending field, when the code names one. */
730
+ readonly field?: string;
731
+ constructor(code: ContentBindingErrorCode, message: string, field?: string);
732
+ }
733
+ /** True when this binding selects a declared subset (v2) rather than v1's implicit scope. */
734
+ declare function isFieldBinding(binding: ContentBinding): boolean;
735
+ /**
736
+ * Whether a field binding covers this action type, per the profile's `appliesTo`.
737
+ *
738
+ * Read STRICTLY — an undeclared action type is NOT covered. This differs from
739
+ * how bounds read the same key (there, an unknown action type enforces the
740
+ * bound, because an extra limit is safe). Here the two directions are not
741
+ * symmetric: applying a field binding to a call that carries no content refuses
742
+ * a legitimate action, so an unknown action type must fall outside rather than
743
+ * inside. Callers are expected to warn on the undeclared case — it is a
744
+ * manifest bug either way.
745
+ */
746
+ declare function bindingAppliesTo(binding: ContentBinding, actionType: string | undefined): boolean;
747
+ /**
748
+ * Build the object a v2 binding hashes: exactly the declared `fields` that
749
+ * carry a value, canonicalized. Exported so a verifier can construct the same
750
+ * object from what they hold and see it before hashing.
751
+ *
752
+ * Throws {@link ContentBindingError} rather than returning a partial result —
753
+ * see that class for why refusing is the only safe outcome.
754
+ */
755
+ declare function selectBoundFields(binding: ContentBinding, args: Record<string, unknown>): Record<string, unknown>;
756
+ /**
757
+ * Compute a v2 field-binding hash from raw tool arguments: select the declared
758
+ * subset, then hash it by the declared `kind`. Convenience over
759
+ * {@link selectBoundFields} + {@link computeContentHash} for the common path.
760
+ */
761
+ declare function computeFieldsContentHash(binding: ContentBinding, args: Record<string, unknown>): string;
629
762
 
630
763
  /**
631
764
  * Intent hashing + intent-disclosure binding (HAP v0.5).
@@ -917,4 +1050,4 @@ declare function listProfiles(): string[];
917
1050
  declare function getAllProfiles(): AgentProfile[];
918
1051
  declare function clearProfiles(): void;
919
1052
 
920
- export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, type ContentBinding, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ResolvedDomain, type Subject, type SubjectValidation, attestationId, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, deriveIdentityLine, encodeAttestationBlob, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isV4Attestation, listProfiles, registerProfile, validateBoundsParams, validateContextParams, validateFrameParams, validateSubject, verify, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash };
1053
+ export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ResolvedDomain, type Subject, type SubjectValidation, attestationId, bindingAppliesTo, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFieldsContentHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, deriveIdentityLine, encodeAttestationBlob, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isFieldBinding, isV4Attestation, listProfiles, registerProfile, selectBoundFields, validateBoundsParams, validateContextParams, validateFrameParams, validateSubject, verify, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash };