@sealkeeper/schema 0.4.6 → 0.4.8

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 (90) hide show
  1. package/README.md +2 -2
  2. package/dist/api.d.ts +787 -35
  3. package/dist/api.js +357 -44
  4. package/dist/conformance.d.ts +4 -0
  5. package/dist/conformance.js +7 -0
  6. package/dist/credential.d.ts +196 -27
  7. package/dist/credential.js +128 -80
  8. package/dist/dimensions.d.ts +16 -3
  9. package/dist/dimensions.js +48 -2
  10. package/dist/events.d.ts +1 -0
  11. package/dist/events.js +13 -1
  12. package/dist/fingerprint-conformance.d.ts +17 -0
  13. package/dist/fingerprint-conformance.js +83 -0
  14. package/dist/fingerprint.d.ts +132 -0
  15. package/dist/fingerprint.js +166 -0
  16. package/dist/goal.d.ts +16 -1
  17. package/dist/goal.js +29 -6
  18. package/dist/handshake-conformance.d.ts +18 -0
  19. package/dist/handshake-conformance.js +276 -0
  20. package/dist/handshake.d.ts +66 -0
  21. package/dist/handshake.js +187 -0
  22. package/dist/hidden.d.ts +1 -0
  23. package/dist/hidden.js +5 -0
  24. package/dist/index.d.ts +4 -1
  25. package/dist/index.js +4 -1
  26. package/dist/moderation.js +1 -4
  27. package/dist/policy.d.ts +1 -1
  28. package/dist/policy.js +1 -1
  29. package/dist/seal-conformance.js +148 -4
  30. package/dist/seal-verify.d.ts +21 -0
  31. package/dist/seal-verify.js +63 -0
  32. package/dist/standing.d.ts +61 -0
  33. package/dist/standing.js +132 -4
  34. package/dist/task-templates.d.ts +46 -0
  35. package/dist/task-templates.js +501 -0
  36. package/dist/tasks.d.ts +43 -0
  37. package/dist/tasks.js +103 -0
  38. package/dist/template-conformance.d.ts +10 -0
  39. package/dist/template-conformance.js +298 -0
  40. package/dist/top-dimensions.d.ts +3 -3
  41. package/dist/top-dimensions.js +12 -6
  42. package/package.json +4 -8
  43. package/dist/db/agent-milestones.d.ts +0 -75
  44. package/dist/db/agent-milestones.js +0 -16
  45. package/dist/db/agent-renames.d.ts +0 -126
  46. package/dist/db/agent-renames.js +0 -20
  47. package/dist/db/agents.d.ts +0 -351
  48. package/dist/db/agents.js +0 -99
  49. package/dist/db/client.d.ts +0 -2561
  50. package/dist/db/client.js +0 -61
  51. package/dist/db/credentials.d.ts +0 -143
  52. package/dist/db/credentials.js +0 -16
  53. package/dist/db/deleted-operators.d.ts +0 -75
  54. package/dist/db/deleted-operators.js +0 -13
  55. package/dist/db/events.d.ts +0 -160
  56. package/dist/db/events.js +0 -41
  57. package/dist/db/feed-items.d.ts +0 -109
  58. package/dist/db/feed-items.js +0 -21
  59. package/dist/db/index.d.ts +0 -76
  60. package/dist/db/index.js +0 -28
  61. package/dist/db/migrate.d.ts +0 -1
  62. package/dist/db/migrate.js +0 -34
  63. package/dist/db/migrator.d.ts +0 -6
  64. package/dist/db/migrator.js +0 -36
  65. package/dist/db/operator-identities.d.ts +0 -211
  66. package/dist/db/operator-identities.js +0 -49
  67. package/dist/db/operator-level-grants.d.ts +0 -109
  68. package/dist/db/operator-level-grants.js +0 -30
  69. package/dist/db/operator-slugs.d.ts +0 -92
  70. package/dist/db/operator-slugs.js +0 -25
  71. package/dist/db/operators.d.ts +0 -228
  72. package/dist/db/operators.js +0 -39
  73. package/dist/db/quota-counters.d.ts +0 -109
  74. package/dist/db/quota-counters.js +0 -17
  75. package/dist/db/ratings.d.ts +0 -160
  76. package/dist/db/ratings.js +0 -27
  77. package/dist/db/scores.d.ts +0 -160
  78. package/dist/db/scores.js +0 -15
  79. package/dist/db/standing.d.ts +0 -264
  80. package/dist/db/standing.js +0 -34
  81. package/dist/db/task-claim-failures.d.ts +0 -143
  82. package/dist/db/task-claim-failures.js +0 -37
  83. package/dist/db/task-outcomes.d.ts +0 -145
  84. package/dist/db/task-outcomes.js +0 -25
  85. package/dist/db/tasks.d.ts +0 -381
  86. package/dist/db/tasks.js +0 -106
  87. package/dist/db/timestamps.d.ts +0 -4
  88. package/dist/db/timestamps.js +0 -24
  89. package/dist/db/url.d.ts +0 -12
  90. package/dist/db/url.js +0 -25
