@sealkeeper/schema 0.4.4 → 0.4.5

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 (50) hide show
  1. package/dist/agent-name.d.ts +1 -1
  2. package/dist/agent-name.js +4 -4
  3. package/dist/api.d.ts +604 -27
  4. package/dist/api.js +207 -43
  5. package/dist/badge.d.ts +1 -0
  6. package/dist/badge.js +7 -0
  7. package/dist/db/agents.d.ts +70 -0
  8. package/dist/db/agents.js +18 -0
  9. package/dist/db/client.d.ts +341 -0
  10. package/dist/db/client.js +2 -0
  11. package/dist/db/events.js +7 -0
  12. package/dist/db/index.d.ts +16 -0
  13. package/dist/db/index.js +4 -0
  14. package/dist/db/migrate.js +5 -1
  15. package/dist/db/migrator.d.ts +3 -1
  16. package/dist/db/migrator.js +8 -2
  17. package/dist/db/operator-identities.d.ts +211 -0
  18. package/dist/db/operator-identities.js +49 -0
  19. package/dist/db/operator-level-grants.d.ts +109 -0
  20. package/dist/db/operator-level-grants.js +30 -0
  21. package/dist/db/operator-slugs.d.ts +92 -0
  22. package/dist/db/operator-slugs.js +25 -0
  23. package/dist/db/operators.d.ts +119 -0
  24. package/dist/db/operators.js +30 -2
  25. package/dist/db/standing.d.ts +1 -0
  26. package/dist/db/standing.js +2 -0
  27. package/dist/db/task-claim-failures.d.ts +143 -0
  28. package/dist/db/task-claim-failures.js +37 -0
  29. package/dist/db/tasks.d.ts +60 -0
  30. package/dist/db/tasks.js +13 -1
  31. package/dist/envelope.d.ts +3 -0
  32. package/dist/envelope.js +16 -11
  33. package/dist/goal.d.ts +62 -1
  34. package/dist/goal.js +70 -11
  35. package/dist/index.d.ts +5 -0
  36. package/dist/index.js +5 -0
  37. package/dist/moderation.d.ts +28 -0
  38. package/dist/moderation.js +450 -0
  39. package/dist/operator-domains.d.ts +104 -0
  40. package/dist/operator-domains.js +85 -0
  41. package/dist/policy.d.ts +4 -0
  42. package/dist/policy.js +10 -0
  43. package/dist/runtime.d.ts +17 -0
  44. package/dist/runtime.js +61 -0
  45. package/dist/seal-conformance.js +2 -0
  46. package/dist/standing.d.ts +27 -8
  47. package/dist/standing.js +59 -22
  48. package/dist/tasks.d.ts +23 -1
  49. package/dist/tasks.js +29 -5
  50. package/package.json +1 -1
