@sealkeeper/schema 0.4.5 → 0.4.7

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 (73) hide show
  1. package/README.md +2 -2
  2. package/dist/api.d.ts +30 -38
  3. package/dist/api.js +87 -42
  4. package/dist/client-address.d.ts +6 -0
  5. package/dist/client-address.js +81 -0
  6. package/dist/credential.d.ts +1 -66
  7. package/dist/credential.js +25 -63
  8. package/dist/events.d.ts +1 -0
  9. package/dist/events.js +13 -1
  10. package/dist/goal.js +2 -2
  11. package/dist/hidden.d.ts +1 -0
  12. package/dist/hidden.js +5 -0
  13. package/dist/index.d.ts +3 -1
  14. package/dist/index.js +3 -1
  15. package/dist/json-shape.d.ts +9 -0
  16. package/dist/json-shape.js +70 -0
  17. package/dist/moderation.d.ts +2 -1
  18. package/dist/moderation.js +169 -75
  19. package/dist/seal-conformance.js +2 -2
  20. package/dist/seal-verify.d.ts +21 -0
  21. package/dist/seal-verify.js +63 -0
  22. package/dist/standing.js +1 -1
  23. package/dist/tasks.d.ts +2 -0
  24. package/dist/tasks.js +14 -4
  25. package/package.json +2 -6
  26. package/dist/db/agent-milestones.d.ts +0 -75
  27. package/dist/db/agent-milestones.js +0 -16
  28. package/dist/db/agent-renames.d.ts +0 -126
  29. package/dist/db/agent-renames.js +0 -20
  30. package/dist/db/agents.d.ts +0 -266
  31. package/dist/db/agents.js +0 -79
  32. package/dist/db/client.d.ts +0 -2471
  33. package/dist/db/client.js +0 -40
  34. package/dist/db/credentials.d.ts +0 -143
  35. package/dist/db/credentials.js +0 -16
  36. package/dist/db/deleted-operators.d.ts +0 -75
  37. package/dist/db/deleted-operators.js +0 -13
  38. package/dist/db/events.d.ts +0 -160
  39. package/dist/db/events.js +0 -41
  40. package/dist/db/feed-items.d.ts +0 -109
  41. package/dist/db/feed-items.js +0 -21
  42. package/dist/db/index.d.ts +0 -76
  43. package/dist/db/index.js +0 -28
  44. package/dist/db/migrate.d.ts +0 -1
  45. package/dist/db/migrate.js +0 -34
  46. package/dist/db/migrator.d.ts +0 -4
  47. package/dist/db/migrator.js +0 -26
  48. package/dist/db/operator-identities.d.ts +0 -211
  49. package/dist/db/operator-identities.js +0 -49
  50. package/dist/db/operator-level-grants.d.ts +0 -109
  51. package/dist/db/operator-level-grants.js +0 -30
  52. package/dist/db/operator-slugs.d.ts +0 -92
  53. package/dist/db/operator-slugs.js +0 -25
  54. package/dist/db/operators.d.ts +0 -228
  55. package/dist/db/operators.js +0 -39
  56. package/dist/db/quota-counters.d.ts +0 -109
  57. package/dist/db/quota-counters.js +0 -17
  58. package/dist/db/ratings.d.ts +0 -160
  59. package/dist/db/ratings.js +0 -27
  60. package/dist/db/scores.d.ts +0 -160
  61. package/dist/db/scores.js +0 -15
  62. package/dist/db/standing.d.ts +0 -264
  63. package/dist/db/standing.js +0 -34
  64. package/dist/db/task-claim-failures.d.ts +0 -143
  65. package/dist/db/task-claim-failures.js +0 -37
  66. package/dist/db/task-outcomes.d.ts +0 -145
  67. package/dist/db/task-outcomes.js +0 -25
  68. package/dist/db/tasks.d.ts +0 -381
  69. package/dist/db/tasks.js +0 -100
  70. package/dist/db/timestamps.d.ts +0 -4
  71. package/dist/db/timestamps.js +0 -24
  72. package/dist/db/url.d.ts +0 -12
  73. package/dist/db/url.js +0 -25
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { AgentId, Ed25519PublicKey } from './agent-id.js';
3
3
  import { Dimension } from './dimensions.js';
4
4
  import { Jws } from './envelope.js';
5
- import { Version } from './events.js';
5
+ import { StoredVersion } from './events.js';
6
6
  import { CountedCounts, Level, StandingCounts } from './standing.js';
7
7
  // The issuer every SEAL carries as iss from the SealKeeper cutover on.
8
8
  export const CREDENTIAL_ISSUER = 'sealkeeper.run';
