@sealant/api-contracts-next 0.39.0-next.695 → 0.39.0-next.700
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/capture-owner-map.d.ts +66 -0
- package/dist/capture-owner-map.js +104 -0
- package/dist/core-api/control-plane.d.ts +80 -1
- package/dist/core-api/workspaces.d.ts +355 -5
- package/dist/core-api/workspaces.js +191 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +6 -2
|
@@ -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
|
+
};
|
|
@@ -855,7 +855,52 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
|
|
|
855
855
|
readonly email: import("effect/Schema").String;
|
|
856
856
|
readonly name: import("effect/Schema").String;
|
|
857
857
|
readonly createdAt: import("effect/Schema").String;
|
|
858
|
-
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./users.js").UserNotFoundError | typeof import("./users.js").UserInternalServerError>, never, never>, false> | import("effect/unstable/httpapi/HttpApiGroup").HttpApiGroup<"workspaces", import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"
|
|
858
|
+
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./users.js").UserNotFoundError | typeof import("./users.js").UserInternalServerError>, never, never>, false> | import("effect/unstable/httpapi/HttpApiGroup").HttpApiGroup<"workspaces", import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"applyWorkspaceDotfiles", "POST", "/v1/workspaces/:workspaceId/dotfiles", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
|
|
859
|
+
readonly workspaceId: import("effect/Schema").String;
|
|
860
|
+
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
|
|
861
|
+
readonly ownerUserId: import("effect/Schema").String;
|
|
862
|
+
readonly onBehalfOfUserId: import("effect/Schema").String;
|
|
863
|
+
readonly user: import("effect/Schema").String;
|
|
864
|
+
readonly home: import("effect/Schema").String;
|
|
865
|
+
readonly repository: import("effect/Schema").optional<import("effect/Schema").Struct<{
|
|
866
|
+
readonly url: import("effect/Schema").String;
|
|
867
|
+
readonly ref: import("effect/Schema").optional<import("effect/Schema").String>;
|
|
868
|
+
readonly manager: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
|
|
869
|
+
readonly bootstrap: import("effect/Schema").optional<import("effect/Schema").Boolean>;
|
|
870
|
+
readonly bootstrapCommand: import("effect/Schema").optional<import("effect/Schema").String>;
|
|
871
|
+
}>>;
|
|
872
|
+
readonly archives: import("effect/Schema").optional<import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
873
|
+
readonly data: import("effect/Schema").String;
|
|
874
|
+
readonly manager: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
|
|
875
|
+
readonly target: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["home", "config"]>>;
|
|
876
|
+
readonly bootstrap: import("effect/Schema").optional<import("effect/Schema").Boolean>;
|
|
877
|
+
readonly bootstrapCommand: import("effect/Schema").optional<import("effect/Schema").String>;
|
|
878
|
+
}>>>;
|
|
879
|
+
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
|
|
880
|
+
readonly runId: import("effect/Schema").String;
|
|
881
|
+
readonly workspaceId: import("effect/Schema").String;
|
|
882
|
+
readonly attemptId: import("effect/Schema").optional<import("effect/Schema").String>;
|
|
883
|
+
readonly ownerUserId: import("effect/Schema").String;
|
|
884
|
+
readonly harnessId: import("effect/Schema").String;
|
|
885
|
+
readonly mode: import("effect/Schema").Literals<readonly ["one-shot", "interactive"]>;
|
|
886
|
+
readonly status: import("effect/Schema").Literals<readonly ["queued", "running", "completed", "failed", "cancelled"]>;
|
|
887
|
+
readonly prompt: import("effect/Schema").optional<import("effect/Schema").String>;
|
|
888
|
+
readonly command: import("effect/Schema").optional<import("effect/Schema").Struct<{
|
|
889
|
+
readonly executable: import("effect/Schema").String;
|
|
890
|
+
readonly args: import("effect/Schema").$Array<import("effect/Schema").String>;
|
|
891
|
+
readonly cwd: import("effect/Schema").optional<import("effect/Schema").String>;
|
|
892
|
+
readonly argCount: import("effect/Schema").optional<import("effect/Schema").Number>;
|
|
893
|
+
readonly argLengths: import("effect/Schema").optional<import("effect/Schema").$Array<import("effect/Schema").Number>>;
|
|
894
|
+
}>>;
|
|
895
|
+
readonly metadata: import("effect/Schema").optional<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Unknown>>;
|
|
896
|
+
readonly exitCode: import("effect/Schema").optional<import("effect/Schema").Number>;
|
|
897
|
+
readonly errorMessage: import("effect/Schema").optional<import("effect/Schema").String>;
|
|
898
|
+
readonly startedAt: import("effect/Schema").optional<import("effect/Schema").String>;
|
|
899
|
+
readonly finishedAt: import("effect/Schema").optional<import("effect/Schema").String>;
|
|
900
|
+
readonly recordDeletedAt: import("effect/Schema").optional<import("effect/Schema").String>;
|
|
901
|
+
readonly createdAt: import("effect/Schema").String;
|
|
902
|
+
readonly updatedAt: import("effect/Schema").String;
|
|
903
|
+
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./workspaces.js").WorkspaceBadRequestError | typeof import("./workspaces.js").WorkspaceForbiddenError | typeof import("./workspaces.js").WorkspaceNotFoundError | typeof import("./workspaces.js").WorkspaceConflictError | typeof import("./workspaces.js").WorkspaceBadGatewayError | typeof import("./workspaces.js").WorkspaceServiceUnavailableError | typeof import("./workspaces.js").WorkspaceInternalServerError>, never, never> | import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"bindWorkspace", "POST", "/v1/workspaces/:workspaceId/bind", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
|
|
859
904
|
readonly workspaceId: import("effect/Schema").String;
|
|
860
905
|
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
|
|
861
906
|
readonly ownerUserId: import("effect/Schema").String;
|
|
@@ -1026,6 +1071,7 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
|
|
|
1026
1071
|
readonly runningMs: import("effect/Schema").Number;
|
|
1027
1072
|
readonly boundMs: import("effect/Schema").Number;
|
|
1028
1073
|
}>>;
|
|
1074
|
+
readonly ownerMap: import("effect/Schema").optional<import("effect/Schema").Boolean>;
|
|
1029
1075
|
}>>, 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
1076
|
readonly workspaceId: import("effect/Schema").String;
|
|
1031
1077
|
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
|
|
@@ -1164,6 +1210,7 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
|
|
|
1164
1210
|
readonly runningMs: import("effect/Schema").Number;
|
|
1165
1211
|
readonly boundMs: import("effect/Schema").Number;
|
|
1166
1212
|
}>>;
|
|
1213
|
+
readonly ownerMap: import("effect/Schema").optional<import("effect/Schema").Boolean>;
|
|
1167
1214
|
}>>, 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
1215
|
readonly idempotencyKey: import("effect/Schema").String;
|
|
1169
1216
|
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
|
|
@@ -1284,6 +1331,14 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
|
|
|
1284
1331
|
readonly connectedAccountId: import("effect/Schema").String;
|
|
1285
1332
|
readonly name: import("effect/Schema").String;
|
|
1286
1333
|
}>>;
|
|
1334
|
+
readonly pi: import("effect/Schema").optional<import("effect/Schema").Struct<{
|
|
1335
|
+
readonly connectedAccountId: import("effect/Schema").String;
|
|
1336
|
+
readonly name: import("effect/Schema").String;
|
|
1337
|
+
}>>;
|
|
1338
|
+
readonly opencode: import("effect/Schema").optional<import("effect/Schema").Struct<{
|
|
1339
|
+
readonly connectedAccountId: import("effect/Schema").String;
|
|
1340
|
+
readonly name: import("effect/Schema").String;
|
|
1341
|
+
}>>;
|
|
1287
1342
|
}>;
|
|
1288
1343
|
}>>;
|
|
1289
1344
|
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./workspaces.js").WorkspaceForbiddenError | typeof import("./workspaces.js").WorkspaceNotFoundError | typeof import("./workspaces.js").WorkspaceConflictError | typeof import("./workspaces.js").WorkspaceInternalServerError>, never, never> | import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"listWorkspaceEvents", "GET", "/v1/workspaces/:workspaceId/events", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
|
|
@@ -1358,6 +1413,9 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
|
|
|
1358
1413
|
readonly claude: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").String>>;
|
|
1359
1414
|
readonly codex: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").String>>;
|
|
1360
1415
|
readonly github: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").String>>;
|
|
1416
|
+
readonly pi: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").String>>;
|
|
1417
|
+
readonly opencode: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").String>>;
|
|
1418
|
+
readonly partial: import("effect/Schema").optional<import("effect/Schema").Boolean>;
|
|
1361
1419
|
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
|
|
1362
1420
|
readonly workspaceId: import("effect/Schema").String;
|
|
1363
1421
|
readonly runId: import("effect/Schema").String;
|
|
@@ -1377,8 +1435,21 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
|
|
|
1377
1435
|
readonly connectedAccountId: import("effect/Schema").String;
|
|
1378
1436
|
readonly name: import("effect/Schema").String;
|
|
1379
1437
|
}>>;
|
|
1438
|
+
readonly pi: import("effect/Schema").optional<import("effect/Schema").Struct<{
|
|
1439
|
+
readonly connectedAccountId: import("effect/Schema").String;
|
|
1440
|
+
readonly name: import("effect/Schema").String;
|
|
1441
|
+
}>>;
|
|
1442
|
+
readonly opencode: import("effect/Schema").optional<import("effect/Schema").Struct<{
|
|
1443
|
+
readonly connectedAccountId: import("effect/Schema").String;
|
|
1444
|
+
readonly name: import("effect/Schema").String;
|
|
1445
|
+
}>>;
|
|
1380
1446
|
}>;
|
|
1381
1447
|
}>;
|
|
1448
|
+
readonly skipped: import("effect/Schema").optional<import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
1449
|
+
readonly provider: import("effect/Schema").Literals<readonly ["claude", "codex", "github", "pi", "opencode"]>;
|
|
1450
|
+
readonly code: import("effect/Schema").Literals<readonly ["connected-account-missing", "connected-account-invalid", "connected-account-unsupported", "login-file-unusable"]>;
|
|
1451
|
+
readonly message: import("effect/Schema").String;
|
|
1452
|
+
}>>>;
|
|
1382
1453
|
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./workspaces.js").WorkspaceBadRequestError | typeof import("./workspaces.js").WorkspaceForbiddenError | typeof import("./workspaces.js").WorkspaceNotFoundError | typeof import("./workspaces.js").WorkspaceConflictError | typeof import("./workspaces.js").WorkspaceBadGatewayError | typeof import("./workspaces.js").WorkspaceServiceUnavailableError | typeof import("./workspaces.js").WorkspaceInternalServerError>, never, never> | import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"recoverWorkspace", "POST", "/v1/workspaces/:workspaceId/recover", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
|
|
1383
1454
|
readonly workspaceId: import("effect/Schema").String;
|
|
1384
1455
|
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
|
|
@@ -1410,6 +1481,14 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
|
|
|
1410
1481
|
readonly workspaceId: import("effect/Schema").String;
|
|
1411
1482
|
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
|
|
1412
1483
|
readonly ownerUserId: import("effect/Schema").String;
|
|
1484
|
+
readonly expectedOwnerMap: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").Struct<{
|
|
1485
|
+
readonly gid: import("effect/Schema").Number;
|
|
1486
|
+
readonly worktreeUid: import("effect/Schema").Number;
|
|
1487
|
+
readonly people: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
1488
|
+
readonly id: import("effect/Schema").String;
|
|
1489
|
+
readonly uid: import("effect/Schema").Number;
|
|
1490
|
+
}>>;
|
|
1491
|
+
}>>>;
|
|
1413
1492
|
}>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
|
|
1414
1493
|
readonly worktreeId: import("effect/Schema").String;
|
|
1415
1494
|
readonly epoch: import("effect/Schema").Number;
|
|
@@ -186,6 +186,95 @@ export declare const execWorkspaceRequestSchema: Schema.Struct<{
|
|
|
186
186
|
readonly user: Schema.optional<Schema.String>;
|
|
187
187
|
}>;
|
|
188
188
|
export type ExecWorkspaceRequest = typeof execWorkspaceRequestSchema.Type;
|
|
189
|
+
/** The `harnessId` stamped on runs that apply a person's dotfiles (`applyWorkspaceDotfiles`). */
|
|
190
|
+
export declare const dotfilesRunHarnessId = "dotfiles";
|
|
191
|
+
/** How a dotfiles tree is applied: chezmoi and stow layouts are detected by `auto`. */
|
|
192
|
+
export declare const workspaceDotfilesManagerSchema: Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>;
|
|
193
|
+
/**
|
|
194
|
+
* A dotfiles repository cloned as the person (no credential reaches the workspace: a repository
|
|
195
|
+
* only the person's own identity can reach is resolved by the caller and sent as an archive).
|
|
196
|
+
*/
|
|
197
|
+
export declare const applyWorkspaceDotfilesRepositorySchema: Schema.Struct<{
|
|
198
|
+
/** An `https://` clone URL with no credential in it (no `user:token@`). */
|
|
199
|
+
readonly url: Schema.String;
|
|
200
|
+
/** Branch or tag; omitted clones the remote's default branch. */
|
|
201
|
+
readonly ref: Schema.optional<Schema.String>;
|
|
202
|
+
readonly manager: Schema.optional<Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
|
|
203
|
+
/** Run the bootstrap command after applying (when the checkout has it). Default true. */
|
|
204
|
+
readonly bootstrap: Schema.optional<Schema.Boolean>;
|
|
205
|
+
/** Relative to the checkout; default `./install.sh`. */
|
|
206
|
+
readonly bootstrapCommand: Schema.optional<Schema.String>;
|
|
207
|
+
}>;
|
|
208
|
+
/** A caller-resolved dotfiles tree, as at create (`spec.runtime.dotfilesArchives`). */
|
|
209
|
+
export declare const applyWorkspaceDotfilesArchiveSchema: Schema.Struct<{
|
|
210
|
+
/** base64 of a `.tar.gz`; at most 6 MiB of base64 (about 4 MiB decoded). */
|
|
211
|
+
readonly data: Schema.String;
|
|
212
|
+
readonly manager: Schema.optional<Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
|
|
213
|
+
/** Where the tree lands: `home` (default) or `config` (`~/.config`, copy manager only). */
|
|
214
|
+
readonly target: Schema.optional<Schema.Literals<readonly ["home", "config"]>>;
|
|
215
|
+
/** Run `./install.sh` (or `bootstrapCommand`) after applying when present. Default true. */
|
|
216
|
+
readonly bootstrap: Schema.optional<Schema.Boolean>;
|
|
217
|
+
readonly bootstrapCommand: Schema.optional<Schema.String>;
|
|
218
|
+
}>;
|
|
219
|
+
/**
|
|
220
|
+
* Apply a person's dotfiles into their home of a RUNNING workspace, as their Linux user
|
|
221
|
+
* (docs/connected-accounts-design.md §6g; Mend's ADR 0016 decision 11): the same sources as a
|
|
222
|
+
* create's dotfiles (a repository, archives, or both; the repository first, archives after in
|
|
223
|
+
* order) and the same applier (sealantd's: chezmoi, stow or copy, then each tree's bootstrap).
|
|
224
|
+
*
|
|
225
|
+
* `user` must exist and must not be root or in root's group, and `home` must be its passwd home: an
|
|
226
|
+
* existing directory of the user's, reached without a symbolic link. The clone, chezmoi, stow and the
|
|
227
|
+
* bootstrap run as the user; sealantd's archive staging and `copy` manager still run as root inside
|
|
228
|
+
* the home and follow links the person planted there, until sealantd's fix for it lands (see
|
|
229
|
+
* docs/connected-accounts-design.md §6g). `onBehalfOfUserId` names whose dotfiles they are: it is
|
|
230
|
+
* recorded on the run, and a home whose logins another person holds is refused (`home-held`). Answered `202` with a run (`harnessId`
|
|
231
|
+
* `dotfiles`) once the archives are staged: the run's first `processStarted` is the bootstrap,
|
|
232
|
+
* started once every file is applied; its output and exit are the run's. The run completes with the
|
|
233
|
+
* bootstrap's exit code (0 when no tree had one) and fails, with the daemon's words, when the apply
|
|
234
|
+
* was refused or the bootstrap ran past 30 minutes (it is stopped).
|
|
235
|
+
*/
|
|
236
|
+
export declare const applyWorkspaceDotfilesRequestSchema: Schema.Struct<{
|
|
237
|
+
/** The workspace's owner (owner scope; uniform 404 otherwise). */
|
|
238
|
+
readonly ownerUserId: Schema.String;
|
|
239
|
+
/** The Sealant user whose dotfiles these are: recorded on the run (`metadata.dotfiles`). */
|
|
240
|
+
readonly onBehalfOfUserId: Schema.String;
|
|
241
|
+
/** The Linux user to apply as (a login name or a uid); never root. */
|
|
242
|
+
readonly user: Schema.String;
|
|
243
|
+
/** The user's passwd home: absolute, normalised, never under `/workspace`. */
|
|
244
|
+
readonly home: Schema.String;
|
|
245
|
+
readonly repository: Schema.optional<Schema.Struct<{
|
|
246
|
+
/** An `https://` clone URL with no credential in it (no `user:token@`). */
|
|
247
|
+
readonly url: Schema.String;
|
|
248
|
+
/** Branch or tag; omitted clones the remote's default branch. */
|
|
249
|
+
readonly ref: Schema.optional<Schema.String>;
|
|
250
|
+
readonly manager: Schema.optional<Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
|
|
251
|
+
/** Run the bootstrap command after applying (when the checkout has it). Default true. */
|
|
252
|
+
readonly bootstrap: Schema.optional<Schema.Boolean>;
|
|
253
|
+
/** Relative to the checkout; default `./install.sh`. */
|
|
254
|
+
readonly bootstrapCommand: Schema.optional<Schema.String>;
|
|
255
|
+
}>>;
|
|
256
|
+
readonly archives: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
257
|
+
/** base64 of a `.tar.gz`; at most 6 MiB of base64 (about 4 MiB decoded). */
|
|
258
|
+
readonly data: Schema.String;
|
|
259
|
+
readonly manager: Schema.optional<Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
|
|
260
|
+
/** Where the tree lands: `home` (default) or `config` (`~/.config`, copy manager only). */
|
|
261
|
+
readonly target: Schema.optional<Schema.Literals<readonly ["home", "config"]>>;
|
|
262
|
+
/** Run `./install.sh` (or `bootstrapCommand`) after applying when present. Default true. */
|
|
263
|
+
readonly bootstrap: Schema.optional<Schema.Boolean>;
|
|
264
|
+
readonly bootstrapCommand: Schema.optional<Schema.String>;
|
|
265
|
+
}>>>;
|
|
266
|
+
}>;
|
|
267
|
+
export type ApplyWorkspaceDotfilesRequest = typeof applyWorkspaceDotfilesRequestSchema.Type;
|
|
268
|
+
/**
|
|
269
|
+
* Stable `code`s of the `WorkspaceConflictError` a dotfiles apply answers, nothing applied:
|
|
270
|
+
* `workspace-not-running` (no ready executor), `dotfiles-user-unsupported` (the workspace's
|
|
271
|
+
* sealantd cannot apply dotfiles as a user), `user-unknown` (no such user: make it first),
|
|
272
|
+
* `user-root` (the user is root or in root's group), `home-mismatch` (the home is not the user's
|
|
273
|
+
* passwd home), `home-unusable` (the home is missing, not a directory, not the user's, or reached
|
|
274
|
+
* through a symbolic link), `home-held` (another person's logins are held in the home).
|
|
275
|
+
*/
|
|
276
|
+
export declare const workspaceDotfilesConflictCodes: readonly ["workspace-not-running", "dotfiles-user-unsupported", "user-unknown", "user-root", "home-mismatch", "home-unusable", "home-held"];
|
|
277
|
+
export type WorkspaceDotfilesConflictCode = (typeof workspaceDotfilesConflictCodes)[number];
|
|
189
278
|
/**
|
|
190
279
|
* Bind a standby workspace's working directory, or a bindable extra mount, to one subdirectory of
|
|
191
280
|
* its root (sealantd ADR-0014). `mountPath` defaults to the working directory; an empty `subpath`
|
|
@@ -406,6 +495,12 @@ export declare const workspaceCaptureStatusSchema: Schema.Struct<{
|
|
|
406
495
|
readonly runningMs: Schema.Number;
|
|
407
496
|
readonly boundMs: Schema.Number;
|
|
408
497
|
}>>;
|
|
498
|
+
/**
|
|
499
|
+
* The executor booted under an owner map (`SEALANT_CAPTURE_OWNER_MAP`): its restores give each
|
|
500
|
+
* mapped person's saved directory to them and the worktree to the group. Absent from a control
|
|
501
|
+
* plane that predates it; false from a daemon that predates it.
|
|
502
|
+
*/
|
|
503
|
+
readonly ownerMap: Schema.optional<Schema.Boolean>;
|
|
409
504
|
}>;
|
|
410
505
|
export type WorkspaceCaptureStatus = typeof workspaceCaptureStatusSchema.Type;
|
|
411
506
|
/**
|
|
@@ -417,6 +512,21 @@ export type WorkspaceCaptureStatus = typeof workspaceCaptureStatusSchema.Type;
|
|
|
417
512
|
*/
|
|
418
513
|
export declare const replanWorkspaceCaptureRequestSchema: Schema.Struct<{
|
|
419
514
|
readonly ownerUserId: Schema.String;
|
|
515
|
+
/**
|
|
516
|
+
* The owner map the claim needs (a capture source's `ownerMap`), or `null` for none. The daemon
|
|
517
|
+
* reads its map only at boot, so a re-plan restores under the map the executor was launched
|
|
518
|
+
* with: when this is given and differs from that map (including a standby launched with none),
|
|
519
|
+
* the re-plan is refused with `409` code `owner-map-mismatch` before the daemon is reached.
|
|
520
|
+
* Omitted, nothing is compared.
|
|
521
|
+
*/
|
|
522
|
+
readonly expectedOwnerMap: Schema.optional<Schema.NullOr<Schema.Struct<{
|
|
523
|
+
readonly gid: Schema.Number;
|
|
524
|
+
readonly worktreeUid: Schema.Number;
|
|
525
|
+
readonly people: Schema.$Array<Schema.Struct<{
|
|
526
|
+
readonly id: Schema.String;
|
|
527
|
+
readonly uid: Schema.Number;
|
|
528
|
+
}>>;
|
|
529
|
+
}>>>;
|
|
420
530
|
}>;
|
|
421
531
|
export type ReplanWorkspaceCaptureRequest = typeof replanWorkspaceCaptureRequestSchema.Type;
|
|
422
532
|
export declare const workspaceCaptureReplannedSchema: Schema.Struct<{
|
|
@@ -597,9 +707,26 @@ export declare const inspectWorkspaceImageResponseSchema: Schema.Struct<{
|
|
|
597
707
|
}>;
|
|
598
708
|
}>;
|
|
599
709
|
export type InspectWorkspaceImageResponse = typeof inspectWorkspaceImageResponseSchema.Type;
|
|
600
|
-
/**
|
|
601
|
-
|
|
710
|
+
/**
|
|
711
|
+
* Stable codes of a refused connected account, carried with the account's `provider` by the error
|
|
712
|
+
* that refuses it (a create's or a credentials put's): `connected-account-missing`
|
|
713
|
+
* (`WorkspaceNotFoundError`, 404: the person has no such account, or it is archived or someone
|
|
714
|
+
* else's) and `connected-account-invalid` (`WorkspaceConflictError`, 409: the account is marked
|
|
715
|
+
* invalid, or holds an unusable credential; reconnect it) and `connected-account-unsupported`
|
|
716
|
+
* (`WorkspaceConflictError`, 409: the account cannot serve what was asked, as a Codex API key cannot
|
|
717
|
+
* be pi's or opencode's ChatGPT login). Nothing was written either way.
|
|
718
|
+
*/
|
|
719
|
+
export declare const connectedAccountRefusalCodes: readonly ["connected-account-missing", "connected-account-invalid", "connected-account-unsupported"];
|
|
720
|
+
export type ConnectedAccountRefusalCode = (typeof connectedAccountRefusalCodes)[number];
|
|
721
|
+
/**
|
|
722
|
+
* The providers a home holds logins for (docs/connected-accounts-design.md §6c). `pi` and
|
|
723
|
+
* `opencode` are the person's ChatGPT login (a Codex connected account), written into each tool's
|
|
724
|
+
* own `auth.json`.
|
|
725
|
+
*/
|
|
726
|
+
export declare const workspaceCredentialHomeProviders: readonly ["claude", "codex", "github", "pi", "opencode"];
|
|
602
727
|
export type WorkspaceCredentialHomeProvider = (typeof workspaceCredentialHomeProviders)[number];
|
|
728
|
+
/** The connected account's provider each home provider's login is made from. */
|
|
729
|
+
export declare const workspaceCredentialHomeAccountProvider: Readonly<Record<WorkspaceCredentialHomeProvider, "claude" | "codex" | "github">>;
|
|
603
730
|
/**
|
|
604
731
|
* Put one person's logins into one home of a RUNNING workspace (docs/connected-accounts-design.md
|
|
605
732
|
* §6c). A home holds one person's logins while it is held: the first put names its person, a put
|
|
@@ -631,6 +758,25 @@ export declare const putWorkspaceCredentialsRequestSchema: Schema.Struct<{
|
|
|
631
758
|
readonly claude: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
632
759
|
readonly codex: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
633
760
|
readonly github: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
761
|
+
/**
|
|
762
|
+
* pi's ChatGPT login, made from a Codex account of `onBehalfOfUserId` (an id or a Codex account
|
|
763
|
+
* name), as the `openai-codex` entry of `<home>/.pi/agent/auth.json`. The file is merged: its
|
|
764
|
+
* other entries stay, and an `openai-codex` login the person made in pi is never replaced.
|
|
765
|
+
*/
|
|
766
|
+
readonly pi: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
767
|
+
/**
|
|
768
|
+
* opencode's ChatGPT login, made the same way, as the `openai` entry of opencode's `auth.json`
|
|
769
|
+
* (`<home>/.local/share/opencode/auth.json`, followed through links to where it really is, which
|
|
770
|
+
* must be inside the home). A login the person made in opencode is never replaced or removed.
|
|
771
|
+
*/
|
|
772
|
+
readonly opencode: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
773
|
+
/**
|
|
774
|
+
* Write what the person has connected and leave out the rest: a provider whose account is refused
|
|
775
|
+
* (`connected-account-missing`, `-invalid` or `-unsupported`) is not written, its login is removed
|
|
776
|
+
* from the home as `null` would remove it, and the answer's `skipped` says which and why, instead
|
|
777
|
+
* of the whole put failing. Every other refusal still fails the put. Default false.
|
|
778
|
+
*/
|
|
779
|
+
readonly partial: Schema.optional<Schema.Boolean>;
|
|
634
780
|
}>;
|
|
635
781
|
export type PutWorkspaceCredentialsRequest = typeof putWorkspaceCredentialsRequestSchema.Type;
|
|
636
782
|
/** One account whose copy a home holds. */
|
|
@@ -662,9 +808,33 @@ export declare const workspaceCredentialHomeSchema: Schema.Struct<{
|
|
|
662
808
|
/** The account's name under its provider, as it is now. */
|
|
663
809
|
readonly name: Schema.String;
|
|
664
810
|
}>>;
|
|
811
|
+
/** The Codex account pi's ChatGPT login is made from. */
|
|
812
|
+
readonly pi: Schema.optional<Schema.Struct<{
|
|
813
|
+
readonly connectedAccountId: Schema.String;
|
|
814
|
+
/** The account's name under its provider, as it is now. */
|
|
815
|
+
readonly name: Schema.String;
|
|
816
|
+
}>>;
|
|
817
|
+
/** The Codex account opencode's ChatGPT login is made from. */
|
|
818
|
+
readonly opencode: Schema.optional<Schema.Struct<{
|
|
819
|
+
readonly connectedAccountId: Schema.String;
|
|
820
|
+
/** The account's name under its provider, as it is now. */
|
|
821
|
+
readonly name: Schema.String;
|
|
822
|
+
}>>;
|
|
665
823
|
}>;
|
|
666
824
|
}>;
|
|
667
825
|
export type WorkspaceCredentialHome = typeof workspaceCredentialHomeSchema.Type;
|
|
826
|
+
/** A provider a partial put left out, and why (its account's refusal). */
|
|
827
|
+
export declare const workspaceCredentialSkipSchema: Schema.Struct<{
|
|
828
|
+
readonly provider: Schema.Literals<readonly ["claude", "codex", "github", "pi", "opencode"]>;
|
|
829
|
+
/**
|
|
830
|
+
* The account's refusal, or `login-file-unusable`: pi's or opencode's `auth.json` in the home
|
|
831
|
+
* cannot be written there (not a regular file, or really outside the home).
|
|
832
|
+
*/
|
|
833
|
+
readonly code: Schema.Literals<readonly ["connected-account-missing", "connected-account-invalid", "connected-account-unsupported", "login-file-unusable"]>;
|
|
834
|
+
/** The refusal's words, as a whole put would have answered them. */
|
|
835
|
+
readonly message: Schema.String;
|
|
836
|
+
}>;
|
|
837
|
+
export type WorkspaceCredentialSkip = typeof workspaceCredentialSkipSchema.Type;
|
|
668
838
|
export declare const putWorkspaceCredentialsResponseSchema: Schema.Struct<{
|
|
669
839
|
readonly workspaceId: Schema.String;
|
|
670
840
|
/** The launch attempt whose executor was written. */
|
|
@@ -691,8 +861,31 @@ export declare const putWorkspaceCredentialsResponseSchema: Schema.Struct<{
|
|
|
691
861
|
/** The account's name under its provider, as it is now. */
|
|
692
862
|
readonly name: Schema.String;
|
|
693
863
|
}>>;
|
|
864
|
+
/** The Codex account pi's ChatGPT login is made from. */
|
|
865
|
+
readonly pi: Schema.optional<Schema.Struct<{
|
|
866
|
+
readonly connectedAccountId: Schema.String;
|
|
867
|
+
/** The account's name under its provider, as it is now. */
|
|
868
|
+
readonly name: Schema.String;
|
|
869
|
+
}>>;
|
|
870
|
+
/** The Codex account opencode's ChatGPT login is made from. */
|
|
871
|
+
readonly opencode: Schema.optional<Schema.Struct<{
|
|
872
|
+
readonly connectedAccountId: Schema.String;
|
|
873
|
+
/** The account's name under its provider, as it is now. */
|
|
874
|
+
readonly name: Schema.String;
|
|
875
|
+
}>>;
|
|
694
876
|
}>;
|
|
695
877
|
}>;
|
|
878
|
+
/** A partial put's left-out providers, in the order named (empty when all were written). */
|
|
879
|
+
readonly skipped: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
880
|
+
readonly provider: Schema.Literals<readonly ["claude", "codex", "github", "pi", "opencode"]>;
|
|
881
|
+
/**
|
|
882
|
+
* The account's refusal, or `login-file-unusable`: pi's or opencode's `auth.json` in the home
|
|
883
|
+
* cannot be written there (not a regular file, or really outside the home).
|
|
884
|
+
*/
|
|
885
|
+
readonly code: Schema.Literals<readonly ["connected-account-missing", "connected-account-invalid", "connected-account-unsupported", "login-file-unusable"]>;
|
|
886
|
+
/** The refusal's words, as a whole put would have answered them. */
|
|
887
|
+
readonly message: Schema.String;
|
|
888
|
+
}>>>;
|
|
696
889
|
}>;
|
|
697
890
|
export type PutWorkspaceCredentialsResponse = typeof putWorkspaceCredentialsResponseSchema.Type;
|
|
698
891
|
/** Release one home: its login files are removed and its record deleted. */
|
|
@@ -738,6 +931,18 @@ export declare const listWorkspaceCredentialsResponseSchema: Schema.Struct<{
|
|
|
738
931
|
/** The account's name under its provider, as it is now. */
|
|
739
932
|
readonly name: Schema.String;
|
|
740
933
|
}>>;
|
|
934
|
+
/** The Codex account pi's ChatGPT login is made from. */
|
|
935
|
+
readonly pi: Schema.optional<Schema.Struct<{
|
|
936
|
+
readonly connectedAccountId: Schema.String;
|
|
937
|
+
/** The account's name under its provider, as it is now. */
|
|
938
|
+
readonly name: Schema.String;
|
|
939
|
+
}>>;
|
|
940
|
+
/** The Codex account opencode's ChatGPT login is made from. */
|
|
941
|
+
readonly opencode: Schema.optional<Schema.Struct<{
|
|
942
|
+
readonly connectedAccountId: Schema.String;
|
|
943
|
+
/** The account's name under its provider, as it is now. */
|
|
944
|
+
readonly name: Schema.String;
|
|
945
|
+
}>>;
|
|
741
946
|
}>;
|
|
742
947
|
}>>;
|
|
743
948
|
}>;
|
|
@@ -745,13 +950,15 @@ export type ListWorkspaceCredentialsResponse = typeof listWorkspaceCredentialsRe
|
|
|
745
950
|
/**
|
|
746
951
|
* Stable `code`s of the `WorkspaceConflictError` the credential routes answer:
|
|
747
952
|
* `workspace-not-running` (no ready executor), `connected-account-invalid` (a named account is
|
|
748
|
-
* marked invalid: reconnect it
|
|
953
|
+
* marked invalid or unusable: reconnect it; `provider` names its provider),
|
|
954
|
+
* `connected-account-unsupported` (a Codex account named for pi or opencode is not a ChatGPT
|
|
955
|
+
* login), `home-held` (the home holds another person's logins: release it
|
|
749
956
|
* first; `/root` while the launch's own logins are at `$HOME`, or for anyone but the workspace's
|
|
750
957
|
* owner), `home-unusable` (the home does not exist, is not a directory, is reached through a
|
|
751
958
|
* symbolic link, or a login directory in it links outside it), `home-busy` (another write into the
|
|
752
959
|
* home, or too many into this workspace, is still running: nothing was done, try again).
|
|
753
960
|
*/
|
|
754
|
-
export declare const workspaceCredentialsConflictCodes: readonly ["workspace-not-running", "connected-account-invalid", "home-held", "home-unusable", "home-busy"];
|
|
961
|
+
export declare const workspaceCredentialsConflictCodes: readonly ["workspace-not-running", "connected-account-invalid", "connected-account-unsupported", "home-held", "home-unusable", "home-busy"];
|
|
755
962
|
export type WorkspaceCredentialsConflictCode = (typeof workspaceCredentialsConflictCodes)[number];
|
|
756
963
|
export declare const expireWorkspaceRequestSchema: Schema.Struct<{
|
|
757
964
|
readonly ownerUserId: Schema.String;
|
|
@@ -1398,6 +1605,10 @@ export declare class WorkspaceForbiddenError extends WorkspaceForbiddenError_bas
|
|
|
1398
1605
|
}
|
|
1399
1606
|
declare const WorkspaceNotFoundError_base: Schema.Class<WorkspaceNotFoundError, Schema.TaggedStruct<"WorkspaceNotFoundError", {
|
|
1400
1607
|
readonly message: Schema.String;
|
|
1608
|
+
/** A stable reason, where one applies (`connected-account-missing`). */
|
|
1609
|
+
readonly code: Schema.optional<Schema.String>;
|
|
1610
|
+
/** The provider of the connected account the reason is about. */
|
|
1611
|
+
readonly provider: Schema.optional<Schema.Literals<readonly ["claude", "codex", "github"]>>;
|
|
1401
1612
|
}>, import("effect/Cause").YieldableError>;
|
|
1402
1613
|
export declare class WorkspaceNotFoundError extends WorkspaceNotFoundError_base {
|
|
1403
1614
|
}
|
|
@@ -1405,6 +1616,8 @@ declare const WorkspaceConflictError_base: Schema.Class<WorkspaceConflictError,
|
|
|
1405
1616
|
readonly message: Schema.String;
|
|
1406
1617
|
/** A stable reason, where one applies (`create-cancelled`: the create's key was cancelled). */
|
|
1407
1618
|
readonly code: Schema.optional<Schema.String>;
|
|
1619
|
+
/** The provider of the connected account the reason is about (`connected-account-invalid`). */
|
|
1620
|
+
readonly provider: Schema.optional<Schema.Literals<readonly ["claude", "codex", "github"]>>;
|
|
1408
1621
|
}>, import("effect/Cause").YieldableError>;
|
|
1409
1622
|
export declare class WorkspaceConflictError extends WorkspaceConflictError_base {
|
|
1410
1623
|
}
|
|
@@ -1423,7 +1636,63 @@ declare const WorkspaceInternalServerError_base: Schema.Class<WorkspaceInternalS
|
|
|
1423
1636
|
}>, import("effect/Cause").YieldableError>;
|
|
1424
1637
|
export declare class WorkspaceInternalServerError extends WorkspaceInternalServerError_base {
|
|
1425
1638
|
}
|
|
1426
|
-
export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", HttpApiEndpoint.HttpApiEndpoint<"
|
|
1639
|
+
export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", HttpApiEndpoint.HttpApiEndpoint<"applyWorkspaceDotfiles", "POST", "/:workspaceId/dotfiles", HttpApiEndpoint.StringTree<Schema.Struct<{
|
|
1640
|
+
readonly workspaceId: Schema.String;
|
|
1641
|
+
}>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
|
|
1642
|
+
/** The workspace's owner (owner scope; uniform 404 otherwise). */
|
|
1643
|
+
readonly ownerUserId: Schema.String;
|
|
1644
|
+
/** The Sealant user whose dotfiles these are: recorded on the run (`metadata.dotfiles`). */
|
|
1645
|
+
readonly onBehalfOfUserId: Schema.String;
|
|
1646
|
+
/** The Linux user to apply as (a login name or a uid); never root. */
|
|
1647
|
+
readonly user: Schema.String;
|
|
1648
|
+
/** The user's passwd home: absolute, normalised, never under `/workspace`. */
|
|
1649
|
+
readonly home: Schema.String;
|
|
1650
|
+
readonly repository: Schema.optional<Schema.Struct<{
|
|
1651
|
+
/** An `https://` clone URL with no credential in it (no `user:token@`). */
|
|
1652
|
+
readonly url: Schema.String;
|
|
1653
|
+
/** Branch or tag; omitted clones the remote's default branch. */
|
|
1654
|
+
readonly ref: Schema.optional<Schema.String>;
|
|
1655
|
+
readonly manager: Schema.optional<Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
|
|
1656
|
+
/** Run the bootstrap command after applying (when the checkout has it). Default true. */
|
|
1657
|
+
readonly bootstrap: Schema.optional<Schema.Boolean>;
|
|
1658
|
+
/** Relative to the checkout; default `./install.sh`. */
|
|
1659
|
+
readonly bootstrapCommand: Schema.optional<Schema.String>;
|
|
1660
|
+
}>>;
|
|
1661
|
+
readonly archives: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
1662
|
+
/** base64 of a `.tar.gz`; at most 6 MiB of base64 (about 4 MiB decoded). */
|
|
1663
|
+
readonly data: Schema.String;
|
|
1664
|
+
readonly manager: Schema.optional<Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
|
|
1665
|
+
/** Where the tree lands: `home` (default) or `config` (`~/.config`, copy manager only). */
|
|
1666
|
+
readonly target: Schema.optional<Schema.Literals<readonly ["home", "config"]>>;
|
|
1667
|
+
/** Run `./install.sh` (or `bootstrapCommand`) after applying when present. Default true. */
|
|
1668
|
+
readonly bootstrap: Schema.optional<Schema.Boolean>;
|
|
1669
|
+
readonly bootstrapCommand: Schema.optional<Schema.String>;
|
|
1670
|
+
}>>>;
|
|
1671
|
+
}>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
|
|
1672
|
+
readonly runId: Schema.String;
|
|
1673
|
+
readonly workspaceId: Schema.String;
|
|
1674
|
+
readonly attemptId: Schema.optional<Schema.String>;
|
|
1675
|
+
readonly ownerUserId: Schema.String;
|
|
1676
|
+
readonly harnessId: Schema.String;
|
|
1677
|
+
readonly mode: Schema.Literals<readonly ["one-shot", "interactive"]>;
|
|
1678
|
+
readonly status: Schema.Literals<readonly ["queued", "running", "completed", "failed", "cancelled"]>;
|
|
1679
|
+
readonly prompt: Schema.optional<Schema.String>;
|
|
1680
|
+
readonly command: Schema.optional<Schema.Struct<{
|
|
1681
|
+
readonly executable: Schema.String;
|
|
1682
|
+
readonly args: Schema.$Array<Schema.String>;
|
|
1683
|
+
readonly cwd: Schema.optional<Schema.String>;
|
|
1684
|
+
readonly argCount: Schema.optional<Schema.Number>;
|
|
1685
|
+
readonly argLengths: Schema.optional<Schema.$Array<Schema.Number>>;
|
|
1686
|
+
}>>;
|
|
1687
|
+
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
1688
|
+
readonly exitCode: Schema.optional<Schema.Number>;
|
|
1689
|
+
readonly errorMessage: Schema.optional<Schema.String>;
|
|
1690
|
+
readonly startedAt: Schema.optional<Schema.String>;
|
|
1691
|
+
readonly finishedAt: Schema.optional<Schema.String>;
|
|
1692
|
+
readonly recordDeletedAt: Schema.optional<Schema.String>;
|
|
1693
|
+
readonly createdAt: Schema.String;
|
|
1694
|
+
readonly updatedAt: Schema.String;
|
|
1695
|
+
}>>, HttpApiEndpoint.Json<typeof WorkspaceBadRequestError | typeof WorkspaceForbiddenError | typeof WorkspaceNotFoundError | typeof WorkspaceConflictError | typeof WorkspaceBadGatewayError | typeof WorkspaceServiceUnavailableError | typeof WorkspaceInternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"bindWorkspace", "POST", "/:workspaceId/bind", HttpApiEndpoint.StringTree<Schema.Struct<{
|
|
1427
1696
|
readonly workspaceId: Schema.String;
|
|
1428
1697
|
}>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
|
|
1429
1698
|
readonly ownerUserId: Schema.String;
|
|
@@ -1705,6 +1974,12 @@ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", Ht
|
|
|
1705
1974
|
readonly runningMs: Schema.Number;
|
|
1706
1975
|
readonly boundMs: Schema.Number;
|
|
1707
1976
|
}>>;
|
|
1977
|
+
/**
|
|
1978
|
+
* The executor booted under an owner map (`SEALANT_CAPTURE_OWNER_MAP`): its restores give each
|
|
1979
|
+
* mapped person's saved directory to them and the worktree to the group. Absent from a control
|
|
1980
|
+
* plane that predates it; false from a daemon that predates it.
|
|
1981
|
+
*/
|
|
1982
|
+
readonly ownerMap: Schema.optional<Schema.Boolean>;
|
|
1708
1983
|
}>>, HttpApiEndpoint.Json<typeof WorkspaceBadRequestError | typeof WorkspaceNotFoundError | typeof WorkspaceConflictError | typeof WorkspaceInternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"getWorkspace", "GET", "/:workspaceId", HttpApiEndpoint.StringTree<Schema.Struct<{
|
|
1709
1984
|
readonly workspaceId: Schema.String;
|
|
1710
1985
|
}>>, HttpApiEndpoint.StringTree<Schema.Struct<{
|
|
@@ -1956,6 +2231,12 @@ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", Ht
|
|
|
1956
2231
|
readonly runningMs: Schema.Number;
|
|
1957
2232
|
readonly boundMs: Schema.Number;
|
|
1958
2233
|
}>>;
|
|
2234
|
+
/**
|
|
2235
|
+
* The executor booted under an owner map (`SEALANT_CAPTURE_OWNER_MAP`): its restores give each
|
|
2236
|
+
* mapped person's saved directory to them and the worktree to the group. Absent from a control
|
|
2237
|
+
* plane that predates it; false from a daemon that predates it.
|
|
2238
|
+
*/
|
|
2239
|
+
readonly ownerMap: Schema.optional<Schema.Boolean>;
|
|
1959
2240
|
}>>, HttpApiEndpoint.Json<typeof WorkspaceBadRequestError | typeof WorkspaceNotFoundError | typeof WorkspaceConflictError | typeof WorkspaceInternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"getWorkspaceCreate", "GET", "/idempotency-keys/:idempotencyKey", HttpApiEndpoint.StringTree<Schema.Struct<{
|
|
1960
2241
|
readonly idempotencyKey: Schema.String;
|
|
1961
2242
|
}>>, HttpApiEndpoint.StringTree<Schema.Struct<{
|
|
@@ -2121,6 +2402,18 @@ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", Ht
|
|
|
2121
2402
|
/** The account's name under its provider, as it is now. */
|
|
2122
2403
|
readonly name: Schema.String;
|
|
2123
2404
|
}>>;
|
|
2405
|
+
/** The Codex account pi's ChatGPT login is made from. */
|
|
2406
|
+
readonly pi: Schema.optional<Schema.Struct<{
|
|
2407
|
+
readonly connectedAccountId: Schema.String;
|
|
2408
|
+
/** The account's name under its provider, as it is now. */
|
|
2409
|
+
readonly name: Schema.String;
|
|
2410
|
+
}>>;
|
|
2411
|
+
/** The Codex account opencode's ChatGPT login is made from. */
|
|
2412
|
+
readonly opencode: Schema.optional<Schema.Struct<{
|
|
2413
|
+
readonly connectedAccountId: Schema.String;
|
|
2414
|
+
/** The account's name under its provider, as it is now. */
|
|
2415
|
+
readonly name: Schema.String;
|
|
2416
|
+
}>>;
|
|
2124
2417
|
}>;
|
|
2125
2418
|
}>>;
|
|
2126
2419
|
}>>, HttpApiEndpoint.Json<typeof WorkspaceForbiddenError | typeof WorkspaceNotFoundError | typeof WorkspaceConflictError | typeof WorkspaceInternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"listWorkspaceEvents", "GET", "/:workspaceId/events", HttpApiEndpoint.StringTree<Schema.Struct<{
|
|
@@ -2229,6 +2522,25 @@ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", Ht
|
|
|
2229
2522
|
readonly claude: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
2230
2523
|
readonly codex: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
2231
2524
|
readonly github: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
2525
|
+
/**
|
|
2526
|
+
* pi's ChatGPT login, made from a Codex account of `onBehalfOfUserId` (an id or a Codex account
|
|
2527
|
+
* name), as the `openai-codex` entry of `<home>/.pi/agent/auth.json`. The file is merged: its
|
|
2528
|
+
* other entries stay, and an `openai-codex` login the person made in pi is never replaced.
|
|
2529
|
+
*/
|
|
2530
|
+
readonly pi: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
2531
|
+
/**
|
|
2532
|
+
* opencode's ChatGPT login, made the same way, as the `openai` entry of opencode's `auth.json`
|
|
2533
|
+
* (`<home>/.local/share/opencode/auth.json`, followed through links to where it really is, which
|
|
2534
|
+
* must be inside the home). A login the person made in opencode is never replaced or removed.
|
|
2535
|
+
*/
|
|
2536
|
+
readonly opencode: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
2537
|
+
/**
|
|
2538
|
+
* Write what the person has connected and leave out the rest: a provider whose account is refused
|
|
2539
|
+
* (`connected-account-missing`, `-invalid` or `-unsupported`) is not written, its login is removed
|
|
2540
|
+
* from the home as `null` would remove it, and the answer's `skipped` says which and why, instead
|
|
2541
|
+
* of the whole put failing. Every other refusal still fails the put. Default false.
|
|
2542
|
+
*/
|
|
2543
|
+
readonly partial: Schema.optional<Schema.Boolean>;
|
|
2232
2544
|
}>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
|
|
2233
2545
|
readonly workspaceId: Schema.String;
|
|
2234
2546
|
/** The launch attempt whose executor was written. */
|
|
@@ -2255,8 +2567,31 @@ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", Ht
|
|
|
2255
2567
|
/** The account's name under its provider, as it is now. */
|
|
2256
2568
|
readonly name: Schema.String;
|
|
2257
2569
|
}>>;
|
|
2570
|
+
/** The Codex account pi's ChatGPT login is made from. */
|
|
2571
|
+
readonly pi: Schema.optional<Schema.Struct<{
|
|
2572
|
+
readonly connectedAccountId: Schema.String;
|
|
2573
|
+
/** The account's name under its provider, as it is now. */
|
|
2574
|
+
readonly name: Schema.String;
|
|
2575
|
+
}>>;
|
|
2576
|
+
/** The Codex account opencode's ChatGPT login is made from. */
|
|
2577
|
+
readonly opencode: Schema.optional<Schema.Struct<{
|
|
2578
|
+
readonly connectedAccountId: Schema.String;
|
|
2579
|
+
/** The account's name under its provider, as it is now. */
|
|
2580
|
+
readonly name: Schema.String;
|
|
2581
|
+
}>>;
|
|
2258
2582
|
}>;
|
|
2259
2583
|
}>;
|
|
2584
|
+
/** A partial put's left-out providers, in the order named (empty when all were written). */
|
|
2585
|
+
readonly skipped: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
2586
|
+
readonly provider: Schema.Literals<readonly ["claude", "codex", "github", "pi", "opencode"]>;
|
|
2587
|
+
/**
|
|
2588
|
+
* The account's refusal, or `login-file-unusable`: pi's or opencode's `auth.json` in the home
|
|
2589
|
+
* cannot be written there (not a regular file, or really outside the home).
|
|
2590
|
+
*/
|
|
2591
|
+
readonly code: Schema.Literals<readonly ["connected-account-missing", "connected-account-invalid", "connected-account-unsupported", "login-file-unusable"]>;
|
|
2592
|
+
/** The refusal's words, as a whole put would have answered them. */
|
|
2593
|
+
readonly message: Schema.String;
|
|
2594
|
+
}>>>;
|
|
2260
2595
|
}>>, HttpApiEndpoint.Json<typeof WorkspaceBadRequestError | typeof WorkspaceForbiddenError | typeof WorkspaceNotFoundError | typeof WorkspaceConflictError | typeof WorkspaceBadGatewayError | typeof WorkspaceServiceUnavailableError | typeof WorkspaceInternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"recoverWorkspace", "POST", "/:workspaceId/recover", HttpApiEndpoint.StringTree<Schema.Struct<{
|
|
2261
2596
|
readonly workspaceId: Schema.String;
|
|
2262
2597
|
}>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
|
|
@@ -2290,6 +2625,21 @@ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", Ht
|
|
|
2290
2625
|
readonly workspaceId: Schema.String;
|
|
2291
2626
|
}>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
|
|
2292
2627
|
readonly ownerUserId: Schema.String;
|
|
2628
|
+
/**
|
|
2629
|
+
* The owner map the claim needs (a capture source's `ownerMap`), or `null` for none. The daemon
|
|
2630
|
+
* reads its map only at boot, so a re-plan restores under the map the executor was launched
|
|
2631
|
+
* with: when this is given and differs from that map (including a standby launched with none),
|
|
2632
|
+
* the re-plan is refused with `409` code `owner-map-mismatch` before the daemon is reached.
|
|
2633
|
+
* Omitted, nothing is compared.
|
|
2634
|
+
*/
|
|
2635
|
+
readonly expectedOwnerMap: Schema.optional<Schema.NullOr<Schema.Struct<{
|
|
2636
|
+
readonly gid: Schema.Number;
|
|
2637
|
+
readonly worktreeUid: Schema.Number;
|
|
2638
|
+
readonly people: Schema.$Array<Schema.Struct<{
|
|
2639
|
+
readonly id: Schema.String;
|
|
2640
|
+
readonly uid: Schema.Number;
|
|
2641
|
+
}>>;
|
|
2642
|
+
}>>>;
|
|
2293
2643
|
}>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
|
|
2294
2644
|
readonly worktreeId: Schema.String;
|
|
2295
2645
|
readonly epoch: Schema.Number;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi";
|
|
3
3
|
import { BudgetExceededError } from "./budgets.js";
|
|
4
|
+
import { connectedAccountProviderSchema } from "./connected-accounts.js";
|
|
4
5
|
import { runCommandSchema, runSchema } from "./runs.js";
|
|
5
6
|
import { workspaceProcessUserSchema } from "./sessions.js";
|
|
6
7
|
const NonEmptyString = Schema.String.check(Schema.isNonEmpty(), Schema.isTrimmed());
|
|
@@ -169,6 +170,82 @@ export const execWorkspaceRequestSchema = Schema.Struct({
|
|
|
169
170
|
/** Run every command as this Linux user (see `workspaceProcessUserSchema`). */
|
|
170
171
|
user: Schema.optional(workspaceProcessUserSchema),
|
|
171
172
|
});
|
|
173
|
+
/** The `harnessId` stamped on runs that apply a person's dotfiles (`applyWorkspaceDotfiles`). */
|
|
174
|
+
export const dotfilesRunHarnessId = "dotfiles";
|
|
175
|
+
/** How a dotfiles tree is applied: chezmoi and stow layouts are detected by `auto`. */
|
|
176
|
+
export const workspaceDotfilesManagerSchema = Schema.Literals(["auto", "chezmoi", "stow", "copy"]);
|
|
177
|
+
/**
|
|
178
|
+
* A dotfiles repository cloned as the person (no credential reaches the workspace: a repository
|
|
179
|
+
* only the person's own identity can reach is resolved by the caller and sent as an archive).
|
|
180
|
+
*/
|
|
181
|
+
export const applyWorkspaceDotfilesRepositorySchema = Schema.Struct({
|
|
182
|
+
/** An `https://` clone URL with no credential in it (no `user:token@`). */
|
|
183
|
+
url: NonEmptyString.check(Schema.isPattern(/^https:\/\/[^\s'"\\]+$/)),
|
|
184
|
+
/** Branch or tag; omitted clones the remote's default branch. */
|
|
185
|
+
ref: Schema.optional(NonEmptyString),
|
|
186
|
+
manager: Schema.optional(workspaceDotfilesManagerSchema),
|
|
187
|
+
/** Run the bootstrap command after applying (when the checkout has it). Default true. */
|
|
188
|
+
bootstrap: Schema.optional(Schema.Boolean),
|
|
189
|
+
/** Relative to the checkout; default `./install.sh`. */
|
|
190
|
+
bootstrapCommand: Schema.optional(NonEmptyString),
|
|
191
|
+
});
|
|
192
|
+
/** A caller-resolved dotfiles tree, as at create (`spec.runtime.dotfilesArchives`). */
|
|
193
|
+
export const applyWorkspaceDotfilesArchiveSchema = Schema.Struct({
|
|
194
|
+
/** base64 of a `.tar.gz`; at most 6 MiB of base64 (about 4 MiB decoded). */
|
|
195
|
+
data: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(6 * 1024 * 1024), Schema.isPattern(/^[A-Za-z0-9+/]+={0,2}$/)),
|
|
196
|
+
manager: Schema.optional(workspaceDotfilesManagerSchema),
|
|
197
|
+
/** Where the tree lands: `home` (default) or `config` (`~/.config`, copy manager only). */
|
|
198
|
+
target: Schema.optional(Schema.Literals(["home", "config"])),
|
|
199
|
+
/** Run `./install.sh` (or `bootstrapCommand`) after applying when present. Default true. */
|
|
200
|
+
bootstrap: Schema.optional(Schema.Boolean),
|
|
201
|
+
bootstrapCommand: Schema.optional(NonEmptyString),
|
|
202
|
+
});
|
|
203
|
+
/**
|
|
204
|
+
* Apply a person's dotfiles into their home of a RUNNING workspace, as their Linux user
|
|
205
|
+
* (docs/connected-accounts-design.md §6g; Mend's ADR 0016 decision 11): the same sources as a
|
|
206
|
+
* create's dotfiles (a repository, archives, or both; the repository first, archives after in
|
|
207
|
+
* order) and the same applier (sealantd's: chezmoi, stow or copy, then each tree's bootstrap).
|
|
208
|
+
*
|
|
209
|
+
* `user` must exist and must not be root or in root's group, and `home` must be its passwd home: an
|
|
210
|
+
* existing directory of the user's, reached without a symbolic link. The clone, chezmoi, stow and the
|
|
211
|
+
* bootstrap run as the user; sealantd's archive staging and `copy` manager still run as root inside
|
|
212
|
+
* the home and follow links the person planted there, until sealantd's fix for it lands (see
|
|
213
|
+
* docs/connected-accounts-design.md §6g). `onBehalfOfUserId` names whose dotfiles they are: it is
|
|
214
|
+
* recorded on the run, and a home whose logins another person holds is refused (`home-held`). Answered `202` with a run (`harnessId`
|
|
215
|
+
* `dotfiles`) once the archives are staged: the run's first `processStarted` is the bootstrap,
|
|
216
|
+
* started once every file is applied; its output and exit are the run's. The run completes with the
|
|
217
|
+
* bootstrap's exit code (0 when no tree had one) and fails, with the daemon's words, when the apply
|
|
218
|
+
* was refused or the bootstrap ran past 30 minutes (it is stopped).
|
|
219
|
+
*/
|
|
220
|
+
export const applyWorkspaceDotfilesRequestSchema = Schema.Struct({
|
|
221
|
+
/** The workspace's owner (owner scope; uniform 404 otherwise). */
|
|
222
|
+
ownerUserId: NonEmptyString,
|
|
223
|
+
/** The Sealant user whose dotfiles these are: recorded on the run (`metadata.dotfiles`). */
|
|
224
|
+
onBehalfOfUserId: NonEmptyString,
|
|
225
|
+
/** The Linux user to apply as (a login name or a uid); never root. */
|
|
226
|
+
user: workspaceProcessUserSchema,
|
|
227
|
+
/** The user's passwd home: absolute, normalised, never under `/workspace`. */
|
|
228
|
+
home: NonEmptyString,
|
|
229
|
+
repository: Schema.optional(applyWorkspaceDotfilesRepositorySchema),
|
|
230
|
+
archives: Schema.optional(Schema.Array(applyWorkspaceDotfilesArchiveSchema).check(Schema.isMaxLength(4))),
|
|
231
|
+
});
|
|
232
|
+
/**
|
|
233
|
+
* Stable `code`s of the `WorkspaceConflictError` a dotfiles apply answers, nothing applied:
|
|
234
|
+
* `workspace-not-running` (no ready executor), `dotfiles-user-unsupported` (the workspace's
|
|
235
|
+
* sealantd cannot apply dotfiles as a user), `user-unknown` (no such user: make it first),
|
|
236
|
+
* `user-root` (the user is root or in root's group), `home-mismatch` (the home is not the user's
|
|
237
|
+
* passwd home), `home-unusable` (the home is missing, not a directory, not the user's, or reached
|
|
238
|
+
* through a symbolic link), `home-held` (another person's logins are held in the home).
|
|
239
|
+
*/
|
|
240
|
+
export const workspaceDotfilesConflictCodes = [
|
|
241
|
+
"workspace-not-running",
|
|
242
|
+
"dotfiles-user-unsupported",
|
|
243
|
+
"user-unknown",
|
|
244
|
+
"user-root",
|
|
245
|
+
"home-mismatch",
|
|
246
|
+
"home-unusable",
|
|
247
|
+
"home-held",
|
|
248
|
+
];
|
|
172
249
|
/**
|
|
173
250
|
* Bind a standby workspace's working directory, or a bindable extra mount, to one subdirectory of
|
|
174
251
|
* its root (sealantd ADR-0014). `mountPath` defaults to the working directory; an empty `subpath`
|
|
@@ -357,6 +434,12 @@ export const workspaceCaptureStatusSchema = Schema.Struct({
|
|
|
357
434
|
* past its bound, and from a daemon or control plane that predates it.
|
|
358
435
|
*/
|
|
359
436
|
overdue: Schema.optional(captureOverdueSchema),
|
|
437
|
+
/**
|
|
438
|
+
* The executor booted under an owner map (`SEALANT_CAPTURE_OWNER_MAP`): its restores give each
|
|
439
|
+
* mapped person's saved directory to them and the worktree to the group. Absent from a control
|
|
440
|
+
* plane that predates it; false from a daemon that predates it.
|
|
441
|
+
*/
|
|
442
|
+
ownerMap: Schema.optional(Schema.Boolean),
|
|
360
443
|
});
|
|
361
444
|
/**
|
|
362
445
|
* Re-plan a capture-sourced workspace (sealantd 0.15 `capture.replan`, the claim hook): the daemon
|
|
@@ -367,6 +450,18 @@ export const workspaceCaptureStatusSchema = Schema.Struct({
|
|
|
367
450
|
*/
|
|
368
451
|
export const replanWorkspaceCaptureRequestSchema = Schema.Struct({
|
|
369
452
|
ownerUserId: NonEmptyString,
|
|
453
|
+
/**
|
|
454
|
+
* The owner map the claim needs (a capture source's `ownerMap`), or `null` for none. The daemon
|
|
455
|
+
* reads its map only at boot, so a re-plan restores under the map the executor was launched
|
|
456
|
+
* with: when this is given and differs from that map (including a standby launched with none),
|
|
457
|
+
* the re-plan is refused with `409` code `owner-map-mismatch` before the daemon is reached.
|
|
458
|
+
* Omitted, nothing is compared.
|
|
459
|
+
*/
|
|
460
|
+
expectedOwnerMap: Schema.optional(Schema.NullOr(Schema.Struct({
|
|
461
|
+
gid: Schema.Number,
|
|
462
|
+
worktreeUid: Schema.Number,
|
|
463
|
+
people: Schema.Array(Schema.Struct({ id: Schema.String, uid: Schema.Number })),
|
|
464
|
+
}))),
|
|
370
465
|
});
|
|
371
466
|
export const workspaceCaptureReplannedSchema = Schema.Struct({
|
|
372
467
|
worktreeId: NonEmptyString,
|
|
@@ -499,8 +594,34 @@ export const inspectWorkspaceImageResponseSchema = Schema.Struct({
|
|
|
499
594
|
/** The image's per-person capability (`unknown` when none has been built yet). */
|
|
500
595
|
personLayout: workspaceImagePersonLayoutSchema,
|
|
501
596
|
});
|
|
502
|
-
/**
|
|
503
|
-
|
|
597
|
+
/**
|
|
598
|
+
* Stable codes of a refused connected account, carried with the account's `provider` by the error
|
|
599
|
+
* that refuses it (a create's or a credentials put's): `connected-account-missing`
|
|
600
|
+
* (`WorkspaceNotFoundError`, 404: the person has no such account, or it is archived or someone
|
|
601
|
+
* else's) and `connected-account-invalid` (`WorkspaceConflictError`, 409: the account is marked
|
|
602
|
+
* invalid, or holds an unusable credential; reconnect it) and `connected-account-unsupported`
|
|
603
|
+
* (`WorkspaceConflictError`, 409: the account cannot serve what was asked, as a Codex API key cannot
|
|
604
|
+
* be pi's or opencode's ChatGPT login). Nothing was written either way.
|
|
605
|
+
*/
|
|
606
|
+
export const connectedAccountRefusalCodes = [
|
|
607
|
+
"connected-account-missing",
|
|
608
|
+
"connected-account-invalid",
|
|
609
|
+
"connected-account-unsupported",
|
|
610
|
+
];
|
|
611
|
+
/**
|
|
612
|
+
* The providers a home holds logins for (docs/connected-accounts-design.md §6c). `pi` and
|
|
613
|
+
* `opencode` are the person's ChatGPT login (a Codex connected account), written into each tool's
|
|
614
|
+
* own `auth.json`.
|
|
615
|
+
*/
|
|
616
|
+
export const workspaceCredentialHomeProviders = [
|
|
617
|
+
"claude",
|
|
618
|
+
"codex",
|
|
619
|
+
"github",
|
|
620
|
+
"pi",
|
|
621
|
+
"opencode",
|
|
622
|
+
];
|
|
623
|
+
/** The connected account's provider each home provider's login is made from. */
|
|
624
|
+
export const workspaceCredentialHomeAccountProvider = { claude: "claude", codex: "codex", github: "github", pi: "codex", opencode: "codex" };
|
|
504
625
|
/**
|
|
505
626
|
* Put one person's logins into one home of a RUNNING workspace (docs/connected-accounts-design.md
|
|
506
627
|
* §6c). A home holds one person's logins while it is held: the first put names its person, a put
|
|
@@ -532,6 +653,25 @@ export const putWorkspaceCredentialsRequestSchema = Schema.Struct({
|
|
|
532
653
|
claude: Schema.optional(Schema.NullOr(NonEmptyString)),
|
|
533
654
|
codex: Schema.optional(Schema.NullOr(NonEmptyString)),
|
|
534
655
|
github: Schema.optional(Schema.NullOr(NonEmptyString)),
|
|
656
|
+
/**
|
|
657
|
+
* pi's ChatGPT login, made from a Codex account of `onBehalfOfUserId` (an id or a Codex account
|
|
658
|
+
* name), as the `openai-codex` entry of `<home>/.pi/agent/auth.json`. The file is merged: its
|
|
659
|
+
* other entries stay, and an `openai-codex` login the person made in pi is never replaced.
|
|
660
|
+
*/
|
|
661
|
+
pi: Schema.optional(Schema.NullOr(NonEmptyString)),
|
|
662
|
+
/**
|
|
663
|
+
* opencode's ChatGPT login, made the same way, as the `openai` entry of opencode's `auth.json`
|
|
664
|
+
* (`<home>/.local/share/opencode/auth.json`, followed through links to where it really is, which
|
|
665
|
+
* must be inside the home). A login the person made in opencode is never replaced or removed.
|
|
666
|
+
*/
|
|
667
|
+
opencode: Schema.optional(Schema.NullOr(NonEmptyString)),
|
|
668
|
+
/**
|
|
669
|
+
* Write what the person has connected and leave out the rest: a provider whose account is refused
|
|
670
|
+
* (`connected-account-missing`, `-invalid` or `-unsupported`) is not written, its login is removed
|
|
671
|
+
* from the home as `null` would remove it, and the answer's `skipped` says which and why, instead
|
|
672
|
+
* of the whole put failing. Every other refusal still fails the put. Default false.
|
|
673
|
+
*/
|
|
674
|
+
partial: Schema.optional(Schema.Boolean),
|
|
535
675
|
});
|
|
536
676
|
/** One account whose copy a home holds. */
|
|
537
677
|
export const workspaceHomeAccountSchema = Schema.Struct({
|
|
@@ -549,14 +689,31 @@ export const workspaceCredentialHomeSchema = Schema.Struct({
|
|
|
549
689
|
claude: Schema.optional(workspaceHomeAccountSchema),
|
|
550
690
|
codex: Schema.optional(workspaceHomeAccountSchema),
|
|
551
691
|
github: Schema.optional(workspaceHomeAccountSchema),
|
|
692
|
+
/** The Codex account pi's ChatGPT login is made from. */
|
|
693
|
+
pi: Schema.optional(workspaceHomeAccountSchema),
|
|
694
|
+
/** The Codex account opencode's ChatGPT login is made from. */
|
|
695
|
+
opencode: Schema.optional(workspaceHomeAccountSchema),
|
|
552
696
|
}),
|
|
553
697
|
});
|
|
698
|
+
/** A provider a partial put left out, and why (its account's refusal). */
|
|
699
|
+
export const workspaceCredentialSkipSchema = Schema.Struct({
|
|
700
|
+
provider: Schema.Literals(workspaceCredentialHomeProviders),
|
|
701
|
+
/**
|
|
702
|
+
* The account's refusal, or `login-file-unusable`: pi's or opencode's `auth.json` in the home
|
|
703
|
+
* cannot be written there (not a regular file, or really outside the home).
|
|
704
|
+
*/
|
|
705
|
+
code: Schema.Literals([...connectedAccountRefusalCodes, "login-file-unusable"]),
|
|
706
|
+
/** The refusal's words, as a whole put would have answered them. */
|
|
707
|
+
message: Schema.String,
|
|
708
|
+
});
|
|
554
709
|
export const putWorkspaceCredentialsResponseSchema = Schema.Struct({
|
|
555
710
|
workspaceId: NonEmptyString,
|
|
556
711
|
/** The launch attempt whose executor was written. */
|
|
557
712
|
runId: NonEmptyString,
|
|
558
713
|
/** The home as it is after the put. */
|
|
559
714
|
home: workspaceCredentialHomeSchema,
|
|
715
|
+
/** A partial put's left-out providers, in the order named (empty when all were written). */
|
|
716
|
+
skipped: Schema.optional(Schema.Array(workspaceCredentialSkipSchema)),
|
|
560
717
|
});
|
|
561
718
|
/** Release one home: its login files are removed and its record deleted. */
|
|
562
719
|
export const releaseWorkspaceCredentialsQuerySchema = Schema.Struct({
|
|
@@ -582,7 +739,9 @@ export const listWorkspaceCredentialsResponseSchema = Schema.Struct({
|
|
|
582
739
|
/**
|
|
583
740
|
* Stable `code`s of the `WorkspaceConflictError` the credential routes answer:
|
|
584
741
|
* `workspace-not-running` (no ready executor), `connected-account-invalid` (a named account is
|
|
585
|
-
* marked invalid: reconnect it
|
|
742
|
+
* marked invalid or unusable: reconnect it; `provider` names its provider),
|
|
743
|
+
* `connected-account-unsupported` (a Codex account named for pi or opencode is not a ChatGPT
|
|
744
|
+
* login), `home-held` (the home holds another person's logins: release it
|
|
586
745
|
* first; `/root` while the launch's own logins are at `$HOME`, or for anyone but the workspace's
|
|
587
746
|
* owner), `home-unusable` (the home does not exist, is not a directory, is reached through a
|
|
588
747
|
* symbolic link, or a login directory in it links outside it), `home-busy` (another write into the
|
|
@@ -591,6 +750,7 @@ export const listWorkspaceCredentialsResponseSchema = Schema.Struct({
|
|
|
591
750
|
export const workspaceCredentialsConflictCodes = [
|
|
592
751
|
"workspace-not-running",
|
|
593
752
|
"connected-account-invalid",
|
|
753
|
+
"connected-account-unsupported",
|
|
594
754
|
"home-held",
|
|
595
755
|
"home-unusable",
|
|
596
756
|
"home-busy",
|
|
@@ -889,12 +1049,18 @@ export class WorkspaceForbiddenError extends Schema.TaggedErrorClass()("Workspac
|
|
|
889
1049
|
}
|
|
890
1050
|
export class WorkspaceNotFoundError extends Schema.TaggedErrorClass()("WorkspaceNotFoundError", {
|
|
891
1051
|
message: Schema.String,
|
|
1052
|
+
/** A stable reason, where one applies (`connected-account-missing`). */
|
|
1053
|
+
code: Schema.optional(NonEmptyString),
|
|
1054
|
+
/** The provider of the connected account the reason is about. */
|
|
1055
|
+
provider: Schema.optional(connectedAccountProviderSchema),
|
|
892
1056
|
}, { httpApiStatus: 404 }) {
|
|
893
1057
|
}
|
|
894
1058
|
export class WorkspaceConflictError extends Schema.TaggedErrorClass()("WorkspaceConflictError", {
|
|
895
1059
|
message: Schema.String,
|
|
896
1060
|
/** A stable reason, where one applies (`create-cancelled`: the create's key was cancelled). */
|
|
897
1061
|
code: Schema.optional(NonEmptyString),
|
|
1062
|
+
/** The provider of the connected account the reason is about (`connected-account-invalid`). */
|
|
1063
|
+
provider: Schema.optional(connectedAccountProviderSchema),
|
|
898
1064
|
}, { httpApiStatus: 409 }) {
|
|
899
1065
|
}
|
|
900
1066
|
export class WorkspaceBadGatewayError extends Schema.TaggedErrorClass()("WorkspaceBadGatewayError", {
|
|
@@ -1023,6 +1189,27 @@ HttpApiEndpoint.post("execWorkspace", "/:workspaceId/exec", {
|
|
|
1023
1189
|
],
|
|
1024
1190
|
}))
|
|
1025
1191
|
.add(
|
|
1192
|
+
// Async like exec: 202 + the queued run once the archives are staged; poll `GET /v1/runs/:runId`.
|
|
1193
|
+
HttpApiEndpoint.post("applyWorkspaceDotfiles", "/:workspaceId/dotfiles", {
|
|
1194
|
+
params: workspaceIdParams,
|
|
1195
|
+
payload: applyWorkspaceDotfilesRequestSchema,
|
|
1196
|
+
success: runSchema.pipe(HttpApiSchema.status(202)),
|
|
1197
|
+
error: [
|
|
1198
|
+
// Root, a malformed home, a URL with a credential, or nothing to apply.
|
|
1199
|
+
WorkspaceBadRequestError,
|
|
1200
|
+
// Only a service key may apply a person's dotfiles.
|
|
1201
|
+
WorkspaceForbiddenError,
|
|
1202
|
+
WorkspaceNotFoundError,
|
|
1203
|
+
// See `workspaceDotfilesConflictCodes`.
|
|
1204
|
+
WorkspaceConflictError,
|
|
1205
|
+
// The executor did not confirm the staging.
|
|
1206
|
+
WorkspaceBadGatewayError,
|
|
1207
|
+
// Core cannot reach the executor.
|
|
1208
|
+
WorkspaceServiceUnavailableError,
|
|
1209
|
+
WorkspaceInternalServerError,
|
|
1210
|
+
],
|
|
1211
|
+
}))
|
|
1212
|
+
.add(
|
|
1026
1213
|
// Async: 202 = the stop was accepted and enqueued; the worker removes the container and the
|
|
1027
1214
|
// workspace transitions to "stopped". Idempotent — stopping a stopped workspace is a no-op 202.
|
|
1028
1215
|
HttpApiEndpoint.post("stopWorkspace", "/:workspaceId/stop", {
|
|
@@ -1088,7 +1275,7 @@ HttpApiEndpoint.post("putWorkspaceCredentials", "/:workspaceId/credentials", {
|
|
|
1088
1275
|
WorkspaceBadRequestError,
|
|
1089
1276
|
// The caller may not act for the people named (only a service key may).
|
|
1090
1277
|
WorkspaceForbiddenError,
|
|
1091
|
-
// The workspace or a named account (
|
|
1278
|
+
// The workspace, or a named account (`connected-account-missing` with its `provider`).
|
|
1092
1279
|
WorkspaceNotFoundError,
|
|
1093
1280
|
// See `workspaceCredentialsConflictCodes`.
|
|
1094
1281
|
WorkspaceConflictError,
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sealant/api-contracts-next",
|
|
3
|
-
"version": "0.39.0-next.
|
|
3
|
+
"version": "0.39.0-next.700",
|
|
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": "
|
|
35
|
+
"gitHead": "78e1763231f005b670f7291cfb86917cec639598",
|
|
32
36
|
"publishConfig": {
|
|
33
37
|
"access": "public"
|
|
34
38
|
}
|