@@ -1,9 +1,10 @@
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
- import { Version } from './events.js';
6
- import { CountedCounts, Level, StandingCounts } from './standing.js';
5
+ import { StoredVersion } from './events.js';
6
+ import { HANDSHAKE_MAX_CHARS } from './handshake.js';
7
+ import { CountedCounts, CountedCountsV3, Level, StandingCounts, StandingCountsV3, } from './standing.js';
7
8
  // The issuer every SEAL carries as iss from the SealKeeper cutover on.
8
9
  export const CREDENTIAL_ISSUER = 'sealkeeper.run';
9
10
  // Issuers a verifier still accepts for a while after the cutover. A SEAL
@@ -29,7 +30,6 @@ export function acceptedIssuer(iss, nowSeconds) {
29
30
  export const WELL_KNOWN_PATH = '/.well-known/seal.json';
30
31
  export const LEGACY_WELL_KNOWN_PATH = '/.well-known/vouched.json';
31
32
  export const WELL_KNOWN_URL = `https://${CREDENTIAL_ISSUER}${WELL_KNOWN_PATH}`;
32
- export const LEGACY_WELL_KNOWN_URL = `https://vouched.run${LEGACY_WELL_KNOWN_PATH}`;
33
33
  // The card extension a SEAL travels under. The two old URIs are kept beside
34
34
  // it for one release, VOU-77 drops them.
35
35
  export const SEAL_EXTENSION_URI = 'https://sealkeeper.run/ext/seal/v1';
@@ -51,17 +51,14 @@ const Count = z.int().min(0);
51
51
  // The version of the SEAL standard this issuer writes, carried as ver.
52
52
  export const SEAL_VERSION = 1;
53
53
  // Every version a verifier understands. Version 2 is version 1 plus
54
- // `counted`, the counted evidence the level read (VOU-139). CLIs 0.4.x
55
- // understand version 1 only and refuse any other ver, so the issuer keeps
56
- // writing version 1 until they are gone, and verifiers from this release
57
- // on accept both. SEAL standard section 9.
58
- export const SEAL_VERSIONS = [1, 2];
59
- // SEALs issued before version 1 carry no ver. Verifiers accept them as
60
- // legacy until the end of 25 September 2026 UTC, which is past the 24 hour
61
- // life of any SEAL issued before the deploy. From then on a SEAL without
62
- // ver is broken, unsupported_version. Unix seconds, the first second no
63
- // longer accepted.
64
- export const LEGACY_UNTIL = Date.UTC(2026, 8, 26) / 1000;
54
+ // `counted`, the counted evidence the level read (VOU-139). Version 3 is
55
+ // version 2 plus the posted counts, `fingerprint` and `state`, with the
56
+ // deprecated `version` dropped (VOU-77). CLIs before 0.4.5 understand
57
+ // version 1 only, and CLIs before 0.4.11 do not understand version 3, and
58
+ // each refuses a ver it does not know. So the issuer keeps writing version
59
+ // 1 until every CLI before 0.4.11 is gone. Verifiers accept all three from
60
+ // CLI 0.4.11 on. SEAL standard section 9.
61
+ export const SEAL_VERSIONS = [1, 2, 3];
65
62
  // One identity attestation reference, SEAL standard section 4a. A pointer
66
63
  // to an attestation an external provider made, never the attestation, a
67
64
  // name or a tenant id. kind is one of the four names or a URL for anything
@@ -83,8 +80,16 @@ export const IdentityReference = z.strictObject({
83
80
  // revoked, so every reference it still holds is current.
84
81
  export const operatorVerified = (identity) => identity.some((r) => r.scope === 'operator');
85
82
  // Each score is 0 to 1, or null when unearned (standard section 2). A
86
- // payload with a score outside that is malformed.
87
- const Scores = z.partialRecord(Dimension, z.number().min(0).max(1).nullable());
83
+ // payload with a score outside that is malformed. The issuer writes the
84
+ // base dimensions and competence by category only (Dimension). A verifier
85
+ // also accepts competence by task type (AcceptedDimension), the key SEALs
86
+ // carried before RT-3, so a SEAL issued before the change stays valid until
87
+ // it expires and a verifier never has to know which keys an issuer writes
88
+ // today. The API reads its stored SEALs back with the issued shape, so a
89
+ // stored SEAL with a task type key fails that parse and is reissued.
90
+ const Score = z.number().min(0).max(1).nullable();
91
+ const IssuedScores = z.partialRecord(Dimension, Score);
92
+ const VerifiedScores = z.partialRecord(AcceptedDimension, Score);
88
93
  // A SEAL lives at most 24 hours from issue, standard section 2. The issuer's
89
94
  // CREDENTIAL_TTL_HOURS may not go above it, and a payload whose exp is
90
95
  // further from its iat is malformed.
@@ -99,6 +104,26 @@ export const SEAL_CLOCK_SKEW_SECONDS = 300;
99
104
  // and parseSealPayload checks the clock with acceptedIssuer first.
100
105
  const IssuedIssuer = z.literal(CREDENTIAL_ISSUER);
101
106
  const VerifiedIssuer = z.enum([CREDENTIAL_ISSUER, ...LEGACY_ISSUERS]);
107
+ // The state of a version 3 SEAL, how the agent's current fingerprint stands
108
+ // against the one its level was last confirmed under. The set is closed.
109
+ // matches is the only value the issuer writes until the re-check flow
110
+ // exists. changed and provisional are reserved for it, so a verifier of
111
+ // version 3 already reads them.
112
+ export const SealState = z.enum(['matches', 'changed', 'provisional']);
113
+ // The fingerprint of a version 3 SEAL. hash is SHA-256 over bytes a later
114
+ // change to the standard defines, base64url, and a verifier treats it as
115
+ // opaque until then. at is Unix seconds of when the level was last
116
+ // confirmed under it, never after iat. null when the agent has sent no
117
+ // fingerprint. A 32 byte value in base64url has 43 characters and only
118
+ // some last characters, the rule agent-id.ts uses for a key.
119
+ export const SealFingerprint = z
120
+ .strictObject({
121
+ hash: z
122
+ .string()
123
+ .regex(/^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/, 'SHA-256, base64url'),
124
+ at: Seconds,
125
+ })
126
+ .nullable();
102
127
  const withinMaxTtl = (c) => c.exp - c.iat <= SEAL_MAX_TTL_SECONDS;
103
128
  const MAX_TTL_MESSAGE = 'exp must be at most 24 hours after iat';
104
129
  /*
@@ -111,17 +136,17 @@ const MAX_TTL_MESSAGE = 'exp must be at most 24 hours after iat';
111
136
  * Unix seconds of the agent's newest accepted event, null when it has sent
112
137
  * none, and dormant_days whole days since then at issue, null with it.
113
138
  */
114
- const v1Payload = (iss) => z
139
+ const v1Payload = (iss, scores) => z
115
140
  .strictObject({
116
141
  iss,
117
142
  sub: AgentId,
118
143
  ver: z.literal(SEAL_VERSION),
119
144
  iat: Seconds,
120
145
  exp: Seconds,
121
- agent_version: Version,
122
- version: Version,
146
+ agent_version: StoredVersion,
147
+ version: StoredVersion,
123
148
  level: Level,
124
- scores: Scores,
149
+ scores,
125
150
  counts: StandingCounts,
126
151
  operator: z.strictObject({ verified: z.boolean() }),
127
152
  identity: z.array(IdentityReference).max(16),
@@ -136,17 +161,17 @@ const v1Payload = (iss) => z
136
161
  * counted evidence the level read (CountedCounts). counts stay the raw
137
162
  * facts. Each counted value is at most its raw count.
138
163
  */
139
- const v2Payload = (iss) => z
164
+ const v2Payload = (iss, scores) => z
140
165
  .strictObject({
141
166
  iss,
142
167
  sub: AgentId,
143
168
  ver: z.literal(2),
144
169
  iat: Seconds,
145
170
  exp: Seconds,
146
- agent_version: Version,
147
- version: Version,
171
+ agent_version: StoredVersion,
172
+ version: StoredVersion,
148
173
  level: Level,
149
- scores: Scores,
174
+ scores,
150
175
  counts: StandingCounts,
151
176
  counted: CountedCounts,
152
177
  operator: z.strictObject({ verified: z.boolean() }),
@@ -160,65 +185,80 @@ const v2Payload = (iss) => z
160
185
  .refine((c) => c.counted.seed_tasks <= c.counts.seed_tasks &&
161
186
  c.counted.server_checked_tasks <= c.counts.server_checked_tasks &&
162
187
  c.counted.confirmed_tasks <= c.counts.confirmed_tasks, 'counted must not exceed counts');
163
- // The payload issued before version 1, with no ver. Accepted by verifiers
164
- // until LEGACY_UNTIL, never issued again. seed_tasks is optional because a
165
- // SEAL issued before it was added has none.
166
- const legacyPayload = (iss) => z
188
+ /*
189
+ * Version 3. Every version 2 field with the same meaning except version,
190
+ * which is dropped (VOU-77), so agent_version alone names the agent
191
+ * version. counts gains the three posted counts (StandingCountsV3), counted
192
+ * gains the counted twins of posted_tasks and posted_confirmed_tasks
193
+ * (CountedCountsV3), and fingerprint and state are new. Each counted value
194
+ * is at most its raw count, and fingerprint.at is not after iat. Nothing
195
+ * issues it yet.
196
+ */
197
+ const v3Payload = (iss, scores) => z
167
198
  .strictObject({
168
199
  iss,
169
200
  sub: AgentId,
201
+ ver: z.literal(3),
170
202
  iat: Seconds,
171
203
  exp: Seconds,
172
- version: Version,
173
- scores: Scores,
174
- counts: z.strictObject({
175
- events: Count,
176
- verified_tasks: Count,
177
- seed_tasks: Count.optional(),
178
- }),
204
+ agent_version: StoredVersion,
205
+ level: Level,
206
+ scores,
207
+ counts: StandingCountsV3,
208
+ counted: CountedCountsV3,
209
+ fingerprint: SealFingerprint,
210
+ state: SealState,
211
+ operator: z.strictObject({ verified: z.boolean() }),
212
+ identity: z.array(IdentityReference).max(16),
213
+ last_active: Seconds.nullable(),
214
+ dormant_days: Count.nullable(),
179
215
  })
180
216
  .refine((c) => c.exp > c.iat, 'exp must be after iat')
181
- .refine(withinMaxTtl, MAX_TTL_MESSAGE);
182
- // The issuer side. iss is CREDENTIAL_ISSUER only.
183
- export const CredentialPayload = v1Payload(IssuedIssuer);
184
- export const CredentialPayloadV2 = v2Payload(IssuedIssuer);
185
- export const LegacyCredentialPayload = legacyPayload(IssuedIssuer);
186
- // The verifier side. iss may also be a LEGACY_ISSUERS entry. The shape has
217
+ .refine(withinMaxTtl, MAX_TTL_MESSAGE)
218
+ .refine((c) => c.counted.seed_tasks <= c.counts.seed_tasks &&
219
+ c.counted.server_checked_tasks <= c.counts.server_checked_tasks &&
220
+ c.counted.confirmed_tasks <= c.counts.confirmed_tasks &&
221
+ c.counted.posted_tasks <= c.counts.posted_tasks &&
222
+ c.counted.posted_confirmed_tasks <= c.counts.posted_confirmed_tasks, 'counted must not exceed counts')
223
+ .refine((c) => c.fingerprint === null || c.fingerprint.at <= c.iat, 'fingerprint.at must not be after iat');
224
+ // The issuer side. iss is CREDENTIAL_ISSUER only, and scores carry
225
+ // competence by category only.
226
+ export const CredentialPayload = v1Payload(IssuedIssuer, IssuedScores);
227
+ export const CredentialPayloadV2 = v2Payload(IssuedIssuer, IssuedScores);
228
+ export const CredentialPayloadV3 = v3Payload(IssuedIssuer, IssuedScores);
229
+ // The verifier side. iss may also be a LEGACY_ISSUERS entry, and scores may
230
+ // also carry competence by task type. The shape has
187
231
  // no clock, so read these through parseSealPayload, never on their own.
188
- export const VerifiedCredentialPayload = v1Payload(VerifiedIssuer);
189
- export const VerifiedCredentialPayloadV2 = v2Payload(VerifiedIssuer);
190
- export const VerifiedLegacyCredentialPayload = legacyPayload(VerifiedIssuer);
191
- // Any payload a verifier accepts today, version 1, version 2 or legacy,
192
- // from the current issuer or a legacy one.
232
+ export const VerifiedCredentialPayload = v1Payload(VerifiedIssuer, VerifiedScores);
233
+ export const VerifiedCredentialPayloadV2 = v2Payload(VerifiedIssuer, VerifiedScores);
234
+ export const VerifiedCredentialPayloadV3 = v3Payload(VerifiedIssuer, VerifiedScores);
235
+ // Any payload a verifier accepts today, version 1, 2 or 3, from the current
236
+ // issuer or a legacy one. Each version has every field of the one before
237
+ // with the same meaning, except version, which version 3 drops. A reader
238
+ // takes the agent version from agent_version, which all three carry.
193
239
  export const SealPayload = z.union([
194
240
  VerifiedCredentialPayload,
195
241
  VerifiedCredentialPayloadV2,
196
- VerifiedLegacyCredentialPayload,
242
+ VerifiedCredentialPayloadV3,
197
243
  ]);
198
- // A versioned payload carries every legacy field too, so the guard is on
199
- // ver, the narrower of the two. Its false branch is the legacy shape. The
200
- // name is kept from when version 1 was the only one.
201
- export const isSealV1 = (p) => 'ver' in p;
202
244
  // The counted evidence a SEAL carries, version 2 on, null before.
203
245
  export const sealCounted = (p) => 'counted' in p ? p.counted : null;
204
246
  const hasVer = (raw) => typeof raw === 'object' && raw !== null && 'ver' in raw;
205
247
  /*
206
248
  * Whether a verifier understands the version of a signed payload. Read
207
- * after the signature and the issuer check out and before the shape. ver 1
208
- * and 2 (SEAL_VERSIONS) are understood. Any other ver is unsupported_version, as section 9 of the
209
- * standard says, never valid with an unknown meaning. No ver at all is a
210
- * legacy SEAL, accepted before LEGACY_UNTIL and unsupported_version from
211
- * then on. Anything that is not an object is left to the shape check.
249
+ * after the signature and the issuer check out and before the shape. ver 1,
250
+ * 2 and 3 (SEAL_VERSIONS) are understood. Any other ver is
251
+ * unsupported_version, as section 9 of the standard says, never valid with
252
+ * an unknown meaning. So is no ver at all, a SEAL issued before version 1,
253
+ * whose last one expired on 26 September 2026. Anything that is not an
254
+ * object is left to the shape check.
212
255
  */
213
- export function sealVersionProblem(raw, nowSeconds) {
256
+ export function sealVersionProblem(raw) {
214
257
  if (typeof raw !== 'object' || raw === null)
215
258
  return null;
216
- if (hasVer(raw)) {
217
- return SEAL_VERSIONS.includes(raw.ver)
218
- ? null
219
- : 'unsupported_version';
220
- }
221
- return nowSeconds < LEGACY_UNTIL ? null : 'unsupported_version';
259
+ return hasVer(raw) && SEAL_VERSIONS.includes(raw.ver)
260
+ ? null
261
+ : 'unsupported_version';
222
262
  }
223
263
  /*
224
264
  * The iat half of step 4 of the standard, section 3. "Check exp is in the
@@ -232,9 +272,8 @@ export function sealIatProblem(iat, nowSeconds) {
232
272
  return iat - nowSeconds > SEAL_CLOCK_SKEW_SECONDS ? 'not_yet_valid' : null;
233
273
  }
234
274
  // The issuer, then the version check above, then the shape for that
235
- // version, in the standard's order. The API and the web read a verified
236
- // payload through this. The CLI, which reads payloads loosely, calls
237
- // acceptedIssuer and sealVersionProblem itself.
275
+ // version, in the standard's order. verifySeal reads a verified payload
276
+ // through this.
238
277
  export function parseSealPayload(raw, nowSeconds) {
239
278
  const iss = typeof raw === 'object' && raw !== null && 'iss' in raw
240
279
  ? raw.iss
@@ -242,12 +281,13 @@ export function parseSealPayload(raw, nowSeconds) {
242
281
  if (!acceptedIssuer(iss, nowSeconds)) {
243
282
  return { ok: false, reason: 'wrong_issuer' };
244
283
  }
245
- const problem = sealVersionProblem(raw, nowSeconds);
284
+ const problem = sealVersionProblem(raw);
246
285
  if (problem)
247
286
  return { ok: false, reason: problem };
248
- const parsed = (!hasVer(raw)
249
- ? VerifiedLegacyCredentialPayload
250
- : raw.ver === 2
287
+ const ver = hasVer(raw) ? raw.ver : undefined;
288
+ const parsed = (ver === 3
289
+ ? VerifiedCredentialPayloadV3
290
+ : ver === 2
251
291
  ? VerifiedCredentialPayloadV2
252
292
  : VerifiedCredentialPayload).safeParse(raw);
253
293
  return parsed.success
@@ -278,10 +318,17 @@ export const LegacySealExtension = z.strictObject({
278
318
  description: z.string().min(1).max(512),
279
319
  params: z.strictObject({ credential: Jws }),
280
320
  });
321
+ // The current extension may also carry handshake, the agent's signed
322
+ // handshake (VB-6, ./handshake.ts) beside the SEAL, refreshed each time the
323
+ // card is written. A reader takes params loosely, so one that does not know
324
+ // handshake ignores it. The two old URIs carry the SEAL alone.
281
325
  export const SealExtension = z.strictObject({
282
326
  uri: z.literal(SEAL_EXTENSION_URI),
283
327
  description: z.string().min(1).max(512),
284
- params: z.strictObject({ credential: Jws }),
328
+ params: z.strictObject({
329
+ credential: Jws,
330
+ handshake: Jws.max(HANDSHAKE_MAX_CHARS).optional(),
331
+ }),
285
332
  });
286
333
  export const credentialExtension = (jws) => ({
287
334
  uri: CREDENTIAL_EXTENSION_URI,
@@ -293,15 +340,17 @@ export const legacySealExtension = (jws) => ({
293
340
  description: EXTENSION_DESCRIPTION,
294
341
  params: { credential: jws },
295
342
  });
296
- export const sealExtension = (jws) => ({
343
+ export const sealExtension = (jws, handshake) => ({
297
344
  uri: SEAL_EXTENSION_URI,
298
345
  description: EXTENSION_DESCRIPTION,
299
- params: { credential: jws },
346
+ params: handshake === undefined
347
+ ? { credential: jws }
348
+ : { credential: jws, handshake },
300
349
  });
301
350
  // The extensions a card carries for one SEAL, in SEAL_EXTENSION_URIS order,
302
- // the current URI first.
303
- export const sealExtensions = (jws) => [
304
- sealExtension(jws),
351
+ // the current URI first. A handshake goes on the current one only.
352
+ export const sealExtensions = (jws, handshake) => [
353
+ sealExtension(jws, handshake),
305
354
  legacySealExtension(jws),
306
355
  credentialExtension(jws),
307
356
  ];
@@ -314,9 +363,8 @@ export const AgentSkill = z.strictObject({
314
363
  examples: z.array(z.string().min(1).max(1024)).max(32).optional(),
315
364
  });
316
365
  const Modes = z.array(z.string().min(1).max(128)).max(32).default(['text']);
317
- // The subset of the A2A agent card that Vouched emits, strict so the CLI
318
- // never writes a field it did not mean to. Reading someone else's card uses
319
- // the loose AgentCardResponse instead.
366
+ // The subset of the A2A agent card that SealKeeper emits, strict so the
367
+ // CLI never writes a field it did not mean to.
320
368
  export const AgentCard = z.strictObject({
321
369
  protocolVersion: z.literal(A2A_PROTOCOL_VERSION),
322
370
  name: z.string().min(1).max(128),
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { TaskCategory } 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<`competence:${string}`>;
12
+ export declare const CompetenceDimension: z.ZodTemplateLiteral<"competence:code" | "competence:conversation" | "competence:data" | "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<`competence:${string}`>]>;
21
+ }>, z.ZodTemplateLiteral<"competence:code" | "competence:conversation" | "competence:data" | "competence:operations" | "competence:other" | "competence:research" | "competence:writing">]>;
19
22
  export type Dimension = z.infer<typeof Dimension>;
20
- export declare const competence: (taskType: TaskType) => CompetenceDimension;
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: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: TaskCategory) => CompetenceDimension;
33
+ export declare const categoryOf: (dimension: string) => TaskCategory | null;
21
34
  export declare const isTrustDimension: (dimension: string) => boolean;
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { TASK_CATEGORIES, TaskCategory } 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,54 @@ export const BaseDimension = z.enum([
6
7
  'cost_latency',
7
8
  'provenance',
8
9
  ]);
9
- export const CompetenceDimension = z.templateLiteral(['competence:', TaskType]);
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.
15
+ */
16
+ export const CompetenceDimension = z.templateLiteral([
17
+ 'competence:',
18
+ TaskCategory,
19
+ ]);
20
+ // The most task types a competence entry's breakdown lists, the most
21
+ // claimed first, so the answer stays small however many free task types an
22
+ // agent took.
23
+ export const COMPETENCE_TYPES_MAX = 32;
24
+ // The seven competence dimensions in category order, the order every list
25
+ // shows them in.
26
+ export const COMPETENCE_DIMENSIONS = TASK_CATEGORIES.map((c) => CompetenceDimension.parse(`competence:${c}`));
27
+ // What the issuer writes and what a request may name, the base dimensions
28
+ // and the seven competence categories.
10
29
  export const Dimension = z.union([BaseDimension, CompetenceDimension]);
11
- export const competence = (taskType) => CompetenceDimension.parse(`competence:${taskType}`);
30
+ /*
31
+ * competence:<task_type>, the competence key SEALs carried before RT-3,
32
+ * when competence was per task type. The issuer no longer writes it. Every category is also a valid task type, so this pattern
33
+ * takes both kinds of key.
34
+ */
35
+ export const TypeCompetenceDimension = z.templateLiteral([
36
+ 'competence:',
37
+ TaskType,
38
+ ]);
39
+ /*
40
+ * Any dimension a reader accepts, issued today or before RT-3. A verifier
41
+ * reads SEAL scores with it, so a SEAL issued with a task type key stays
42
+ * valid for the rest of its 24 hours, and so do stored feed items that
43
+ * name one. Never used for what the issuer writes or a request names.
44
+ */
45
+ export const AcceptedDimension = z.union([
46
+ BaseDimension,
47
+ CompetenceDimension,
48
+ TypeCompetenceDimension,
49
+ ]);
50
+ export const competence = (category) => CompetenceDimension.parse(`competence:${category}`);
51
+ // The category inside a competence dimension, null for any other.
52
+ export const categoryOf = (dimension) => {
53
+ const parsed = CompetenceDimension.safeParse(dimension);
54
+ return parsed.success
55
+ ? parsed.data.slice('competence:'.length)
56
+ : null;
57
+ };
12
58
  // Reliability, safety, provenance and every competence dimension are trust.
13
59
  // They need verified work from another operator before they get a value.
14
60
  // cost_latency comes from usage events and is information, not trust.
package/dist/events.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ export declare const StoredVersion: z.ZodString;
2
3
  export declare const Version: z.ZodString;
3
4
  export declare const EventType: z.ZodEnum<{
4
5
  incident: "incident";
package/dist/events.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { TaskType } from './dimensions.js';
3
+ import { HIDDEN } from './hidden.js';
3
4
  import { Sha256Hex, TaskOutcome } from './tasks.js';
4
5
  /*
5
6
  * The Zod pattern, shown once. Every schema file in this package follows it.
@@ -25,7 +26,18 @@ import { Sha256Hex, TaskOutcome } from './tasks.js';
25
26
  *
26
27
  * Use parse to throw on bad input and safeParse to get { success, data, error }.
27
28
  */
28
- export const Version = z.string().min(1).max(32);
29
+ /*
30
+ * An agent version as it is stored and read back. Only the length rule,
31
+ * since a row written before Version had a character rule may hold
32
+ * anything. Every answer and every read of a stored row uses this, so an
33
+ * old stored version never throws. New input is parsed with Version.
34
+ */
35
+ export const StoredVersion = z.string().min(1).max(32);
36
+ // An agent version as a caller sends it, in an event, a registration or a
37
+ // version change. It is signed into the SEAL and shown on public pages, so
38
+ // it refuses the characters OperatorDisplayName refuses (HIDDEN) and a
39
+ // space at either end, on top of the length rule.
40
+ export const Version = StoredVersion.refine((v) => !HIDDEN.test(v), 'No control characters').refine((v) => v === v.trim(), 'No space at the start or end');
29
41
  const Name = z
30
42
  .string()
31
43
  .min(1)
@@ -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
+ ];