@@ -29,7 +29,6 @@ export function acceptedIssuer(iss, nowSeconds) {
29
29
  export const WELL_KNOWN_PATH = '/.well-known/seal.json';
30
30
  export const LEGACY_WELL_KNOWN_PATH = '/.well-known/vouched.json';
31
31
  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
32
  // The card extension a SEAL travels under. The two old URIs are kept beside
34
33
  // it for one release, VOU-77 drops them.
35
34
  export const SEAL_EXTENSION_URI = 'https://sealkeeper.run/ext/seal/v1';
@@ -56,12 +55,6 @@ export const SEAL_VERSION = 1;
56
55
  // writing version 1 until they are gone, and verifiers from this release
57
56
  // on accept both. SEAL standard section 9.
58
57
  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;
65
58
  // One identity attestation reference, SEAL standard section 4a. A pointer
66
59
  // to an attestation an external provider made, never the attestation, a
67
60
  // name or a tenant id. kind is one of the four names or a URL for anything
@@ -118,8 +111,8 @@ const v1Payload = (iss) => z
118
111
  ver: z.literal(SEAL_VERSION),
119
112
  iat: Seconds,
120
113
  exp: Seconds,
121
- agent_version: Version,
122
- version: Version,
114
+ agent_version: StoredVersion,
115
+ version: StoredVersion,
123
116
  level: Level,
124
117
  scores: Scores,
125
118
  counts: StandingCounts,
@@ -143,8 +136,8 @@ const v2Payload = (iss) => z
143
136
  ver: z.literal(2),
144
137
  iat: Seconds,
145
138
  exp: Seconds,
146
- agent_version: Version,
147
- version: Version,
139
+ agent_version: StoredVersion,
140
+ version: StoredVersion,
148
141
  level: Level,
149
142
  scores: Scores,
150
143
  counts: StandingCounts,
@@ -160,65 +153,38 @@ const v2Payload = (iss) => z
160
153
  .refine((c) => c.counted.seed_tasks <= c.counts.seed_tasks &&
161
154
  c.counted.server_checked_tasks <= c.counts.server_checked_tasks &&
162
155
  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
167
- .strictObject({
168
- iss,
169
- sub: AgentId,
170
- iat: Seconds,
171
- 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
- }),
179
- })
180
- .refine((c) => c.exp > c.iat, 'exp must be after iat')
181
- .refine(withinMaxTtl, MAX_TTL_MESSAGE);
182
156
  // The issuer side. iss is CREDENTIAL_ISSUER only.
183
157
  export const CredentialPayload = v1Payload(IssuedIssuer);
184
158
  export const CredentialPayloadV2 = v2Payload(IssuedIssuer);
185
- export const LegacyCredentialPayload = legacyPayload(IssuedIssuer);
186
159
  // The verifier side. iss may also be a LEGACY_ISSUERS entry. The shape has
187
160
  // no clock, so read these through parseSealPayload, never on their own.
188
161
  export const VerifiedCredentialPayload = v1Payload(VerifiedIssuer);
189
162
  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.
163
+ // Any payload a verifier accepts today, version 1 or version 2, from the
164
+ // current issuer or a legacy one. Version 2 has every version 1 field with
165
+ // the same meaning, so a reader of version 1 fields takes both.
193
166
  export const SealPayload = z.union([
194
167
  VerifiedCredentialPayload,
195
168
  VerifiedCredentialPayloadV2,
196
- VerifiedLegacyCredentialPayload,
197
169
  ]);
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
170
  // The counted evidence a SEAL carries, version 2 on, null before.
203
171
  export const sealCounted = (p) => 'counted' in p ? p.counted : null;
204
172
  const hasVer = (raw) => typeof raw === 'object' && raw !== null && 'ver' in raw;
205
173
  /*
206
174
  * Whether a verifier understands the version of a signed payload. Read
207
175
  * 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.
176
+ * and 2 (SEAL_VERSIONS) are understood. Any other ver is
177
+ * unsupported_version, as section 9 of the standard says, never valid with
178
+ * an unknown meaning. So is no ver at all, a SEAL issued before version 1,
179
+ * whose last one expired on 26 September 2026. Anything that is not an
180
+ * object is left to the shape check.
212
181
  */
