@sealant/api-contracts-next 0.39.0-next.694 → 0.39.0-next.696

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.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The owner map of a capture workspace (sealantd ADR-0015 "Per-person saved directories", Mend's
3
+ * ADR 0016 decision 8): who owns what the daemon's restore writes, and so whether its executor is a
4
+ * per-person one. The ONE definition of its shape, its bounds and its wire encoding, shared by the
5
+ * SDK (client-side refusal with the server's wording), the control-plane validators (every parse:
6
+ * create, worker, restart) and the runtime adapters (the boot env).
7
+ *
8
+ * The daemon reads it from its boot environment as `SEALANT_CAPTURE_OWNER_MAP`, JSON
9
+ * `{"gid":…,"worktree":…,"people":{"<id>":uid,…}}`, parsed strictly (unknown fields refused):
10
+ *
11
+ * - each listed person's saved directory (`<harness home>/people/<id>/`) is restored owned by their
12
+ * uid and the group, the directory itself 0710, its `conversations/` group-readable and -writable;
13
+ * - the worktree and its git directory are given to the group (owned by `worktreeUid`, setgid, the
14
+ * owner's bits copied to the group);
15
+ * - a root daemon whose map names at least one person does not set no-new-privileges, so every
16
+ * person's passwordless `sudo` works. A map naming nobody keeps it.
17
+ *
18
+ * Deliberately dependency-free, like `workspace-environment.ts`: data plus pure functions.
19
+ */
20
+ /** The daemon's name for the owner map in its boot environment. */
21
+ export declare const CAPTURE_OWNER_MAP_ENV = "SEALANT_CAPTURE_OWNER_MAP";
22
+ /** The shared group every person is in: Mend's `mend`, the reserved range's first id. */
23
+ export declare const CAPTURE_OWNER_MAP_GID = 40000;
24
+ /**
25
+ * The uids people may have: the reserved range (40000–49999) less its first id, the group's. The
26
+ * managed images leave the whole range free (their probe checks it).
27
+ */
28
+ export declare const CAPTURE_OWNER_MAP_UID_RANGE: {
29
+ readonly first: 40001;
30
+ readonly last: 49999;
31
+ };
32
+ /** At most this many people in one map. */
33
+ export declare const CAPTURE_OWNER_MAP_MAX_PEOPLE = 256;
34
+ /**
35
+ * A person's id: the name of their saved directory under `people/` (Mend's account id). One plain
36
+ * path component of at most 128 characters that starts with a letter, a digit or `_` (so never `.`
37
+ * or `..`).
38
+ */
39
+ export declare const CAPTURE_OWNER_MAP_ID_PATTERN: RegExp;
40
+ /** One person in an owner map. */
41
+ export interface CaptureOwnerMapPerson {
42
+ /** The name of their saved directory under `<harness home>/people/` (Mend's account id). */
43
+ readonly id: string;
44
+ /** Their Linux uid, in {@link CAPTURE_OWNER_MAP_UID_RANGE}. */
45
+ readonly uid: number;
46
+ }
47
+ /** Who owns what a capture restore writes. */
48
+ export interface CaptureOwnerMap {
49
+ /** The shared group every person is in: {@link CAPTURE_OWNER_MAP_GID}. */
50
+ readonly gid: number;
51
+ /** The change's owner: owns the worktree root and its git directory. */
52
+ readonly worktreeUid: number;
53
+ /** Each current member. A removed member is left out: their directory is restored root's. */
54
+ readonly people: ReadonlyArray<CaptureOwnerMapPerson>;
55
+ }
56
+ /**
57
+ * What is wrong with a map, in the words the control plane answers with; empty when nothing is.
58
+ * Checks the gid, every uid's range, every id's form, and that no id and no uid appears twice (one
59
+ * person's saved directory is never handed to another).
60
+ */
61
+ export declare const captureOwnerMapProblems: (map: CaptureOwnerMap) => readonly string[];
62
+ /**
63
+ * The map as the daemon parses it (`SEALANT_CAPTURE_OWNER_MAP`): compact JSON, `people` an object
64
+ * keyed by id in code-point order (the daemon's own order), always present.
65
+ */
66
+ export declare const encodeCaptureOwnerMap: (map: CaptureOwnerMap) => string;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The owner map of a capture workspace (sealantd ADR-0015 "Per-person saved directories", Mend's
3
+ * ADR 0016 decision 8): who owns what the daemon's restore writes, and so whether its executor is a
4
+ * per-person one. The ONE definition of its shape, its bounds and its wire encoding, shared by the
5
+ * SDK (client-side refusal with the server's wording), the control-plane validators (every parse:
6
+ * create, worker, restart) and the runtime adapters (the boot env).
7
+ *
8
+ * The daemon reads it from its boot environment as `SEALANT_CAPTURE_OWNER_MAP`, JSON
9
+ * `{"gid":…,"worktree":…,"people":{"<id>":uid,…}}`, parsed strictly (unknown fields refused):
10
+ *
11
+ * - each listed person's saved directory (`<harness home>/people/<id>/`) is restored owned by their
12
+ * uid and the group, the directory itself 0710, its `conversations/` group-readable and -writable;
13
+ * - the worktree and its git directory are given to the group (owned by `worktreeUid`, setgid, the
14
+ * owner's bits copied to the group);
15
+ * - a root daemon whose map names at least one person does not set no-new-privileges, so every
16
+ * person's passwordless `sudo` works. A map naming nobody keeps it.
17
+ *
18
+ * Deliberately dependency-free, like `workspace-environment.ts`: data plus pure functions.
19
+ */
20
+ /** The daemon's name for the owner map in its boot environment. */
21
+ export const CAPTURE_OWNER_MAP_ENV = "SEALANT_CAPTURE_OWNER_MAP";
22
+ /** The shared group every person is in: Mend's `mend`, the reserved range's first id. */
23
+ export const CAPTURE_OWNER_MAP_GID = 40_000;
24
+ /**
25
+ * The uids people may have: the reserved range (40000–49999) less its first id, the group's. The
26
+ * managed images leave the whole range free (their probe checks it).
27
+ */
28
+ export const CAPTURE_OWNER_MAP_UID_RANGE = { first: 40_001, last: 49_999 };
29
+ /** At most this many people in one map. */
30
+ export const CAPTURE_OWNER_MAP_MAX_PEOPLE = 256;
31
+ /**
32
+ * A person's id: the name of their saved directory under `people/` (Mend's account id). One plain
33
+ * path component of at most 128 characters that starts with a letter, a digit or `_` (so never `.`
34
+ * or `..`).
35
+ */
36
+ export const CAPTURE_OWNER_MAP_ID_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$/u;
37
+ const isUid = (value) => Number.isInteger(value) &&
38
+ value >= CAPTURE_OWNER_MAP_UID_RANGE.first &&
39
+ value <= CAPTURE_OWNER_MAP_UID_RANGE.last;
40
+ const UID_RANGE_TEXT = `${CAPTURE_OWNER_MAP_UID_RANGE.first}–${CAPTURE_OWNER_MAP_UID_RANGE.last}`;
41
+ /**
42
+ * What is wrong with a map, in the words the control plane answers with; empty when nothing is.
43
+ * Checks the gid, every uid's range, every id's form, and that no id and no uid appears twice (one
44
+ * person's saved directory is never handed to another).
45
+ */
46
+ export const captureOwnerMapProblems = (map) => {
47
+ // A plain JavaScript caller may hand anything: refused in words, never a TypeError.
48
+ const shape = map;
49
+ if (typeof shape !== "object" || shape === null) {
50
+ return ["ownerMap must be an object { gid, worktreeUid, people }"];
51
+ }
52
+ if (!Array.isArray(map.people)) {
53
+ return ["ownerMap.people must be an array of { id, uid }"];
54
+ }
55
+ const problems = [];
56
+ if (map.gid !== CAPTURE_OWNER_MAP_GID) {
57
+ problems.push(`ownerMap.gid must be ${CAPTURE_OWNER_MAP_GID} (the mend group), not ${map.gid}`);
58
+ }
59
+ if (!isUid(map.worktreeUid)) {
60
+ problems.push(`ownerMap.worktreeUid ${map.worktreeUid} is not a uid in ${UID_RANGE_TEXT}`);
61
+ }
62
+ if (map.people.length > CAPTURE_OWNER_MAP_MAX_PEOPLE) {
63
+ problems.push(`ownerMap.people has ${map.people.length} people; the maximum is ${CAPTURE_OWNER_MAP_MAX_PEOPLE}`);
64
+ return problems;
65
+ }
66
+ const ids = new Set();
67
+ const uids = new Map();
68
+ for (const [index, person] of map.people.entries()) {
69
+ const entry = person;
70
+ if (typeof entry !== "object" || entry === null) {
71
+ problems.push(`ownerMap.people[${index}] must be an object { id, uid }`);
72
+ continue;
73
+ }
74
+ if (typeof person.id !== "string" || !CAPTURE_OWNER_MAP_ID_PATTERN.test(person.id)) {
75
+ problems.push(`ownerMap.people[${index}].id must be one directory name of letters, digits, '.', '_' or '-' (at most 128, not starting with '.' or '-')`);
76
+ continue;
77
+ }
78
+ if (ids.has(person.id)) {
79
+ problems.push(`ownerMap.people names ${person.id} twice`);
80
+ }
81
+ ids.add(person.id);
82
+ if (!isUid(person.uid)) {
83
+ problems.push(`ownerMap.people ${person.id} has uid ${person.uid}, not a uid in ${UID_RANGE_TEXT}`);
84
+ continue;
85
+ }
86
+ const other = uids.get(person.uid);
87
+ if (other !== undefined && other !== person.id) {
88
+ problems.push(`ownerMap.people gives ${other} and ${person.id} the same uid ${person.uid}`);
89
+ }
90
+ uids.set(person.uid, person.id);
91
+ }
92
+ return problems;
93
+ };
94
+ /**
95
+ * The map as the daemon parses it (`SEALANT_CAPTURE_OWNER_MAP`): compact JSON, `people` an object
96
+ * keyed by id in code-point order (the daemon's own order), always present.
97
+ */
98
+ export const encodeCaptureOwnerMap = (map) => {
99
+ // `fromEntries` defines own properties, so an id such as `__proto__` stays a key.
100
+ const people = Object.fromEntries(map.people
101
+ .toSorted((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
102
+ .map((person) => [person.id, person.uid]));
103
+ return JSON.stringify({ gid: map.gid, worktree: map.worktreeUid, people });
104
+ };
@@ -462,7 +462,7 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
462
462
  readonly recordDeletedAt: import("effect/Schema").optional<import("effect/Schema").String>;
463
463
  readonly createdAt: import("effect/Schema").String;
464
464
  readonly updatedAt: import("effect/Schema").String;
465
- }>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./runs.js").RunNotFoundError | typeof import("./runs.js").RunInternalServerError>, never, never> | import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"getRunChanges", "GET", "/v1/runs/:runId/changes", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
465
+ }>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./budgets.ts").BudgetExceededError | typeof import("./runs.js").RunNotFoundError | typeof import("./runs.js").RunInternalServerError>, never, never> | import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"getRunChanges", "GET", "/v1/runs/:runId/changes", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
466
466
  readonly runId: import("effect/Schema").String;
467
467
  }>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
468
468
  readonly ownerUserId: import("effect/Schema").optional<import("effect/Schema").String>;
@@ -1026,6 +1026,7 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
1026
1026
  readonly runningMs: import("effect/Schema").Number;
1027
1027
  readonly boundMs: import("effect/Schema").Number;
1028
1028
  }>>;
