@sealkeeper/schema 0.4.7 → 0.4.9
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/dist/api.d.ts +4411 -364
- package/dist/api.js +1753 -46
- package/dist/blocks.d.ts +21 -0
- package/dist/blocks.js +43 -0
- package/dist/cli-version.d.ts +7 -0
- package/dist/cli-version.js +89 -0
- package/dist/conformance.d.ts +4 -0
- package/dist/conformance.js +7 -0
- package/dist/credential.d.ts +763 -21
- package/dist/credential.js +253 -35
- package/dist/dimensions.d.ts +16 -3
- package/dist/dimensions.js +52 -2
- package/dist/fingerprint-conformance.d.ts +17 -0
- package/dist/fingerprint-conformance.js +83 -0
- package/dist/fingerprint.d.ts +133 -0
- package/dist/fingerprint.js +178 -0
- package/dist/game.d.ts +100 -0
- package/dist/game.js +190 -0
- package/dist/goal.d.ts +22 -1
- package/dist/goal.js +50 -7
- package/dist/handshake-conformance.d.ts +18 -0
- package/dist/handshake-conformance.js +276 -0
- package/dist/handshake.d.ts +66 -0
- package/dist/handshake.js +188 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/model-comparison.d.ts +70 -0
- package/dist/model-comparison.js +208 -0
- package/dist/model-name.d.ts +3 -0
- package/dist/model-name.js +48 -0
- package/dist/moderation.d.ts +2 -0
- package/dist/moderation.js +14 -8
- package/dist/policy.d.ts +1 -1
- package/dist/policy.js +1 -1
- package/dist/seal-conformance.js +263 -2
- package/dist/standing.d.ts +90 -0
- package/dist/standing.js +253 -13
- package/dist/task-templates.d.ts +47 -0
- package/dist/task-templates.js +506 -0
- package/dist/tasks.d.ts +97 -0
- package/dist/tasks.js +230 -0
- package/dist/template-conformance.d.ts +10 -0
- package/dist/template-conformance.js +298 -0
- package/dist/top-dimensions.d.ts +3 -3
- package/dist/top-dimensions.js +16 -6
- package/package.json +3 -3
package/dist/credential.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { AgentId, Ed25519PublicKey } from './agent-id.js';
|
|
3
|
-
import { Dimension } from './dimensions.js';
|
|
3
|
+
import { AcceptedDimension, Dimension } from './dimensions.js';
|
|
4
4
|
import { Jws } from './envelope.js';
|
|
5
5
|
import { StoredVersion } from './events.js';
|
|
6
|
-
import {
|
|
6
|
+
import { HANDSHAKE_MAX_CHARS } from './handshake.js';
|
|
7
|
+
import { CountedCounts, CountedCountsV3, Level, StandingCounts, StandingCountsV3, } from './standing.js';
|
|
8
|
+
import { STORED_TASK_CATEGORIES, StoredTaskCategory } from './tasks.js';
|
|
7
9
|
// The issuer every SEAL carries as iss from the SealKeeper cutover on.
|
|
8
10
|
export const CREDENTIAL_ISSUER = 'sealkeeper.run';
|
|
9
11
|
// Issuers a verifier still accepts for a while after the cutover. A SEAL
|
|
@@ -47,14 +49,33 @@ export const SEAL_EXTENSION_URIS = [
|
|
|
47
49
|
];
|
|
48
50
|
const Seconds = z.int().min(0);
|
|
49
51
|
const Count = z.int().min(0);
|
|
50
|
-
//
|
|
52
|
+
// Version 1 of the SEAL standard, the ver of a version 1 SEAL. The API
|
|
53
|
+
// writes version 1, 3 or 4, whichever its SEAL_ISSUE_VERSION names.
|
|
51
54
|
export const SEAL_VERSION = 1;
|
|
52
55
|
// Every version a verifier understands. Version 2 is version 1 plus
|
|
53
|
-
// `counted`, the counted evidence the level read (VOU-139).
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
56
|
+
// `counted`, the counted evidence the level read (VOU-139). Version 3 is
|
|
57
|
+
// version 2 plus the posted counts, `fingerprint` and `state`, with the
|
|
58
|
+
// deprecated `version` dropped (VOU-77). Version 4 is version 3 plus
|
|
59
|
+
// `trust` and `top_categories` (VOU-560). CLIs before 0.4.5 understand
|
|
60
|
+
// version 1 only, CLIs before 0.4.11 do not understand version 3 and CLIs
|
|
61
|
+
// before 0.4.14 do not understand version 4, and each refuses a ver it does
|
|
62
|
+
// not know. So the issuer keeps writing version 1 until every CLI before
|
|
63
|
+
// the first that reads the version it moves to is gone, and then moves
|
|
64
|
+
// when the API's SEAL_ISSUE_VERSION is set by hand. Verifiers accept
|
|
65
|
+
// versions 1, 2 and 3 from CLI 0.4.11 on and all four from CLI 0.4.14 on.
|
|
66
|
+
// SEAL standard section 9.
|
|
67
|
+
export const SEAL_VERSIONS = [1, 2, 3, 4];
|
|
68
|
+
// The first CLI that reads SEAL version 3, a CliVersion. Gone means no CLI
|
|
69
|
+
// older than this, and no signed request without a CLI version, for
|
|
70
|
+
// SEAL_V3_QUIET_DAYS whole days. GET /internal/metrics measures it from
|
|
71
|
+
// the CLI version each signed request names (VOU-454), in cliVersions.
|
|
72
|
+
export const SEAL_V3_FIRST_CLI = '0.4.11';
|
|
73
|
+
export const SEAL_V3_QUIET_DAYS = 14;
|
|
74
|
+
// The first CLI that reads SEAL version 4, a CliVersion, under the same
|
|
75
|
+
// rule and the same SEAL_V3_QUIET_DAYS. It is also the first CLI that
|
|
76
|
+
// names its version on a signed request, so every older one is a request
|
|
77
|
+
// without a CLI version, which cliVersions already counts as old.
|
|
78
|
+
export const SEAL_V4_FIRST_CLI = '0.4.14';
|
|
58
79
|
// One identity attestation reference, SEAL standard section 4a. A pointer
|
|
59
80
|
// to an attestation an external provider made, never the attestation, a
|
|
60
81
|
// name or a tenant id. kind is one of the four names or a URL for anything
|
|
@@ -76,8 +97,16 @@ export const IdentityReference = z.strictObject({
|
|
|
76
97
|
// revoked, so every reference it still holds is current.
|
|
77
98
|
export const operatorVerified = (identity) => identity.some((r) => r.scope === 'operator');
|
|
78
99
|
// Each score is 0 to 1, or null when unearned (standard section 2). A
|
|
79
|
-
// payload with a score outside that is malformed.
|
|
80
|
-
|
|
100
|
+
// payload with a score outside that is malformed. The issuer writes the
|
|
101
|
+
// base dimensions and competence by category only (Dimension). A verifier
|
|
102
|
+
// also accepts competence by task type (AcceptedDimension), the key SEALs
|
|
103
|
+
// carried before RT-3, so a SEAL issued before the change stays valid until
|
|
104
|
+
// it expires and a verifier never has to know which keys an issuer writes
|
|
105
|
+
// today. The API reads its stored SEALs back with the issued shape, so a
|
|
106
|
+
// stored SEAL with a task type key fails that parse and is reissued.
|
|
107
|
+
const Score = z.number().min(0).max(1).nullable();
|
|
108
|
+
const IssuedScores = z.partialRecord(Dimension, Score);
|
|
109
|
+
const VerifiedScores = z.partialRecord(AcceptedDimension, Score);
|
|
81
110
|
// A SEAL lives at most 24 hours from issue, standard section 2. The issuer's
|
|
82
111
|
// CREDENTIAL_TTL_HOURS may not go above it, and a payload whose exp is
|
|
83
112
|
// further from its iat is malformed.
|
|
@@ -86,12 +115,95 @@ export const SEAL_MAX_TTL_SECONDS = 24 * 3600;
|
|
|
86
115
|
// valid. Five minutes covers clock drift between issuer and verifier.
|
|
87
116
|
export const SEAL_CLOCK_SKEW_SECONDS = 300;
|
|
88
117
|
// iss on the issuer side is CREDENTIAL_ISSUER and nothing else. The API
|
|
89
|
-
// writes it and reads its stored SEALs back with
|
|
118
|
+
// writes it and reads its stored SEALs back with IssuedSealPayload, so a
|
|
90
119
|
// stored SEAL with a legacy issuer fails that parse and is reissued. Only
|
|
91
120
|
// a verifier accepts a legacy issuer, through the Verified shapes below,
|
|
92
121
|
// and parseSealPayload checks the clock with acceptedIssuer first.
|
|
93
122
|
const IssuedIssuer = z.literal(CREDENTIAL_ISSUER);
|
|
94
123
|
const VerifiedIssuer = z.enum([CREDENTIAL_ISSUER, ...LEGACY_ISSUERS]);
|
|
124
|
+
// The state of a version 3 or 4 SEAL, how the agent's current fingerprint
|
|
125
|
+
// stands against the one the SEAL carries. The set is closed. matches is
|
|
126
|
+
// the only value the issuer writes until the re-check flow exists, and
|
|
127
|
+
// means only that the fingerprint is the one on the issuer's record at
|
|
128
|
+
// issue. With a null fingerprint it says nothing. changed and provisional
|
|
129
|
+
// are reserved for that flow, so a verifier of version 3 already reads
|
|
130
|
+
// them.
|
|
131
|
+
export const SealState = z.enum(['matches', 'changed', 'provisional']);
|
|
132
|
+
// The fingerprint of a version 3 or 4 SEAL. hash is SHA-256 over bytes a later
|
|
133
|
+
// change to the standard defines, base64url, and a verifier treats it as
|
|
134
|
+
// opaque until then. It is the agent's current fingerprint on the issuer's
|
|
135
|
+
// record at issue. at is Unix seconds of when the agent's software says it
|
|
136
|
+
// captured it, never after iat, unchecked, and no level is checked against
|
|
137
|
+
// it (SEAL standard section 4b). null when the agent has sent no
|
|
138
|
+
// fingerprint. A 32 byte value in base64url has 43 characters and only
|
|
139
|
+
// some last characters, the rule agent-id.ts uses for a key.
|
|
140
|
+
export const SealFingerprint = z
|
|
141
|
+
.strictObject({
|
|
142
|
+
hash: z
|
|
143
|
+
.string()
|
|
144
|
+
.regex(/^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/, 'SHA-256, base64url'),
|
|
145
|
+
at: Seconds,
|
|
146
|
+
})
|
|
147
|
+
.nullable();
|
|
148
|
+
// The most entries top_categories of a version 4 SEAL holds.
|
|
149
|
+
export const SEAL_TOP_CATEGORIES_MAX = 3;
|
|
150
|
+
// One entry of top_categories in a version 4 SEAL, a category of the
|
|
151
|
+
// agent version's Trust Score and its score there, a whole number.
|
|
152
|
+
export const SealTopCategory = z.strictObject({
|
|
153
|
+
category: StoredTaskCategory,
|
|
154
|
+
score: Count,
|
|
155
|
+
});
|
|
156
|
+
// A stored score as a SEAL carries it, rounded as the Trust answer rounds.
|
|
157
|
+
// A value that is not a finite number, which no scoring run writes, reads 0.
|
|
158
|
+
const whole = (n) => Number.isFinite(n) ? Math.max(0, Math.round(n)) : 0;
|
|
159
|
+
const categoryOrder = (c) => STORED_TASK_CATEGORIES.indexOf(c);
|
|
160
|
+
// Highest score first, a tie in the order of STORED_TASK_CATEGORIES, the
|
|
161
|
+
// order topDimensions keeps for the competence categories. Negative when a
|
|
162
|
+
// goes first.
|
|
163
|
+
const bySealOrder = (a, b) => b.score - a.score || categoryOrder(a.category) - categoryOrder(b.category);
|
|
164
|
+
// top_categories of a version 4 SEAL. At most SEAL_TOP_CATEGORIES_MAX,
|
|
165
|
+
// each category once, in bySealOrder, so the order a verifier reads is the
|
|
166
|
+
// one sealTrust writes.
|
|
167
|
+
export const SealTopCategories = z
|
|
168
|
+
.array(SealTopCategory)
|
|
169
|
+
.max(SEAL_TOP_CATEGORIES_MAX)
|
|
170
|
+
.refine((list) => list.every((c, i) => i === 0 || bySealOrder(list[i - 1], c) < 0), 'top_categories must be highest first, a tie in category order, each category once');
|
|
171
|
+
/*
|
|
172
|
+
* trust and top_categories of a version 4 SEAL, from the stored rows the
|
|
173
|
+
* scoring run wrote for the agent version the SEAL is for, its Trust Score
|
|
174
|
+
* (standing.trust_score) and its categories (standing.trust_categories).
|
|
175
|
+
* The issuer computes nothing new. trust is the Trust Score rounded to a
|
|
176
|
+
* whole number as GET /v1/agents/:id/trust rounds it, 0 with none stored.
|
|
177
|
+
* top_categories is the SEAL_TOP_CATEGORIES_MAX categories with the
|
|
178
|
+
* highest stored score, a tie in category order, chosen before rounding so
|
|
179
|
+
* they are the three the trust answer ranks first, save an exact tie at the
|
|
180
|
+
* third, which it breaks by name. Each is then rounded the same way and put
|
|
181
|
+
* in bySealOrder, empty with none stored. A key that is not a stored task
|
|
182
|
+
* category and an entry without a finite score are left out, and a
|
|
183
|
+
* trust_score that is not finite reads 0, so a stray stored value never
|
|
184
|
+
* stops a SEAL from being issued. The issuer reads this and nothing else
|
|
185
|
+
* for the two fields, and the verifier's shape holds the same order. A
|
|
186
|
+
* value is never below 0, as no stored score is, so a SEAL is never
|
|
187
|
+
* refused over it.
|
|
188
|
+
*/
|
|
189
|
+
export function sealTrust(score, categories) {
|
|
190
|
+
const stored = [];
|
|
191
|
+
for (const [key, c] of Object.entries(categories ?? {})) {
|
|
192
|
+
const category = StoredTaskCategory.safeParse(key);
|
|
193
|
+
const raw = c?.score;
|
|
194
|
+
if (category.success && typeof raw === 'number' && Number.isFinite(raw)) {
|
|
195
|
+
stored.push({ category: category.data, score: raw });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
trust: whole(score ?? 0),
|
|
200
|
+
top_categories: stored
|
|
201
|
+
.sort(bySealOrder)
|
|
202
|
+
.slice(0, SEAL_TOP_CATEGORIES_MAX)
|
|
203
|
+
.map((c) => ({ category: c.category, score: whole(c.score) }))
|
|
204
|
+
.sort(bySealOrder),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
95
207
|
const withinMaxTtl = (c) => c.exp - c.iat <= SEAL_MAX_TTL_SECONDS;
|
|
96
208
|
const MAX_TTL_MESSAGE = 'exp must be at most 24 hours after iat';
|
|
97
209
|
/*
|
|
@@ -104,7 +216,7 @@ const MAX_TTL_MESSAGE = 'exp must be at most 24 hours after iat';
|
|
|
104
216
|
* Unix seconds of the agent's newest accepted event, null when it has sent
|
|
105
217
|
* none, and dormant_days whole days since then at issue, null with it.
|
|
106
218
|
*/
|
|
107
|
-
const v1Payload = (iss) => z
|
|
219
|
+
const v1Payload = (iss, scores) => z
|
|
108
220
|
.strictObject({
|
|
109
221
|
iss,
|
|
110
222
|
sub: AgentId,
|
|
@@ -114,7 +226,7 @@ const v1Payload = (iss) => z
|
|
|
114
226
|
agent_version: StoredVersion,
|
|
115
227
|
version: StoredVersion,
|
|
116
228
|
level: Level,
|
|
117
|
-
scores
|
|
229
|
+
scores,
|
|
118
230
|
counts: StandingCounts,
|
|
119
231
|
operator: z.strictObject({ verified: z.boolean() }),
|
|
120
232
|
identity: z.array(IdentityReference).max(16),
|
|
@@ -129,7 +241,7 @@ const v1Payload = (iss) => z
|
|
|
129
241
|
* counted evidence the level read (CountedCounts). counts stay the raw
|
|
130
242
|
* facts. Each counted value is at most its raw count.
|
|
131
243
|
*/
|
|
132
|
-
const v2Payload = (iss) => z
|
|
244
|
+
const v2Payload = (iss, scores) => z
|
|
133
245
|
.strictObject({
|
|
134
246
|
iss,
|
|
135
247
|
sub: AgentId,
|
|
@@ -139,7 +251,7 @@ const v2Payload = (iss) => z
|
|
|
139
251
|
agent_version: StoredVersion,
|
|
140
252
|
version: StoredVersion,
|
|
141
253
|
level: Level,
|
|
142
|
-
scores
|
|
254
|
+
scores,
|
|
143
255
|
counts: StandingCounts,
|
|
144
256
|
counted: CountedCounts,
|
|
145
257
|
operator: z.strictObject({ verified: z.boolean() }),
|
|
@@ -153,27 +265,119 @@ const v2Payload = (iss) => z
|
|
|
153
265
|
.refine((c) => c.counted.seed_tasks <= c.counts.seed_tasks &&
|
|
154
266
|
c.counted.server_checked_tasks <= c.counts.server_checked_tasks &&
|
|
155
267
|
c.counted.confirmed_tasks <= c.counts.confirmed_tasks, 'counted must not exceed counts');
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
268
|
+
/*
|
|
269
|
+
* Version 3. Every version 2 field with the same meaning except version,
|
|
270
|
+
* which is dropped (VOU-77), so agent_version alone names the agent
|
|
271
|
+
* version. counts gains the three posted counts (StandingCountsV3), counted
|
|
272
|
+
* gains the counted twins of posted_tasks and posted_confirmed_tasks
|
|
273
|
+
* (CountedCountsV3), and fingerprint and state are new. Each counted value
|
|
274
|
+
* is at most its raw count, and fingerprint.at is not after iat. The API
|
|
275
|
+
* issues it when its SEAL_ISSUE_VERSION is 3.
|
|
276
|
+
*/
|
|
277
|
+
const v3Payload = (iss, scores) => z
|
|
278
|
+
.strictObject({
|
|
279
|
+
iss,
|
|
280
|
+
sub: AgentId,
|
|
281
|
+
ver: z.literal(3),
|
|
282
|
+
iat: Seconds,
|
|
283
|
+
exp: Seconds,
|
|
284
|
+
agent_version: StoredVersion,
|
|
285
|
+
level: Level,
|
|
286
|
+
scores,
|
|
287
|
+
counts: StandingCountsV3,
|
|
288
|
+
counted: CountedCountsV3,
|
|
289
|
+
fingerprint: SealFingerprint,
|
|
290
|
+
state: SealState,
|
|
291
|
+
operator: z.strictObject({ verified: z.boolean() }),
|
|
292
|
+
identity: z.array(IdentityReference).max(16),
|
|
293
|
+
last_active: Seconds.nullable(),
|
|
294
|
+
dormant_days: Count.nullable(),
|
|
295
|
+
})
|
|
296
|
+
.refine((c) => c.exp > c.iat, 'exp must be after iat')
|
|
297
|
+
.refine(withinMaxTtl, MAX_TTL_MESSAGE)
|
|
298
|
+
.refine((c) => c.counted.seed_tasks <= c.counts.seed_tasks &&
|
|
299
|
+
c.counted.server_checked_tasks <= c.counts.server_checked_tasks &&
|
|
300
|
+
c.counted.confirmed_tasks <= c.counts.confirmed_tasks &&
|
|
301
|
+
c.counted.posted_tasks <= c.counts.posted_tasks &&
|
|
302
|
+
c.counted.posted_confirmed_tasks <= c.counts.posted_confirmed_tasks, 'counted must not exceed counts')
|
|
303
|
+
.refine((c) => c.fingerprint === null || c.fingerprint.at <= c.iat, 'fingerprint.at must not be after iat');
|
|
304
|
+
/*
|
|
305
|
+
* Version 4. Every version 3 field with the same meaning, plus trust and
|
|
306
|
+
* top_categories (VOU-560, D-UI-4, D-UI-5), both from sealTrust. trust is
|
|
307
|
+
* the Trust Score of the agent version the SEAL is for, a whole number.
|
|
308
|
+
* top_categories is its highest categories, at most three, in the order
|
|
309
|
+
* SealTopCategories holds. Both are what the agent has earned, read from
|
|
310
|
+
* the issuer's own rows at issue, never a permission and never a level.
|
|
311
|
+
* The API issues it when its SEAL_ISSUE_VERSION is 4.
|
|
312
|
+
*/
|
|
313
|
+
const v4Payload = (iss, scores) => z
|
|
314
|
+
.strictObject({
|
|
315
|
+
iss,
|
|
316
|
+
sub: AgentId,
|
|
317
|
+
ver: z.literal(4),
|
|
318
|
+
iat: Seconds,
|
|
319
|
+
exp: Seconds,
|
|
320
|
+
agent_version: StoredVersion,
|
|
321
|
+
level: Level,
|
|
322
|
+
scores,
|
|
323
|
+
counts: StandingCountsV3,
|
|
324
|
+
counted: CountedCountsV3,
|
|
325
|
+
fingerprint: SealFingerprint,
|
|
326
|
+
state: SealState,
|
|
327
|
+
trust: Count,
|
|
328
|
+
top_categories: SealTopCategories,
|
|
329
|
+
operator: z.strictObject({ verified: z.boolean() }),
|
|
330
|
+
identity: z.array(IdentityReference).max(16),
|
|
331
|
+
last_active: Seconds.nullable(),
|
|
332
|
+
dormant_days: Count.nullable(),
|
|
333
|
+
})
|
|
334
|
+
.refine((c) => c.exp > c.iat, 'exp must be after iat')
|
|
335
|
+
.refine(withinMaxTtl, MAX_TTL_MESSAGE)
|
|
336
|
+
.refine((c) => c.counted.seed_tasks <= c.counts.seed_tasks &&
|
|
337
|
+
c.counted.server_checked_tasks <= c.counts.server_checked_tasks &&
|
|
338
|
+
c.counted.confirmed_tasks <= c.counts.confirmed_tasks &&
|
|
339
|
+
c.counted.posted_tasks <= c.counts.posted_tasks &&
|
|
340
|
+
c.counted.posted_confirmed_tasks <= c.counts.posted_confirmed_tasks, 'counted must not exceed counts')
|
|
341
|
+
.refine((c) => c.fingerprint === null || c.fingerprint.at <= c.iat, 'fingerprint.at must not be after iat');
|
|
342
|
+
// The issuer side. iss is CREDENTIAL_ISSUER only, and scores carry
|
|
343
|
+
// competence by category only.
|
|
344
|
+
export const CredentialPayload = v1Payload(IssuedIssuer, IssuedScores);
|
|
345
|
+
export const CredentialPayloadV2 = v2Payload(IssuedIssuer, IssuedScores);
|
|
346
|
+
export const CredentialPayloadV3 = v3Payload(IssuedIssuer, IssuedScores);
|
|
347
|
+
export const CredentialPayloadV4 = v4Payload(IssuedIssuer, IssuedScores);
|
|
348
|
+
// What the issuer writes, version 1, 3 or 4, whichever the API's
|
|
349
|
+
// SEAL_ISSUE_VERSION names. The API serves it and reads its stored SEALs
|
|
350
|
+
// back with it, so a SEAL stored before the setting changed still parses
|
|
351
|
+
// and is reissued once. Version 2 is never issued.
|
|
352
|
+
export const IssuedSealPayload = z.union([
|
|
353
|
+
CredentialPayload,
|
|
354
|
+
CredentialPayloadV3,
|
|
355
|
+
CredentialPayloadV4,
|
|
356
|
+
]);
|
|
357
|
+
// The verifier side. iss may also be a LEGACY_ISSUERS entry, and scores may
|
|
358
|
+
// also carry competence by task type. The shape has
|
|
160
359
|
// no clock, so read these through parseSealPayload, never on their own.
|
|
161
|
-
export const VerifiedCredentialPayload = v1Payload(VerifiedIssuer);
|
|
162
|
-
export const VerifiedCredentialPayloadV2 = v2Payload(VerifiedIssuer);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
//
|
|
360
|
+
export const VerifiedCredentialPayload = v1Payload(VerifiedIssuer, VerifiedScores);
|
|
361
|
+
export const VerifiedCredentialPayloadV2 = v2Payload(VerifiedIssuer, VerifiedScores);
|
|
362
|
+
export const VerifiedCredentialPayloadV3 = v3Payload(VerifiedIssuer, VerifiedScores);
|
|
363
|
+
export const VerifiedCredentialPayloadV4 = v4Payload(VerifiedIssuer, VerifiedScores);
|
|
364
|
+
// Any payload a verifier accepts today, version 1, 2, 3 or 4, from the
|
|
365
|
+
// current issuer or a legacy one. Each version has every field of the one
|
|
366
|
+
// before with the same meaning, except version, which version 3 drops. A
|
|
367
|
+
// reader takes the agent version from agent_version, which all four carry.
|
|
166
368
|
export const SealPayload = z.union([
|
|
167
369
|
VerifiedCredentialPayload,
|
|
168
370
|
VerifiedCredentialPayloadV2,
|
|
371
|
+
VerifiedCredentialPayloadV3,
|
|
372
|
+
VerifiedCredentialPayloadV4,
|
|
169
373
|
]);
|
|
170
374
|
// The counted evidence a SEAL carries, version 2 on, null before.
|
|
171
375
|
export const sealCounted = (p) => 'counted' in p ? p.counted : null;
|
|
172
376
|
const hasVer = (raw) => typeof raw === 'object' && raw !== null && 'ver' in raw;
|
|
173
377
|
/*
|
|
174
378
|
* Whether a verifier understands the version of a signed payload. Read
|
|
175
|
-
* after the signature and the issuer check out and before the shape. ver 1
|
|
176
|
-
* and
|
|
379
|
+
* after the signature and the issuer check out and before the shape. ver 1,
|
|
380
|
+
* 2, 3 and 4 (SEAL_VERSIONS) are understood. Any other ver is
|
|
177
381
|
* unsupported_version, as section 9 of the standard says, never valid with
|
|
178
382
|
* an unknown meaning. So is no ver at all, a SEAL issued before version 1,
|
|
179
383
|
* whose last one expired on 26 September 2026. Anything that is not an
|
|
@@ -210,9 +414,14 @@ export function parseSealPayload(raw, nowSeconds) {
|
|
|
210
414
|
const problem = sealVersionProblem(raw);
|
|
211
415
|
if (problem)
|
|
212
416
|
return { ok: false, reason: problem };
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
417
|
+
const ver = hasVer(raw) ? raw.ver : undefined;
|
|
418
|
+
const parsed = (ver === 4
|
|
419
|
+
? VerifiedCredentialPayloadV4
|
|
420
|
+
: ver === 3
|
|
421
|
+
? VerifiedCredentialPayloadV3
|
|
422
|
+
: ver === 2
|
|
423
|
+
? VerifiedCredentialPayloadV2
|
|
424
|
+
: VerifiedCredentialPayload).safeParse(raw);
|
|
216
425
|
return parsed.success
|
|
217
426
|
? { ok: true, payload: parsed.data }
|
|
218
427
|
: { ok: false, reason: 'malformed' };
|
|
@@ -241,10 +450,17 @@ export const LegacySealExtension = z.strictObject({
|
|
|
241
450
|
description: z.string().min(1).max(512),
|
|
242
451
|
params: z.strictObject({ credential: Jws }),
|
|
243
452
|
});
|
|
453
|
+
// The current extension may also carry handshake, the agent's signed
|
|
454
|
+
// handshake (VB-6, ./handshake.ts) beside the SEAL, refreshed each time the
|
|
455
|
+
// card is written. A reader takes params loosely, so one that does not know
|
|
456
|
+
// handshake ignores it. The two old URIs carry the SEAL alone.
|
|
244
457
|
export const SealExtension = z.strictObject({
|
|
245
458
|
uri: z.literal(SEAL_EXTENSION_URI),
|
|
246
459
|
description: z.string().min(1).max(512),
|
|
247
|
-
params: z.strictObject({
|
|
460
|
+
params: z.strictObject({
|
|
461
|
+
credential: Jws,
|
|
462
|
+
handshake: Jws.max(HANDSHAKE_MAX_CHARS).optional(),
|
|
463
|
+
}),
|
|
248
464
|
});
|
|
249
465
|
export const credentialExtension = (jws) => ({
|
|
250
466
|
uri: CREDENTIAL_EXTENSION_URI,
|
|
@@ -256,15 +472,17 @@ export const legacySealExtension = (jws) => ({
|
|
|
256
472
|
description: EXTENSION_DESCRIPTION,
|
|
257
473
|
params: { credential: jws },
|
|
258
474
|
});
|
|
259
|
-
export const sealExtension = (jws) => ({
|
|
475
|
+
export const sealExtension = (jws, handshake) => ({
|
|
260
476
|
uri: SEAL_EXTENSION_URI,
|
|
261
477
|
description: EXTENSION_DESCRIPTION,
|
|
262
|
-
params:
|
|
478
|
+
params: handshake === undefined
|
|
479
|
+
? { credential: jws }
|
|
480
|
+
: { credential: jws, handshake },
|
|
263
481
|
});
|
|
264
482
|
// The extensions a card carries for one SEAL, in SEAL_EXTENSION_URIS order,
|
|
265
|
-
// the current URI first.
|
|
266
|
-
export const sealExtensions = (jws) => [
|
|
267
|
-
sealExtension(jws),
|
|
483
|
+
// the current URI first. A handshake goes on the current one only.
|
|
484
|
+
export const sealExtensions = (jws, handshake) => [
|
|
485
|
+
sealExtension(jws, handshake),
|
|
268
486
|
legacySealExtension(jws),
|
|
269
487
|
credentialExtension(jws),
|
|
270
488
|
];
|
package/dist/dimensions.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { StoredTaskCategory } from './tasks.js';
|
|
2
3
|
export declare const TaskType: z.ZodString;
|
|
3
4
|
export type TaskType = z.infer<typeof TaskType>;
|
|
4
5
|
export declare const BaseDimension: z.ZodEnum<{
|
|
@@ -8,14 +9,26 @@ export declare const BaseDimension: z.ZodEnum<{
|
|
|
8
9
|
safety: "safety";
|
|
9
10
|
}>;
|
|
10
11
|
export type BaseDimension = z.infer<typeof BaseDimension>;
|
|
11
|
-
export declare const CompetenceDimension: z.ZodTemplateLiteral
|
|
12
|
+
export declare const CompetenceDimension: z.ZodTemplateLiteral<"competence:code" | "competence:conversation" | "competence:data" | "competence:math" | "competence:operations" | "competence:other" | "competence:research" | "competence:writing">;
|
|
12
13
|
export type CompetenceDimension = z.infer<typeof CompetenceDimension>;
|
|
14
|
+
export declare const COMPETENCE_TYPES_MAX = 32;
|
|
15
|
+
export declare const COMPETENCE_DIMENSIONS: readonly CompetenceDimension[];
|
|
13
16
|
export declare const Dimension: z.ZodUnion<readonly [z.ZodEnum<{
|
|
14
17
|
cost_latency: "cost_latency";
|
|
15
18
|
provenance: "provenance";
|
|
16
19
|
reliability: "reliability";
|
|
17
20
|
safety: "safety";
|
|
18
|
-
}>, z.ZodTemplateLiteral
|
|
21
|
+
}>, z.ZodTemplateLiteral<"competence:code" | "competence:conversation" | "competence:data" | "competence:math" | "competence:operations" | "competence:other" | "competence:research" | "competence:writing">]>;
|
|
19
22
|
export type Dimension = z.infer<typeof Dimension>;
|
|
20
|
-
export declare const
|
|
23
|
+
export declare const TypeCompetenceDimension: z.ZodTemplateLiteral<`competence:${string}`>;
|
|
24
|
+
export type TypeCompetenceDimension = z.infer<typeof TypeCompetenceDimension>;
|
|
25
|
+
export declare const AcceptedDimension: z.ZodUnion<readonly [z.ZodEnum<{
|
|
26
|
+
cost_latency: "cost_latency";
|
|
27
|
+
provenance: "provenance";
|
|
28
|
+
reliability: "reliability";
|
|
29
|
+
safety: "safety";
|
|
30
|
+
}>, z.ZodTemplateLiteral<"competence:code" | "competence:conversation" | "competence:data" | "competence:math" | "competence:operations" | "competence:other" | "competence:research" | "competence:writing">, z.ZodTemplateLiteral<`competence:${string}`>]>;
|
|
31
|
+
export type AcceptedDimension = z.infer<typeof AcceptedDimension>;
|
|
32
|
+
export declare const competence: (category: StoredTaskCategory) => CompetenceDimension;
|
|
33
|
+
export declare const categoryOf: (dimension: string) => StoredTaskCategory | null;
|
|
21
34
|
export declare const isTrustDimension: (dimension: string) => boolean;
|
package/dist/dimensions.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { STORED_TASK_CATEGORIES, StoredTaskCategory } from './tasks.js';
|
|
2
3
|
export const TaskType = z.string().regex(/^[a-z0-9_-]{1,32}$/);
|
|
3
4
|
export const BaseDimension = z.enum([
|
|
4
5
|
'reliability',
|
|
@@ -6,9 +7,58 @@ export const BaseDimension = z.enum([
|
|
|
6
7
|
'cost_latency',
|
|
7
8
|
'provenance',
|
|
8
9
|
]);
|
|
9
|
-
|
|
10
|
+
/*
|
|
11
|
+
* Competence rolls up to the task's category (RT-3), one dimension per
|
|
12
|
+
* category, competence:data and so on. The per task type values stay a
|
|
13
|
+
* breakdown under the category on the score answer and are never a
|
|
14
|
+
* dimension of their own. It reads the stored categories, conversation and
|
|
15
|
+
* other included, since a task keeps the category it was scored under
|
|
16
|
+
* (D-UI-12), so the run still scores them and a stored score or a SEAL
|
|
17
|
+
* that names one still parses until its tasks leave the window.
|
|
18
|
+
*/
|
|
19
|
+
export const CompetenceDimension = z.templateLiteral([
|
|
20
|
+
'competence:',
|
|
21
|
+
StoredTaskCategory,
|
|
22
|
+
]);
|
|
23
|
+
// The most task types a competence entry's breakdown lists, the most
|
|
24
|
+
// claimed first, so the answer stays small however many free task types an
|
|
25
|
+
// agent took.
|
|
26
|
+
export const COMPETENCE_TYPES_MAX = 32;
|
|
27
|
+
// The eight competence dimensions in stored category order, the order every
|
|
28
|
+
// list shows them in. The scoring run keeps a stored competence row only
|
|
29
|
+
// when it names one of them.
|
|
30
|
+
export const COMPETENCE_DIMENSIONS = STORED_TASK_CATEGORIES.map((c) => CompetenceDimension.parse(`competence:${c}`));
|
|
31
|
+
// What the issuer writes and what a request may name, the base dimensions
|
|
32
|
+
// and the eight competence categories.
|
|
10
33
|
export const Dimension = z.union([BaseDimension, CompetenceDimension]);
|
|
11
|
-
|
|
34
|
+
/*
|
|
35
|
+
* competence:<task_type>, the competence key SEALs carried before RT-3,
|
|
36
|
+
* when competence was per task type. The issuer no longer writes it. Every category is also a valid task type, so this pattern
|
|
37
|
+
* takes both kinds of key.
|
|
38
|
+
*/
|
|
39
|
+
export const TypeCompetenceDimension = z.templateLiteral([
|
|
40
|
+
'competence:',
|
|
41
|
+
TaskType,
|
|
42
|
+
]);
|
|
43
|
+
/*
|
|
44
|
+
* Any dimension a reader accepts, issued today or before RT-3. A verifier
|
|
45
|
+
* reads SEAL scores with it, so a SEAL issued with a task type key stays
|
|
46
|
+
* valid for the rest of its 24 hours, and so do stored feed items that
|
|
47
|
+
* name one. Never used for what the issuer writes or a request names.
|
|
48
|
+
*/
|
|
49
|
+
export const AcceptedDimension = z.union([
|
|
50
|
+
BaseDimension,
|
|
51
|
+
CompetenceDimension,
|
|
52
|
+
TypeCompetenceDimension,
|
|
53
|
+
]);
|
|
54
|
+
export const competence = (category) => CompetenceDimension.parse(`competence:${category}`);
|
|
55
|
+
// The category inside a competence dimension, null for any other.
|
|
56
|
+
export const categoryOf = (dimension) => {
|
|
57
|
+
const parsed = CompetenceDimension.safeParse(dimension);
|
|
58
|
+
return parsed.success
|
|
59
|
+
? parsed.data.slice('competence:'.length)
|
|
60
|
+
: null;
|
|
61
|
+
};
|
|
12
62
|
// Reliability, safety, provenance and every competence dimension are trust.
|
|
13
63
|
// They need verified work from another operator before they get a value.
|
|
14
64
|
// cost_latency comes from usage events and is information, not trust.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FingerprintParts } from './fingerprint.js';
|
|
2
|
+
export type PartHashVector = {
|
|
3
|
+
name: string;
|
|
4
|
+
sub: string;
|
|
5
|
+
content: string;
|
|
6
|
+
hash: string;
|
|
7
|
+
};
|
|
8
|
+
export type FingerprintVector = {
|
|
9
|
+
name: string;
|
|
10
|
+
sub: string;
|
|
11
|
+
parts: FingerprintParts;
|
|
12
|
+
canonical: string;
|
|
13
|
+
hash: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const FINGERPRINT_VECTOR_SUB = "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo";
|
|
16
|
+
export declare const PART_HASH_VECTORS: readonly PartHashVector[];
|
|
17
|
+
export declare const FINGERPRINT_VECTORS: readonly FingerprintVector[];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export const FINGERPRINT_VECTOR_SUB = '11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo';
|
|
2
|
+
const sub = FINGERPRINT_VECTOR_SUB;
|
|
3
|
+
const MODEL = '6soeG-P3CXqRWuQ6H4urtEb45deX7u3mbPL5-qAKQLo';
|
|
4
|
+
const PROMPT = 'mLFX469k6BHZ3-B4a8L_jTSM2ZxJY3u4indqI00En-c';
|
|
5
|
+
const TOOLS = 'otJ7Lv3rmxSYzTJO3fcRNAFpmWHzzovNQQJ7HT9pIO8';
|
|
6
|
+
const FRAMEWORK = 'fZr0sK5GawVx1whZ8jTP7ZEHoteH22nOQS0fH4-UeOw';
|
|
7
|
+
export const PART_HASH_VECTORS = [
|
|
8
|
+
{
|
|
9
|
+
name: 'empty content',
|
|
10
|
+
sub,
|
|
11
|
+
content: '',
|
|
12
|
+
hash: '92JJjLzUuso4nM82PbsP-RKqxI1cEBakXLzWFqVoYjg',
|
|
13
|
+
},
|
|
14
|
+
{ name: 'one model id', sub, content: 'claude-sonnet-4-5', hash: MODEL },
|
|
15
|
+
{
|
|
16
|
+
name: 'a prompt',
|
|
17
|
+
sub,
|
|
18
|
+
content: 'You are the research agent alice runs.',
|
|
19
|
+
hash: PROMPT,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'sorted tool lines',
|
|
23
|
+
sub,
|
|
24
|
+
content: 'mcp:github\nmcp:linear\ntool:mcp__github__create_issue',
|
|
25
|
+
hash: TOOLS,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'a framework and its version',
|
|
29
|
+
sub,
|
|
30
|
+
content: 'claude-code@2.1.283',
|
|
31
|
+
hash: FRAMEWORK,
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
export const FINGERPRINT_VECTORS = [
|
|
35
|
+
{
|
|
36
|
+
name: 'every part declared',
|
|
37
|
+
sub,
|
|
38
|
+
parts: {
|
|
39
|
+
model_set: { hash: MODEL },
|
|
40
|
+
prompt: { hash: PROMPT },
|
|
41
|
+
tools: { hash: TOOLS },
|
|
42
|
+
framework: { hash: FRAMEWORK },
|
|
43
|
+
},
|
|
44
|
+
canonical: `${MODEL}\n${PROMPT}\n${TOOLS}\n${FRAMEWORK}`,
|
|
45
|
+
hash: 'unFXnGMKWNOP4f_NnbearJn0prLlxoGia2LaZcqnzy8',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'prompt not declared',
|
|
49
|
+
sub,
|
|
50
|
+
parts: {
|
|
51
|
+
model_set: { hash: MODEL },
|
|
52
|
+
prompt: 'not_declared',
|
|
53
|
+
tools: { hash: TOOLS },
|
|
54
|
+
framework: { hash: FRAMEWORK },
|
|
55
|
+
},
|
|
56
|
+
canonical: `${MODEL}\nnot_declared\n${TOOLS}\n${FRAMEWORK}`,
|
|
57
|
+
hash: 'BHAfx6dALmCdt3aXz-g6iAjLLGDurK95DZm15ZjIVZg',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'tools unstable, prompt not declared',
|
|
61
|
+
sub,
|
|
62
|
+
parts: {
|
|
63
|
+
model_set: { hash: MODEL },
|
|
64
|
+
prompt: 'not_declared',
|
|
65
|
+
tools: 'unstable',
|
|
66
|
+
framework: { hash: FRAMEWORK },
|
|
67
|
+
},
|
|
68
|
+
canonical: `${MODEL}\nnot_declared\nunstable\n${FRAMEWORK}`,
|
|
69
|
+
hash: 'u50ZFfkaGkhaMqciRzxv8meCaFxXycHCr9IasIEPwgk',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'nothing declared',
|
|
73
|
+
sub,
|
|
74
|
+
parts: {
|
|
75
|
+
model_set: 'not_declared',
|
|
76
|
+
prompt: 'not_declared',
|
|
77
|
+
tools: 'not_declared',
|
|
78
|
+
framework: 'not_declared',
|
|
79
|
+
},
|
|
80
|
+
canonical: 'not_declared\nnot_declared\nnot_declared\nnot_declared',
|
|
81
|
+
hash: 'GupgdwZ9bk92irIz_drScpdSxs6gX2CrSEL9A-ndiHI',
|
|
82
|
+
},
|
|
83
|
+
];
|