@sealkeeper/schema 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +350 -1
- package/dist/api.js +183 -27
- package/dist/db/agent-milestones.d.ts +75 -0
- package/dist/db/agent-milestones.js +16 -0
- package/dist/db/agents.d.ts +51 -0
- package/dist/db/agents.js +22 -1
- package/dist/db/client.d.ts +177 -0
- package/dist/db/client.js +2 -0
- package/dist/db/feed-items.js +6 -1
- package/dist/db/index.d.ts +5 -1
- package/dist/db/index.js +1 -0
- package/dist/db/tasks.d.ts +51 -0
- package/dist/db/tasks.js +35 -2
- package/dist/envelope.d.ts +11 -0
- package/dist/envelope.js +70 -0
- package/package.json +1 -1
package/dist/db/client.d.ts
CHANGED
|
@@ -252,6 +252,57 @@ export declare function createDb(url: string, options?: DbOptions): import("driz
|
|
|
252
252
|
identity: undefined;
|
|
253
253
|
generated: undefined;
|
|
254
254
|
}, {}, {}>;
|
|
255
|
+
dirtySince: import("drizzle-orm/pg-core").PgColumn<{
|
|
256
|
+
name: "dirty_since";
|
|
257
|
+
tableName: "agents";
|
|
258
|
+
dataType: "date";
|
|
259
|
+
columnType: "PgTimestamp";
|
|
260
|
+
data: Date;
|
|
261
|
+
driverParam: string;
|
|
262
|
+
notNull: false;
|
|
263
|
+
hasDefault: true;
|
|
264
|
+
isPrimaryKey: false;
|
|
265
|
+
isAutoincrement: false;
|
|
266
|
+
hasRuntimeDefault: false;
|
|
267
|
+
enumValues: undefined;
|
|
268
|
+
baseColumn: never;
|
|
269
|
+
identity: undefined;
|
|
270
|
+
generated: undefined;
|
|
271
|
+
}, {}, {}>;
|
|
272
|
+
dirtyGen: import("drizzle-orm/pg-core").PgColumn<{
|
|
273
|
+
name: "dirty_gen";
|
|
274
|
+
tableName: "agents";
|
|
275
|
+
dataType: "number";
|
|
276
|
+
columnType: "PgBigInt53";
|
|
277
|
+
data: number;
|
|
278
|
+
driverParam: string | number;
|
|
279
|
+
notNull: true;
|
|
280
|
+
hasDefault: true;
|
|
281
|
+
isPrimaryKey: false;
|
|
282
|
+
isAutoincrement: false;
|
|
283
|
+
hasRuntimeDefault: false;
|
|
284
|
+
enumValues: undefined;
|
|
285
|
+
baseColumn: never;
|
|
286
|
+
identity: undefined;
|
|
287
|
+
generated: undefined;
|
|
288
|
+
}, {}, {}>;
|
|
289
|
+
recomputeAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
290
|
+
name: "recompute_at";
|
|
291
|
+
tableName: "agents";
|
|
292
|
+
dataType: "date";
|
|
293
|
+
columnType: "PgTimestamp";
|
|
294
|
+
data: Date;
|
|
295
|
+
driverParam: string;
|
|
296
|
+
notNull: false;
|
|
297
|
+
hasDefault: false;
|
|
298
|
+
isPrimaryKey: false;
|
|
299
|
+
isAutoincrement: false;
|
|
300
|
+
hasRuntimeDefault: false;
|
|
301
|
+
enumValues: undefined;
|
|
302
|
+
baseColumn: never;
|
|
303
|
+
identity: undefined;
|
|
304
|
+
generated: undefined;
|
|
305
|
+
}, {}, {}>;
|
|
255
306
|
};
|
|
256
307
|
dialect: 'pg';
|
|
257
308
|
}>;
|
|
@@ -504,6 +555,57 @@ export declare function createDb(url: string, options?: DbOptions): import("driz
|
|
|
504
555
|
identity: undefined;
|
|
505
556
|
generated: undefined;
|
|
506
557
|
}, {}, {}>;
|
|
558
|
+
assigneeAgentId: import("drizzle-orm/pg-core").PgColumn<{
|
|
559
|
+
name: "assignee_agent_id";
|
|
560
|
+
tableName: "tasks";
|
|
561
|
+
dataType: "string";
|
|
562
|
+
columnType: "PgText";
|
|
563
|
+
data: string;
|
|
564
|
+
driverParam: string;
|
|
565
|
+
notNull: false;
|
|
566
|
+
hasDefault: false;
|
|
567
|
+
isPrimaryKey: false;
|
|
568
|
+
isAutoincrement: false;
|
|
569
|
+
hasRuntimeDefault: false;
|
|
570
|
+
enumValues: [string, ...string[]];
|
|
571
|
+
baseColumn: never;
|
|
572
|
+
identity: undefined;
|
|
573
|
+
generated: undefined;
|
|
574
|
+
}, {}, {}>;
|
|
575
|
+
pairSettledAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
576
|
+
name: "pair_settled_at";
|
|
577
|
+
tableName: "tasks";
|
|
578
|
+
dataType: "date";
|
|
579
|
+
columnType: "PgTimestamp";
|
|
580
|
+
data: Date;
|
|
581
|
+
driverParam: string;
|
|
582
|
+
notNull: false;
|
|
583
|
+
hasDefault: false;
|
|
584
|
+
isPrimaryKey: false;
|
|
585
|
+
isAutoincrement: false;
|
|
586
|
+
hasRuntimeDefault: false;
|
|
587
|
+
enumValues: undefined;
|
|
588
|
+
baseColumn: never;
|
|
589
|
+
identity: undefined;
|
|
590
|
+
generated: undefined;
|
|
591
|
+
}, {}, {}>;
|
|
592
|
+
overPairCap: import("drizzle-orm/pg-core").PgColumn<{
|
|
593
|
+
name: "over_pair_cap";
|
|
594
|
+
tableName: "tasks";
|
|
595
|
+
dataType: "boolean";
|
|
596
|
+
columnType: "PgBoolean";
|
|
597
|
+
data: boolean;
|
|
598
|
+
driverParam: boolean;
|
|
599
|
+
notNull: true;
|
|
600
|
+
hasDefault: true;
|
|
601
|
+
isPrimaryKey: false;
|
|
602
|
+
isAutoincrement: false;
|
|
603
|
+
hasRuntimeDefault: false;
|
|
604
|
+
enumValues: undefined;
|
|
605
|
+
baseColumn: never;
|
|
606
|
+
identity: undefined;
|
|
607
|
+
generated: undefined;
|
|
608
|
+
}, {}, {}>;
|
|
507
609
|
taskType: import("drizzle-orm/pg-core").PgColumn<{
|
|
508
610
|
name: "task_type";
|
|
509
611
|
tableName: "tasks";
|
|
@@ -1814,6 +1916,81 @@ export declare function createDb(url: string, options?: DbOptions): import("driz
|
|
|
1814
1916
|
};
|
|
1815
1917
|
dialect: 'pg';
|
|
1816
1918
|
}>;
|
|
1919
|
+
agentMilestones: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
1920
|
+
name: "agent_milestones";
|
|
1921
|
+
schema: undefined;
|
|
1922
|
+
columns: {
|
|
1923
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
1924
|
+
name: "created_at";
|
|
1925
|
+
tableName: "agent_milestones";
|
|
1926
|
+
dataType: "date";
|
|
1927
|
+
columnType: "PgTimestamp";
|
|
1928
|
+
data: Date;
|
|
1929
|
+
driverParam: string;
|
|
1930
|
+
notNull: true;
|
|
1931
|
+
hasDefault: true;
|
|
1932
|
+
isPrimaryKey: false;
|
|
1933
|
+
isAutoincrement: false;
|
|
1934
|
+
hasRuntimeDefault: false;
|
|
1935
|
+
enumValues: undefined;
|
|
1936
|
+
baseColumn: never;
|
|
1937
|
+
identity: undefined;
|
|
1938
|
+
generated: undefined;
|
|
1939
|
+
}, {}, {}>;
|
|
1940
|
+
updatedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
1941
|
+
name: "updated_at";
|
|
1942
|
+
tableName: "agent_milestones";
|
|
1943
|
+
dataType: "date";
|
|
1944
|
+
columnType: "PgTimestamp";
|
|
1945
|
+
data: Date;
|
|
1946
|
+
driverParam: string;
|
|
1947
|
+
notNull: true;
|
|
1948
|
+
hasDefault: true;
|
|
1949
|
+
isPrimaryKey: false;
|
|
1950
|
+
isAutoincrement: false;
|
|
1951
|
+
hasRuntimeDefault: false;
|
|
1952
|
+
enumValues: undefined;
|
|
1953
|
+
baseColumn: never;
|
|
1954
|
+
identity: undefined;
|
|
1955
|
+
generated: undefined;
|
|
1956
|
+
}, {}, {}>;
|
|
1957
|
+
agentId: import("drizzle-orm/pg-core").PgColumn<{
|
|
1958
|
+
name: "agent_id";
|
|
1959
|
+
tableName: "agent_milestones";
|
|
1960
|
+
dataType: "string";
|
|
1961
|
+
columnType: "PgText";
|
|
1962
|
+
data: string;
|
|
1963
|
+
driverParam: string;
|
|
1964
|
+
notNull: true;
|
|
1965
|
+
hasDefault: false;
|
|
1966
|
+
isPrimaryKey: false;
|
|
1967
|
+
isAutoincrement: false;
|
|
1968
|
+
hasRuntimeDefault: false;
|
|
1969
|
+
enumValues: [string, ...string[]];
|
|
1970
|
+
baseColumn: never;
|
|
1971
|
+
identity: undefined;
|
|
1972
|
+
generated: undefined;
|
|
1973
|
+
}, {}, {}>;
|
|
1974
|
+
milestone: import("drizzle-orm/pg-core").PgColumn<{
|
|
1975
|
+
name: "milestone";
|
|
1976
|
+
tableName: "agent_milestones";
|
|
1977
|
+
dataType: "string";
|
|
1978
|
+
columnType: "PgText";
|
|
1979
|
+
data: string;
|
|
1980
|
+
driverParam: string;
|
|
1981
|
+
notNull: true;
|
|
1982
|
+
hasDefault: false;
|
|
1983
|
+
isPrimaryKey: false;
|
|
1984
|
+
isAutoincrement: false;
|
|
1985
|
+
hasRuntimeDefault: false;
|
|
1986
|
+
enumValues: [string, ...string[]];
|
|
1987
|
+
baseColumn: never;
|
|
1988
|
+
identity: undefined;
|
|
1989
|
+
generated: undefined;
|
|
1990
|
+
}, {}, {}>;
|
|
1991
|
+
};
|
|
1992
|
+
dialect: 'pg';
|
|
1993
|
+
}>;
|
|
1817
1994
|
deletedOperators: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
1818
1995
|
name: "deleted_operators";
|
|
1819
1996
|
schema: undefined;
|
package/dist/db/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
2
2
|
import postgres from 'postgres';
|
|
3
|
+
import { agentMilestones } from './agent-milestones.js';
|
|
3
4
|
import { agentRenames } from './agent-renames.js';
|
|
4
5
|
import { agents } from './agents.js';
|
|
5
6
|
import { credentials } from './credentials.js';
|
|
@@ -27,6 +28,7 @@ const schema = {
|
|
|
27
28
|
quotaCounters,
|
|
28
29
|
feedItems,
|
|
29
30
|
agentRenames,
|
|
31
|
+
agentMilestones,
|
|
30
32
|
deletedOperators,
|
|
31
33
|
};
|
|
32
34
|
export function createDb(url, options = {}) {
|
package/dist/db/feed-items.js
CHANGED
|
@@ -13,4 +13,9 @@ export const feedItems = pgTable('feed_items', {
|
|
|
13
13
|
agentId: text('agent_id').references(() => agents.id),
|
|
14
14
|
payload: jsonb('payload').notNull(),
|
|
15
15
|
...timestamps,
|
|
16
|
-
}, (t) => [
|
|
16
|
+
}, (t) => [
|
|
17
|
+
index('feed_items_created_at_idx').on(t.createdAt),
|
|
18
|
+
// Milestones are written once per agent, and deleting an agent removes
|
|
19
|
+
// its items. Both look up by agent and kind.
|
|
20
|
+
index('feed_items_agent_id_kind_idx').on(t.agentId, t.kind),
|
|
21
|
+
]);
|
package/dist/db/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { agentMilestones } from './agent-milestones.js';
|
|
1
2
|
import type { agentRenames } from './agent-renames.js';
|
|
2
3
|
import type { credentials } from './credentials.js';
|
|
3
4
|
import type { deletedOperators } from './deleted-operators.js';
|
|
@@ -10,8 +11,9 @@ import type { scores } from './scores.js';
|
|
|
10
11
|
import type { standing } from './standing.js';
|
|
11
12
|
import type { taskOutcomes } from './task-outcomes.js';
|
|
12
13
|
import type { tasks } from './tasks.js';
|
|
13
|
-
export { and, asc, desc, eq, gt, inArray, isNotNull, isNull, lt, lte, max, ne, notInArray, or, sql, } from 'drizzle-orm';
|
|
14
|
+
export { and, asc, desc, eq, gt, inArray, isNotNull, isNull, lt, lte, max, ne, notInArray, or, type SQL, sql, } from 'drizzle-orm';
|
|
14
15
|
export { DrizzleQueryError } from 'drizzle-orm/errors';
|
|
16
|
+
export { agentMilestones } from './agent-milestones.js';
|
|
15
17
|
export { agentRenames } from './agent-renames.js';
|
|
16
18
|
export type { Agent, NewAgent } from './agents.js';
|
|
17
19
|
export { agents } from './agents.js';
|
|
@@ -52,5 +54,7 @@ export type AgentRename = typeof agentRenames.$inferSelect;
|
|
|
52
54
|
export type NewAgentRename = typeof agentRenames.$inferInsert;
|
|
53
55
|
export type FeedItem = typeof feedItems.$inferSelect;
|
|
54
56
|
export type NewFeedItem = typeof feedItems.$inferInsert;
|
|
57
|
+
export type AgentMilestone = typeof agentMilestones.$inferSelect;
|
|
58
|
+
export type NewAgentMilestone = typeof agentMilestones.$inferInsert;
|
|
55
59
|
export type DeletedOperator = typeof deletedOperators.$inferSelect;
|
|
56
60
|
export type NewDeletedOperator = typeof deletedOperators.$inferInsert;
|
package/dist/db/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { and, asc, desc, eq, gt, inArray, isNotNull, isNull, lt, lte, max, ne, n
|
|
|
4
4
|
// What every failed query throws. Its message holds the SQL and every bound
|
|
5
5
|
// param, so the API error log must never print it.
|
|
6
6
|
export { DrizzleQueryError } from 'drizzle-orm/errors';
|
|
7
|
+
export { agentMilestones } from './agent-milestones.js';
|
|
7
8
|
export { agentRenames } from './agent-renames.js';
|
|
8
9
|
export { agents } from './agents.js';
|
|
9
10
|
export { createDb } from './client.js';
|
package/dist/db/tasks.d.ts
CHANGED
|
@@ -87,6 +87,57 @@ export declare const tasks: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
|
87
87
|
identity: undefined;
|
|
88
88
|
generated: undefined;
|
|
89
89
|
}, {}, {}>;
|
|
90
|
+
assigneeAgentId: import("drizzle-orm/pg-core").PgColumn<{
|
|
91
|
+
name: "assignee_agent_id";
|
|
92
|
+
tableName: "tasks";
|
|
93
|
+
dataType: "string";
|
|
94
|
+
columnType: "PgText";
|
|
95
|
+
data: string;
|
|
96
|
+
driverParam: string;
|
|
97
|
+
notNull: false;
|
|
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
|
+
pairSettledAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
108
|
+
name: "pair_settled_at";
|
|
109
|
+
tableName: "tasks";
|
|
110
|
+
dataType: "date";
|
|
111
|
+
columnType: "PgTimestamp";
|
|
112
|
+
data: Date;
|
|
113
|
+
driverParam: string;
|
|
114
|
+
notNull: false;
|
|
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
|
+
overPairCap: import("drizzle-orm/pg-core").PgColumn<{
|
|
125
|
+
name: "over_pair_cap";
|
|
126
|
+
tableName: "tasks";
|
|
127
|
+
dataType: "boolean";
|
|
128
|
+
columnType: "PgBoolean";
|
|
129
|
+
data: boolean;
|
|
130
|
+
driverParam: boolean;
|
|
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
|
+
}, {}, {}>;
|
|
90
141
|
taskType: import("drizzle-orm/pg-core").PgColumn<{
|
|
91
142
|
name: "task_type";
|
|
92
143
|
tableName: "tasks";
|
package/dist/db/tasks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { sql } from 'drizzle-orm';
|
|
2
|
-
import { index, jsonb, pgTable, text, timestamp, uuid, } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { boolean, index, 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', {
|
|
@@ -8,6 +8,21 @@ export const tasks = pgTable('tasks', {
|
|
|
8
8
|
.notNull()
|
|
9
9
|
.references(() => agents.id),
|
|
10
10
|
claimantAgentId: text('claimant_agent_id').references(() => agents.id),
|
|
11
|
+
// The one agent an addressed task is for, null for an open task. Only it
|
|
12
|
+
// can claim the task, and the open pool leaves it out. Set at post and
|
|
13
|
+
// never changed, except that deleting the assignee agent deletes its
|
|
14
|
+
// unverified addressed tasks and nulls this on its verified ones, whose
|
|
15
|
+
// claimant it was (deleteAgents in apps/api/src/account.ts).
|
|
16
|
+
assigneeAgentId: text('assignee_agent_id').references(() => agents.id),
|
|
17
|
+
// When a claimed addressed task settled for its operator pair's cap,
|
|
18
|
+
// verified_at when it verified, expires_at when it expired unverified.
|
|
19
|
+
// Null for an open task and until it settles. Set once, with
|
|
20
|
+
// over_pair_cap, by apps/api/src/pair-cap.ts.
|
|
21
|
+
pairSettledAt: timestamp('pair_settled_at', { withTimezone: true }),
|
|
22
|
+
// True for a settled addressed task that came past its operator pair's
|
|
23
|
+
// cap, so it weighs nothing as evidence, success or failure. False for
|
|
24
|
+
// every other task.
|
|
25
|
+
overPairCap: boolean('over_pair_cap').notNull().default(false),
|
|
11
26
|
taskType: text('task_type').notNull(),
|
|
12
27
|
spec: jsonb('spec').notNull(),
|
|
13
28
|
verification: jsonb('verification').notNull(),
|
|
@@ -36,10 +51,28 @@ export const tasks = pgTable('tasks', {
|
|
|
36
51
|
index('tasks_poster_agent_id_idx').on(t.posterAgentId),
|
|
37
52
|
// The claim cap per claimant, activity counts, scoring and evidence.
|
|
38
53
|
index('tasks_claimant_agent_id_idx').on(t.claimantAgentId),
|
|
39
|
-
// GET /v1/tasks?state=open, oldest first.
|
|
54
|
+
// GET /v1/tasks?state=open and the board, oldest or newest first. The
|
|
55
|
+
// open pool filters out addressed tasks on top of it.
|
|
40
56
|
index('tasks_open_posted_at_idx')
|
|
41
57
|
.on(t.postedAt, t.id)
|
|
42
58
|
.where(sql `${t.claimedAt} is null and ${t.verifiedAt} is null`),
|
|
59
|
+
// Open tasks addressed to one agent, oldest first. GET /v1/tasks?assignee=
|
|
60
|
+
// and the per assignee caps.
|
|
61
|
+
index('tasks_open_assignee_posted_at_idx')
|
|
62
|
+
.on(t.assigneeAgentId, t.postedAt, t.id)
|
|
63
|
+
.where(sql `${t.assigneeAgentId} is not null and ${t.claimedAt} is null and ${t.verifiedAt} is null`),
|
|
64
|
+
// Addressed tasks by assignee and settle time. The pair cap counts an
|
|
65
|
+
// assignee's settled tasks over the last days, and a deletion finds the
|
|
66
|
+
// tasks addressed to it.
|
|
67
|
+
index('tasks_assignee_settled_at_idx')
|
|
68
|
+
.on(t.assigneeAgentId, t.pairSettledAt)
|
|
69
|
+
.where(sql `${t.assigneeAgentId} is not null`),
|
|
70
|
+
// Claimed addressed tasks not yet settled for the pair cap, by settle
|
|
71
|
+
// time. The pair cap walks and the scoring run's pass read it. Small,
|
|
72
|
+
// since a task leaves it once it is decided.
|
|
73
|
+
index('tasks_addressed_pending_idx')
|
|
74
|
+
.on(sql `least(${t.verifiedAt}, ${t.expiresAt})`)
|
|
75
|
+
.where(sql `${t.assigneeAgentId} is not null and ${t.claimedAt} is not null and ${t.pairSettledAt} is null`),
|
|
43
76
|
// GET /v1/tasks?state=claimed, oldest first.
|
|
44
77
|
index('tasks_claimed_posted_at_idx')
|
|
45
78
|
.on(t.postedAt, t.id)
|
package/dist/envelope.d.ts
CHANGED
|
@@ -14,3 +14,14 @@ export declare function verify(jws: string, publicKey: Uint8Array): Promise<{
|
|
|
14
14
|
header: JwsHeader;
|
|
15
15
|
payload: unknown;
|
|
16
16
|
}>;
|
|
17
|
+
export declare const Audience: z.ZodString;
|
|
18
|
+
export declare function audienceOf(apiUrl: string): string;
|
|
19
|
+
export declare function withAudience<T extends object>(payload: T, aud: string): T & {
|
|
20
|
+
aud: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function signRequest(payload: object, privateKey: Uint8Array, kid: string, aud: string): Promise<string>;
|
|
23
|
+
export type AudienceCheck = {
|
|
24
|
+
result: 'match' | 'missing' | 'wrong';
|
|
25
|
+
payload: unknown;
|
|
26
|
+
};
|
|
27
|
+
export declare function readAudience(payload: unknown, accepted: readonly string[]): AudienceCheck;
|
package/dist/envelope.js
CHANGED
|
@@ -68,3 +68,73 @@ export async function verify(jws, publicKey) {
|
|
|
68
68
|
const header = parseHeader(parts.header);
|
|
69
69
|
return { header, payload: parseJson(parts.payload, 'payload') };
|
|
70
70
|
}
|
|
71
|
+
// Signed request audience (VOU-111). Every agent-signed request names the
|
|
72
|
+
// API it is meant for with `aud`, a top level key inside the signed payload,
|
|
73
|
+
// so a request captured for one API cannot be replayed to another. SEALs
|
|
74
|
+
// carry `iss` instead and do not use this.
|
|
75
|
+
function isHttpOrigin(v) {
|
|
76
|
+
try {
|
|
77
|
+
const url = new URL(v);
|
|
78
|
+
return ((url.protocol === 'http:' || url.protocol === 'https:') &&
|
|
79
|
+
url.origin === v);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// An origin with no trailing slash, no path, a lowercase host and no default
|
|
86
|
+
// port, such as https://api.sealkeeper.run.
|
|
87
|
+
export const Audience = z
|
|
88
|
+
.string()
|
|
89
|
+
.max(256)
|
|
90
|
+
.refine(isHttpOrigin, 'aud must be an origin such as https://api.sealkeeper.run');
|
|
91
|
+
// The audience for an API base URL, its origin.
|
|
92
|
+
export function audienceOf(apiUrl) {
|
|
93
|
+
let url;
|
|
94
|
+
try {
|
|
95
|
+
url = new URL(apiUrl);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new EnvelopeError('API URL is not a URL');
|
|
99
|
+
}
|
|
100
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
101
|
+
throw new EnvelopeError('API URL must be http or https');
|
|
102
|
+
}
|
|
103
|
+
if (url.origin === 'null') {
|
|
104
|
+
throw new EnvelopeError('API URL has no origin');
|
|
105
|
+
}
|
|
106
|
+
return url.origin;
|
|
107
|
+
}
|
|
108
|
+
// A copy of the payload with `aud` added. Never mutates the input.
|
|
109
|
+
export function withAudience(payload, aud) {
|
|
110
|
+
if (!Audience.safeParse(aud).success) {
|
|
111
|
+
throw new EnvelopeError('aud must be an origin');
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(payload)) {
|
|
114
|
+
throw new EnvelopeError('A signed request payload must be an object');
|
|
115
|
+
}
|
|
116
|
+
if (Object.hasOwn(payload, 'aud')) {
|
|
117
|
+
throw new EnvelopeError('Payload already has an aud');
|
|
118
|
+
}
|
|
119
|
+
return { ...payload, aud };
|
|
120
|
+
}
|
|
121
|
+
// How every agent-signed request is signed. The payload gains `aud` inside
|
|
122
|
+
// the signed bytes. `sign` stays for SEALs, which carry `iss`.
|
|
123
|
+
export async function signRequest(payload, privateKey, kid, aud) {
|
|
124
|
+
return sign(withAudience(payload, aud), privateKey, kid);
|
|
125
|
+
}
|
|
126
|
+
// Read `aud` off a verified payload. Call only after `verify`. The compare
|
|
127
|
+
// is exact, the received value is never normalised. A payload that is not a
|
|
128
|
+
// plain object comes back unchanged as missing, so the request schema still
|
|
129
|
+
// rejects it.
|
|
130
|
+
export function readAudience(payload, accepted) {
|
|
131
|
+
if (typeof payload !== 'object' ||
|
|
132
|
+
payload === null ||
|
|
133
|
+
Array.isArray(payload) ||
|
|
134
|
+
!Object.hasOwn(payload, 'aud')) {
|
|
135
|
+
return { result: 'missing', payload };
|
|
136
|
+
}
|
|
137
|
+
const { aud, ...rest } = payload;
|
|
138
|
+
const match = typeof aud === 'string' && accepted.includes(aud);
|
|
139
|
+
return { result: match ? 'match' : 'wrong', payload: rest };
|
|
140
|
+
}
|