1029
+ readonly ownerMap: import("effect/Schema").optional<import("effect/Schema").Boolean>;
1029
1030
  }>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./workspaces.js").WorkspaceBadRequestError | typeof import("./workspaces.js").WorkspaceNotFoundError | typeof import("./workspaces.js").WorkspaceConflictError | typeof import("./workspaces.js").WorkspaceInternalServerError>, never, never> | import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"getWorkspace", "GET", "/v1/workspaces/:workspaceId", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
1030
1031
  readonly workspaceId: import("effect/Schema").String;
1031
1032
  }>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
@@ -1164,6 +1165,7 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
1164
1165
  readonly runningMs: import("effect/Schema").Number;
1165
1166
  readonly boundMs: import("effect/Schema").Number;
1166
1167
  }>>;
1168
+ readonly ownerMap: import("effect/Schema").optional<import("effect/Schema").Boolean>;
1167
1169
  }>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./workspaces.js").WorkspaceBadRequestError | typeof import("./workspaces.js").WorkspaceNotFoundError | typeof import("./workspaces.js").WorkspaceConflictError | typeof import("./workspaces.js").WorkspaceInternalServerError>, never, never> | import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"getWorkspaceCreate", "GET", "/v1/workspaces/idempotency-keys/:idempotencyKey", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