213
- export function sealVersionProblem(raw, nowSeconds) {
182
+ export function sealVersionProblem(raw) {
214
183
  if (typeof raw !== 'object' || raw === null)
215
184
  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';
185
+ return hasVer(raw) && SEAL_VERSIONS.includes(raw.ver)
186
+ ? null
187
+ : 'unsupported_version';
222
188
  }
223
189
  /*
224
190
  * The iat half of step 4 of the standard, section 3. "Check exp is in the
@@ -232,9 +198,8 @@ export function sealIatProblem(iat, nowSeconds) {
232
198
  return iat - nowSeconds > SEAL_CLOCK_SKEW_SECONDS ? 'not_yet_valid' : null;
233
199
  }
234
200
  // 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.
201
+ // version, in the standard's order. verifySeal reads a verified payload
202
+ // through this.
238
203
  export function parseSealPayload(raw, nowSeconds) {
239
204
  const iss = typeof raw === 'object' && raw !== null && 'iss' in raw
240
205
  ? raw.iss
@@ -242,14 +207,12 @@ export function parseSealPayload(raw, nowSeconds) {
242
207
  if (!acceptedIssuer(iss, nowSeconds)) {
243
208
  return { ok: false, reason: 'wrong_issuer' };
244
209
  }
245
- const problem = sealVersionProblem(raw, nowSeconds);
210
+ const problem = sealVersionProblem(raw);
246
211
  if (problem)
247
212
  return { ok: false, reason: problem };
248
- const parsed = (!hasVer(raw)
249
- ? VerifiedLegacyCredentialPayload
250
- : raw.ver === 2
251
- ? VerifiedCredentialPayloadV2
252
- : VerifiedCredentialPayload).safeParse(raw);
213
+ const parsed = (hasVer(raw) && raw.ver === 2
214
+ ? VerifiedCredentialPayloadV2
215
+ : VerifiedCredentialPayload).safeParse(raw);
253
216
  return parsed.success
254
217
  ? { ok: true, payload: parsed.data }
255
218
  : { ok: false, reason: 'malformed' };
@@ -314,9 +277,8 @@ export const AgentSkill = z.strictObject({
314
277
  examples: z.array(z.string().min(1).max(1024)).max(32).optional(),
315
278
  });
316
279
  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.
280
+ // The subset of the A2A agent card that SealKeeper emits, strict so the
281
+ // CLI never writes a field it did not mean to.
320
282
  export const AgentCard = z.strictObject({
321
283
  protocolVersion: z.literal(A2A_PROTOCOL_VERSION),
322
284
  name: z.string().min(1).max(128),
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)
package/dist/goal.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { AgentId } from './agent-id.js';
3
- import { Version } from './events.js';
3
+ import { StoredVersion } from './events.js';
4
4
  import { LADDER, Level } from './standing.js';
5
5
  // GET /v1/agents/:id/goal. What the agent's current version needs for its
6
6
  // next level, threshold by threshold, and what to do next. The API sends
@@ -158,7 +158,7 @@ export const GoalPending = z.strictObject({
158
158
  });
159
159
  export const GoalResponse = z.strictObject({
160
160
  agentId: AgentId,
161
- version: Version,
161
+ version: StoredVersion,
162
162
  level: Level,
163
163
  nextLevel: Level.nullable(),
164
164
  ladder: z.array(GoalLadderStep),
@@ -0,0 +1 @@
1
+ export declare const HIDDEN: RegExp;
package/dist/hidden.js ADDED
@@ -0,0 +1,5 @@
1
+ // Control, format, private use, unassigned and lone surrogate characters
2
+ // (bidi overrides, zero width joiners and the like), and the line and
3
+ // paragraph separators, which can hide, reorder or break what a reader sees.
4
+ // Shared by the text rules in this package and not exported from it.
5
+ export const HIDDEN = /[\p{C}\p{Zl}\p{Zp}]/u;
package/dist/index.d.ts CHANGED
@@ -3,16 +3,18 @@ export * from './agent-name.js';
3
3
  export * from './api.js';
4
4
  export * from './badge.js';
5
5
  export * from './base64url.js';
6
+ export * from './client-address.js';
6
7
  export * from './credential.js';
7
8
  export * from './dimensions.js';
8
9
  export * from './envelope.js';
9
10
  export * from './events.js';
10
11
  export * from './goal.js';
12
+ export * from './json-shape.js';
11
13
  export * from './moderation.js';
12
14
  export * from './operator-domains.js';
13
15
  export * from './policy.js';
14
16
  export * from './runtime.js';
17
+ export * from './seal-verify.js';
15
18
  export * from './standing.js';
16
19
  export * from './tasks.js';
17
20
  export * from './top-dimensions.js';
18
- export declare const SCHEMA_VERSION = 1;
package/dist/index.js CHANGED
@@ -3,16 +3,18 @@ export * from './agent-name.js';
3
3
  export * from './api.js';
4
4
  export * from './badge.js';
5
5
  export * from './base64url.js';
6
+ export * from './client-address.js';
6
7
  export * from './credential.js';
7
8
  export * from './dimensions.js';
8
9
  export * from './envelope.js';
9
10
  export * from './events.js';
10
11
  export * from './goal.js';
12
+ export * from './json-shape.js';
11
13
  export * from './moderation.js';
12
14
  export * from './operator-domains.js';
13
15
  export * from './policy.js';
14
16
  export * from './runtime.js';
17
+ export * from './seal-verify.js';
15
18
  export * from './standing.js';
16
19
  export * from './tasks.js';
17
20
  export * from './top-dimensions.js';
18
- export const SCHEMA_VERSION = 1;
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ export type JsonShapeLimits = {
3
+ readonly maxDepth: number;
4
+ readonly maxKeys: number;
5
+ readonly maxBytes: number;
6
+ };
7
+ export type JsonShapeIssue = 'too_deep' | 'too_many_keys';
8
+ export declare function jsonShapeIssue(value: unknown, limits: Pick<JsonShapeLimits, 'maxDepth' | 'maxKeys'>): JsonShapeIssue | null;
9
+ export declare function boundedJsonObject(name: string, limits: JsonShapeLimits): z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -0,0 +1,70 @@
1
+ import { z } from 'zod';
2
+ import { utf8Encode } from './base64url.js';
3
+ /*
4
+ * The first depth or key count limit a JSON value breaks, or null when it
5
+ * keeps to both.
6
+ *
7
+ * Walks with its own stack rather than recursing, so a value nested
8
+ * thousands deep cannot overflow the call stack, and stops at the first
9
+ * breach. Run it before JSON.stringify on anything a caller sent.
10
+ * stringify recurses and throws RangeError past about 7,700 levels, and
11
+ * indented output grows with the square of the depth (VOU-214).
12
+ */
13
+ export function jsonShapeIssue(value, limits) {
14
+ const stack = [];
15
+ const push = (v, depth) => {
16
+ if (typeof v === 'object' && v !== null)
17
+ stack.push({ node: v, depth });
18
+ };
19
+ push(value, 1);
20
+ let keys = 0;
21
+ for (let top = stack.pop(); top !== undefined; top = stack.pop()) {
22
+ if (top.depth > limits.maxDepth)
23
+ return 'too_deep';
24
+ let children;
25
+ if (Array.isArray(top.node)) {
26
+ children = top.node;
27
+ }
28
+ else {
29
+ children = Object.values(top.node);
30
+ keys += children.length;
31
+ if (keys > limits.maxKeys)
32
+ return 'too_many_keys';
33
+ }
34
+ for (const child of children)
35
+ push(child, top.depth + 1);
36
+ }
37
+ return null;
38
+ }
39
+ /*
40
+ * A JSON object held to the limits. The depth and key checks run first, so
41
+ * the byte check only ever stringifies a value that cannot throw, and a
42
+ * breach is a Zod issue (a 400 at the API), never a RangeError (a 500).
43
+ * superRefine is used rather than refine so each breach gets its own
44
+ * message and the byte check can be skipped once one is found.
45
+ */
46
+ export function boundedJsonObject(name, limits) {
47
+ return z.record(z.string(), z.unknown()).superRefine((value, ctx) => {
48
+ const issue = jsonShapeIssue(value, limits);
49
+ if (issue === 'too_deep') {
50
+ ctx.addIssue({
51
+ code: 'custom',
52
+ message: `${name} must nest at most ${limits.maxDepth} levels deep`,
53
+ });
54
+ return;
55
+ }
56
+ if (issue === 'too_many_keys') {
57
+ ctx.addIssue({
58
+ code: 'custom',
59
+ message: `${name} must have at most ${limits.maxKeys} keys`,
60
+ });
61
+ return;
62
+ }
63
+ if (utf8Encode(JSON.stringify(value)).length > limits.maxBytes) {
64
+ ctx.addIssue({
65
+ code: 'custom',
66
+ message: `${name} must be at most ${limits.maxBytes} bytes`,
67
+ });
68
+ }
69
+ });
70
+ }
@@ -15,12 +15,13 @@ export declare const PROTECTED_PREFIXES: readonly string[];
15
15
  export declare function protectedNameOf(text: string): string | null;
16
16
  export declare const RESERVED_WORDS: readonly string[];
17
17
  export declare function isReservedWord(text: string): boolean;
18
- export type NameRefusalCode = 'name_protected' | 'name_reserved';
18
+ export type NameRefusalCode = 'name_protected' | 'name_reserved' | 'name_mixed_script';
19
19
  export type NameRefusal = {
20
20
  code: NameRefusalCode;
21
21
  message: string;
22
22
  };
23
23
  export declare function moderateSlug(slug: string): NameRefusal | null;
24
+ export declare function mixedScriptOf(text: string): string | null;
24
25
  export declare function moderateDisplayName(name: string): NameRefusal | null;
25
26
  export declare function slugBase(login: string): string;
26
27
  export declare const suffixedSlug: (base: string, n: number) => string;