@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.
- package/content/integrations/calendar.json +6 -3
- package/content/integrations/crm.json +6 -3
- package/content/integrations/deploy-github.json +5 -1
- package/content/integrations/gmail.json +16 -6
- package/content/integrations/linkedin.json +75 -16
- package/content/integrations/records.json +76 -14
- package/dist/mcp-server/http.mjs +215 -28
- package/dist/ui/assets/index-DIcuh0I4.css +1 -0
- package/dist/ui/assets/index-Dl5isaOV.js +105 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/mockups/audit-and-header.html +192 -0
- package/node_modules/@hap/core/dist/index.d.mts +139 -6
- package/node_modules/@hap/core/dist/index.d.ts +139 -6
- package/node_modules/@hap/core/dist/index.js +93 -0
- package/node_modules/@hap/core/dist/index.mjs +88 -0
- package/node_modules/@hap/core/package.json +1 -1
- package/node_modules/@hap/core/src/content-binding.ts +168 -0
- package/node_modules/@hap/core/src/types.ts +85 -5
- package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +8 -0
- package/node_modules/jose/dist/webapi/jwe/general/encrypt.js +1 -1
- package/node_modules/jose/dist/webapi/jwks/local.js +3 -3
- package/node_modules/jose/dist/webapi/jwks/remote.js +1 -1
- package/node_modules/jose/dist/webapi/key/generate_key_pair.js +3 -3
- package/node_modules/jose/dist/webapi/key/generate_secret.js +2 -2
- package/node_modules/jose/dist/webapi/lib/asn1.js +3 -3
- package/node_modules/jose/dist/webapi/lib/jwe_algorithms.js +6 -15
- package/node_modules/jose/dist/webapi/lib/jws_algorithms.js +2 -5
- package/node_modules/jose/dist/webapi/lib/key.js +1 -1
- package/node_modules/jose/dist/webapi/lib/key_algorithm.js +8 -8
- package/node_modules/jose/package.json +1 -1
- package/package.json +2 -2
- package/profiles/deploy/0.7.profile.json +2 -1
- package/profiles/email/0.5.profile.json +200 -0
- package/profiles/index.json +1 -0
- package/dist/ui/assets/index-DoZvxHLN.js +0 -105
- package/dist/ui/assets/index-JHaCddDE.css +0 -1
package/dist/ui/index.html
CHANGED
|
@@ -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-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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
|
-
*
|
|
271
|
-
* canonicalize. It does NOT name the tool field: that is tool-specific
|
|
272
|
-
* resolved at runtime (the same content-field resolver the footer uses
|
|
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
|
-
*
|
|
271
|
-
* canonicalize. It does NOT name the tool field: that is tool-specific
|
|
272
|
-
* resolved at runtime (the same content-field resolver the footer uses
|
|
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 };
|