1168
1170
  readonly idempotencyKey: import("effect/Schema").String;
1169
1171
  }>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
@@ -1410,6 +1412,14 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
1410
1412
  readonly workspaceId: import("effect/Schema").String;
1411
1413
  }>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
1412
1414
  readonly ownerUserId: import("effect/Schema").String;
1415
+ readonly expectedOwnerMap: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Struct<{
1416
+ readonly gid: import("effect/Schema").Number;
1417
+ readonly worktreeUid: import("effect/Schema").Number;
1418
+ readonly people: import("effect/Schema").$Array<import("effect/Schema").Struct<{
1419
+ readonly id: import("effect/Schema").String;
1420
+ readonly uid: import("effect/Schema").Number;
1421
+ }>>;
1422
+ }>>>;
1413
1423
  }>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
1414
1424
  readonly worktreeId: import("effect/Schema").String;
1415
1425
  readonly epoch: import("effect/Schema").Number;
@@ -380,7 +380,7 @@ export declare const RunsGroup: HttpApiGroup.HttpApiGroup<"runs", HttpApiEndpoin
380
380
  readonly recordDeletedAt: Schema.optional<Schema.String>;
381
381
  readonly createdAt: Schema.String;
382
382
  readonly updatedAt: Schema.String;
383
- }>>, HttpApiEndpoint.Json<typeof RunNotFoundError | typeof RunInternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"getRunChanges", "GET", "/:runId/changes", HttpApiEndpoint.StringTree<Schema.Struct<{
383
+ }>>, HttpApiEndpoint.Json<typeof BudgetExceededError | typeof RunNotFoundError | typeof RunInternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"getRunChanges", "GET", "/:runId/changes", HttpApiEndpoint.StringTree<Schema.Struct<{
384
384
  readonly runId: Schema.String;
385
385
  }>>, HttpApiEndpoint.StringTree<Schema.Struct<{
386
386
  readonly ownerUserId: Schema.optional<Schema.String>;
@@ -258,7 +258,8 @@ export const RunsGroup = HttpApiGroup.make("runs")
258
258
  params: runIdParams,
259
259
  query: runOwnerQuerySchema,
260
260
  success: runSchema,
261
- error: [RunNotFoundError, RunInternalServerError],
261
+ // The per-credential request budget answers 429 before any handler runs.
262
+ error: [BudgetExceededError, RunNotFoundError, RunInternalServerError],
262
263
  }))