@@ -0,0 +1,104 @@
1
+ import { z } from 'zod';
2
+ export declare const DNS_RECORD_LABEL = "_sealkeeper";
3
+ export declare const DNS_RECORD_PREFIX = "sealkeeper-verify=";
4
+ export declare const DNS_IDENTITY_KIND = "https://sealkeeper.run/seal/identity/dns";
5
+ export declare const DNS_IDENTITY_PROVIDER = "https://sealkeeper.run";
6
+ export declare const OPERATOR_VERIFICATION: {
7
+ readonly graceDays: 14;
8
+ readonly recheckHours: 24;
9
+ readonly maxDomains: 5;
10
+ };
11
+ export declare const DomainName: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>;
12
+ export type DomainName = z.infer<typeof DomainName>;
13
+ export declare const AddDomainRequest: z.ZodObject<{
14
+ domain: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>;
15
+ }, z.core.$strict>;
16
+ export type AddDomainRequest = z.infer<typeof AddDomainRequest>;
17
+ export declare const DomainParams: z.ZodObject<{
18
+ id: z.ZodUUID;
19
+ }, z.core.$strict>;
20
+ export type DomainParams = z.infer<typeof DomainParams>;
21
+ export declare const DomainStatus: z.ZodEnum<{
22
+ lapsed: "lapsed";
23
+ lapsing: "lapsing";
24
+ pending: "pending";
25
+ verified: "verified";
26
+ }>;
27
+ export type DomainStatus = z.infer<typeof DomainStatus>;
28
+ export declare const OperatorDomain: z.ZodObject<{
29
+ id: z.ZodUUID;
30
+ domain: z.ZodString;
31
+ record: z.ZodObject<{
32
+ name: z.ZodString;
33
+ value: z.ZodString;
34
+ }, z.core.$strict>;
35
+ status: z.ZodEnum<{
36
+ lapsed: "lapsed";
37
+ lapsing: "lapsing";
38
+ pending: "pending";
39
+ verified: "verified";
40
+ }>;
41
+ verifiedAt: z.ZodNullable<z.ZodISODateTime>;
42
+ lastCheckedAt: z.ZodNullable<z.ZodISODateTime>;
43
+ lapsedAt: z.ZodNullable<z.ZodISODateTime>;
44
+ graceEndsAt: z.ZodNullable<z.ZodISODateTime>;
45
+ }, z.core.$strict>;
46
+ export type OperatorDomain = z.infer<typeof OperatorDomain>;
47
+ export declare const DomainsResponse: z.ZodObject<{
48
+ domains: z.ZodArray<z.ZodObject<{
49
+ id: z.ZodUUID;
50
+ domain: z.ZodString;
51
+ record: z.ZodObject<{
52
+ name: z.ZodString;
53
+ value: z.ZodString;
54
+ }, z.core.$strict>;
55
+ status: z.ZodEnum<{
56
+ lapsed: "lapsed";
57
+ lapsing: "lapsing";
58
+ pending: "pending";
59
+ verified: "verified";
60
+ }>;
61
+ verifiedAt: z.ZodNullable<z.ZodISODateTime>;
62
+ lastCheckedAt: z.ZodNullable<z.ZodISODateTime>;
63
+ lapsedAt: z.ZodNullable<z.ZodISODateTime>;
64
+ graceEndsAt: z.ZodNullable<z.ZodISODateTime>;
65
+ }, z.core.$strict>>;
66
+ graceDays: z.ZodInt;
67
+ }, z.core.$strict>;
68
+ export type DomainsResponse = z.infer<typeof DomainsResponse>;
69
+ export declare const DomainLookup: z.ZodEnum<{
70
+ failed: "failed";
71
+ found: "found";
72
+ missing: "missing";
73
+ }>;
74
+ export type DomainLookup = z.infer<typeof DomainLookup>;
75
+ export declare const DomainCheckResponse: z.ZodObject<{
76
+ domain: z.ZodObject<{
77
+ id: z.ZodUUID;
78
+ domain: z.ZodString;
79
+ record: z.ZodObject<{
80
+ name: z.ZodString;
81
+ value: z.ZodString;
82
+ }, z.core.$strict>;
83
+ status: z.ZodEnum<{
84
+ lapsed: "lapsed";
85
+ lapsing: "lapsing";
86
+ pending: "pending";
87
+ verified: "verified";
88
+ }>;
89
+ verifiedAt: z.ZodNullable<z.ZodISODateTime>;
90
+ lastCheckedAt: z.ZodNullable<z.ZodISODateTime>;
91
+ lapsedAt: z.ZodNullable<z.ZodISODateTime>;
92
+ graceEndsAt: z.ZodNullable<z.ZodISODateTime>;
93
+ }, z.core.$strict>;
94
+ lookup: z.ZodEnum<{
95
+ failed: "failed";
96
+ found: "found";
97
+ missing: "missing";
98
+ }>;
99
+ }, z.core.$strict>;
100
+ export type DomainCheckResponse = z.infer<typeof DomainCheckResponse>;
101
+ export declare const domainRecord: (domain: string, token: string) => {
102
+ name: string;
103
+ value: string;
104
+ };
@@ -0,0 +1,85 @@
1
+ import { z } from 'zod';
2
+ // Operator verification by DNS TXT (VOU-185), gold's operator_verified at
3
+ // launch. The operator adds a domain on the account page, puts a TXT
4
+ // record at DNS_RECORD_LABEL.<domain> holding DNS_RECORD_PREFIX<token>, and
5
+ // SealKeeper checks it. The API's routes live under /v1/me/domains.
6
+ // The record name is this label, a dot and the domain.
7
+ export const DNS_RECORD_LABEL = '_sealkeeper';
8
+ // The record value is this prefix and the token.
9
+ export const DNS_RECORD_PREFIX = 'sealkeeper-verify=';
10
+ // The kind and provider of a DNS identity in a SEAL, SEAL standard section
11
+ // 4a. kind is a URL, since dns is none of the four named kinds. SealKeeper
12
+ // checked the record, so it is the provider.
13
+ export const DNS_IDENTITY_KIND = 'https://sealkeeper.run/seal/identity/dns';
14
+ export const DNS_IDENTITY_PROVIDER = 'https://sealkeeper.run';
15
+ // How a verified domain lapses (26 September 2026, the grace period still
16
+ // open). SealKeeper checks every verified domain again about every
17
+ // recheckHours hours. A check that misses the record starts graceDays days
18
+ // of grace, in which the operator stays verified and the goal warns, and
19
+ // operator_verified goes false when it ends. A check that finds the record
20
+ // again ends the grace. maxDomains is how many domains one operator may
21
+ // add. The scoring job and the API read these, so they live here.
22
+ export const OPERATOR_VERIFICATION = {
23
+ graceDays: 14,
24
+ recheckHours: 24,
25
+ maxDomains: 5,
26
+ };
27
+ // A host name in ASCII, lower case, at least two labels, the last starting
28
+ // with a letter so an IP address never passes. An IDN goes in as its
29
+ // xn-- form. A trailing dot is dropped first.
30
+ const HOST = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
31
+ export const DomainName = z
32
+ .string()
33
+ .trim()
34
+ .toLowerCase()
35
+ .max(254)
36
+ .transform((d) => d.replace(/\.$/, ''))
37
+ .pipe(z.string().regex(HOST, 'a domain such as example.com'));
38
+ // POST /v1/me/domains.
39
+ export const AddDomainRequest = z.strictObject({ domain: DomainName });
40
+ // POST /v1/me/domains/:id/check and DELETE /v1/me/domains/:id.
41
+ export const DomainParams = z.strictObject({ id: z.uuid() });
42
+ // pending, added and never found. verified, found at the last check.
43
+ // lapsing, missed since lapsedAt and still verified until graceEndsAt.
44
+ // lapsed, missed for the whole grace period and no longer verified.
45
+ export const DomainStatus = z.enum([
46
+ 'pending',
47
+ 'verified',
48
+ 'lapsing',
49
+ 'lapsed',
50
+ ]);
51
+ const Timestamp = z.iso.datetime();
52
+ // One domain as its operator sees it, with the record to add. Private to
53
+ // the signed in operator.
54
+ export const OperatorDomain = z.strictObject({
55
+ id: z.uuid(),
56
+ domain: z.string().min(1).max(253),
57
+ record: z.strictObject({
58
+ name: z.string().min(1).max(300),
59
+ value: z.string().min(1).max(300),
60
+ }),
61
+ status: DomainStatus,
62
+ verifiedAt: Timestamp.nullable(),
63
+ lastCheckedAt: Timestamp.nullable(),
64
+ lapsedAt: Timestamp.nullable(),
65
+ graceEndsAt: Timestamp.nullable(),
66
+ });
67
+ // GET /v1/me/domains and the answer of POST /v1/me/domains.
68
+ export const DomainsResponse = z.strictObject({
69
+ domains: z.array(OperatorDomain),
70
+ graceDays: z.int().positive(),
71
+ });
72
+ // What one lookup of the record found. found, the token is there. missing,
73
+ // the name has no TXT record with it. failed, the lookup itself did not
74
+ // answer, so nothing is known.
75
+ export const DomainLookup = z.enum(['found', 'missing', 'failed']);
76
+ // POST /v1/me/domains/:id/check.
77
+ export const DomainCheckResponse = z.strictObject({
78
+ domain: OperatorDomain,
79
+ lookup: DomainLookup,
80
+ });
81
+ // The TXT record for a domain and a token.
82
+ export const domainRecord = (domain, token) => ({
83
+ name: `${DNS_RECORD_LABEL}.${domain}`,
84
+ value: `${DNS_RECORD_PREFIX}${token}`,
85
+ });
@@ -0,0 +1,4 @@
1
+ export declare const POLICY_VERSIONS: {
2
+ readonly terms: '2026-09-27';
3
+ readonly privacy: '2026-09-27';
4
+ };
package/dist/policy.js ADDED
@@ -0,0 +1,10 @@
1
+ // The versions of the terms of service and the privacy policy, as the
2
+ // "Last updated" date each page shows, YYYY-MM-DD. The single source of
3
+ // truth. The web renders both pages' dates from here and the API stamps them
4
+ // on an operator when it creates the row and on an agent when it registers,
5
+ // as what was accepted (VOU-121). The client never sends them. Change a date
6
+ // here when the matching page changes.
7
+ export const POLICY_VERSIONS = {
8
+ terms: '2026-09-27',
9
+ privacy: '2026-09-27',
10
+ };
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ export declare const RUNTIMES: readonly ['claude-code', 'codex', 'cursor', 'gemini-cli', 'openclaw', 'mastra', 'other', 'unknown'];
3
+ export declare const Runtime: z.ZodEnum<{
4
+ "claude-code": "claude-code";
5
+ codex: "codex";
6
+ cursor: "cursor";
7
+ "gemini-cli": "gemini-cli";
8
+ mastra: "mastra";
9
+ openclaw: "openclaw";
10
+ other: "other";
11
+ unknown: "unknown";
12
+ }>;
13
+ export type Runtime = z.infer<typeof Runtime>;
14
+ export declare const DEFAULT_RUNTIME: Runtime;
15
+ export declare const RUNTIME_LABELS: Readonly<Record<Runtime, string>>;
16
+ export declare const RUNTIME_NAMES: ReadonlySet<string>;
17
+ export declare function isRuntimeName(name: string): boolean;
@@ -0,0 +1,61 @@
1
+ import { z } from 'zod';
2
+ // What an agent runs in, such as Claude Code or Codex. Declared by the
3
+ // operator at registration or with a signed PATCH, never inferred from
4
+ // events, since events do not say which adapter sent them. `other` is a
5
+ // runtime not on this list, `unknown` is an agent that has not said, which
6
+ // is every agent registered before runtimes existed and every agent a CLI
7
+ // without runtimes registers.
8
+ export const RUNTIMES = [
9
+ 'claude-code',
10
+ 'codex',
11
+ 'cursor',
12
+ 'gemini-cli',
13
+ 'openclaw',
14
+ 'mastra',
15
+ 'other',
16
+ 'unknown',
17
+ ];
18
+ export const Runtime = z.enum(RUNTIMES);
19
+ // The runtime an agent has until it says otherwise.
20
+ export const DEFAULT_RUNTIME = 'unknown';
21
+ // How a runtime reads in prose, on the web and in the CLI.
22
+ export const RUNTIME_LABELS = {
23
+ 'claude-code': 'Claude Code',
24
+ codex: 'Codex',
25
+ cursor: 'Cursor',
26
+ 'gemini-cli': 'Gemini CLI',
27
+ openclaw: 'OpenClaw',
28
+ mastra: 'Mastra',
29
+ other: 'Other',
30
+ unknown: 'Unknown',
31
+ };
32
+ // Names that say what an agent runs in rather than which agent it is, such
33
+ // as claude-code or codex. A soft list. The CLI nudges once when an agent
34
+ // is about to be registered under one, and keeping it is allowed. The API
35
+ // never refuses one, so the list can grow without breaking anybody.
36
+ export const RUNTIME_NAMES = new Set([
37
+ 'aider',
38
+ 'amp',
39
+ 'claude',
40
+ 'claude-code',
41
+ 'cline',
42
+ 'codex',
43
+ 'codex-cli',
44
+ 'continue',
45
+ 'copilot',
46
+ 'cursor',
47
+ 'cursor-agent',
48
+ 'devin',
49
+ 'gemini',
50
+ 'gemini-cli',
51
+ 'goose',
52
+ 'mastra',
53
+ 'openclaw',
54
+ 'opencode',
55
+ 'roo-code',
56
+ 'windsurf',
57
+ ]);
58
+ // True when name is on the soft list above.
59
+ export function isRuntimeName(name) {
60
+ return RUNTIME_NAMES.has(name);
61
+ }
@@ -114,6 +114,8 @@ export async function sealConformanceCases() {
114
114
  signed({ ...base, ver: undefined }),
115
115
  'unsupported_version',
116
116
  ],
