@sealkeeper/schema 0.4.3 → 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.
- package/dist/agent-name.d.ts +1 -1
- package/dist/agent-name.js +4 -4
- package/dist/api.d.ts +744 -27
- package/dist/api.js +232 -43
- package/dist/badge.d.ts +1 -0
- package/dist/badge.js +7 -0
- package/dist/credential.d.ts +186 -1
- package/dist/credential.js +55 -8
- package/dist/db/agents.d.ts +70 -0
- package/dist/db/agents.js +18 -0
- package/dist/db/client.d.ts +398 -0
- package/dist/db/client.js +2 -0
- package/dist/db/events.js +7 -0
- package/dist/db/index.d.ts +17 -1
- package/dist/db/index.js +5 -1
- package/dist/db/migrate.js +5 -1
- package/dist/db/migrator.d.ts +3 -1
- package/dist/db/migrator.js +8 -2
- package/dist/db/operator-identities.d.ts +211 -0
- package/dist/db/operator-identities.js +49 -0
- package/dist/db/operator-level-grants.d.ts +109 -0
- package/dist/db/operator-level-grants.js +30 -0
- package/dist/db/operator-slugs.d.ts +92 -0
- package/dist/db/operator-slugs.js +25 -0
- package/dist/db/operators.d.ts +119 -0
- package/dist/db/operators.js +30 -2
- package/dist/db/standing.d.ts +36 -0
- package/dist/db/standing.js +5 -0
- package/dist/db/task-claim-failures.d.ts +143 -0
- package/dist/db/task-claim-failures.js +37 -0
- package/dist/db/task-outcomes.d.ts +19 -0
- package/dist/db/task-outcomes.js +8 -1
- package/dist/db/tasks.d.ts +79 -0
- package/dist/db/tasks.js +19 -1
- package/dist/envelope.d.ts +3 -0
- package/dist/envelope.js +16 -11
- package/dist/goal.d.ts +149 -0
- package/dist/goal.js +171 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/moderation.d.ts +28 -0
- package/dist/moderation.js +450 -0
- package/dist/operator-domains.d.ts +104 -0
- package/dist/operator-domains.js +85 -0
- package/dist/policy.d.ts +4 -0
- package/dist/policy.js +10 -0
- package/dist/runtime.d.ts +17 -0
- package/dist/runtime.js +61 -0
- package/dist/seal-conformance.js +32 -1
- package/dist/standing.d.ts +68 -0
- package/dist/standing.js +132 -6
- package/dist/tasks.d.ts +23 -1
- package/dist/tasks.js +29 -5
- package/package.json +1 -1
package/dist/db/operators.js
CHANGED
|
@@ -1,11 +1,39 @@
|
|
|
1
|
-
import { bigint, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
1
|
+
import { bigint, pgTable, text, timestamp, uniqueIndex, uuid, } from 'drizzle-orm/pg-core';
|
|
2
2
|
import { timestamps } from './timestamps.js';
|
|
3
|
+
// github_id is the identity, since logins change. login is kept fresh from
|
|
4
|
+
// GitHub and shows only in the operator section of a profile. slug is what
|
|
5
|
+
// the operator is known by, the first half of every handle, as in
|
|
6
|
+
// alice/claude-code. It defaults to the login lowercased and changes on the
|
|
7
|
+
// web only (PATCH /v1/me). display_name is free text, and display_name_key
|
|
8
|
+
// is its normalised form (displayNameKey in @sealkeeper/schema), unique so
|
|
9
|
+
// two operators never hold names that read the same, even under concurrent
|
|
10
|
+
// writes. slug_changed_at is when the slug last changed, null until the
|
|
11
|
+
// first change, for the one change every SLUG_CHANGE_DAYS.
|
|
12
|
+
// slug_notice_dismissed_at is when the operator dismissed the one time
|
|
13
|
+
// notice on /me that says what its slug is and that it can change, null
|
|
14
|
+
// until then (VOU-190). Held here, not in the browser, so it shows once
|
|
15
|
+
// across devices. terms_version and privacy_version are the "Last updated"
|
|
16
|
+
// dates of the terms and the privacy policy (POLICY_VERSIONS in
|
|
17
|
+
// @sealkeeper/schema) in force when the API created the row, which is when
|
|
18
|
+
// the operator accepted them (VOU-121). Null for operators created before.
|
|
3
19
|
export const operators = pgTable('operators', {
|
|
4
20
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
5
21
|
githubId: bigint('github_id', { mode: 'number' }).notNull().unique(),
|
|
6
22
|
login: text('login').notNull(),
|
|
23
|
+
slug: text('slug').notNull(),
|
|
24
|
+
displayName: text('display_name').notNull(),
|
|
25
|
+
displayNameKey: text('display_name_key').notNull(),
|
|
26
|
+
slugChangedAt: timestamp('slug_changed_at', { withTimezone: true }),
|
|
27
|
+
slugNoticeDismissedAt: timestamp('slug_notice_dismissed_at', {
|
|
28
|
+
withTimezone: true,
|
|
29
|
+
}),
|
|
7
30
|
accountCreatedAt: timestamp('account_created_at', {
|
|
8
31
|
withTimezone: true,
|
|
9
32
|
}).notNull(),
|
|
33
|
+
termsVersion: text('terms_version'),
|
|
34
|
+
privacyVersion: text('privacy_version'),
|
|
10
35
|
...timestamps,
|
|
11
|
-
})
|
|
36
|
+
}, (t) => [
|
|
37
|
+
uniqueIndex('operators_slug_unique').on(t.slug),
|
|
38
|
+
uniqueIndex('operators_display_name_key_unique').on(t.displayNameKey),
|
|
39
|
+
]);
|
package/dist/db/standing.d.ts
CHANGED
|
@@ -10,6 +10,23 @@ export type StoredCounts = {
|
|
|
10
10
|
confirmed_operators: number;
|
|
11
11
|
history_span_days: number;
|
|
12
12
|
safety_incidents_180d: number;
|
|
13
|
+
clean_days: number;
|
|
14
|
+
gold_confirmed_tasks: number;
|
|
15
|
+
gold_confirmed_operators: number;
|
|
16
|
+
counted_verified_tasks: number;
|
|
17
|
+
counted_seed_tasks: number;
|
|
18
|
+
counted_server_checked_tasks: number;
|
|
19
|
+
counted_confirmed_tasks: number;
|
|
20
|
+
counted_gold_confirmed_tasks: number;
|
|
21
|
+
};
|
|
22
|
+
export type StoredLevelInputs = {
|
|
23
|
+
reliability: number | null;
|
|
24
|
+
safety: number | null;
|
|
25
|
+
provenance: number | null;
|
|
26
|
+
model_declared: boolean;
|
|
27
|
+
operator_verified: boolean;
|
|
28
|
+
ratings: number;
|
|
29
|
+
ratings_net_positive: boolean;
|
|
13
30
|
};
|
|
14
31
|
export declare const standing: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
15
32
|
name: "standing";
|
|
@@ -121,6 +138,25 @@ export declare const standing: import("drizzle-orm/pg-core").PgTableWithColumns<
|
|
|
121
138
|
}, {}, {
|
|
122
139
|
$type: StoredCounts;
|
|
123
140
|
}>;
|
|
141
|
+
levelInputs: import("drizzle-orm/pg-core").PgColumn<{
|
|
142
|
+
name: "level_inputs";
|
|
143
|
+
tableName: "standing";
|
|
144
|
+
dataType: "json";
|
|
145
|
+
columnType: "PgJsonb";
|
|
146
|
+
data: StoredLevelInputs;
|
|
147
|
+
driverParam: unknown;
|
|
148
|
+
notNull: false;
|
|
149
|
+
hasDefault: false;
|
|
150
|
+
isPrimaryKey: false;
|
|
151
|
+
isAutoincrement: false;
|
|
152
|
+
hasRuntimeDefault: false;
|
|
153
|
+
enumValues: undefined;
|
|
154
|
+
baseColumn: never;
|
|
155
|
+
identity: undefined;
|
|
156
|
+
generated: undefined;
|
|
157
|
+
}, {}, {
|
|
158
|
+
$type: StoredLevelInputs;
|
|
159
|
+
}>;
|
|
124
160
|
historyDays: import("drizzle-orm/pg-core").PgColumn<{
|
|
125
161
|
name: "history_days";
|
|
126
162
|
tableName: "standing";
|
package/dist/db/standing.js
CHANGED
|
@@ -9,6 +9,8 @@ import { timestamps } from './timestamps.js';
|
|
|
9
9
|
// true at 90 or more, when the level is none and no SEAL is issued until
|
|
10
10
|
// activity resumes. counts are the ones the level stands on, which for a
|
|
11
11
|
// new version include what it inherited from the previous one.
|
|
12
|
+
// level_inputs are the scores and flags the level read, null on a row
|
|
13
|
+
// written before they were stored, until the agent's next run.
|
|
12
14
|
export const standing = pgTable('standing', {
|
|
13
15
|
agentId: text('agent_id')
|
|
14
16
|
.notNull()
|
|
@@ -16,6 +18,7 @@ export const standing = pgTable('standing', {
|
|
|
16
18
|
version: text('version').notNull(),
|
|
17
19
|
level: text('level').$type().notNull(),
|
|
18
20
|
counts: jsonb('counts').$type().notNull(),
|
|
21
|
+
levelInputs: jsonb('level_inputs').$type(),
|
|
19
22
|
historyDays: integer('history_days').notNull(),
|
|
20
23
|
lastActive: timestamp('last_active', { withTimezone: true }),
|
|
21
24
|
dormantDays: integer('dormant_days'),
|
|
@@ -25,5 +28,7 @@ export const standing = pgTable('standing', {
|
|
|
25
28
|
...timestamps,
|
|
26
29
|
}, (t) => [
|
|
27
30
|
primaryKey({ columns: [t.agentId, t.version] }),
|
|
31
|
+
// The issued levels only. Platinum is reserved on LADDER and has no
|
|
32
|
+
// standing row until it is issued with a SEAL ver bump.
|
|
28
33
|
check('standing_level', sql `${t.level} in ('none', 'bronze', 'silver', 'gold')`),
|
|
29
34
|
]);
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export declare const taskClaimFailures: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
2
|
+
name: "task_claim_failures";
|
|
3
|
+
schema: undefined;
|
|
4
|
+
columns: {
|
|
5
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
6
|
+
name: "created_at";
|
|
7
|
+
tableName: "task_claim_failures";
|
|
8
|
+
dataType: "date";
|
|
9
|
+
columnType: "PgTimestamp";
|
|
10
|
+
data: Date;
|
|
11
|
+
driverParam: string;
|
|
12
|
+
notNull: true;
|
|
13
|
+
hasDefault: true;
|
|
14
|
+
isPrimaryKey: false;
|
|
15
|
+
isAutoincrement: false;
|
|
16
|
+
hasRuntimeDefault: false;
|
|
17
|
+
enumValues: undefined;
|
|
18
|
+
baseColumn: never;
|
|
19
|
+
identity: undefined;
|
|
20
|
+
generated: undefined;
|
|
21
|
+
}, {}, {}>;
|
|
22
|
+
updatedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
23
|
+
name: "updated_at";
|
|
24
|
+
tableName: "task_claim_failures";
|
|
25
|
+
dataType: "date";
|
|
26
|
+
columnType: "PgTimestamp";
|
|
27
|
+
data: Date;
|
|
28
|
+
driverParam: string;
|
|
29
|
+
notNull: true;
|
|
30
|
+
hasDefault: true;
|
|
31
|
+
isPrimaryKey: false;
|
|
32
|
+
isAutoincrement: false;
|
|
33
|
+
hasRuntimeDefault: false;
|
|
34
|
+
enumValues: undefined;
|
|
35
|
+
baseColumn: never;
|
|
36
|
+
identity: undefined;
|
|
37
|
+
generated: undefined;
|
|
38
|
+
}, {}, {}>;
|
|
39
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
40
|
+
name: "id";
|
|
41
|
+
tableName: "task_claim_failures";
|
|
42
|
+
dataType: "string";
|
|
43
|
+
columnType: "PgUUID";
|
|
44
|
+
data: string;
|
|
45
|
+
driverParam: string;
|
|
46
|
+
notNull: true;
|
|
47
|
+
hasDefault: true;
|
|
48
|
+
isPrimaryKey: true;
|
|
49
|
+
isAutoincrement: false;
|
|
50
|
+
hasRuntimeDefault: false;
|
|
51
|
+
enumValues: undefined;
|
|
52
|
+
baseColumn: never;
|
|
53
|
+
identity: undefined;
|
|
54
|
+
generated: undefined;
|
|
55
|
+
}, {}, {}>;
|
|
56
|
+
taskId: import("drizzle-orm/pg-core").PgColumn<{
|
|
57
|
+
name: "task_id";
|
|
58
|
+
tableName: "task_claim_failures";
|
|
59
|
+
dataType: "string";
|
|
60
|
+
columnType: "PgUUID";
|
|
61
|
+
data: string;
|
|
62
|
+
driverParam: string;
|
|
63
|
+
notNull: true;
|
|
64
|
+
hasDefault: false;
|
|
65
|
+
isPrimaryKey: false;
|
|
66
|
+
isAutoincrement: false;
|
|
67
|
+
hasRuntimeDefault: false;
|
|
68
|
+
enumValues: undefined;
|
|
69
|
+
baseColumn: never;
|
|
70
|
+
identity: undefined;
|
|
71
|
+
generated: undefined;
|
|
72
|
+
}, {}, {}>;
|
|
73
|
+
agentId: import("drizzle-orm/pg-core").PgColumn<{
|
|
74
|
+
name: "agent_id";
|
|
75
|
+
tableName: "task_claim_failures";
|
|
76
|
+
dataType: "string";
|
|
77
|
+
columnType: "PgText";
|
|
78
|
+
data: string;
|
|
79
|
+
driverParam: string;
|
|
80
|
+
notNull: true;
|
|
81
|
+
hasDefault: false;
|
|
82
|
+
isPrimaryKey: false;
|
|
83
|
+
isAutoincrement: false;
|
|
84
|
+
hasRuntimeDefault: false;
|
|
85
|
+
enumValues: [string, ...string[]];
|
|
86
|
+
baseColumn: never;
|
|
87
|
+
identity: undefined;
|
|
88
|
+
generated: undefined;
|
|
89
|
+
}, {}, {}>;
|
|
90
|
+
version: import("drizzle-orm/pg-core").PgColumn<{
|
|
91
|
+
name: "version";
|
|
92
|
+
tableName: "task_claim_failures";
|
|
93
|
+
dataType: "string";
|
|
94
|
+
columnType: "PgText";
|
|
95
|
+
data: string;
|
|
96
|
+
driverParam: string;
|
|
97
|
+
notNull: true;
|
|
98
|
+
hasDefault: false;
|
|
99
|
+
isPrimaryKey: false;
|
|
100
|
+
isAutoincrement: false;
|
|
101
|
+
hasRuntimeDefault: false;
|
|
102
|
+
enumValues: [string, ...string[]];
|
|
103
|
+
baseColumn: never;
|
|
104
|
+
identity: undefined;
|
|
105
|
+
generated: undefined;
|
|
106
|
+
}, {}, {}>;
|
|
107
|
+
claimedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
108
|
+
name: "claimed_at";
|
|
109
|
+
tableName: "task_claim_failures";
|
|
110
|
+
dataType: "date";
|
|
111
|
+
columnType: "PgTimestamp";
|
|
112
|
+
data: Date;
|
|
113
|
+
driverParam: string;
|
|
114
|
+
notNull: true;
|
|
115
|
+
hasDefault: false;
|
|
116
|
+
isPrimaryKey: false;
|
|
117
|
+
isAutoincrement: false;
|
|
118
|
+
hasRuntimeDefault: false;
|
|
119
|
+
enumValues: undefined;
|
|
120
|
+
baseColumn: never;
|
|
121
|
+
identity: undefined;
|
|
122
|
+
generated: undefined;
|
|
123
|
+
}, {}, {}>;
|
|
124
|
+
failedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
125
|
+
name: "failed_at";
|
|
126
|
+
tableName: "task_claim_failures";
|
|
127
|
+
dataType: "date";
|
|
128
|
+
columnType: "PgTimestamp";
|
|
129
|
+
data: Date;
|
|
130
|
+
driverParam: string;
|
|
131
|
+
notNull: true;
|
|
132
|
+
hasDefault: true;
|
|
133
|
+
isPrimaryKey: false;
|
|
134
|
+
isAutoincrement: false;
|
|
135
|
+
hasRuntimeDefault: false;
|
|
136
|
+
enumValues: undefined;
|
|
137
|
+
baseColumn: never;
|
|
138
|
+
identity: undefined;
|
|
139
|
+
generated: undefined;
|
|
140
|
+
}, {}, {}>;
|
|
141
|
+
};
|
|
142
|
+
dialect: 'pg';
|
|
143
|
+
}>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { index, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { agents } from './agents.js';
|
|
3
|
+
import { tasks } from './tasks.js';
|
|
4
|
+
import { timestamps } from './timestamps.js';
|
|
5
|
+
/*
|
|
6
|
+
* Claims released at the failed submit cap (TASK_MAX_FAILED_SUBMITS in
|
|
7
|
+
* apps/api/src/routes/tasks.ts). The release nulls the task's claimant and
|
|
8
|
+
* claimed_at, so without this row the claim would drop out of the agent's
|
|
9
|
+
* reliability. The scoring run counts each row as a claim that never
|
|
10
|
+
* verified, like a claim held until expiry.
|
|
11
|
+
*
|
|
12
|
+
* claimed_at is the released claim's, and version the claimant's version
|
|
13
|
+
* at that claim, worked out at the release the same way the run credits a
|
|
14
|
+
* task (claimVersion in apps/api/src/scoring/run.ts). failed_at is the
|
|
15
|
+
* submit that reached the cap. Deleting the agent or the task deletes the
|
|
16
|
+
* row (deleteAgents in apps/api/src/account.ts).
|
|
17
|
+
*/
|
|
18
|
+
export const taskClaimFailures = pgTable('task_claim_failures', {
|
|
19
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
20
|
+
taskId: uuid('task_id')
|
|
21
|
+
.notNull()
|
|
22
|
+
.references(() => tasks.id),
|
|
23
|
+
agentId: text('agent_id')
|
|
24
|
+
.notNull()
|
|
25
|
+
.references(() => agents.id),
|
|
26
|
+
version: text('version').notNull(),
|
|
27
|
+
claimedAt: timestamp('claimed_at', { withTimezone: true }).notNull(),
|
|
28
|
+
failedAt: timestamp('failed_at', { withTimezone: true })
|
|
29
|
+
.notNull()
|
|
30
|
+
.defaultNow(),
|
|
31
|
+
...timestamps,
|
|
32
|
+
}, (t) => [
|
|
33
|
+
// The scoring run's read, one agent's released claims by claim time.
|
|
34
|
+
index('task_claim_failures_agent_claimed_at_idx').on(t.agentId, t.claimedAt),
|
|
35
|
+
// A deletion's rows by task.
|
|
36
|
+
index('task_claim_failures_task_id_idx').on(t.taskId),
|
|
37
|
+
]);
|
|
@@ -121,6 +121,25 @@ export declare const taskOutcomes: import("drizzle-orm/pg-core").PgTableWithColu
|
|
|
121
121
|
identity: undefined;
|
|
122
122
|
generated: undefined;
|
|
123
123
|
}, {}, {}>;
|
|
124
|
+
origin: import("drizzle-orm/pg-core").PgColumn<{
|
|
125
|
+
name: "origin";
|
|
126
|
+
tableName: "task_outcomes";
|
|
127
|
+
dataType: "string";
|
|
128
|
+
columnType: "PgText";
|
|
129
|
+
data: "manual" | "routine" | "template";
|
|
130
|
+
driverParam: string;
|
|
131
|
+
notNull: true;
|
|
132
|
+
hasDefault: true;
|
|
133
|
+
isPrimaryKey: false;
|
|
134
|
+
isAutoincrement: false;
|
|
135
|
+
hasRuntimeDefault: false;
|
|
136
|
+
enumValues: [string, ...string[]];
|
|
137
|
+
baseColumn: never;
|
|
138
|
+
identity: undefined;
|
|
139
|
+
generated: undefined;
|
|
140
|
+
}, {}, {
|
|
141
|
+
$type: "manual" | "routine" | "template";
|
|
142
|
+
}>;
|
|
124
143
|
};
|
|
125
144
|
dialect: 'pg';
|
|
126
145
|
}>;
|
package/dist/db/task-outcomes.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sql } from 'drizzle-orm';
|
|
2
|
+
import { check, jsonb, pgTable, text, unique, uuid } from 'drizzle-orm/pg-core';
|
|
2
3
|
import { agents } from './agents.js';
|
|
3
4
|
import { tasks } from './tasks.js';
|
|
4
5
|
import { timestamps } from './timestamps.js';
|
|
@@ -12,7 +13,13 @@ export const taskOutcomes = pgTable('task_outcomes', {
|
|
|
12
13
|
.references(() => agents.id),
|
|
13
14
|
outcome: text('outcome').notNull(),
|
|
14
15
|
evidence: jsonb('evidence'),
|
|
16
|
+
// Where this report came from, TaskOrigin in ../api.ts, from the signed
|
|
17
|
+
// outcome payload. manual when the payload has none. A new report
|
|
18
|
+
// replaces it with the rest. Gold leaves out a confirmed task either of
|
|
19
|
+
// whose reports is routine.
|
|
20
|
+
origin: text('origin').$type().notNull().default('manual'),
|
|
15
21
|
...timestamps,
|
|
16
22
|
}, (t) => [
|
|
23
|
+
check('task_outcomes_origin', sql `${t.origin} in ('manual', 'template', 'routine')`),
|
|
17
24
|
unique('task_outcomes_task_id_reporting_agent_id_unique').on(t.taskId, t.reportingAgentId),
|
|
18
25
|
]);
|
package/dist/db/tasks.d.ts
CHANGED
|
@@ -138,6 +138,25 @@ export declare const tasks: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
|
138
138
|
identity: undefined;
|
|
139
139
|
generated: undefined;
|
|
140
140
|
}, {}, {}>;
|
|
141
|
+
origin: import("drizzle-orm/pg-core").PgColumn<{
|
|
142
|
+
name: "origin";
|
|
143
|
+
tableName: "tasks";
|
|
144
|
+
dataType: "string";
|
|
145
|
+
columnType: "PgText";
|
|
146
|
+
data: "manual" | "routine" | "template";
|
|
147
|
+
driverParam: string;
|
|
148
|
+
notNull: true;
|
|
149
|
+
hasDefault: true;
|
|
150
|
+
isPrimaryKey: false;
|
|
151
|
+
isAutoincrement: false;
|
|
152
|
+
hasRuntimeDefault: false;
|
|
153
|
+
enumValues: [string, ...string[]];
|
|
154
|
+
baseColumn: never;
|
|
155
|
+
identity: undefined;
|
|
156
|
+
generated: undefined;
|
|
157
|
+
}, {}, {
|
|
158
|
+
$type: "manual" | "routine" | "template";
|
|
159
|
+
}>;
|
|
141
160
|
taskType: import("drizzle-orm/pg-core").PgColumn<{
|
|
142
161
|
name: "task_type";
|
|
143
162
|
tableName: "tasks";
|
|
@@ -280,6 +299,66 @@ export declare const tasks: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
|
280
299
|
text: string;
|
|
281
300
|
};
|
|
282
301
|
}>;
|
|
302
|
+
failedSubmits: import("drizzle-orm/pg-core").PgColumn<{
|
|
303
|
+
name: "failed_submits";
|
|
304
|
+
tableName: "tasks";
|
|
305
|
+
dataType: "number";
|
|
306
|
+
columnType: "PgInteger";
|
|
307
|
+
data: number;
|
|
308
|
+
driverParam: string | number;
|
|
309
|
+
notNull: true;
|
|
310
|
+
hasDefault: true;
|
|
311
|
+
isPrimaryKey: false;
|
|
312
|
+
isAutoincrement: false;
|
|
313
|
+
hasRuntimeDefault: false;
|
|
314
|
+
enumValues: undefined;
|
|
315
|
+
baseColumn: never;
|
|
316
|
+
identity: undefined;
|
|
317
|
+
generated: undefined;
|
|
318
|
+
}, {}, {}>;
|
|
319
|
+
barredAgentIds: import("drizzle-orm/pg-core").PgColumn<{
|
|
320
|
+
name: "barred_agent_ids";
|
|
321
|
+
tableName: "tasks";
|
|
322
|
+
dataType: "array";
|
|
323
|
+
columnType: "PgArray";
|
|
324
|
+
data: string[];
|
|
325
|
+
driverParam: string | string[];
|
|
326
|
+
notNull: true;
|
|
327
|
+
hasDefault: true;
|
|
328
|
+
isPrimaryKey: false;
|
|
329
|
+
isAutoincrement: false;
|
|
330
|
+
hasRuntimeDefault: false;
|
|
331
|
+
enumValues: [string, ...string[]];
|
|
332
|
+
baseColumn: import("drizzle-orm").Column<{
|
|
333
|
+
name: "barred_agent_ids";
|
|
334
|
+
tableName: "tasks";
|
|
335
|
+
dataType: "string";
|
|
336
|
+
columnType: "PgText";
|
|
337
|
+
data: string;
|
|
338
|
+
driverParam: string;
|
|
339
|
+
notNull: false;
|
|
340
|
+
hasDefault: false;
|
|
341
|
+
isPrimaryKey: false;
|
|
342
|
+
isAutoincrement: false;
|
|
343
|
+
hasRuntimeDefault: false;
|
|
344
|
+
enumValues: [string, ...string[]];
|
|
345
|
+
baseColumn: never;
|
|
346
|
+
identity: undefined;
|
|
347
|
+
generated: undefined;
|
|
348
|
+
}, {}, {}>;
|
|
349
|
+
identity: undefined;
|
|
350
|
+
generated: undefined;
|
|
351
|
+
}, {}, {
|
|
352
|
+
baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
|
|
353
|
+
name: "barred_agent_ids";
|
|
354
|
+
dataType: 'string';
|
|
355
|
+
columnType: 'PgText';
|
|
356
|
+
data: string;
|
|
357
|
+
enumValues: [string, ...string[]];
|
|
358
|
+
driverParam: string;
|
|
359
|
+
}, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
|
|
360
|
+
size: undefined;
|
|
361
|
+
}>;
|
|
283
362
|
expiresAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
284
363
|
name: "expires_at";
|
|
285
364
|
tableName: "tasks";
|
package/dist/db/tasks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { sql } from 'drizzle-orm';
|
|
2
|
-
import { boolean, index, jsonb, pgTable, text, timestamp, uuid, } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { boolean, check, index, integer, jsonb, pgTable, text, timestamp, uuid, } from 'drizzle-orm/pg-core';
|
|
3
3
|
import { agents } from './agents.js';
|
|
4
4
|
import { timestamps } from './timestamps.js';
|
|
5
5
|
export const tasks = pgTable('tasks', {
|
|
@@ -23,6 +23,11 @@ export const tasks = pgTable('tasks', {
|
|
|
23
23
|
// cap, so it weighs nothing as evidence, success or failure. False for
|
|
24
24
|
// every other task.
|
|
25
25
|
overPairCap: boolean('over_pair_cap').notNull().default(false),
|
|
26
|
+
// Where the post came from, TaskOrigin in ../api.ts, from the signed
|
|
27
|
+
// post payload. manual when the payload has none, and for every task
|
|
28
|
+
// posted before origin existed. Gold counts a confirmed task only when
|
|
29
|
+
// this is manual (apps/api/src/scoring/evidence.ts).
|
|
30
|
+
origin: text('origin').$type().notNull().default('manual'),
|
|
26
31
|
taskType: text('task_type').notNull(),
|
|
27
32
|
spec: jsonb('spec').notNull(),
|
|
28
33
|
verification: jsonb('verification').notNull(),
|
|
@@ -37,6 +42,18 @@ export const tasks = pgTable('tasks', {
|
|
|
37
42
|
// does not survive a read. postgres-js parses jsonb and Drizzle then
|
|
38
43
|
// JSON.parses any string it gets back, so "123" would return as 123.
|
|
39
44
|
submission: jsonb('submission').$type(),
|
|
45
|
+
// Failed submits on the current claim of a hash or schema task. The
|
|
46
|
+
// submit route counts them up, and at the cap (TASK_MAX_FAILED_SUBMITS
|
|
47
|
+
// in apps/api/src/routes/tasks.ts) it releases the claim and sets this
|
|
48
|
+
// back to 0 for the next claimant.
|
|
49
|
+
failedSubmits: integer('failed_submits').notNull().default(0),
|
|
50
|
+
// Agents whose claim was released at the failed submit cap. None of them
|
|
51
|
+
// can claim this task again, so guessing at a public hash costs a claim
|
|
52
|
+
// each three tries. Deleting an agent takes its id out.
|
|
53
|
+
barredAgentIds: text('barred_agent_ids')
|
|
54
|
+
.array()
|
|
55
|
+
.notNull()
|
|
56
|
+
.default(sql `'{}'::text[]`),
|
|
40
57
|
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
|
41
58
|
...timestamps,
|
|
42
59
|
},
|
|
@@ -47,6 +64,7 @@ export const tasks = pgTable('tasks', {
|
|
|
47
64
|
* state derivation in apps/api/src/routes/tasks.ts (stateWhere).
|
|
48
65
|
*/
|
|
49
66
|
(t) => [
|
|
67
|
+
check('tasks_origin', sql `${t.origin} in ('manual', 'template', 'routine')`),
|
|
50
68
|
// The open task cap per poster, and a deletion's tasks by poster.
|
|
51
69
|
index('tasks_poster_agent_id_idx').on(t.posterAgentId),
|
|
52
70
|
// The claim cap per claimant, activity counts, scoring and evidence.
|
package/dist/envelope.d.ts
CHANGED
|
@@ -5,8 +5,11 @@ export declare const JwsHeader: z.ZodObject<{
|
|
|
5
5
|
kid: z.ZodString;
|
|
6
6
|
}, z.core.$strict>;
|
|
7
7
|
export type JwsHeader = z.infer<typeof JwsHeader>;
|
|
8
|
+
export type EnvelopeErrorCode = 'malformed' | 'bad_signature' | 'invalid_input';
|
|
8
9
|
export declare class EnvelopeError extends Error {
|
|
10
|
+
readonly code: EnvelopeErrorCode;
|
|
9
11
|
name: string;
|
|
12
|
+
constructor(code: EnvelopeErrorCode, message: string);
|
|
10
13
|
}
|
|
11
14
|
export declare function sign(payload: unknown, privateKey: Uint8Array, kid: string): Promise<string>;
|
|
12
15
|
export declare function decodeHeader(jws: string): JwsHeader;
|
package/dist/envelope.js
CHANGED
|
@@ -11,11 +11,16 @@ export const JwsHeader = z.strictObject({
|
|
|
11
11
|
kid: z.string().min(1).max(128),
|
|
12
12
|
});
|
|
13
13
|
export class EnvelopeError extends Error {
|
|
14
|
+
code;
|
|
14
15
|
name = 'EnvelopeError';
|
|
16
|
+
constructor(code, message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
15
20
|
}
|
|
16
21
|
function split(jws) {
|
|
17
22
|
if (!Jws.safeParse(jws).success) {
|
|
18
|
-
throw new EnvelopeError('Envelope is not three base64url parts');
|
|
23
|
+
throw new EnvelopeError('malformed', 'Envelope is not three base64url parts');
|
|
19
24
|
}
|
|
20
25
|
const [header, payload, signature] = jws.split('.');
|
|
21
26
|
return { header, payload, signature };
|
|
@@ -25,13 +30,13 @@ function parseJson(part, what) {
|
|
|
25
30
|
return JSON.parse(utf8Decode(base64urlDecode(part)));
|
|
26
31
|
}
|
|
27
32
|
catch {
|
|
28
|
-
throw new EnvelopeError(`Envelope ${what} is not base64url JSON`);
|
|
33
|
+
throw new EnvelopeError('malformed', `Envelope ${what} is not base64url JSON`);
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
function parseHeader(part) {
|
|
32
37
|
const result = JwsHeader.safeParse(parseJson(part, 'header'));
|
|
33
38
|
if (!result.success) {
|
|
34
|
-
throw new EnvelopeError('Envelope header must be alg EdDSA with a kid');
|
|
39
|
+
throw new EnvelopeError('malformed', 'Envelope header must be alg EdDSA with a kid');
|
|
35
40
|
}
|
|
36
41
|
return result.data;
|
|
37
42
|
}
|
|
@@ -39,7 +44,7 @@ export async function sign(payload, privateKey, kid) {
|
|
|
39
44
|
const header = JwsHeader.parse({ alg: 'EdDSA', kid });
|
|
40
45
|
const body = JSON.stringify(payload);
|
|
41
46
|
if (body === undefined)
|
|
42
|
-
throw new EnvelopeError('Payload is not JSON');
|
|
47
|
+
throw new EnvelopeError('invalid_input', 'Payload is not JSON');
|
|
43
48
|
const signingInput = `${base64urlEncode(utf8Encode(JSON.stringify(header)))}.${base64urlEncode(utf8Encode(body))}`;
|
|
44
49
|
const signature = await signAsync(utf8Encode(signingInput), privateKey);
|
|
45
50
|
return `${signingInput}.${base64urlEncode(signature)}`;
|
|
@@ -64,7 +69,7 @@ export async function verify(jws, publicKey) {
|
|
|
64
69
|
ok = false;
|
|
65
70
|
}
|
|
66
71
|
if (!ok)
|
|
67
|
-
throw new EnvelopeError('Envelope signature is invalid');
|
|
72
|
+
throw new EnvelopeError('bad_signature', 'Envelope signature is invalid');
|
|
68
73
|
const header = parseHeader(parts.header);
|
|
69
74
|
return { header, payload: parseJson(parts.payload, 'payload') };
|
|
70
75
|
}
|
|
@@ -95,26 +100,26 @@ export function audienceOf(apiUrl) {
|
|
|
95
100
|
url = new URL(apiUrl);
|
|
96
101
|
}
|
|
97
102
|
catch {
|
|
98
|
-
throw new EnvelopeError('API URL is not a URL');
|
|
103
|
+
throw new EnvelopeError('invalid_input', 'API URL is not a URL');
|
|
99
104
|
}
|
|
100
105
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
101
|
-
throw new EnvelopeError('API URL must be http or https');
|
|
106
|
+
throw new EnvelopeError('invalid_input', 'API URL must be http or https');
|
|
102
107
|
}
|
|
103
108
|
if (url.origin === 'null') {
|
|
104
|
-
throw new EnvelopeError('API URL has no origin');
|
|
109
|
+
throw new EnvelopeError('invalid_input', 'API URL has no origin');
|
|
105
110
|
}
|
|
106
111
|
return url.origin;
|
|
107
112
|
}
|
|
108
113
|
// A copy of the payload with `aud` added. Never mutates the input.
|
|
109
114
|
export function withAudience(payload, aud) {
|
|
110
115
|
if (!Audience.safeParse(aud).success) {
|
|
111
|
-
throw new EnvelopeError('aud must be an origin');
|
|
116
|
+
throw new EnvelopeError('invalid_input', 'aud must be an origin');
|
|
112
117
|
}
|
|
113
118
|
if (Array.isArray(payload)) {
|
|
114
|
-
throw new EnvelopeError('A signed request payload must be an object');
|
|
119
|
+
throw new EnvelopeError('invalid_input', 'A signed request payload must be an object');
|
|
115
120
|
}
|
|
116
121
|
if (Object.hasOwn(payload, 'aud')) {
|
|
117
|
-
throw new EnvelopeError('Payload already has an aud');
|
|
122
|
+
throw new EnvelopeError('invalid_input', 'Payload already has an aud');
|
|
118
123
|
}
|
|
119
124
|
return { ...payload, aud };
|
|
120
125
|
}
|