263
264
  .add(HttpApiEndpoint.patch("updateRun", "/:runId", {
264
265
  params: runIdParams,
@@ -406,6 +406,12 @@ export declare const workspaceCaptureStatusSchema: Schema.Struct<{
406
406
  readonly runningMs: Schema.Number;
407
407
  readonly boundMs: Schema.Number;
408
408
  }>>;
409
+ /**
410
+ * The executor booted under an owner map (`SEALANT_CAPTURE_OWNER_MAP`): its restores give each
411
+ * mapped person's saved directory to them and the worktree to the group. Absent from a control
412
+ * plane that predates it; false from a daemon that predates it.
413
+ */
414
+ readonly ownerMap: Schema.optional<Schema.Boolean>;
409
415
  }>;
410
416
  export type WorkspaceCaptureStatus = typeof workspaceCaptureStatusSchema.Type;
411
417
  /**
@@ -417,6 +423,21 @@ export type WorkspaceCaptureStatus = typeof workspaceCaptureStatusSchema.Type;
417
423
  */
418
424
  export declare const replanWorkspaceCaptureRequestSchema: Schema.Struct<{
419
425
  readonly ownerUserId: Schema.String;
426
+ /**
427
+ * The owner map the claim needs (a capture source's `ownerMap`), or `null` for none. The daemon
428
+ * reads its map only at boot, so a re-plan restores under the map the executor was launched
429
+ * with: when this is given and differs from that map (including a standby launched with none),
430
+ * the re-plan is refused with `409` code `owner-map-mismatch` before the daemon is reached.
431
+ * Omitted, nothing is compared.
432
+ */
433
+ readonly expectedOwnerMap: Schema.optional<Schema.NullOr<Schema.Struct<{
434
+ readonly gid: Schema.Number;
435
+ readonly worktreeUid: Schema.Number;
436
+ readonly people: Schema.$Array<Schema.Struct<{
437
+ readonly id: Schema.String;
438
+ readonly uid: Schema.Number;
439
+ }>>;
440
+ }>>>;
420
441
  }>;
421
442
  export type ReplanWorkspaceCaptureRequest = typeof replanWorkspaceCaptureRequestSchema.Type;
422
443
  export declare const workspaceCaptureReplannedSchema: Schema.Struct<{
@@ -1705,6 +1726,12 @@ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", Ht
1705
1726
  readonly runningMs: Schema.Number;
1706
1727
  readonly boundMs: Schema.Number;
1707
1728
  }>>;
1729
+ /**
1730
+ * The executor booted under an owner map (`SEALANT_CAPTURE_OWNER_MAP`): its restores give each
1731
+ * mapped person's saved directory to them and the worktree to the group. Absent from a control
1732
+ * plane that predates it; false from a daemon that predates it.
1733
+ */
1734
+ readonly ownerMap: Schema.optional<Schema.Boolean>;
1708
1735
  }>>, HttpApiEndpoint.Json<typeof WorkspaceBadRequestError | typeof WorkspaceNotFoundError | typeof WorkspaceConflictError | typeof WorkspaceInternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"getWorkspace", "GET", "/:workspaceId", HttpApiEndpoint.StringTree<Schema.Struct<{
1709
1736
  readonly workspaceId: Schema.String;
1710
1737
  }>>, HttpApiEndpoint.StringTree<Schema.Struct<{
@@ -1956,6 +1983,12 @@ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", Ht
1956
1983
  readonly runningMs: Schema.Number;
1957
1984
  readonly boundMs: Schema.Number;
1958
1985
  }>>;
1986
+ /**
1987
+ * The executor booted under an owner map (`SEALANT_CAPTURE_OWNER_MAP`): its restores give each
1988
+ * mapped person's saved directory to them and the worktree to the group. Absent from a control
1989
+ * plane that predates it; false from a daemon that predates it.
1990
+ */
1991
+ readonly ownerMap: Schema.optional<Schema.Boolean>;
1959
1992
  }>>, HttpApiEndpoint.Json<typeof WorkspaceBadRequestError | typeof WorkspaceNotFoundError | typeof WorkspaceConflictError | typeof WorkspaceInternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"getWorkspaceCreate", "GET", "/idempotency-keys/:idempotencyKey", HttpApiEndpoint.StringTree<Schema.Struct<{
1960
1993
  readonly idempotencyKey: Schema.String;
1961
1994
  }>>, HttpApiEndpoint.StringTree<Schema.Struct<{
@@ -2290,6 +2323,21 @@ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", Ht
2290
2323
  readonly workspaceId: Schema.String;
2291
2324
  }>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