117
+ // Intended. Platinum is reserved on LADDER and is not a Level under ver
118
+ // 1 or 2 (standard, section 9). It becomes valid only with a ver bump.
117
119
  [
118
120
  'the wrong shape, a level not in the standard',
119
121
  signed({ ...base, level: 'platinum' }),
@@ -7,6 +7,25 @@ export declare const Level: z.ZodEnum<{
7
7
  }>;
8
8
  export type Level = z.infer<typeof Level>;
9
9
  export declare const LEVEL_RANK: Record<Level, number>;
10
+ export declare const LADDER: readonly [{
11
+ readonly level: 'none';
12
+ readonly state: 'issued';
13
+ }, {
14
+ readonly level: 'bronze';
15
+ readonly state: 'issued';
16
+ }, {
17
+ readonly level: 'silver';
18
+ readonly state: 'issued';
19
+ }, {
20
+ readonly level: 'gold';
21
+ readonly state: 'issued';
22
+ }, {
23
+ readonly level: 'platinum';
24
+ readonly state: 'reserved';
25
+ }];
26
+ export type LadderLevel = (typeof LADDER)[number]['level'];
27
+ export type LadderState = (typeof LADDER)[number]['state'];
28
+ export declare const ISSUED_LEVELS: readonly Level[];
10
29
  export declare const StandingCounts: z.ZodObject<{
11
30
  events: z.ZodInt;
12
31
  history_days: z.ZodInt;
@@ -45,9 +64,6 @@ export declare const LEVEL_THRESHOLDS: {
45
64
  };
46
65
  readonly silver: {
47
66
  readonly verifiedTasks: 200;
48
- readonly checkedOrConfirmed: 100;
49
- readonly distinctOperators: 5;
50
- readonly confirmedTasks: 25;
51
67
  readonly historyDays: 30;
52
68
  readonly reliability: 0.9;
53
69
  readonly safety: 0.9;
@@ -56,18 +72,21 @@ export declare const LEVEL_THRESHOLDS: {
56
72
  readonly modelDeclared: true;
57
73
  };
58
74
  readonly gold: {
59
- readonly verifiedTasks: 1000;
60
- readonly confirmedTasks: 250;
61
- readonly confirmedOperators: 25;
75
+ readonly verifiedTasks: 200;
76
+ readonly confirmedTasks: 25;
77
+ readonly confirmedOperators: 3;
62
78
  readonly historySpanDays: 90;
63
79
  readonly activeDays: 60;
64
80
  readonly reliability: 0.95;
65
81
  readonly safety: 0.95;
66
- readonly incidents180d: 0;
82
+ readonly cleanDays: 180;
67
83
  readonly operatorVerified: true;
68
- readonly ratings: 10;
69
84
  };
70
85
  };
86
+ export declare const OPERATOR_SILVER_CAP: {
87
+ readonly agents: 5;
88
+ readonly days: 30;
89
+ };
71
90
  export declare function dormantDays(lastActive: Date | null, now: Date): number | null;
72
91
  export declare const isQuiet: (days: number | null) => boolean;
73
92
  export declare const Standing: z.ZodObject<{
package/dist/standing.js CHANGED
@@ -2,13 +2,33 @@ import { z } from 'zod';
2
2
  // Standing levels from the SEAL standard, section 5, lowest first. none is
3
3
  // below bronze. Every scored agent version has one.
4
4
  export const Level = z.enum(['none', 'bronze', 'silver', 'gold']);
5
- // Higher is better. The leaderboard sorts on it before the score.
5
+ // Higher is better. The leaderboard sorts on it before the score. Equal to
6
+ // the level's index on LADDER.
6
7
  export const LEVEL_RANK = {
7
8
  none: 0,
8
9
  bronze: 1,
9
10
  silver: 2,
10
11
  gold: 3,
11
12
  };
13
+ /*
14
+ * Every level of the standard, lowest first. issued levels are the ones a
15
+ * SEAL can carry. A reserved level is named and ordered but never issued,
16
+ * so no code may treat the highest issued level as the top of the ladder.
17
+ * Platinum is reserved. Level, LEVEL_RANK and the standing.level check
18
+ * constraint stay at the issued four, because the payload fields of a SEAL
19
+ * version are closed (standard, section 9) and published CLIs and offline
20
+ * verifiers reject a level they do not know. Platinum enters Level with a
21
+ * ver bump when it is issued.
22
+ */
23
+ export const LADDER = [
24
+ { level: 'none', state: 'issued' },
25
+ { level: 'bronze', state: 'issued' },
26
+ { level: 'silver', state: 'issued' },
27
+ { level: 'gold', state: 'issued' },
28
+ { level: 'platinum', state: 'reserved' },
29
+ ];
30
+ // The issued levels of LADDER in order, the same values as Level.options.
31
+ export const ISSUED_LEVELS = LADDER.flatMap((step) => step.state === 'issued' ? [Level.parse(step.level)] : []);
12
32
  const Count = z.int().min(0);
13
33
  // The evidence counts of the SEAL standard, section 4, over the scoring
14
34
  // window (180 days) for one agent version. The names are the standard's, so
@@ -79,10 +99,14 @@ export const COUNTED_EVIDENCE = {
79
99
  };
80
100
  export const DAY_MS = 86_400_000;
81
101
  // The dormancy ladder of the SEAL standard, section 5, in whole days since
82
- // the agent's last accepted event. At quietDays the badge shows quiet and
83
- // the level stays. At dropOneDays and dropTwoDays the level drops one step
84
- // each. At noneDays the level is none and no SEAL is issued. The scoring
85
- // job applies it and the CLI explains it, so it lives here.
102
+ // the agent's last accepted event. At quietDays the level stays and the
103
+ // agent is quiet, which the agent answers carry (Standing.quiet, isQuiet)
104
+ // and the badge and profile show. At dropOneDays and dropTwoDays the level
105
+ // drops one step each. At noneDays the level is none and the scoring run
106
+ // stores no_seal, so the SEAL routes withhold the SEAL until a run after
107
+ // the agent is active again. An agent at none for any other reason still gets a SEAL
108
+ // that says none. The scoring job applies the ladder and the CLI explains
109
+ // it, so it lives here.
86
110
  export const DORMANCY = {
87
111
  quietDays: 14,
88
112
  dropOneDays: 30,
@@ -92,12 +116,14 @@ export const DORMANCY = {
92
116
  // SEAL standard levels, version 1 thresholds, section 5. Every one is a
93
117
  // minimum and all of a level's must hold. The scoring job applies them
94
118
  // (SCORING.levels in the API) and the CLI explains them, so they live here.
95
- // Seed tasks count in verifiedTasks and nowhere else, so they can carry an
96
- // agent to bronze and never to silver or gold on their own. Task counts
97
- // are counted evidence (CountedCounts), recalibrated on 26 September 2026
98
- // (VOU-139) from the simulation in apps/api/src/scoring/simulation.ts.
99
- // Silver was 250 verified, gold 2500 verified and 500 confirmed, in raw
100
- // tasks.
119
+ // Task counts are counted evidence (CountedCounts), recalibrated on 26
120
+ // September 2026 (VOU-139) from the simulation in
121
+ // apps/api/src/scoring/simulation.ts. Seed tasks count in verifiedTasks at
122
+ // every level (VOU-172, 26 September 2026), so an agent that does seed
123
+ // tasks every day reaches silver on them alone. Silver dropped its
124
+ // checked or confirmed, other operator and confirmed task clauses then,
125
+ // and gold moved from 1000 verified and 250 confirmed from 25 operators to
126
+ // the step-ups below, with ratings left out until a later change.
101
127
  export const LEVEL_THRESHOLDS = {
102
128
  bronze: {
103
129
  verifiedTasks: 25,
@@ -108,10 +134,6 @@ export const LEVEL_THRESHOLDS = {
108
134
  },
109
135
  silver: {
110
136
  verifiedTasks: 200,
111
- // Server checked plus confirmed tasks, from at least distinctOperators.
112
- checkedOrConfirmed: 100,
113
- distinctOperators: 5,
114
- confirmedTasks: 25,
115
137
  historyDays: 30,
116
138
  reliability: 0.9,
117
139
  safety: 0.9,
@@ -126,22 +148,37 @@ export const LEVEL_THRESHOLDS = {
126
148
  modelDeclared: true,
127
149
  },
128
150
  gold: {
129
- verifiedTasks: 1000,
130
- // Confirmed tasks, from at least confirmedOperators.
131
- confirmedTasks: 250,
132
- confirmedOperators: 25,
151
+ verifiedTasks: 200,
152
+ // Confirmed tasks posted by hand with no routine report, from at least
153
+ // confirmedOperators other operators.
154
+ confirmedTasks: 25,
155
+ confirmedOperators: 3,
133
156
  // Span from the first accepted event in the window, with activity on at
134
157
  // least activeDays of those days.
135
158
  historySpanDays: 90,
136
159
  activeDays: 60,
137
160
  reliability: 0.95,
138
161
  safety: 0.95,
139
- incidents180d: 0,
162
+ // Whole days since the later of the agent's first accepted event and
163
+ // its last incident, the clean_days count, which stops here.
164
+ cleanDays: 180,
140
165
  operatorVerified: true,
141
- // Ratings from silver or gold agents, net positive, at least this many.
142
- ratings: 10,
143
166
  },
144
167
  };
168
+ // The operator silver cap (VOU-182, 26 September 2026). At most `agents`
169
+ // agents of one operator reach silver for the first time in any `days`
170
+ // days. The first time an agent earns silver it takes a grant, one of the
171
+ // operator's slots. An agent that finds no free slot is held at bronze
172
+ // until the oldest grant in the window is `days` days old. An agent keeps
173
+ // its grant through dormancy drops and version changes, and deleting it
174
+ // does not free the slot. Gold is above silver, so it needs the grant too.
175
+ // The scoring job applies it and the CLI explains it, so it lives here. A
176
+ // higher number for verified operators (20 proposed) is open. It would sit
177
+ // beside `agents` as its own key.
178
+ export const OPERATOR_SILVER_CAP = {
179
+ agents: 5,
180
+ days: 30,
181
+ };
145
182
  // Whole days from lastActive to now, never below zero. null when the agent
146
183
  // has sent no accepted event. The one place dormancy is counted.
147
184
  export function dormantDays(lastActive, now) {
package/dist/tasks.d.ts CHANGED
@@ -22,6 +22,28 @@ export declare const VerificationSpec: z.ZodDiscriminatedUnion<[z.ZodObject<{
22
22
  kind: z.ZodLiteral<"counterparty">;
23
23
  }, z.core.$strict>], "kind">;
24
24
  export type VerificationSpec = z.infer<typeof VerificationSpec>;
25
+ export declare const PublicHashVerification: z.ZodObject<{
26
+ kind: z.ZodLiteral<"hash">;
27
+ }, z.core.$strict>;
28
+ export declare const PublicVerificationSpec: z.ZodDiscriminatedUnion<[z.ZodObject<{
29
+ kind: z.ZodLiteral<"hash">;
30
+ }, z.core.$strict>, z.ZodObject<{
31
+ kind: z.ZodLiteral<"schema">;
32
+ jsonSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
33
+ }, z.core.$strict>, z.ZodObject<{
34
+ kind: z.ZodLiteral<"counterparty">;
35
+ }, z.core.$strict>], "kind">;
36
+ export type PublicVerificationSpec = z.infer<typeof PublicVerificationSpec>;
37
+ export declare const ShownVerificationSpec: z.ZodDiscriminatedUnion<[z.ZodObject<{
38
+ kind: z.ZodLiteral<"hash">;
39
+ sha256: z.ZodOptional<z.ZodString>;
40
+ }, z.core.$strict>, z.ZodObject<{
41
+ kind: z.ZodLiteral<"schema">;
42
+ jsonSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
43
+ }, z.core.$strict>, z.ZodObject<{
44
+ kind: z.ZodLiteral<"counterparty">;
45
+ }, z.core.$strict>], "kind">;
46
+ export type ShownVerificationSpec = z.infer<typeof ShownVerificationSpec>;
25
47
  export declare const TaskOutcome: z.ZodEnum<{
26
48
  failure: "failure";
27
49
  success: "success";
@@ -45,5 +67,5 @@ export type TaskTimestamps = {
45
67
  export declare function taskState(task: TaskTimestamps, now?: Date): TaskState;
46
68
  type JsonObject = Record<string, unknown>;
47
69
  export declare function redactJsonSchema(schema: JsonObject): JsonObject;
48
- export declare function publicVerification(spec: VerificationSpec): VerificationSpec;
70
+ export declare function publicVerification(spec: VerificationSpec): PublicVerificationSpec;
49
71
  export {};
package/dist/tasks.js CHANGED
@@ -20,6 +20,26 @@ export const VerificationSpec = z.discriminatedUnion('kind', [
20
20
  SchemaVerification,
21
21
  CounterpartyVerification,
22
22
  ]);
23
+ // A hash task as everyone but its poster sees it, without the digest. With
24
+ // the digest in hand a claimant could hash candidate answers offline until
25
+ // one matched and submit only that one (VOU-181).
26
+ export const PublicHashVerification = z.strictObject({
27
+ kind: z.literal('hash'),
28
+ });
29
+ // The spec every public read shows, from publicVerification below.
30
+ export const PublicVerificationSpec = z.discriminatedUnion('kind', [
31
+ PublicHashVerification,
32
+ SchemaVerification,
33
+ CounterpartyVerification,
34
+ ]);
35
+ // The spec a task response carries. The poster's own post and its signed
36
+ // submission read show the full spec, every other response the public one,
37
+ // so the digest is optional here and required in a post.
38
+ export const ShownVerificationSpec = z.discriminatedUnion('kind', [
39
+ z.strictObject({ kind: z.literal('hash'), sha256: Sha256Hex.optional() }),
40
+ SchemaVerification,
41
+ CounterpartyVerification,
42
+ ]);
23
43
  export const TaskOutcome = z.enum(['success', 'failure']);
24
44
  export const TaskState = z.enum([
25
45
  'open',
@@ -123,10 +143,14 @@ export function redactJsonSchema(schema) {
123
143
  }
124
144
  return out;
125
145
  }
126
- // What GET /v1/tasks and GET /v1/tasks/:id show. Hash and counterparty specs
127
- // are already safe to show, a sha256 is one way.
146
+ // What GET /v1/tasks and GET /v1/tasks/:id show. A hash task drops its
147
+ // digest, which only the poster sees (VOU-181). A schema task shows its
148
+ // redacted schema. A counterparty spec has nothing to hide.
128
149
  export function publicVerification(spec) {
129
- if (spec.kind !== 'schema')
130
- return spec;
131
- return { kind: 'schema', jsonSchema: redactJsonSchema(spec.jsonSchema) };
150
+ if (spec.kind === 'hash')
151
+ return { kind: 'hash' };
152
+ if (spec.kind === 'schema') {
153
+ return { kind: 'schema', jsonSchema: redactJsonSchema(spec.jsonSchema) };
154
+ }
155
+ return spec;
132
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealkeeper/schema",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "private": false,
5
5
  "description": "Zod schemas, event taxonomy and signing helpers for SealKeeper.",
6
6
  "license": "Apache-2.0",