2292
2325
  readonly ownerUserId: Schema.String;
2326
+ /**
2327
+ * The owner map the claim needs (a capture source's `ownerMap`), or `null` for none. The daemon
2328
+ * reads its map only at boot, so a re-plan restores under the map the executor was launched
2329
+ * with: when this is given and differs from that map (including a standby launched with none),
2330
+ * the re-plan is refused with `409` code `owner-map-mismatch` before the daemon is reached.
2331
+ * Omitted, nothing is compared.
2332
+ */
2333
+ readonly expectedOwnerMap: Schema.optional<Schema.NullOr<Schema.Struct<{
2334
+ readonly gid: Schema.Number;
2335
+ readonly worktreeUid: Schema.Number;
2336
+ readonly people: Schema.$Array<Schema.Struct<{
2337
+ readonly id: Schema.String;
2338
+ readonly uid: Schema.Number;
2339
+ }>>;
2340
+ }>>>;
2293
2341
  }>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
2294
2342
  readonly worktreeId: Schema.String;
2295
2343
  readonly epoch: Schema.Number;
@@ -357,6 +357,12 @@ export const workspaceCaptureStatusSchema = Schema.Struct({
357
357
  * past its bound, and from a daemon or control plane that predates it.
358
358
  */
359
359
  overdue: Schema.optional(captureOverdueSchema),
360
+ /**
361
+ * The executor booted under an owner map (`SEALANT_CAPTURE_OWNER_MAP`): its restores give each
362
+ * mapped person's saved directory to them and the worktree to the group. Absent from a control
363
+ * plane that predates it; false from a daemon that predates it.
364
+ */
365
+ ownerMap: Schema.optional(Schema.Boolean),
360
366
  });
361
367
  /**
362
368
  * Re-plan a capture-sourced workspace (sealantd 0.15 `capture.replan`, the claim hook): the daemon
@@ -367,6 +373,18 @@ export const workspaceCaptureStatusSchema = Schema.Struct({
367
373
  */
368
374
  export const replanWorkspaceCaptureRequestSchema = Schema.Struct({
369
375
  ownerUserId: NonEmptyString,
376
+ /**
377
+ * The owner map the claim needs (a capture source's `ownerMap`), or `null` for none. The daemon
378
+ * reads its map only at boot, so a re-plan restores under the map the executor was launched
379
+ * with: when this is given and differs from that map (including a standby launched with none),
380
+ * the re-plan is refused with `409` code `owner-map-mismatch` before the daemon is reached.
381
+ * Omitted, nothing is compared.
382
+ */
383
+ expectedOwnerMap: Schema.optional(Schema.NullOr(Schema.Struct({
384
+ gid: Schema.Number,
385
+ worktreeUid: Schema.Number,
386
+ people: Schema.Array(Schema.Struct({ id: Schema.String, uid: Schema.Number })),
387
+ }))),
370
388
  });
371
389
  export const workspaceCaptureReplannedSchema = Schema.Struct({
372
390
  worktreeId: NonEmptyString,
package/dist/index.d.ts CHANGED
@@ -16,3 +16,4 @@ export * from "./core-api/ssh-keys.js";
16
16
  export * from "./core-api/system.js";
17
17
  export * from "./core-api/users.js";
18
18
  export * from "./workspace-environment.js";
19
+ export * from "./capture-owner-map.js";
package/dist/index.js CHANGED
@@ -16,3 +16,4 @@ export * from "./core-api/ssh-keys.js";
16
16
  export * from "./core-api/system.js";
17
17
  export * from "./core-api/users.js";
18
18
  export * from "./workspace-environment.js";
19
+ export * from "./capture-owner-map.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealant/api-contracts-next",
3
- "version": "0.39.0-next.694",
3
+ "version": "0.39.0-next.696",
4
4
  "description": "Contract-first HTTP API definitions for Sealant services",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -17,6 +17,10 @@
17
17
  "./workspace-environment": {
18
18
  "types": "./dist/workspace-environment.d.ts",
19
19
  "import": "./dist/workspace-environment.js"
20
+ },
21
+ "./capture-owner-map": {
22
+ "types": "./dist/capture-owner-map.d.ts",
23
+ "import": "./dist/capture-owner-map.js"
20
24
  }
21
25
  },
22
26
  "files": [
@@ -28,7 +32,7 @@
28
32
  "peerDependencies": {
29
33
  "effect": ">=4.0.0-beta.85 <5"
30
34
  },
31
- "gitHead": "a8b2d5ce98ebd6c83e059122320f7384fab3f233",
35
+ "gitHead": "55326dd1cfb131e4324cecc5cd896f5bee35980c",
32
36
  "publishConfig": {
33
37
  "access": "public"
34
38
  }