@pstdio/pocketcoder-cli 0.7.3 → 0.8.0
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/index.js
CHANGED
|
@@ -109,12 +109,12 @@ var require_main = __commonJS((exports, module) => {
|
|
|
109
109
|
err.code = "MISSING_DATA";
|
|
110
110
|
throw err;
|
|
111
111
|
}
|
|
112
|
-
const
|
|
113
|
-
const length =
|
|
112
|
+
const keys2 = _dotenvKey(options).split(",");
|
|
113
|
+
const length = keys2.length;
|
|
114
114
|
let decrypted;
|
|
115
115
|
for (let i = 0;i < length; i++) {
|
|
116
116
|
try {
|
|
117
|
-
const key =
|
|
117
|
+
const key = keys2[i].trim();
|
|
118
118
|
const attrs = _instructions(result, key);
|
|
119
119
|
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
|
|
120
120
|
break;
|
|
@@ -5214,7 +5214,7 @@ function isYargsInstance(y) {
|
|
|
5214
5214
|
var Yargs = YargsFactory(esm_default);
|
|
5215
5215
|
var yargs_default = Yargs;
|
|
5216
5216
|
// package.json
|
|
5217
|
-
var version = "0.
|
|
5217
|
+
var version = "0.8.0";
|
|
5218
5218
|
|
|
5219
5219
|
// src/command/cli-context.ts
|
|
5220
5220
|
import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
@@ -21235,6 +21235,20 @@ class OperationCapacityExceededError extends Error {
|
|
|
21235
21235
|
var TEMPLATE_STATUSES = ["active", "available", "retired"];
|
|
21236
21236
|
// ../runtime-contracts/src/stores/warm-pools.ts
|
|
21237
21237
|
var WARM_POOL_RUNTIME_STATES = ["provisioning", "ready", "leasing", "leased", "draining", "failed"];
|
|
21238
|
+
// ../runtime-contracts/src/stores/workspaces.ts
|
|
21239
|
+
function purgedContentPatch() {
|
|
21240
|
+
return {
|
|
21241
|
+
launchInput: null,
|
|
21242
|
+
health: {},
|
|
21243
|
+
outputs: {},
|
|
21244
|
+
failureLogTail: null,
|
|
21245
|
+
failureLogTailTruncated: false,
|
|
21246
|
+
failureLastLogSeq: null,
|
|
21247
|
+
resolvedSource: null,
|
|
21248
|
+
registrationDigest: null,
|
|
21249
|
+
reconnectDigest: null
|
|
21250
|
+
};
|
|
21251
|
+
}
|
|
21238
21252
|
// ../../node_modules/.bun/drizzle-orm@1.0.0-rc.4+b8545101ea3de3ba/node_modules/drizzle-orm/sql/functions/aggregate.js
|
|
21239
21253
|
function count2(expression) {
|
|
21240
21254
|
return sql`count(${expression || sql.raw("*")})`.mapWith(Number);
|
|
@@ -21284,6 +21298,9 @@ function createAccessTables(table2 = pgTable) {
|
|
|
21284
21298
|
createdAt: timestamptz("created_at").notNull()
|
|
21285
21299
|
});
|
|
21286
21300
|
const machineKeys = table2("machine_keys", {
|
|
21301
|
+
issuanceRequestId: text2("issuance_request_id"),
|
|
21302
|
+
issuanceRequestDigest: text2("issuance_request_digest"),
|
|
21303
|
+
managedPrincipalIds: text2("managed_principal_ids").array().notNull().default([]),
|
|
21287
21304
|
id: uuid("id").primaryKey(),
|
|
21288
21305
|
principalId: uuid("principal_id").notNull().references(() => principals.id),
|
|
21289
21306
|
secretDigest: bytea2("secret_digest").$type().notNull(),
|
|
@@ -21292,7 +21309,7 @@ function createAccessTables(table2 = pgTable) {
|
|
|
21292
21309
|
expiresAt: timestamptz("expires_at"),
|
|
21293
21310
|
revokedAt: timestamptz("revoked_at"),
|
|
21294
21311
|
lastUsedAt: timestamptz("last_used_at")
|
|
21295
|
-
});
|
|
21312
|
+
}, (table3) => [unique().on(table3.principalId, table3.issuanceRequestId)]);
|
|
21296
21313
|
return { templates: templates2, principals, machineKeys };
|
|
21297
21314
|
}
|
|
21298
21315
|
|
|
@@ -35640,6 +35657,58 @@ var AttachmentResolvedPayload = exports_external.object({
|
|
|
35640
35657
|
descriptors: exports_external.array(AttachmentDescriptorSchema).optional(),
|
|
35641
35658
|
missing_id: exports_external.uuid().optional()
|
|
35642
35659
|
});
|
|
35660
|
+
// ../contracts/src/common/scopes.ts
|
|
35661
|
+
var SCOPES = [
|
|
35662
|
+
"templates:read",
|
|
35663
|
+
"workspaces:create",
|
|
35664
|
+
"workspaces:read",
|
|
35665
|
+
"workspaces:cancel",
|
|
35666
|
+
"workspaces:purge",
|
|
35667
|
+
"workspaces:recover",
|
|
35668
|
+
"keys:read",
|
|
35669
|
+
"keys:write",
|
|
35670
|
+
"workspaces:preserve",
|
|
35671
|
+
"workspaces:restore",
|
|
35672
|
+
"checkpoints:read",
|
|
35673
|
+
"checkpoints:delete",
|
|
35674
|
+
"outputs:read",
|
|
35675
|
+
"conversations:read",
|
|
35676
|
+
"conversations:delete",
|
|
35677
|
+
"services:relay",
|
|
35678
|
+
"attachments:write",
|
|
35679
|
+
"logs:read",
|
|
35680
|
+
"network:read",
|
|
35681
|
+
"terminal:attach",
|
|
35682
|
+
"terminal:read",
|
|
35683
|
+
"admin"
|
|
35684
|
+
];
|
|
35685
|
+
function isScope(value) {
|
|
35686
|
+
return SCOPES.includes(value);
|
|
35687
|
+
}
|
|
35688
|
+
function hasScope(granted, required2) {
|
|
35689
|
+
return granted.includes(required2) || granted.includes("admin");
|
|
35690
|
+
}
|
|
35691
|
+
|
|
35692
|
+
// ../contracts/src/auth/keys.ts
|
|
35693
|
+
var KeyIssueRequestSchema = exports_external.strictObject({
|
|
35694
|
+
request_id: exports_external.string().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/),
|
|
35695
|
+
scopes: exports_external.array(exports_external.enum(SCOPES)).min(1).max(SCOPES.length),
|
|
35696
|
+
expires_at: exports_external.iso.datetime()
|
|
35697
|
+
});
|
|
35698
|
+
var KeyResourceSchema = exports_external.object({
|
|
35699
|
+
id: exports_external.uuid(),
|
|
35700
|
+
principal_id: exports_external.uuid(),
|
|
35701
|
+
scopes: exports_external.array(exports_external.string()),
|
|
35702
|
+
effective_scopes: exports_external.array(exports_external.string()),
|
|
35703
|
+
managed_principal_ids: exports_external.array(exports_external.uuid()),
|
|
35704
|
+
issuance_request_id: exports_external.string().nullable(),
|
|
35705
|
+
created_at: exports_external.iso.datetime(),
|
|
35706
|
+
expires_at: exports_external.iso.datetime().nullable(),
|
|
35707
|
+
revoked_at: exports_external.iso.datetime().nullable(),
|
|
35708
|
+
last_used_at: exports_external.iso.datetime().nullable()
|
|
35709
|
+
});
|
|
35710
|
+
var KeyIssueResponseSchema = exports_external.object({ key: KeyResourceSchema, token: exports_external.string().nullable() });
|
|
35711
|
+
var KeyListResponseSchema = exports_external.object({ items: exports_external.array(KeyResourceSchema), next_cursor: exports_external.uuid().nullable() });
|
|
35643
35712
|
// ../contracts/src/common/canonical.ts
|
|
35644
35713
|
import { createHash as createHash2 } from "crypto";
|
|
35645
35714
|
function canonicalJson(value) {
|
|
@@ -35695,6 +35764,8 @@ var ERROR_CODES = {
|
|
|
35695
35764
|
"auth.invalid_key": 401,
|
|
35696
35765
|
"auth.missing_scope": 403,
|
|
35697
35766
|
"auth.disabled_principal": 403,
|
|
35767
|
+
"principal.not_found": 404,
|
|
35768
|
+
"key.not_found": 404,
|
|
35698
35769
|
"validation.invalid": 400,
|
|
35699
35770
|
"idempotency.conflict": 409,
|
|
35700
35771
|
"capacity.queue_full": 429,
|
|
@@ -35782,33 +35853,6 @@ function CursorQuerySchema(maxLimit, defaultLimit) {
|
|
|
35782
35853
|
limit: exports_external.coerce.number().int().positive().max(maxLimit).default(defaultLimit)
|
|
35783
35854
|
});
|
|
35784
35855
|
}
|
|
35785
|
-
// ../contracts/src/common/scopes.ts
|
|
35786
|
-
var SCOPES = [
|
|
35787
|
-
"templates:read",
|
|
35788
|
-
"workspaces:create",
|
|
35789
|
-
"workspaces:read",
|
|
35790
|
-
"workspaces:cancel",
|
|
35791
|
-
"workspaces:preserve",
|
|
35792
|
-
"workspaces:restore",
|
|
35793
|
-
"checkpoints:read",
|
|
35794
|
-
"checkpoints:delete",
|
|
35795
|
-
"outputs:read",
|
|
35796
|
-
"conversations:read",
|
|
35797
|
-
"conversations:delete",
|
|
35798
|
-
"services:relay",
|
|
35799
|
-
"attachments:write",
|
|
35800
|
-
"logs:read",
|
|
35801
|
-
"network:read",
|
|
35802
|
-
"terminal:attach",
|
|
35803
|
-
"terminal:read",
|
|
35804
|
-
"admin"
|
|
35805
|
-
];
|
|
35806
|
-
function isScope(value) {
|
|
35807
|
-
return SCOPES.includes(value);
|
|
35808
|
-
}
|
|
35809
|
-
function hasScope(granted, required2) {
|
|
35810
|
-
return granted.includes(required2) || granted.includes("admin");
|
|
35811
|
-
}
|
|
35812
35856
|
// ../contracts/src/conversations/conversation.ts
|
|
35813
35857
|
var CONVERSATION_ROLES = ["user", "assistant", "system", "tool"];
|
|
35814
35858
|
var ConversationMetadataSchema = exports_external.record(exports_external.string().min(1).max(64), exports_external.string().max(512)).refine((value) => Object.keys(value).length <= 32, "conversation metadata has at most 32 keys");
|
|
@@ -35935,7 +35979,7 @@ var CHECKPOINT_STATES = ["creating", "ready", "failed", "deleting", "deleted"];
|
|
|
35935
35979
|
function isCheckpointState(value) {
|
|
35936
35980
|
return CHECKPOINT_STATES.some((state) => state === value);
|
|
35937
35981
|
}
|
|
35938
|
-
var OPERATION_KINDS = ["preserve", "restore", "verify", "delete"];
|
|
35982
|
+
var OPERATION_KINDS = ["preserve", "restore", "verify", "delete", "purge"];
|
|
35939
35983
|
var OPERATION_STATES = ["pending", "running", "succeeded", "failed"];
|
|
35940
35984
|
var STORAGE_STATES = [
|
|
35941
35985
|
"allocating",
|
|
@@ -37493,6 +37537,7 @@ function createWorkspaceTables(table2 = pgTable, { principals, machineKeys, temp
|
|
|
37493
37537
|
createdAt: timestamptz("created_at").notNull(),
|
|
37494
37538
|
updatedAt: timestamptz("updated_at").notNull(),
|
|
37495
37539
|
terminalAt: timestamptz("terminal_at"),
|
|
37540
|
+
purgeRequestedAt: timestamptz("purge_requested_at"),
|
|
37496
37541
|
originWorkspaceId: uuid("origin_workspace_id"),
|
|
37497
37542
|
restoredFromCheckpointId: uuid("restored_from_checkpoint_id"),
|
|
37498
37543
|
sourceDescriptor: structuredJson("source_descriptor").$type(),
|
|
@@ -37612,9 +37657,9 @@ var {
|
|
|
37612
37657
|
} = createSchema();
|
|
37613
37658
|
// ../db/src/database/context.ts
|
|
37614
37659
|
var {SQL: SQL6 } = globalThis.Bun;
|
|
37615
|
-
function createDatabaseContext(databaseUrl, schemaName) {
|
|
37660
|
+
function createDatabaseContext(databaseUrl, schemaName, options = {}) {
|
|
37616
37661
|
const schema = assertValidSchema(schemaName);
|
|
37617
|
-
const client = new SQL6(databaseUrl);
|
|
37662
|
+
const client = new SQL6(databaseUrl, options);
|
|
37618
37663
|
return {
|
|
37619
37664
|
schema,
|
|
37620
37665
|
client,
|
|
@@ -37726,9 +37771,63 @@ function requiredRow(row) {
|
|
|
37726
37771
|
return row;
|
|
37727
37772
|
}
|
|
37728
37773
|
|
|
37774
|
+
// ../db/src/modules/auth/key-inventory.ts
|
|
37775
|
+
function createKeyInventory({ db, tables: { principals: principals2, machineKeys: machineKeys2 } }) {
|
|
37776
|
+
async function issue2(input) {
|
|
37777
|
+
return db.transaction(async (tx) => {
|
|
37778
|
+
const [principal] = await tx.select().from(principals2).where(eq(principals2.id, input.principalId)).for("update");
|
|
37779
|
+
if (!principal)
|
|
37780
|
+
throw new ApiError("auth.invalid_key", "Unknown principal.");
|
|
37781
|
+
if (input.issuanceRequestId) {
|
|
37782
|
+
const [existing] = await tx.select().from(machineKeys2).where(and(eq(machineKeys2.principalId, input.principalId), eq(machineKeys2.issuanceRequestId, input.issuanceRequestId)));
|
|
37783
|
+
if (existing)
|
|
37784
|
+
return {
|
|
37785
|
+
key: existing,
|
|
37786
|
+
created: false,
|
|
37787
|
+
conflict: existing.issuanceRequestDigest !== input.issuanceRequestDigest
|
|
37788
|
+
};
|
|
37789
|
+
}
|
|
37790
|
+
if (principal.disabledAt)
|
|
37791
|
+
throw new ApiError("auth.disabled_principal", "This principal is disabled.");
|
|
37792
|
+
if (input.issuanceRequestId && input.expiresAt && input.expiresAt <= new Date)
|
|
37793
|
+
throw new ApiError("validation.invalid", "Key expiry must be in the future.");
|
|
37794
|
+
if (input.scopes.some((scope) => !principal.scopes.includes("admin") && !principal.scopes.includes(scope))) {
|
|
37795
|
+
throw new ApiError("auth.missing_scope", "Key scopes exceed the principal's authority.");
|
|
37796
|
+
}
|
|
37797
|
+
const [key] = await tx.insert(machineKeys2).values(input).returning();
|
|
37798
|
+
return { key: requiredRow(key), created: true, conflict: false };
|
|
37799
|
+
});
|
|
37800
|
+
}
|
|
37801
|
+
return {
|
|
37802
|
+
issueMachineKey: (input) => issue2(input),
|
|
37803
|
+
async insertMachineKey(input) {
|
|
37804
|
+
await issue2(input);
|
|
37805
|
+
},
|
|
37806
|
+
async listMachineKeys(principalId, filter) {
|
|
37807
|
+
return db.select().from(machineKeys2).where(and(eq(machineKeys2.principalId, principalId), filter.cursor ? gt(machineKeys2.id, filter.cursor) : undefined, filter.requestId ? eq(machineKeys2.issuanceRequestId, filter.requestId) : undefined)).orderBy(asc2(machineKeys2.id)).limit(filter.limit);
|
|
37808
|
+
},
|
|
37809
|
+
async revokePrincipalKeys(principalId, at) {
|
|
37810
|
+
await db.transaction(async (tx) => {
|
|
37811
|
+
await tx.select({ id: principals2.id }).from(principals2).where(eq(principals2.id, principalId)).for("update");
|
|
37812
|
+
await tx.update(principals2).set({ disabledAt: at }).where(eq(principals2.id, principalId));
|
|
37813
|
+
await tx.update(machineKeys2).set({ revokedAt: at }).where(and(eq(machineKeys2.principalId, principalId), isNull(machineKeys2.revokedAt)));
|
|
37814
|
+
});
|
|
37815
|
+
}
|
|
37816
|
+
};
|
|
37817
|
+
}
|
|
37818
|
+
|
|
37729
37819
|
// ../db/src/modules/auth/repository.ts
|
|
37730
|
-
function createAuth(
|
|
37820
|
+
function createAuth(context) {
|
|
37821
|
+
const {
|
|
37822
|
+
db,
|
|
37823
|
+
tables: { principals: principals2, machineKeys: machineKeys2 }
|
|
37824
|
+
} = context;
|
|
37731
37825
|
return {
|
|
37826
|
+
...createKeyInventory(context),
|
|
37827
|
+
async getPrincipal(id) {
|
|
37828
|
+
const [row] = await db.select().from(principals2).where(eq(principals2.id, id));
|
|
37829
|
+
return row ?? null;
|
|
37830
|
+
},
|
|
37732
37831
|
async createPrincipal(name2, scopes, templateNames) {
|
|
37733
37832
|
const [row] = await db.insert(principals2).values({ id: randomUUID2(), name: name2, scopes, templateNames, createdAt: sql`now()` }).returning();
|
|
37734
37833
|
return requiredRow(row);
|
|
@@ -37747,9 +37846,6 @@ function createAuth({ db, tables: { principals: principals2, machineKeys: machin
|
|
|
37747
37846
|
async setPrincipalDisabled(id, disabled) {
|
|
37748
37847
|
await db.update(principals2).set({ disabledAt: disabled ? sql`now()` : null }).where(eq(principals2.id, id));
|
|
37749
37848
|
},
|
|
37750
|
-
async insertMachineKey(row) {
|
|
37751
|
-
await db.insert(machineKeys2).values(row);
|
|
37752
|
-
},
|
|
37753
37849
|
async getMachineKeyWithPrincipal(keyId) {
|
|
37754
37850
|
const [row] = await db.select({ key: machineKeys2, principal: principals2 }).from(machineKeys2).innerJoin(principals2, eq(principals2.id, machineKeys2.principalId)).where(eq(machineKeys2.id, keyId));
|
|
37755
37851
|
return row ?? null;
|
|
@@ -37764,16 +37860,71 @@ function createAuth({ db, tables: { principals: principals2, machineKeys: machin
|
|
|
37764
37860
|
};
|
|
37765
37861
|
}
|
|
37766
37862
|
|
|
37863
|
+
// ../db/src/modules/persistence/content.ts
|
|
37864
|
+
async function contentWritable(tx, tables, workspaceId) {
|
|
37865
|
+
const [workspace] = await tx.select({ purgeRequestedAt: tables.workspaces.purgeRequestedAt }).from(tables.workspaces).where(eq(tables.workspaces.id, workspaceId)).for("update");
|
|
37866
|
+
return !workspace?.purgeRequestedAt;
|
|
37867
|
+
}
|
|
37868
|
+
async function requireContentWritable(tx, tables, workspaceId) {
|
|
37869
|
+
if (!await contentWritable(tx, tables, workspaceId)) {
|
|
37870
|
+
throw new ApiError("operation.conflict", "Workspace content is being purged.");
|
|
37871
|
+
}
|
|
37872
|
+
}
|
|
37873
|
+
function createContentPurge({ db, tables }) {
|
|
37874
|
+
return {
|
|
37875
|
+
async listWorkspaceStorage(workspaceId) {
|
|
37876
|
+
return db.select().from(tables.workspaceStorage).where(eq(tables.workspaceStorage.workspaceId, workspaceId));
|
|
37877
|
+
},
|
|
37878
|
+
async purgeWorkspaceContent(workspaceId, at) {
|
|
37879
|
+
await db.transaction(async (tx) => {
|
|
37880
|
+
const [workspace] = await tx.select().from(tables.workspaces).where(eq(tables.workspaces.id, workspaceId)).for("update");
|
|
37881
|
+
if (!workspace?.purgeRequestedAt)
|
|
37882
|
+
throw new Error("Purge has not been admitted");
|
|
37883
|
+
for (const table2 of [
|
|
37884
|
+
tables.workspaceLogs,
|
|
37885
|
+
tables.workspaceOutputs,
|
|
37886
|
+
tables.workspaceConversationMessages,
|
|
37887
|
+
tables.workspaceNetworkEvents,
|
|
37888
|
+
tables.eventOutbox
|
|
37889
|
+
]) {
|
|
37890
|
+
await tx.delete(table2).where(eq(table2.workspaceId, workspaceId));
|
|
37891
|
+
}
|
|
37892
|
+
await tx.insert(tables.workspaceConversations).values({ workspaceId, status: "deleted", deletedAt: at, updatedAt: at }).onConflictDoUpdate({
|
|
37893
|
+
target: tables.workspaceConversations.workspaceId,
|
|
37894
|
+
set: { status: "deleted", expiresAt: null, deletedAt: at, updatedAt: at }
|
|
37895
|
+
});
|
|
37896
|
+
await tx.update(tables.workspaceCheckpoints).set({ manifest: null, manifestDigest: null, sourceProvenance: null, label: null }).where(eq(tables.workspaceCheckpoints.workspaceId, workspaceId));
|
|
37897
|
+
await tx.update(tables.workspaces).set({
|
|
37898
|
+
launchInput: null,
|
|
37899
|
+
outputs: {},
|
|
37900
|
+
metadata: {},
|
|
37901
|
+
health: {},
|
|
37902
|
+
failureLogTail: null,
|
|
37903
|
+
failureLogTailTruncated: false,
|
|
37904
|
+
failureLastLogSeq: null,
|
|
37905
|
+
sourceDescriptor: null,
|
|
37906
|
+
resolvedSource: null,
|
|
37907
|
+
registrationDigest: null,
|
|
37908
|
+
reconnectDigest: null,
|
|
37909
|
+
updatedAt: at
|
|
37910
|
+
}).where(eq(tables.workspaces.id, workspaceId));
|
|
37911
|
+
});
|
|
37912
|
+
}
|
|
37913
|
+
};
|
|
37914
|
+
}
|
|
37915
|
+
|
|
37767
37916
|
// ../db/src/modules/conversations/repository.ts
|
|
37768
37917
|
var MAX_CONVERSATION_BYTES = 50 * 1024 * 1024;
|
|
37769
37918
|
var MAX_CONVERSATION_MESSAGES = 1e5;
|
|
37770
|
-
function createConversations({
|
|
37771
|
-
|
|
37772
|
-
|
|
37773
|
-
|
|
37919
|
+
function createConversations(context) {
|
|
37920
|
+
const {
|
|
37921
|
+
db,
|
|
37922
|
+
tables: { workspaceConversations: conversations2, workspaceConversationMessages: messages }
|
|
37923
|
+
} = context;
|
|
37774
37924
|
return {
|
|
37775
37925
|
async appendConversationMessage(input) {
|
|
37776
37926
|
return db.transaction(async (tx) => {
|
|
37927
|
+
await requireContentWritable(tx, context.tables, input.workspaceId);
|
|
37777
37928
|
await lock(tx, input.workspaceId, 7081);
|
|
37778
37929
|
const [state] = await tx.select({ status: conversations2.status }).from(conversations2).where(eq(conversations2.workspaceId, input.workspaceId)).for("update");
|
|
37779
37930
|
if (state?.status === "deleted")
|
|
@@ -37827,12 +37978,18 @@ function createConversations({
|
|
|
37827
37978
|
|
|
37828
37979
|
// ../db/src/modules/logs/repository.ts
|
|
37829
37980
|
var MAX_LOG_BYTES = 10 * 1024 * 1024;
|
|
37830
|
-
function createLogs(
|
|
37981
|
+
function createLogs(context) {
|
|
37982
|
+
const {
|
|
37983
|
+
db,
|
|
37984
|
+
tables: { workspaceLogs: logs2 }
|
|
37985
|
+
} = context;
|
|
37831
37986
|
return {
|
|
37832
37987
|
async appendLogs(workspaceId, entries) {
|
|
37833
37988
|
if (entries.length === 0)
|
|
37834
37989
|
return;
|
|
37835
37990
|
await db.transaction(async (tx) => {
|
|
37991
|
+
if (!await contentWritable(tx, context.tables, workspaceId))
|
|
37992
|
+
return;
|
|
37836
37993
|
await lock(tx, workspaceId, 7080);
|
|
37837
37994
|
const [stats] = await tx.select({
|
|
37838
37995
|
maxSeq: sql`coalesce(max(${logs2.seq}),0)`.mapWith(Number),
|
|
@@ -37872,12 +38029,18 @@ function createLogs({ db, tables: { workspaceLogs: logs2 } }) {
|
|
|
37872
38029
|
}
|
|
37873
38030
|
|
|
37874
38031
|
// ../db/src/modules/network-audit/repository.ts
|
|
37875
|
-
function createNetworkAudit(
|
|
38032
|
+
function createNetworkAudit(context) {
|
|
38033
|
+
const {
|
|
38034
|
+
db,
|
|
38035
|
+
tables: { workspaceNetworkEvents: events, workspaces: workspaces3 }
|
|
38036
|
+
} = context;
|
|
37876
38037
|
return {
|
|
37877
38038
|
async appendNetworkEvents(workspaceId, sourceSessionId, inputs) {
|
|
37878
38039
|
if (inputs.length === 0)
|
|
37879
38040
|
return;
|
|
37880
38041
|
await db.transaction(async (tx) => {
|
|
38042
|
+
if (!await contentWritable(tx, context.tables, workspaceId))
|
|
38043
|
+
return;
|
|
37881
38044
|
await lock(tx, workspaceId, 7348);
|
|
37882
38045
|
for (const event of inputs) {
|
|
37883
38046
|
const [duplicate] = await tx.select({ seq: events.seq }).from(events).where(and(eq(events.workspaceId, workspaceId), eq(events.sourceSessionId, sourceSessionId), eq(events.sourceSeq, event.source_seq)));
|
|
@@ -37919,7 +38082,11 @@ function createNetworkAudit({ db, tables: { workspaceNetworkEvents: events, work
|
|
|
37919
38082
|
// ../db/src/modules/outbox/repository.ts
|
|
37920
38083
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
37921
38084
|
var CLAIM_LEASE_MS = 60000;
|
|
37922
|
-
function createOutbox(
|
|
38085
|
+
function createOutbox(context) {
|
|
38086
|
+
const {
|
|
38087
|
+
db,
|
|
38088
|
+
tables: { eventOutbox: outbox2 }
|
|
38089
|
+
} = context;
|
|
37923
38090
|
return {
|
|
37924
38091
|
async claimDueEvents(now, limit) {
|
|
37925
38092
|
const due = db.select({ id: outbox2.id }).from(outbox2).where(and(isNull(outbox2.deliveredAt), lte(outbox2.nextAttemptAt, now))).orderBy(asc2(outbox2.occurredAt)).limit(limit).for("update", { skipLocked: true });
|
|
@@ -37933,23 +38100,32 @@ function createOutbox({ db, tables: { eventOutbox: outbox2 } }) {
|
|
|
37933
38100
|
await db.update(outbox2).set({ attemptCount: sql`${outbox2.attemptCount}+1`, lastErrorCode: errorCode, nextAttemptAt }).where(eq(outbox2.id, id));
|
|
37934
38101
|
},
|
|
37935
38102
|
async appendEvent(workspaceId, eventType, payload, at) {
|
|
37936
|
-
await db.
|
|
37937
|
-
|
|
37938
|
-
|
|
37939
|
-
|
|
37940
|
-
|
|
37941
|
-
|
|
37942
|
-
|
|
38103
|
+
await db.transaction(async (tx) => {
|
|
38104
|
+
if (!await contentWritable(tx, context.tables, workspaceId))
|
|
38105
|
+
return;
|
|
38106
|
+
await tx.insert(outbox2).values({
|
|
38107
|
+
id: randomUUID3(),
|
|
38108
|
+
workspaceId,
|
|
38109
|
+
eventType,
|
|
38110
|
+
payload: payload === null ? sql`'null'::jsonb` : payload,
|
|
38111
|
+
occurredAt: at,
|
|
38112
|
+
nextAttemptAt: at
|
|
38113
|
+
});
|
|
37943
38114
|
});
|
|
37944
38115
|
}
|
|
37945
38116
|
};
|
|
37946
38117
|
}
|
|
37947
38118
|
|
|
37948
38119
|
// ../db/src/modules/outputs/repository.ts
|
|
37949
|
-
function createOutputs(
|
|
38120
|
+
function createOutputs(context) {
|
|
38121
|
+
const {
|
|
38122
|
+
db,
|
|
38123
|
+
tables: { workspaceOutputs: outputs2, workspaces: workspaces3 }
|
|
38124
|
+
} = context;
|
|
37950
38125
|
return {
|
|
37951
38126
|
async appendOutput(input) {
|
|
37952
38127
|
return db.transaction(async (tx) => {
|
|
38128
|
+
await requireContentWritable(tx, context.tables, input.workspaceId);
|
|
37953
38129
|
await lock(tx, input.workspaceId, 7081);
|
|
37954
38130
|
const [latest] = await tx.select({ seq: sql`coalesce(max(${outputs2.seq}),0)`.mapWith(Number) }).from(outputs2).where(eq(outputs2.workspaceId, input.workspaceId));
|
|
37955
38131
|
const row = { ...input, seq: requiredRow(latest).seq + 1 };
|
|
@@ -37997,7 +38173,7 @@ function createCheckpoints({ db, tables: { workspaceCheckpoints: checkpoints } }
|
|
|
37997
38173
|
function createOperations({
|
|
37998
38174
|
db,
|
|
37999
38175
|
schema,
|
|
38000
|
-
tables: { workspaceOperations: operations, workspaceCheckpoints: checkpoints }
|
|
38176
|
+
tables: { workspaceOperations: operations, workspaceCheckpoints: checkpoints, workspaces: workspaces3 }
|
|
38001
38177
|
}) {
|
|
38002
38178
|
const incomplete = inArray(operations.state, ["pending", "running"]);
|
|
38003
38179
|
async function countIncomplete(tx = db) {
|
|
@@ -38011,6 +38187,15 @@ function createOperations({
|
|
|
38011
38187
|
const [existing] = await tx.select().from(operations).where(and(eq(operations.principalId, input.principalId), eq(operations.kind, input.kind), eq(operations.idempotencyKey, input.idempotencyKey)));
|
|
38012
38188
|
if (existing)
|
|
38013
38189
|
return { operation: existing, created: false, conflict: existing.requestDigest !== input.requestDigest };
|
|
38190
|
+
if (input.workspaceId) {
|
|
38191
|
+
const [workspace] = await tx.select().from(workspaces3).where(eq(workspaces3.id, input.workspaceId)).for("update");
|
|
38192
|
+
if (workspace?.purgeRequestedAt && input.kind !== "purge") {
|
|
38193
|
+
throw new ApiError("operation.conflict", "Workspace content is being purged.");
|
|
38194
|
+
}
|
|
38195
|
+
if (input.kind === "purge") {
|
|
38196
|
+
await tx.update(workspaces3).set({ purgeRequestedAt: workspace?.purgeRequestedAt ?? input.createdAt }).where(eq(workspaces3.id, input.workspaceId));
|
|
38197
|
+
}
|
|
38198
|
+
}
|
|
38014
38199
|
if (options.maxIncompleteOperations !== undefined && await countIncomplete(tx) >= options.maxIncompleteOperations)
|
|
38015
38200
|
throw new OperationCapacityExceededError;
|
|
38016
38201
|
const [row] = await tx.insert(operations).values(input).returning();
|
|
@@ -38129,6 +38314,152 @@ function createTerminals({ db, tables: { workspaceTerminalSessions: sessions } }
|
|
|
38129
38314
|
|
|
38130
38315
|
// ../db/src/modules/workspaces/events.ts
|
|
38131
38316
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
38317
|
+
// ../auth/src/index.ts
|
|
38318
|
+
import { createHmac, randomBytes, randomUUID as randomUUID5, timingSafeEqual } from "crypto";
|
|
38319
|
+
var KEY_PREFIX = "pkt";
|
|
38320
|
+
function issueMachineKey(pepper) {
|
|
38321
|
+
const id = randomUUID5();
|
|
38322
|
+
const secret = randomBytes(32).toString("base64url");
|
|
38323
|
+
return {
|
|
38324
|
+
id,
|
|
38325
|
+
token: `${KEY_PREFIX}_${id}_${secret}`,
|
|
38326
|
+
secretDigest: digestSecret(pepper, id, secret)
|
|
38327
|
+
};
|
|
38328
|
+
}
|
|
38329
|
+
var KEY_RE = /^pkt_([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})_([A-Za-z0-9_-]+)$/;
|
|
38330
|
+
function parseMachineKey(token) {
|
|
38331
|
+
const match = KEY_RE.exec(token);
|
|
38332
|
+
if (!match) {
|
|
38333
|
+
return null;
|
|
38334
|
+
}
|
|
38335
|
+
return { id: match[1], secret: match[2] };
|
|
38336
|
+
}
|
|
38337
|
+
function digestSecret(pepper, keyId, secret) {
|
|
38338
|
+
return new Uint8Array(createHmac("sha256", pepper).update(`${keyId}:${secret}`).digest());
|
|
38339
|
+
}
|
|
38340
|
+
function verifySecret(pepper, keyId, secret, storedDigest) {
|
|
38341
|
+
const computed = digestSecret(pepper, keyId, secret);
|
|
38342
|
+
if (computed.length !== storedDigest.length) {
|
|
38343
|
+
return false;
|
|
38344
|
+
}
|
|
38345
|
+
return timingSafeEqual(computed, storedDigest);
|
|
38346
|
+
}
|
|
38347
|
+
function generateOpaqueSecret() {
|
|
38348
|
+
return randomBytes(32).toString("base64url");
|
|
38349
|
+
}
|
|
38350
|
+
function digestOpaque(pepper, secret) {
|
|
38351
|
+
return new Uint8Array(createHmac("sha256", pepper).update(secret).digest());
|
|
38352
|
+
}
|
|
38353
|
+
function verifyOpaque(pepper, secret, storedDigest) {
|
|
38354
|
+
const computed = digestOpaque(pepper, secret);
|
|
38355
|
+
if (computed.length !== storedDigest.length) {
|
|
38356
|
+
return false;
|
|
38357
|
+
}
|
|
38358
|
+
return timingSafeEqual(computed, storedDigest);
|
|
38359
|
+
}
|
|
38360
|
+
function signEvent(signingKey, timestamp4, body) {
|
|
38361
|
+
const mac3 = createHmac("sha256", signingKey).update(`${timestamp4}.${body}`).digest("hex");
|
|
38362
|
+
return `sha256=${mac3}`;
|
|
38363
|
+
}
|
|
38364
|
+
var EGRESS_TOKEN_PREFIX = "pce1";
|
|
38365
|
+
var EGRESS_TOKEN_DOMAIN = "pocketcoder-egress-audit-v1\x00";
|
|
38366
|
+
function issueEgressAuditToken(signingKey, subject) {
|
|
38367
|
+
const payload = Buffer.from(JSON.stringify({
|
|
38368
|
+
v: 1,
|
|
38369
|
+
aud: "egress-audit",
|
|
38370
|
+
kind: subject.kind,
|
|
38371
|
+
id: subject.id,
|
|
38372
|
+
exp: subject.expiresAt.getTime()
|
|
38373
|
+
})).toString("base64url");
|
|
38374
|
+
const signature = createHmac("sha256", signingKey).update(`${EGRESS_TOKEN_DOMAIN}${payload}`).digest("base64url");
|
|
38375
|
+
return `${EGRESS_TOKEN_PREFIX}.${payload}.${signature}`;
|
|
38376
|
+
}
|
|
38377
|
+
function verifyEgressAuditToken(signingKey, token, now = new Date) {
|
|
38378
|
+
const [prefix, payload, signature, extra] = token.split(".");
|
|
38379
|
+
if (prefix !== EGRESS_TOKEN_PREFIX || !payload || !signature || extra)
|
|
38380
|
+
return null;
|
|
38381
|
+
const expected = createHmac("sha256", signingKey).update(`${EGRESS_TOKEN_DOMAIN}${payload}`).digest("base64url");
|
|
38382
|
+
const actualBytes = Buffer.from(signature);
|
|
38383
|
+
const expectedBytes = Buffer.from(expected);
|
|
38384
|
+
if (actualBytes.length !== expectedBytes.length || !timingSafeEqual(actualBytes, expectedBytes)) {
|
|
38385
|
+
return null;
|
|
38386
|
+
}
|
|
38387
|
+
try {
|
|
38388
|
+
const value = JSON.parse(Buffer.from(payload, "base64url").toString());
|
|
38389
|
+
if (value.v !== 1 || value.aud !== "egress-audit" || value.kind !== "workspace" && value.kind !== "pool" || typeof value.id !== "string" || !value.id || typeof value.exp !== "number" || value.exp <= now.getTime()) {
|
|
38390
|
+
return null;
|
|
38391
|
+
}
|
|
38392
|
+
return { kind: value.kind, id: value.id };
|
|
38393
|
+
} catch {
|
|
38394
|
+
return null;
|
|
38395
|
+
}
|
|
38396
|
+
}
|
|
38397
|
+
var REDACT_PATTERNS = [/pkt_[0-9a-f-]{36}_[A-Za-z0-9_-]+/g, /(authorization:?\s*bearer\s+)\S+/gi];
|
|
38398
|
+
function redact(text6) {
|
|
38399
|
+
let out = text6;
|
|
38400
|
+
for (const pattern of REDACT_PATTERNS) {
|
|
38401
|
+
out = out.replace(pattern, "[redacted]");
|
|
38402
|
+
}
|
|
38403
|
+
return out;
|
|
38404
|
+
}
|
|
38405
|
+
|
|
38406
|
+
// ../runtime-core/src/keys/key-administration.ts
|
|
38407
|
+
function keyResource(key, principal) {
|
|
38408
|
+
const scopes = key.scopes.length ? key.scopes : principal.scopes;
|
|
38409
|
+
return {
|
|
38410
|
+
id: key.id,
|
|
38411
|
+
principal_id: key.principalId,
|
|
38412
|
+
scopes: key.scopes,
|
|
38413
|
+
effective_scopes: scopes.filter((scope) => principal.scopes.includes("admin") || principal.scopes.includes(scope)),
|
|
38414
|
+
managed_principal_ids: key.managedPrincipalIds,
|
|
38415
|
+
issuance_request_id: key.issuanceRequestId,
|
|
38416
|
+
created_at: key.createdAt.toISOString(),
|
|
38417
|
+
expires_at: key.expiresAt?.toISOString() ?? null,
|
|
38418
|
+
revoked_at: key.revokedAt?.toISOString() ?? null,
|
|
38419
|
+
last_used_at: key.lastUsedAt?.toISOString() ?? null
|
|
38420
|
+
};
|
|
38421
|
+
}
|
|
38422
|
+
async function issuePrincipalKey(store, pepper, principal, request, options = {}) {
|
|
38423
|
+
const scopes = [...new Set(request.scopes)].sort();
|
|
38424
|
+
const managedPrincipalIds = [...new Set(options.managedPrincipalIds ?? [])].sort();
|
|
38425
|
+
if (scopes.some((scope) => !(principal.scopes.includes("admin") || principal.scopes.includes(scope)))) {
|
|
38426
|
+
throw new ApiError("auth.missing_scope", "Key scopes exceed the principal's authority.");
|
|
38427
|
+
}
|
|
38428
|
+
if (!options.operatorBootstrap && scopes.some((scope) => ["admin", "keys:read", "keys:write", "workspaces:recover"].includes(scope))) {
|
|
38429
|
+
throw new ApiError("auth.missing_scope", "Delegated issuance cannot grant administrative authority.");
|
|
38430
|
+
}
|
|
38431
|
+
const expiresAt = request.expires_at ? new Date(request.expires_at) : null;
|
|
38432
|
+
if (!expiresAt && !options.operatorBootstrap || expiresAt && !Number.isFinite(expiresAt.getTime()))
|
|
38433
|
+
throw new ApiError("validation.invalid", "Invalid key expiry.");
|
|
38434
|
+
if (managedPrincipalIds.length && (!options.operatorBootstrap || !principal.scopes.includes("admin"))) {
|
|
38435
|
+
throw new ApiError("auth.missing_scope", "Only an operator can bootstrap delegated authority.");
|
|
38436
|
+
}
|
|
38437
|
+
const generated = issueMachineKey(pepper);
|
|
38438
|
+
const result = await store.issueMachineKey({
|
|
38439
|
+
id: generated.id,
|
|
38440
|
+
secretDigest: generated.secretDigest,
|
|
38441
|
+
principalId: principal.id,
|
|
38442
|
+
scopes,
|
|
38443
|
+
managedPrincipalIds,
|
|
38444
|
+
issuanceRequestId: request.request_id,
|
|
38445
|
+
issuanceRequestDigest: digestOf({
|
|
38446
|
+
scopes,
|
|
38447
|
+
expires_at: expiresAt?.toISOString() ?? null,
|
|
38448
|
+
managed_principal_ids: managedPrincipalIds
|
|
38449
|
+
}),
|
|
38450
|
+
createdAt: new Date,
|
|
38451
|
+
expiresAt,
|
|
38452
|
+
revokedAt: null,
|
|
38453
|
+
lastUsedAt: null
|
|
38454
|
+
});
|
|
38455
|
+
if (result.conflict)
|
|
38456
|
+
throw new ApiError("idempotency.conflict", "Changed key issuance request.");
|
|
38457
|
+
return {
|
|
38458
|
+
created: result.created,
|
|
38459
|
+
key: keyResource(result.key, principal),
|
|
38460
|
+
token: result.created ? generated.token : null
|
|
38461
|
+
};
|
|
38462
|
+
}
|
|
38132
38463
|
// ../runtime-core/src/observability/metrics.ts
|
|
38133
38464
|
function metricKey(name2, labels = {}) {
|
|
38134
38465
|
const entries = Object.entries(labels).sort(([left2], [right2]) => left2.localeCompare(right2));
|
|
@@ -38224,6 +38555,25 @@ class OutboxDispatcher {
|
|
|
38224
38555
|
await this.deps.store.markEventFailed(id, errorCode, new Date(this.now().getTime() + backoff));
|
|
38225
38556
|
}
|
|
38226
38557
|
}
|
|
38558
|
+
// ../runtime-core/src/scheduler/provider-termination.ts
|
|
38559
|
+
async function stopWorkspaceProvider(store, driver, workspace, graceSeconds, at, remove = true) {
|
|
38560
|
+
if (!workspace.providerRef)
|
|
38561
|
+
return;
|
|
38562
|
+
const ref = { kind: workspace.providerKind ?? "", id: "", ...workspace.providerRef };
|
|
38563
|
+
await driver.stop(ref, graceSeconds);
|
|
38564
|
+
const evidence = await driver.terminationEvidence?.(ref);
|
|
38565
|
+
if (evidence) {
|
|
38566
|
+
const current = await store.getWorkspace(workspace.id);
|
|
38567
|
+
if (!current?.providerRef || current.providerRef.id !== workspace.providerRef.id)
|
|
38568
|
+
throw new Error("Termination provider changed");
|
|
38569
|
+
await store.updateWorkspace(workspace.id, {
|
|
38570
|
+
providerRef: { ...current.providerRef, terminationEvidence: evidence }
|
|
38571
|
+
}, at);
|
|
38572
|
+
}
|
|
38573
|
+
if (remove)
|
|
38574
|
+
await driver.remove(ref);
|
|
38575
|
+
}
|
|
38576
|
+
|
|
38227
38577
|
// ../runtime-core/src/reconciliation/reconciliation-metrics.ts
|
|
38228
38578
|
async function measureReconciliation(metrics, kind, skipped, reconcile) {
|
|
38229
38579
|
const startedAt = performance.now();
|
|
@@ -38278,8 +38628,7 @@ async function reconcileDeletion(deps, operation, checkpoint, now) {
|
|
|
38278
38628
|
async function cleanupProvider(deps, workspace) {
|
|
38279
38629
|
if (!workspace?.providerRef)
|
|
38280
38630
|
return;
|
|
38281
|
-
await deps.driver
|
|
38282
|
-
await deps.driver.remove(workspace.providerRef).catch(() => {});
|
|
38631
|
+
await stopWorkspaceProvider(deps.store, deps.driver, workspace, 1, deps.now?.() ?? new Date);
|
|
38283
38632
|
}
|
|
38284
38633
|
async function completePreserve(deps, operation, checkpoint, workspace, now) {
|
|
38285
38634
|
await cleanupProvider(deps, workspace);
|
|
@@ -38314,13 +38663,26 @@ async function failPreserve(deps, operation, context, now) {
|
|
|
38314
38663
|
}
|
|
38315
38664
|
await failOperation(deps, operation, "checkpoint_failed", now);
|
|
38316
38665
|
}
|
|
38666
|
+
async function reconcileRestore(deps, operation, now) {
|
|
38667
|
+
const target = operation.resultWorkspaceId ? await deps.store.getWorkspace(operation.resultWorkspaceId) : await deps.store.getWorkspaceByIdempotency(operation.principalId, `restore:${operation.id}`);
|
|
38668
|
+
if (!target) {
|
|
38669
|
+
await failOperation(deps, operation, "restore_failed", now);
|
|
38670
|
+
return;
|
|
38671
|
+
}
|
|
38672
|
+
if (!operation.resultWorkspaceId) {
|
|
38673
|
+
await deps.store.updateOperation(operation.id, { resultWorkspaceId: target.id }, now);
|
|
38674
|
+
}
|
|
38675
|
+
if (isTerminal(target.state))
|
|
38676
|
+
await failOperation(deps, operation, "restore_failed", now);
|
|
38677
|
+
}
|
|
38317
38678
|
async function reconcileOperation(deps, operation, now) {
|
|
38318
|
-
|
|
38679
|
+
if (operation.kind === "purge")
|
|
38680
|
+
return;
|
|
38319
38681
|
if (operation.kind === "restore") {
|
|
38320
|
-
|
|
38321
|
-
await failOperation(deps, operation, "restore_failed", now);
|
|
38682
|
+
await reconcileRestore(deps, operation, now);
|
|
38322
38683
|
return;
|
|
38323
38684
|
}
|
|
38685
|
+
const context = await loadOperationContext(deps, operation);
|
|
38324
38686
|
if (!context.checkpoint) {
|
|
38325
38687
|
await failOperation(deps, operation, "checkpoint_storage_lost", now);
|
|
38326
38688
|
return;
|
|
@@ -38372,22 +38734,13 @@ async function reconcilePersistence(deps) {
|
|
|
38372
38734
|
}
|
|
38373
38735
|
// ../runtime-core/src/reconciliation/reconcile.ts
|
|
38374
38736
|
async function reconcileProviderRow(deps, row, found, now) {
|
|
38375
|
-
if (row.state === "queued")
|
|
38737
|
+
if (row.state === "queued" || row.state === "terminating")
|
|
38376
38738
|
return;
|
|
38377
38739
|
const mismatched = found && found.templateDigest !== row.templateDigest;
|
|
38378
38740
|
if (mismatched) {
|
|
38379
38741
|
deps.log?.(`reconcile: template digest mismatch for ${row.id}; failing workspace`);
|
|
38380
38742
|
}
|
|
38381
38743
|
const lost = !found || mismatched;
|
|
38382
|
-
if (lost && row.state === "terminating") {
|
|
38383
|
-
await deps.store.transition(row.id, {
|
|
38384
|
-
from: ["terminating"],
|
|
38385
|
-
to: row.terminalIntent ?? "failed",
|
|
38386
|
-
reason: row.reasonCode ?? "provider_lost",
|
|
38387
|
-
at: now
|
|
38388
|
-
});
|
|
38389
|
-
return;
|
|
38390
|
-
}
|
|
38391
38744
|
if (lost && (row.state === "connected" || row.state === "ready")) {
|
|
38392
38745
|
await settleLostStorage(deps, row, now);
|
|
38393
38746
|
await deps.store.transition(row.id, {
|
|
@@ -38510,95 +38863,6 @@ async function loadTemplateDir(store, dir) {
|
|
|
38510
38863
|
}
|
|
38511
38864
|
return result;
|
|
38512
38865
|
}
|
|
38513
|
-
// ../auth/src/index.ts
|
|
38514
|
-
import { createHmac, randomBytes, randomUUID as randomUUID5, timingSafeEqual } from "crypto";
|
|
38515
|
-
var KEY_PREFIX = "pkt";
|
|
38516
|
-
function issueMachineKey(pepper) {
|
|
38517
|
-
const id = randomUUID5();
|
|
38518
|
-
const secret = randomBytes(32).toString("base64url");
|
|
38519
|
-
return {
|
|
38520
|
-
id,
|
|
38521
|
-
token: `${KEY_PREFIX}_${id}_${secret}`,
|
|
38522
|
-
secretDigest: digestSecret(pepper, id, secret)
|
|
38523
|
-
};
|
|
38524
|
-
}
|
|
38525
|
-
var KEY_RE = /^pkt_([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})_([A-Za-z0-9_-]+)$/;
|
|
38526
|
-
function parseMachineKey(token) {
|
|
38527
|
-
const match = KEY_RE.exec(token);
|
|
38528
|
-
if (!match) {
|
|
38529
|
-
return null;
|
|
38530
|
-
}
|
|
38531
|
-
return { id: match[1], secret: match[2] };
|
|
38532
|
-
}
|
|
38533
|
-
function digestSecret(pepper, keyId, secret) {
|
|
38534
|
-
return new Uint8Array(createHmac("sha256", pepper).update(`${keyId}:${secret}`).digest());
|
|
38535
|
-
}
|
|
38536
|
-
function verifySecret(pepper, keyId, secret, storedDigest) {
|
|
38537
|
-
const computed = digestSecret(pepper, keyId, secret);
|
|
38538
|
-
if (computed.length !== storedDigest.length) {
|
|
38539
|
-
return false;
|
|
38540
|
-
}
|
|
38541
|
-
return timingSafeEqual(computed, storedDigest);
|
|
38542
|
-
}
|
|
38543
|
-
function generateOpaqueSecret() {
|
|
38544
|
-
return randomBytes(32).toString("base64url");
|
|
38545
|
-
}
|
|
38546
|
-
function digestOpaque(pepper, secret) {
|
|
38547
|
-
return new Uint8Array(createHmac("sha256", pepper).update(secret).digest());
|
|
38548
|
-
}
|
|
38549
|
-
function verifyOpaque(pepper, secret, storedDigest) {
|
|
38550
|
-
const computed = digestOpaque(pepper, secret);
|
|
38551
|
-
if (computed.length !== storedDigest.length) {
|
|
38552
|
-
return false;
|
|
38553
|
-
}
|
|
38554
|
-
return timingSafeEqual(computed, storedDigest);
|
|
38555
|
-
}
|
|
38556
|
-
function signEvent(signingKey, timestamp4, body) {
|
|
38557
|
-
const mac3 = createHmac("sha256", signingKey).update(`${timestamp4}.${body}`).digest("hex");
|
|
38558
|
-
return `sha256=${mac3}`;
|
|
38559
|
-
}
|
|
38560
|
-
var EGRESS_TOKEN_PREFIX = "pce1";
|
|
38561
|
-
var EGRESS_TOKEN_DOMAIN = "pocketcoder-egress-audit-v1\x00";
|
|
38562
|
-
function issueEgressAuditToken(signingKey, subject) {
|
|
38563
|
-
const payload = Buffer.from(JSON.stringify({
|
|
38564
|
-
v: 1,
|
|
38565
|
-
aud: "egress-audit",
|
|
38566
|
-
kind: subject.kind,
|
|
38567
|
-
id: subject.id,
|
|
38568
|
-
exp: subject.expiresAt.getTime()
|
|
38569
|
-
})).toString("base64url");
|
|
38570
|
-
const signature = createHmac("sha256", signingKey).update(`${EGRESS_TOKEN_DOMAIN}${payload}`).digest("base64url");
|
|
38571
|
-
return `${EGRESS_TOKEN_PREFIX}.${payload}.${signature}`;
|
|
38572
|
-
}
|
|
38573
|
-
function verifyEgressAuditToken(signingKey, token, now = new Date) {
|
|
38574
|
-
const [prefix, payload, signature, extra] = token.split(".");
|
|
38575
|
-
if (prefix !== EGRESS_TOKEN_PREFIX || !payload || !signature || extra)
|
|
38576
|
-
return null;
|
|
38577
|
-
const expected = createHmac("sha256", signingKey).update(`${EGRESS_TOKEN_DOMAIN}${payload}`).digest("base64url");
|
|
38578
|
-
const actualBytes = Buffer.from(signature);
|
|
38579
|
-
const expectedBytes = Buffer.from(expected);
|
|
38580
|
-
if (actualBytes.length !== expectedBytes.length || !timingSafeEqual(actualBytes, expectedBytes)) {
|
|
38581
|
-
return null;
|
|
38582
|
-
}
|
|
38583
|
-
try {
|
|
38584
|
-
const value = JSON.parse(Buffer.from(payload, "base64url").toString());
|
|
38585
|
-
if (value.v !== 1 || value.aud !== "egress-audit" || value.kind !== "workspace" && value.kind !== "pool" || typeof value.id !== "string" || !value.id || typeof value.exp !== "number" || value.exp <= now.getTime()) {
|
|
38586
|
-
return null;
|
|
38587
|
-
}
|
|
38588
|
-
return { kind: value.kind, id: value.id };
|
|
38589
|
-
} catch {
|
|
38590
|
-
return null;
|
|
38591
|
-
}
|
|
38592
|
-
}
|
|
38593
|
-
var REDACT_PATTERNS = [/pkt_[0-9a-f-]{36}_[A-Za-z0-9_-]+/g, /(authorization:?\s*bearer\s+)\S+/gi];
|
|
38594
|
-
function redact(text6) {
|
|
38595
|
-
let out = text6;
|
|
38596
|
-
for (const pattern of REDACT_PATTERNS) {
|
|
38597
|
-
out = out.replace(pattern, "[redacted]");
|
|
38598
|
-
}
|
|
38599
|
-
return out;
|
|
38600
|
-
}
|
|
38601
|
-
|
|
38602
38866
|
// ../runtime-core/src/scheduler/scheduler-base.ts
|
|
38603
38867
|
var FAILURE_LOG_TAIL_BYTES = 16 * 1024;
|
|
38604
38868
|
function decodeFailureLogTail(content, truncated) {
|
|
@@ -38785,8 +39049,20 @@ class SchedulerAdmission {
|
|
|
38785
39049
|
return;
|
|
38786
39050
|
}
|
|
38787
39051
|
}
|
|
39052
|
+
async authorized(row) {
|
|
39053
|
+
if (!this.context.deps.authorizeLaunch)
|
|
39054
|
+
return true;
|
|
39055
|
+
try {
|
|
39056
|
+
return await this.context.deps.authorizeLaunch(row) === true;
|
|
39057
|
+
} catch {
|
|
39058
|
+
this.context.report(`admit.policy.${row.id}`, new Error("Launch policy unavailable"));
|
|
39059
|
+
return false;
|
|
39060
|
+
}
|
|
39061
|
+
}
|
|
38788
39062
|
async launch(row) {
|
|
38789
39063
|
const { store, driver, secrets } = this.context.deps;
|
|
39064
|
+
if (!await this.authorized(row))
|
|
39065
|
+
return false;
|
|
38790
39066
|
const now = this.context.now();
|
|
38791
39067
|
const secret = secrets.generate();
|
|
38792
39068
|
const registrationDigest = secrets.digest(secret);
|
|
@@ -38951,24 +39227,28 @@ class SchedulerLifecycle {
|
|
|
38951
39227
|
connections.signal(row.id, "TERM");
|
|
38952
39228
|
}
|
|
38953
39229
|
if (updated.providerRef) {
|
|
38954
|
-
this.
|
|
39230
|
+
this.finalize(updated, terminalState, reason, this.context.now()).catch((err) => this.context.report(`terminate.${row.id}`, err));
|
|
38955
39231
|
} else {
|
|
38956
39232
|
await this.finalize(updated, terminalState, reason, at);
|
|
38957
39233
|
}
|
|
38958
39234
|
return updated;
|
|
38959
39235
|
}
|
|
38960
|
-
async finalize(row, terminalState, reason, at
|
|
39236
|
+
async finalize(row, terminalState, reason, at) {
|
|
38961
39237
|
const { store, driver, connections } = this.context.deps;
|
|
39238
|
+
const retainStorage = terminalState === "failed" && row.templateSnapshot.spec.persistence.checkpoint.onFailure === "retain-for-recovery";
|
|
38962
39239
|
if (row.providerRef) {
|
|
38963
39240
|
try {
|
|
38964
|
-
await
|
|
38965
|
-
await driver.remove({
|
|
38966
|
-
kind: row.providerKind ?? "",
|
|
38967
|
-
id: "",
|
|
38968
|
-
...row.providerRef
|
|
38969
|
-
});
|
|
39241
|
+
await stopWorkspaceProvider(store, driver, row, this.context.graceSeconds(row), at);
|
|
38970
39242
|
} catch (err) {
|
|
38971
39243
|
this.context.report(`finalize.terminate.${row.id}`, err);
|
|
39244
|
+
await store.transition(row.id, {
|
|
39245
|
+
from: ["provisioning", "connected", "ready"],
|
|
39246
|
+
to: "terminating",
|
|
39247
|
+
reason,
|
|
39248
|
+
at,
|
|
39249
|
+
patch: { terminalIntent: terminalState, registrationDigest: null }
|
|
39250
|
+
});
|
|
39251
|
+
return;
|
|
38972
39252
|
}
|
|
38973
39253
|
}
|
|
38974
39254
|
if (retainStorage) {
|
|
@@ -39008,16 +39288,12 @@ class SchedulerLifecycle {
|
|
|
39008
39288
|
}
|
|
39009
39289
|
});
|
|
39010
39290
|
}
|
|
39011
|
-
async stopAndRemove(ref, graceSeconds) {
|
|
39012
|
-
await this.context.deps.driver.stop(ref, graceSeconds);
|
|
39013
|
-
await this.context.deps.driver.remove(ref);
|
|
39014
|
-
}
|
|
39015
39291
|
async fail(row, reason, at) {
|
|
39016
39292
|
const action = row.templateSnapshot.spec.persistence.checkpoint.onFailure;
|
|
39017
39293
|
if (action === "preserve" && ["connected", "ready"].includes(row.state) && await this.requestPolicyPreserve(row, "failure")) {
|
|
39018
39294
|
return;
|
|
39019
39295
|
}
|
|
39020
|
-
await this.finalize(row, "failed", reason, at
|
|
39296
|
+
await this.finalize(row, "failed", reason, at);
|
|
39021
39297
|
}
|
|
39022
39298
|
async handleProcessExit(row, exitCode, at) {
|
|
39023
39299
|
if (exitCode === 0 && row.templateSnapshot.spec.persistence.checkpoint.onCleanExit === "preserve" && await this.requestPolicyPreserve(row, "clean_exit")) {
|
|
@@ -39062,6 +39338,8 @@ class SchedulerSweep {
|
|
|
39062
39338
|
for (const row of rows) {
|
|
39063
39339
|
try {
|
|
39064
39340
|
await this.sweepRow(row, now);
|
|
39341
|
+
if (row.providerRef)
|
|
39342
|
+
await this.context.deps.driver.inspect(row.providerRef);
|
|
39065
39343
|
} catch (err) {
|
|
39066
39344
|
this.context.report(`sweep.${row.id}`, err);
|
|
39067
39345
|
}
|
|
@@ -39109,9 +39387,12 @@ class SchedulerSweep {
|
|
|
39109
39387
|
}
|
|
39110
39388
|
async sweepTerminating(row, now) {
|
|
39111
39389
|
const stuckMs = 4 * this.context.timeoutMs(row, "terminateGrace") + 5000;
|
|
39112
|
-
|
|
39390
|
+
const history = await this.context.deps.store.listStateHistory(row.id);
|
|
39391
|
+
const termination = history.find((entry) => entry.toState === "terminating");
|
|
39392
|
+
if (!termination || now.getTime() - termination.occurredAt.getTime() <= stuckMs)
|
|
39113
39393
|
return;
|
|
39114
|
-
|
|
39394
|
+
const terminalState = row.terminalIntent ?? "failed";
|
|
39395
|
+
await this.lifecycle.finalize(row, terminalState, row.reasonCode, now);
|
|
39115
39396
|
}
|
|
39116
39397
|
async sweepRow(row, now) {
|
|
39117
39398
|
switch (row.state) {
|
|
@@ -39122,8 +39403,8 @@ class SchedulerSweep {
|
|
|
39122
39403
|
if (row.registrationExpiresAt && now >= row.registrationExpiresAt) {
|
|
39123
39404
|
if (row.provisioningMode === "warm" && row.launchAttempts < this.context.deps.limits.maxLaunchAttempts) {
|
|
39124
39405
|
if (row.providerRef) {
|
|
39125
|
-
await this.context.deps.driver.stop(row.providerRef, 1)
|
|
39126
|
-
await this.context.deps.driver.remove(row.providerRef)
|
|
39406
|
+
await this.context.deps.driver.stop(row.providerRef, 1);
|
|
39407
|
+
await this.context.deps.driver.remove(row.providerRef);
|
|
39127
39408
|
}
|
|
39128
39409
|
await this.context.deps.store.transition(row.id, {
|
|
39129
39410
|
from: ["provisioning"],
|
|
@@ -39181,6 +39462,9 @@ class Scheduler {
|
|
|
39181
39462
|
});
|
|
39182
39463
|
return this.context.activeTick;
|
|
39183
39464
|
}
|
|
39465
|
+
async drain() {
|
|
39466
|
+
await this.context.activeTick;
|
|
39467
|
+
}
|
|
39184
39468
|
async runTick() {
|
|
39185
39469
|
await this.sweeper.sweep();
|
|
39186
39470
|
await this.admission.admit();
|
|
@@ -39476,6 +39760,8 @@ async function appendTransition(context, tx, row, from, reason, at) {
|
|
|
39476
39760
|
reasonCode: reason,
|
|
39477
39761
|
occurredAt: at
|
|
39478
39762
|
});
|
|
39763
|
+
if (row.purgeRequestedAt)
|
|
39764
|
+
return;
|
|
39479
39765
|
const payload = buildEventEnvelope(row, at);
|
|
39480
39766
|
await tx.insert(context.tables.eventOutbox).values({
|
|
39481
39767
|
id: payload.id,
|
|
@@ -39520,7 +39806,7 @@ function createWarmPools(context) {
|
|
|
39520
39806
|
async claimWarmPoolRuntime(claim) {
|
|
39521
39807
|
const result = await db.transaction(async (tx) => {
|
|
39522
39808
|
const [current] = await tx.select().from(workspaces3).where(eq(workspaces3.id, claim.workspaceId)).for("update");
|
|
39523
|
-
if (current?.state !== "queued")
|
|
39809
|
+
if (current?.state !== "queued" || current.purgeRequestedAt)
|
|
39524
39810
|
return null;
|
|
39525
39811
|
const [runtime] = await tx.select().from(runtimes).where(and(eq(runtimes.templateDigest, claim.templateDigest), eq(runtimes.driverKind, claim.driverKind), eq(runtimes.eligibilityFingerprint, claim.eligibilityFingerprint), eq(runtimes.state, "ready"), isNotNull(runtimes.providerRef))).orderBy(asc2(runtimes.readyAt)).limit(1).for("update", { skipLocked: true });
|
|
39526
39812
|
if (!runtime)
|
|
@@ -39586,7 +39872,7 @@ function createAdmission(context) {
|
|
|
39586
39872
|
const workspace = await db.transaction(async (tx) => {
|
|
39587
39873
|
await lock(tx, `${schema}:workspace-admission`, 7351);
|
|
39588
39874
|
const [current] = await tx.select().from(workspaces3).where(eq(workspaces3.id, claim.workspaceId)).for("update");
|
|
39589
|
-
if (current?.state !== "queued")
|
|
39875
|
+
if (current?.state !== "queued" || current.purgeRequestedAt)
|
|
39590
39876
|
return null;
|
|
39591
39877
|
const counts = await countActive(tx);
|
|
39592
39878
|
if (counts.global >= claim.limits.globalActiveWorkspaces)
|
|
@@ -39706,7 +39992,15 @@ function createTransitions(context) {
|
|
|
39706
39992
|
return {
|
|
39707
39993
|
async updateWorkspace(id, patch, at) {
|
|
39708
39994
|
const bumpsChange = Object.keys(patch).some((key) => CHANGE_PATCH_KEYS.has(key));
|
|
39709
|
-
await db.
|
|
39995
|
+
await db.transaction(async (tx) => {
|
|
39996
|
+
const [current] = await tx.select().from(workspaces3).where(eq(workspaces3.id, id)).for("update");
|
|
39997
|
+
await tx.update(workspaces3).set({
|
|
39998
|
+
...patch,
|
|
39999
|
+
...current?.purgeRequestedAt ? purgedContentPatch() : {},
|
|
40000
|
+
updatedAt: at,
|
|
40001
|
+
...bumpsChange ? { changeSeq: sql`${workspaces3.changeSeq}+1` } : {}
|
|
40002
|
+
}).where(eq(workspaces3.id, id));
|
|
40003
|
+
});
|
|
39710
40004
|
if (bumpsChange)
|
|
39711
40005
|
notifyChange(context, id);
|
|
39712
40006
|
},
|
|
@@ -39715,9 +40009,12 @@ function createTransitions(context) {
|
|
|
39715
40009
|
const [current] = await tx.select().from(workspaces3).where(eq(workspaces3.id, id)).for("update");
|
|
39716
40010
|
if (!current || !req.from.includes(current.state) || !canTransition(current.state, req.to))
|
|
39717
40011
|
return null;
|
|
40012
|
+
if (current.purgeRequestedAt && ["provisioning", "connected", "ready", "preserving", "queued"].includes(req.to))
|
|
40013
|
+
return null;
|
|
39718
40014
|
const terminal = isTerminal(req.to);
|
|
39719
40015
|
const [row] = await tx.update(workspaces3).set({
|
|
39720
40016
|
...req.patch,
|
|
40017
|
+
...current.purgeRequestedAt ? purgedContentPatch() : {},
|
|
39721
40018
|
state: req.to,
|
|
39722
40019
|
reasonCode: req.reason,
|
|
39723
40020
|
updatedAt: req.at,
|
|
@@ -39748,9 +40045,12 @@ function createTransitions(context) {
|
|
|
39748
40045
|
|
|
39749
40046
|
// ../db/src/store.ts
|
|
39750
40047
|
class PostgresStore {
|
|
39751
|
-
constructor(databaseUrl, schema = "pocketcoder") {
|
|
39752
|
-
const context = createDatabaseContext(databaseUrl, schema);
|
|
40048
|
+
constructor(databaseUrl, schema = "pocketcoder", options = {}) {
|
|
40049
|
+
const context = createDatabaseContext(databaseUrl, schema, options);
|
|
39753
40050
|
const lifecycle2 = createLifecycle(context);
|
|
40051
|
+
const content = createContentPurge(context);
|
|
40052
|
+
this.listWorkspaceStorage = content.listWorkspaceStorage;
|
|
40053
|
+
this.purgeWorkspaceContent = content.purgeWorkspaceContent;
|
|
39754
40054
|
this.init = lifecycle2.init;
|
|
39755
40055
|
this.close = lifecycle2.close;
|
|
39756
40056
|
this.acquireCoordinatorLease = lifecycle2.acquireCoordinatorLease;
|
|
@@ -39765,6 +40065,10 @@ class PostgresStore {
|
|
|
39765
40065
|
this.listPrincipals = auth2.listPrincipals;
|
|
39766
40066
|
this.updatePrincipal = auth2.updatePrincipal;
|
|
39767
40067
|
this.setPrincipalDisabled = auth2.setPrincipalDisabled;
|
|
40068
|
+
this.getPrincipal = auth2.getPrincipal;
|
|
40069
|
+
this.issueMachineKey = auth2.issueMachineKey;
|
|
40070
|
+
this.listMachineKeys = auth2.listMachineKeys;
|
|
40071
|
+
this.revokePrincipalKeys = auth2.revokePrincipalKeys;
|
|
39768
40072
|
this.insertMachineKey = auth2.insertMachineKey;
|
|
39769
40073
|
this.getMachineKeyWithPrincipal = auth2.getMachineKeyWithPrincipal;
|
|
39770
40074
|
this.revokeMachineKey = auth2.revokeMachineKey;
|
|
@@ -39837,6 +40141,8 @@ class PostgresStore {
|
|
|
39837
40141
|
this.appendEvent = outbox2.appendEvent;
|
|
39838
40142
|
this.waitForWorkspaceChange = createWorkspaceWaiter(context, workspaces3.getWorkspace);
|
|
39839
40143
|
}
|
|
40144
|
+
listWorkspaceStorage;
|
|
40145
|
+
purgeWorkspaceContent;
|
|
39840
40146
|
init;
|
|
39841
40147
|
close;
|
|
39842
40148
|
acquireCoordinatorLease;
|
|
@@ -39849,6 +40155,10 @@ class PostgresStore {
|
|
|
39849
40155
|
listPrincipals;
|
|
39850
40156
|
updatePrincipal;
|
|
39851
40157
|
setPrincipalDisabled;
|
|
40158
|
+
getPrincipal;
|
|
40159
|
+
issueMachineKey;
|
|
40160
|
+
listMachineKeys;
|
|
40161
|
+
revokePrincipalKeys;
|
|
39852
40162
|
insertMachineKey;
|
|
39853
40163
|
getMachineKeyWithPrincipal;
|
|
39854
40164
|
revokeMachineKey;
|
|
@@ -40174,6 +40484,51 @@ class OutputsApi extends WorkspaceCursorApi {
|
|
|
40174
40484
|
}
|
|
40175
40485
|
}
|
|
40176
40486
|
|
|
40487
|
+
// ../sdk/src/resources/keys/keys.ts
|
|
40488
|
+
class KeysApi {
|
|
40489
|
+
transport;
|
|
40490
|
+
constructor(transport) {
|
|
40491
|
+
this.transport = transport;
|
|
40492
|
+
}
|
|
40493
|
+
list(principalId, query = {}, options = {}) {
|
|
40494
|
+
return this.transport.request(`/v1/principals/${encodeURIComponent(principalId)}/keys?${queryString({ limit: query.limit, cursor: query.cursor, request_id: query.requestId })}`, KeyListResponseSchema, options);
|
|
40495
|
+
}
|
|
40496
|
+
async* all(principalId, query = {}, options = {}) {
|
|
40497
|
+
let cursor;
|
|
40498
|
+
do {
|
|
40499
|
+
const page2 = await this.list(principalId, { ...query, cursor }, options);
|
|
40500
|
+
yield* page2.items;
|
|
40501
|
+
cursor = page2.next_cursor ?? undefined;
|
|
40502
|
+
} while (cursor);
|
|
40503
|
+
}
|
|
40504
|
+
issue(principalId, input, options = {}) {
|
|
40505
|
+
return this.transport.request(`/v1/principals/${encodeURIComponent(principalId)}/keys`, KeyIssueResponseSchema, {
|
|
40506
|
+
method: "POST",
|
|
40507
|
+
body: JSON.stringify(input),
|
|
40508
|
+
signal: options.signal
|
|
40509
|
+
});
|
|
40510
|
+
}
|
|
40511
|
+
revoke(principalId, keyId, options = {}) {
|
|
40512
|
+
return this.transport.request(`/v1/principals/${encodeURIComponent(principalId)}/keys/${encodeURIComponent(keyId)}`, exports_external.object({ revoked: exports_external.literal(true) }), { method: "DELETE", signal: options.signal });
|
|
40513
|
+
}
|
|
40514
|
+
revokeAll(principalId, options = {}) {
|
|
40515
|
+
return this.transport.request(`/v1/principals/${encodeURIComponent(principalId)}/keys`, exports_external.object({ revoked: exports_external.literal(true) }), { method: "DELETE", signal: options.signal });
|
|
40516
|
+
}
|
|
40517
|
+
}
|
|
40518
|
+
|
|
40519
|
+
class RecoveryApi {
|
|
40520
|
+
transport;
|
|
40521
|
+
constructor(transport) {
|
|
40522
|
+
this.transport = transport;
|
|
40523
|
+
}
|
|
40524
|
+
purge(principalId, workspaceId, executionKey, options = {}) {
|
|
40525
|
+
return this.transport.request(`/v1/principals/${encodeURIComponent(principalId)}/workspaces/${encodeURIComponent(workspaceId)}/purge`, OperationResourceSchema, { method: "POST", headers: { "Idempotency-Key": executionKey }, body: "{}", signal: options.signal });
|
|
40526
|
+
}
|
|
40527
|
+
operation(principalId, operationId, options = {}) {
|
|
40528
|
+
return this.transport.request(`/v1/principals/${encodeURIComponent(principalId)}/operations/${encodeURIComponent(operationId)}`, OperationResourceSchema, options);
|
|
40529
|
+
}
|
|
40530
|
+
}
|
|
40531
|
+
|
|
40177
40532
|
// ../sdk/src/resources/templates/templates.ts
|
|
40178
40533
|
class TemplatesApi {
|
|
40179
40534
|
transport;
|
|
@@ -40383,6 +40738,9 @@ class WorkspacesApi {
|
|
|
40383
40738
|
preserve(id, input, key, options = {}) {
|
|
40384
40739
|
return this.jsonOperation(`/v1/workspaces/${encodeURIComponent(id)}/preserve`, input, key, PreserveResponseSchema, options);
|
|
40385
40740
|
}
|
|
40741
|
+
purge(id, key, options = {}) {
|
|
40742
|
+
return this.jsonOperation(`/v1/workspaces/${encodeURIComponent(id)}/purge`, {}, key, OperationResourceSchema, options);
|
|
40743
|
+
}
|
|
40386
40744
|
recreate(id, input, key, options = {}) {
|
|
40387
40745
|
return this.jsonOperation(`/v1/workspaces/${encodeURIComponent(id)}/recreate`, input, key, RestoreResponseSchema, options);
|
|
40388
40746
|
}
|
|
@@ -40544,6 +40902,8 @@ class PocketCoderClient {
|
|
|
40544
40902
|
outputs;
|
|
40545
40903
|
administration;
|
|
40546
40904
|
terminals;
|
|
40905
|
+
keys;
|
|
40906
|
+
recovery;
|
|
40547
40907
|
constructor(config2, fetchImpl = fetch) {
|
|
40548
40908
|
this.transport = new PocketCoderTransport(config2, fetchImpl);
|
|
40549
40909
|
this.templates = new TemplatesApi(this.transport);
|
|
@@ -40558,6 +40918,8 @@ class PocketCoderClient {
|
|
|
40558
40918
|
this.outputs = new OutputsApi(this.transport);
|
|
40559
40919
|
this.administration = new AdministrationApi(this.transport);
|
|
40560
40920
|
this.terminals = new TerminalsApi(this.transport);
|
|
40921
|
+
this.keys = new KeysApi(this.transport);
|
|
40922
|
+
this.recovery = new RecoveryApi(this.transport);
|
|
40561
40923
|
}
|
|
40562
40924
|
raw(path, init = {}) {
|
|
40563
40925
|
return this.transport.raw(path, init);
|
|
@@ -40803,7 +41165,7 @@ async function api2(path, init = {}) {
|
|
|
40803
41165
|
}
|
|
40804
41166
|
async function withStore(fn) {
|
|
40805
41167
|
const { url: url2, schema } = dbConfig();
|
|
40806
|
-
const store = new PostgresStore(url2, schema);
|
|
41168
|
+
const store = new PostgresStore(url2, schema, { max: 1 });
|
|
40807
41169
|
try {
|
|
40808
41170
|
return await fn(store);
|
|
40809
41171
|
} finally {
|
|
@@ -41062,66 +41424,103 @@ async function printFailureTail(workspaceId) {
|
|
|
41062
41424
|
} catch {}
|
|
41063
41425
|
}
|
|
41064
41426
|
|
|
41427
|
+
// src/commands/keys/issue.ts
|
|
41428
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
41429
|
+
|
|
41065
41430
|
// src/commands/scopes.ts
|
|
41066
41431
|
function parseScopes(value) {
|
|
41067
|
-
|
|
41068
|
-
for (const item of items)
|
|
41432
|
+
return valueList(value).map((item) => {
|
|
41069
41433
|
if (!isScope(item))
|
|
41070
41434
|
fail(`unknown scope: ${item}`);
|
|
41071
|
-
|
|
41435
|
+
return item;
|
|
41436
|
+
});
|
|
41072
41437
|
}
|
|
41073
41438
|
|
|
41074
41439
|
// src/commands/keys/issue.ts
|
|
41075
41440
|
function addIssueCommand(parser2) {
|
|
41076
|
-
return addAction(parser2, "issue", "Issue a machine key", (command2) => command2.option("principal", {
|
|
41077
|
-
type: "string",
|
|
41078
|
-
demandOption: true,
|
|
41079
|
-
description: "Principal name"
|
|
41080
|
-
}).option("scopes", {
|
|
41441
|
+
return addAction(parser2, "issue", "Issue or reconcile a machine key", (command2) => command2.option("principal", { type: "string", description: "Principal name for local operator bootstrap" }).option("principal-id", { type: "string", description: "Target principal ID through the public API" }).option("request-id", {
|
|
41081
41442
|
type: "string",
|
|
41082
|
-
description: "
|
|
41083
|
-
}).option("expires", {
|
|
41443
|
+
description: "Persist this identity before issuance to reconcile a lost response"
|
|
41444
|
+
}).option("scopes", { type: "string", description: "Comma-separated restricted scopes" }).option("expires", {
|
|
41084
41445
|
type: "string",
|
|
41085
41446
|
default: "never",
|
|
41086
|
-
description: "
|
|
41447
|
+
description: "ISO 8601 expiry; never is local operator only"
|
|
41448
|
+
}).option("manage-principals", {
|
|
41449
|
+
type: "string",
|
|
41450
|
+
description: "Explicit target UUIDs for local operator bootstrap"
|
|
41451
|
+
}).option("json", {
|
|
41452
|
+
type: "boolean",
|
|
41453
|
+
default: false,
|
|
41454
|
+
description: "Print key metadata and one-time token as JSON"
|
|
41455
|
+
}).conflicts("principal", "principal-id").implies("principal-id", "request-id").check((flags) => {
|
|
41456
|
+
if (!flags.principal && !flags["principal-id"])
|
|
41457
|
+
throw new Error("Missing required argument: principal or principal-id");
|
|
41458
|
+
return true;
|
|
41087
41459
|
}), async (flags) => {
|
|
41460
|
+
const expires = need(flags, "expires");
|
|
41461
|
+
const expiresAt = expires === "never" ? null : expires;
|
|
41462
|
+
const scopes = typeof flags.scopes === "string" ? parseScopes(flags.scopes) : [];
|
|
41463
|
+
const requestId = typeof flags["request-id"] === "string" ? flags["request-id"] : randomUUID11();
|
|
41464
|
+
if (typeof flags["principal-id"] === "string") {
|
|
41465
|
+
if (!expiresAt)
|
|
41466
|
+
fail("public key issuance requires --expires <ISO8601>");
|
|
41467
|
+
if (flags["manage-principals"])
|
|
41468
|
+
fail("delegated grants require local operator bootstrap");
|
|
41469
|
+
const result = await controlPlaneClient().keys.issue(flags["principal-id"], {
|
|
41470
|
+
request_id: requestId,
|
|
41471
|
+
scopes,
|
|
41472
|
+
expires_at: expiresAt
|
|
41473
|
+
});
|
|
41474
|
+
console.log(JSON.stringify(result, null, 2));
|
|
41475
|
+
return;
|
|
41476
|
+
}
|
|
41088
41477
|
const pepper = process.env.POCKETCODER_AUTH_PEPPER;
|
|
41089
41478
|
if (!pepper)
|
|
41090
|
-
fail("POCKETCODER_AUTH_PEPPER is required to issue keys");
|
|
41479
|
+
fail("POCKETCODER_AUTH_PEPPER is required to issue keys locally");
|
|
41091
41480
|
await withStore(async (store) => {
|
|
41092
41481
|
const name2 = need(flags, "principal");
|
|
41093
41482
|
const principal = await store.getPrincipalByName(name2);
|
|
41094
41483
|
if (!principal)
|
|
41095
41484
|
fail(`unknown principal: ${name2}`);
|
|
41096
|
-
const
|
|
41097
|
-
const
|
|
41098
|
-
|
|
41099
|
-
|
|
41100
|
-
}
|
|
41101
|
-
const
|
|
41102
|
-
|
|
41103
|
-
|
|
41104
|
-
|
|
41105
|
-
|
|
41106
|
-
|
|
41107
|
-
|
|
41108
|
-
expiresAt,
|
|
41109
|
-
revokedAt: null,
|
|
41110
|
-
lastUsedAt: null
|
|
41111
|
-
});
|
|
41112
|
-
console.log("machine key (shown once, store it now):");
|
|
41113
|
-
console.log(key.token);
|
|
41485
|
+
const managedPrincipalIds = typeof flags["manage-principals"] === "string" ? valueList(flags["manage-principals"]) : [];
|
|
41486
|
+
for (const id of managedPrincipalIds) {
|
|
41487
|
+
if (!/^[0-9a-f-]{36}$/i.test(id) || !await store.getPrincipal(id))
|
|
41488
|
+
fail("unknown managed principal ID");
|
|
41489
|
+
}
|
|
41490
|
+
const result = await issuePrincipalKey(store, pepper, principal, { request_id: requestId, scopes, expires_at: expiresAt }, { managedPrincipalIds, operatorBootstrap: true });
|
|
41491
|
+
if (flags.json || !result.token)
|
|
41492
|
+
console.log(JSON.stringify({ key: result.key, token: result.token }, null, 2));
|
|
41493
|
+
else {
|
|
41494
|
+
console.log(`machine key (shown once; request ${requestId}):`);
|
|
41495
|
+
console.log(result.token);
|
|
41496
|
+
}
|
|
41114
41497
|
});
|
|
41115
41498
|
});
|
|
41116
41499
|
}
|
|
41117
41500
|
|
|
41501
|
+
// src/commands/keys/list.ts
|
|
41502
|
+
function addListCommand2(parser2) {
|
|
41503
|
+
return addAction(parser2, "list", "List authoritative key metadata through the public API", (command2) => command2.option("principal-id", { type: "string", demandOption: true }).option("request-id", { type: "string", description: "Reconcile an issuance request" }).option("cursor", { type: "string" }).option("limit", { type: "number", default: 50 }), async (flags) => {
|
|
41504
|
+
const result = await controlPlaneClient().keys.list(need(flags, "principal-id"), {
|
|
41505
|
+
limit: Number(flags.limit),
|
|
41506
|
+
...typeof flags.cursor === "string" ? { cursor: flags.cursor } : {},
|
|
41507
|
+
...typeof flags["request-id"] === "string" ? { requestId: flags["request-id"] } : {}
|
|
41508
|
+
});
|
|
41509
|
+
console.log(JSON.stringify(result, null, 2));
|
|
41510
|
+
});
|
|
41511
|
+
}
|
|
41512
|
+
|
|
41118
41513
|
// src/commands/keys/revoke.ts
|
|
41119
41514
|
function addRevokeCommand(parser2) {
|
|
41120
|
-
return addAction(parser2, "revoke", "Revoke a machine key", (command2) => command2.option("id", {
|
|
41515
|
+
return addAction(parser2, "revoke", "Revoke a machine key", (command2) => command2.option("principal-id", { type: "string", description: "Target principal ID through the public API" }).option("id", {
|
|
41121
41516
|
type: "string",
|
|
41122
41517
|
demandOption: true,
|
|
41123
41518
|
description: "Machine key ID"
|
|
41124
41519
|
}), async (flags) => {
|
|
41520
|
+
if (typeof flags["principal-id"] === "string") {
|
|
41521
|
+
console.log(JSON.stringify(await controlPlaneClient().keys.revoke(flags["principal-id"], need(flags, "id")), null, 2));
|
|
41522
|
+
return;
|
|
41523
|
+
}
|
|
41125
41524
|
await withStore(async (store) => {
|
|
41126
41525
|
const id = need(flags, "id");
|
|
41127
41526
|
const revoked = await store.revokeMachineKey(id, new Date);
|
|
@@ -41130,13 +41529,20 @@ function addRevokeCommand(parser2) {
|
|
|
41130
41529
|
});
|
|
41131
41530
|
}
|
|
41132
41531
|
|
|
41532
|
+
// src/commands/keys/revoke-all.ts
|
|
41533
|
+
function addRevokeAllCommand(parser2) {
|
|
41534
|
+
return addAction(parser2, "revoke-all", "Disable a principal and revoke every key atomically", (command2) => command2.option("principal-id", { type: "string", demandOption: true }), async (flags) => {
|
|
41535
|
+
console.log(JSON.stringify(await controlPlaneClient().keys.revokeAll(need(flags, "principal-id")), null, 2));
|
|
41536
|
+
});
|
|
41537
|
+
}
|
|
41538
|
+
|
|
41133
41539
|
// src/commands/keys/index.ts
|
|
41134
41540
|
function addKeyCommands(parser2) {
|
|
41135
|
-
return addResource(parser2, "keys", "Manage machine keys", (commands) => addRevokeCommand(
|
|
41541
|
+
return addResource(parser2, "keys", "Manage machine keys", (commands) => [addIssueCommand, addListCommand2, addRevokeCommand, addRevokeAllCommand].reduce((parser3, add) => add(parser3), commands));
|
|
41136
41542
|
}
|
|
41137
41543
|
|
|
41138
41544
|
// src/commands/pools/list.ts
|
|
41139
|
-
function
|
|
41545
|
+
function addListCommand3(parser2) {
|
|
41140
41546
|
return addAction(parser2, "list", "List warm pool inventory and metrics", (command2) => command2.option("json", { type: "boolean", description: "Print JSON" }), async (flags) => {
|
|
41141
41547
|
const body = await controlPlaneClient().administration.warmPools();
|
|
41142
41548
|
if (flags.json)
|
|
@@ -41153,7 +41559,7 @@ function addListCommand2(parser2) {
|
|
|
41153
41559
|
|
|
41154
41560
|
// src/commands/pools/index.ts
|
|
41155
41561
|
function addPoolCommands(parser2) {
|
|
41156
|
-
return addResource(parser2, "pools", "Inspect operator-managed warm capacity",
|
|
41562
|
+
return addResource(parser2, "pools", "Inspect operator-managed warm capacity", addListCommand3);
|
|
41157
41563
|
}
|
|
41158
41564
|
|
|
41159
41565
|
// src/commands/principals/options.ts
|
|
@@ -41179,7 +41585,7 @@ function addCreateCommand(parser2) {
|
|
|
41179
41585
|
}
|
|
41180
41586
|
|
|
41181
41587
|
// src/commands/principals/list.ts
|
|
41182
|
-
function
|
|
41588
|
+
function addListCommand4(parser2) {
|
|
41183
41589
|
return addAction(parser2, "list", "List principals", unchanged, async () => {
|
|
41184
41590
|
await withStore(async (store) => {
|
|
41185
41591
|
for (const principal of await store.listPrincipals()) {
|
|
@@ -41208,21 +41614,72 @@ function addUpdateCommand(parser2) {
|
|
|
41208
41614
|
|
|
41209
41615
|
// src/commands/principals/index.ts
|
|
41210
41616
|
function addPrincipalCommands(parser2) {
|
|
41211
|
-
return addResource(parser2, "principals", "Manage principals", (commands) =>
|
|
41617
|
+
return addResource(parser2, "principals", "Manage principals", (commands) => addListCommand4(addUpdateCommand(addCreateCommand(commands))));
|
|
41212
41618
|
}
|
|
41213
41619
|
|
|
41214
41620
|
// src/commands/server/process.ts
|
|
41215
41621
|
import { spawn, spawnSync } from "child_process";
|
|
41216
|
-
import { createHash as createHash4, randomUUID as
|
|
41217
|
-
import { closeSync, existsSync as existsSync4, mkdirSync, openSync, readFileSync as
|
|
41622
|
+
import { createHash as createHash4, randomUUID as randomUUID28 } from "crypto";
|
|
41623
|
+
import { closeSync, existsSync as existsSync4, mkdirSync, openSync, readFileSync as readFileSync6, renameSync, rmSync, writeFileSync } from "fs";
|
|
41218
41624
|
import { homedir } from "os";
|
|
41219
41625
|
import { dirname as dirname7, join as join8, resolve as resolve11 } from "path";
|
|
41220
41626
|
|
|
41221
41627
|
// ../server/src/config/config.ts
|
|
41222
41628
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
41223
41629
|
|
|
41630
|
+
// ../server/src/lifecycle/launch-policy.ts
|
|
41631
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
41632
|
+
function launchPolicyConfig(env2) {
|
|
41633
|
+
const url2 = env2.POCKETCODER_LAUNCH_POLICY_URL;
|
|
41634
|
+
const tokenFile = env2.POCKETCODER_LAUNCH_POLICY_TOKEN_FILE;
|
|
41635
|
+
if (!url2 && !tokenFile)
|
|
41636
|
+
return;
|
|
41637
|
+
if (!url2 || !tokenFile)
|
|
41638
|
+
throw new Error("Launch policy requires both URL and token file");
|
|
41639
|
+
const parsed = new URL(url2);
|
|
41640
|
+
const loopback = parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]";
|
|
41641
|
+
if (parsed.username || parsed.password || parsed.hash || parsed.search || parsed.protocol !== "https:" && !(parsed.protocol === "http:" && loopback)) {
|
|
41642
|
+
throw new Error("Launch policy requires HTTPS or a loopback HTTP endpoint");
|
|
41643
|
+
}
|
|
41644
|
+
return { url: url2, tokenFile };
|
|
41645
|
+
}
|
|
41646
|
+
function createLaunchPolicy(url2, token) {
|
|
41647
|
+
return async (workspace) => {
|
|
41648
|
+
try {
|
|
41649
|
+
const response = await fetch(url2, {
|
|
41650
|
+
method: "POST",
|
|
41651
|
+
redirect: "error",
|
|
41652
|
+
signal: AbortSignal.timeout(5000),
|
|
41653
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
41654
|
+
body: JSON.stringify({
|
|
41655
|
+
workspace_id: workspace.id,
|
|
41656
|
+
principal_id: workspace.principalId,
|
|
41657
|
+
external_id: workspace.externalId,
|
|
41658
|
+
template_name: workspace.templateName,
|
|
41659
|
+
launch_mode: workspace.launchMode,
|
|
41660
|
+
metadata: workspace.metadata
|
|
41661
|
+
})
|
|
41662
|
+
});
|
|
41663
|
+
if (response.status !== 200)
|
|
41664
|
+
return false;
|
|
41665
|
+
const decision = await response.json();
|
|
41666
|
+
return typeof decision === "object" && decision !== null && "allowed" in decision && decision.allowed === true && "workspace_id" in decision && decision.workspace_id === workspace.id;
|
|
41667
|
+
} catch {
|
|
41668
|
+
return false;
|
|
41669
|
+
}
|
|
41670
|
+
};
|
|
41671
|
+
}
|
|
41672
|
+
function loadLaunchPolicy(config2) {
|
|
41673
|
+
if (!config2)
|
|
41674
|
+
return;
|
|
41675
|
+
const token = readFileSync4(config2.tokenFile, "utf8").trim();
|
|
41676
|
+
if (token.length < 24 || /\s/.test(token))
|
|
41677
|
+
throw new Error("Invalid launch policy control token");
|
|
41678
|
+
return createLaunchPolicy(config2.url, token);
|
|
41679
|
+
}
|
|
41680
|
+
|
|
41224
41681
|
// ../server/src/persistence/persistence-base.ts
|
|
41225
|
-
import { randomUUID as
|
|
41682
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
41226
41683
|
var DEFAULT_PERSISTENCE_LIMITS = {
|
|
41227
41684
|
maxRetainedBytes: 500 * 1024 ** 3,
|
|
41228
41685
|
maxRetainedBytesPerPrincipal: 100 * 1024 ** 3,
|
|
@@ -41365,7 +41822,7 @@ class PersistenceContext {
|
|
|
41365
41822
|
async emitCheckpointEvent(type, row) {
|
|
41366
41823
|
const at = this.now();
|
|
41367
41824
|
await this.deps.store.appendEvent(row.workspaceId, type, {
|
|
41368
|
-
id:
|
|
41825
|
+
id: randomUUID12(),
|
|
41369
41826
|
type,
|
|
41370
41827
|
occurred_at: at.toISOString(),
|
|
41371
41828
|
checkpoint: {
|
|
@@ -41381,7 +41838,7 @@ class PersistenceContext {
|
|
|
41381
41838
|
}
|
|
41382
41839
|
}
|
|
41383
41840
|
// ../server/src/persistence/persistence-checkpoints.ts
|
|
41384
|
-
import { randomUUID as
|
|
41841
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
41385
41842
|
class PersistenceCheckpointService {
|
|
41386
41843
|
context;
|
|
41387
41844
|
constructor(context) {
|
|
@@ -41400,7 +41857,7 @@ class PersistenceCheckpointService {
|
|
|
41400
41857
|
}
|
|
41401
41858
|
const now = this.context.now();
|
|
41402
41859
|
const inserted = await this.context.insertOperation({
|
|
41403
|
-
id:
|
|
41860
|
+
id: randomUUID13(),
|
|
41404
41861
|
principalId: principal.id,
|
|
41405
41862
|
kind: "verify",
|
|
41406
41863
|
state: "running",
|
|
@@ -41479,7 +41936,7 @@ class PersistenceCheckpointService {
|
|
|
41479
41936
|
});
|
|
41480
41937
|
const at = this.context.now();
|
|
41481
41938
|
await this.context.deps.store.appendEvent(workspaceId, "workspace.output_published", {
|
|
41482
|
-
id:
|
|
41939
|
+
id: randomUUID13(),
|
|
41483
41940
|
type: "workspace.output_published",
|
|
41484
41941
|
occurred_at: at.toISOString(),
|
|
41485
41942
|
workspace_id: workspaceId,
|
|
@@ -41490,7 +41947,7 @@ class PersistenceCheckpointService {
|
|
|
41490
41947
|
}
|
|
41491
41948
|
|
|
41492
41949
|
// ../server/src/persistence/persistence-maintenance.ts
|
|
41493
|
-
import { randomUUID as
|
|
41950
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
41494
41951
|
|
|
41495
41952
|
// ../server/src/persistence/persistence-cleanup.ts
|
|
41496
41953
|
async function cleanupPreservedStorage(context, storageId) {
|
|
@@ -41624,7 +42081,7 @@ class PersistenceMaintenanceService {
|
|
|
41624
42081
|
}
|
|
41625
42082
|
const now = this.context.now();
|
|
41626
42083
|
const inserted = await this.context.insertOperation({
|
|
41627
|
-
id:
|
|
42084
|
+
id: randomUUID14(),
|
|
41628
42085
|
principalId: principal.id,
|
|
41629
42086
|
kind: "delete",
|
|
41630
42087
|
state: "running",
|
|
@@ -41666,7 +42123,7 @@ class PersistenceMaintenanceService {
|
|
|
41666
42123
|
}
|
|
41667
42124
|
|
|
41668
42125
|
// ../server/src/persistence/persistence-preserve.ts
|
|
41669
|
-
import { randomUUID as
|
|
42126
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
41670
42127
|
class PreservePersistenceService {
|
|
41671
42128
|
context;
|
|
41672
42129
|
preserveRunner;
|
|
@@ -41692,8 +42149,8 @@ class PreservePersistenceService {
|
|
|
41692
42149
|
throw new ApiError("validation.invalid", "retention may not exceed the template checkpoint policy");
|
|
41693
42150
|
}
|
|
41694
42151
|
this.context.storageDriver();
|
|
41695
|
-
const checkpointId =
|
|
41696
|
-
const operationId =
|
|
42152
|
+
const checkpointId = randomUUID15();
|
|
42153
|
+
const operationId = randomUUID15();
|
|
41697
42154
|
const now = this.context.now();
|
|
41698
42155
|
const operationResult = await this.context.insertOperation({
|
|
41699
42156
|
id: operationId,
|
|
@@ -41809,11 +42266,7 @@ class PersistencePreserveRunner {
|
|
|
41809
42266
|
const quiesced = native || hook ? await this.context.deps.hub.prepareCheckpoint(workspace.id, operationId, deadlineMs) : false;
|
|
41810
42267
|
if (!workspace.providerRef)
|
|
41811
42268
|
return quiesced;
|
|
41812
|
-
await this.context.deps.driver.
|
|
41813
|
-
kind: workspace.providerKind ?? "",
|
|
41814
|
-
id: "",
|
|
41815
|
-
...workspace.providerRef
|
|
41816
|
-
}, Math.max(1, Math.ceil(parseDurationMs(workspace.templateSnapshot.spec.timeouts.terminateGrace) / 1000)));
|
|
42269
|
+
await stopWorkspaceProvider(this.context.deps.store, this.context.deps.driver, workspace, Math.max(1, Math.ceil(parseDurationMs(workspace.templateSnapshot.spec.timeouts.terminateGrace) / 1000)), this.context.now(), false);
|
|
41817
42270
|
return quiesced;
|
|
41818
42271
|
}
|
|
41819
42272
|
async createSnapshot(workspace, checkpoint, storage) {
|
|
@@ -41922,11 +42375,211 @@ class PersistencePreserveRunner {
|
|
|
41922
42375
|
}
|
|
41923
42376
|
}
|
|
41924
42377
|
|
|
41925
|
-
// ../server/src/persistence/persistence-
|
|
42378
|
+
// ../server/src/persistence/persistence-purge.ts
|
|
41926
42379
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
41927
42380
|
|
|
42381
|
+
// ../server/src/persistence/purge-storage.ts
|
|
42382
|
+
class PurgeOwnershipError extends Error {
|
|
42383
|
+
}
|
|
42384
|
+
function storageTargets(workspace, allocations, physical) {
|
|
42385
|
+
const ids = new Set(allocations.map((row) => row.id));
|
|
42386
|
+
if (physical.some((row) => row.workspaceId === workspace.id && !ids.has(row.storageId))) {
|
|
42387
|
+
throw new PurgeOwnershipError("Unrecorded workspace allocation");
|
|
42388
|
+
}
|
|
42389
|
+
return allocations.map((allocation) => {
|
|
42390
|
+
const found = physical.find((row) => row.storageId === allocation.id);
|
|
42391
|
+
if (allocation.principalId !== workspace.principalId || allocation.state === "quarantined" || found?.workspaceId && found.workspaceId !== workspace.id) {
|
|
42392
|
+
throw new PurgeOwnershipError("Storage ownership is unresolved");
|
|
42393
|
+
}
|
|
42394
|
+
const ref = Object.keys(allocation.providerRef).length ? allocation.providerRef : found?.ref;
|
|
42395
|
+
if (ref && ref.id !== allocation.id)
|
|
42396
|
+
throw new PurgeOwnershipError("Mismatched allocation reference");
|
|
42397
|
+
return { id: allocation.id, ref };
|
|
42398
|
+
});
|
|
42399
|
+
}
|
|
42400
|
+
function checkpointTargets(checkpoints, physical, storageIds) {
|
|
42401
|
+
return checkpoints.map((checkpoint) => {
|
|
42402
|
+
if (!storageIds.has(checkpoint.storageId))
|
|
42403
|
+
throw new PurgeOwnershipError("Checkpoint storage ownership is unresolved");
|
|
42404
|
+
const ref = checkpoint.providerRef ?? physical.find((row) => row.checkpointId === checkpoint.id)?.ref;
|
|
42405
|
+
if (ref && ref.id !== checkpoint.id)
|
|
42406
|
+
throw new PurgeOwnershipError("Mismatched checkpoint reference");
|
|
42407
|
+
return { id: checkpoint.id, ref };
|
|
42408
|
+
});
|
|
42409
|
+
}
|
|
42410
|
+
async function purgeStorage(context, workspace) {
|
|
42411
|
+
const { store, storageDriver } = context.deps;
|
|
42412
|
+
const allocations = await store.listWorkspaceStorage(workspace.id);
|
|
42413
|
+
const checkpoints = await store.listCheckpoints(workspace.principalId, { workspaceId: workspace.id });
|
|
42414
|
+
if (!storageDriver) {
|
|
42415
|
+
if (allocations.length || checkpoints.length || workspace.templateSnapshot.spec.persistence.mounts.length) {
|
|
42416
|
+
throw new Error("Storage driver unavailable");
|
|
42417
|
+
}
|
|
42418
|
+
return;
|
|
42419
|
+
}
|
|
42420
|
+
const physicalStorage = await storageDriver.listStorage();
|
|
42421
|
+
const physicalCheckpoints = await storageDriver.listCheckpoints();
|
|
42422
|
+
for (const physical of physicalStorage) {
|
|
42423
|
+
if (!await store.getStorage(physical.storageId))
|
|
42424
|
+
throw new PurgeOwnershipError("Unrecorded storage requires reconciliation");
|
|
42425
|
+
}
|
|
42426
|
+
for (const physical of physicalCheckpoints) {
|
|
42427
|
+
if (!await store.getCheckpoint(physical.checkpointId))
|
|
42428
|
+
throw new PurgeOwnershipError("Unrecorded checkpoint requires reconciliation");
|
|
42429
|
+
}
|
|
42430
|
+
const allocationIds = new Set(allocations.map((row) => row.id));
|
|
42431
|
+
const storage = storageTargets(workspace, allocations, physicalStorage);
|
|
42432
|
+
const snapshots = checkpointTargets(checkpoints, physicalCheckpoints, allocationIds);
|
|
42433
|
+
for (const checkpoint of snapshots) {
|
|
42434
|
+
if (checkpoint.ref)
|
|
42435
|
+
await storageDriver.deleteCheckpoint(checkpoint.ref);
|
|
42436
|
+
await store.updateCheckpoint(checkpoint.id, { state: "deleted", deletedAt: context.now() }, context.now());
|
|
42437
|
+
}
|
|
42438
|
+
for (const allocation of storage) {
|
|
42439
|
+
if (allocation.ref)
|
|
42440
|
+
await storageDriver.deleteStorage(allocation.ref);
|
|
42441
|
+
await store.updateWorkspaceStorage(allocation.id, { state: "deleted", deletedAt: context.now(), retainedUntil: null, lastErrorCode: null }, context.now());
|
|
42442
|
+
}
|
|
42443
|
+
const checkpointIds = new Set(checkpoints.map((row) => row.id));
|
|
42444
|
+
const remainingStorage = (await storageDriver.listStorage()).some((row) => allocationIds.has(row.storageId) || row.workspaceId === workspace.id);
|
|
42445
|
+
const remainingCheckpoints = (await storageDriver.listCheckpoints()).some((row) => checkpointIds.has(row.checkpointId));
|
|
42446
|
+
if (remainingStorage || remainingCheckpoints)
|
|
42447
|
+
throw new Error("Storage deletion not verified");
|
|
42448
|
+
}
|
|
42449
|
+
|
|
42450
|
+
// ../server/src/persistence/persistence-purge.ts
|
|
42451
|
+
class PersistencePurgeService {
|
|
42452
|
+
context;
|
|
42453
|
+
running = new Map;
|
|
42454
|
+
constructor(context) {
|
|
42455
|
+
this.context = context;
|
|
42456
|
+
}
|
|
42457
|
+
async purge(principal, workspaceId, idempotencyKey) {
|
|
42458
|
+
await this.context.deps.workspaces.getOwned(principal, workspaceId);
|
|
42459
|
+
const at = this.context.now();
|
|
42460
|
+
const result = await this.context.insertOperation({
|
|
42461
|
+
id: randomUUID16(),
|
|
42462
|
+
principalId: principal.id,
|
|
42463
|
+
kind: "purge",
|
|
42464
|
+
state: "pending",
|
|
42465
|
+
idempotencyKey,
|
|
42466
|
+
requestDigest: digestOf({ workspace_id: workspaceId }),
|
|
42467
|
+
workspaceId,
|
|
42468
|
+
checkpointId: null,
|
|
42469
|
+
resultWorkspaceId: null,
|
|
42470
|
+
reasonCode: null,
|
|
42471
|
+
attemptCount: 0,
|
|
42472
|
+
createdAt: at,
|
|
42473
|
+
updatedAt: at,
|
|
42474
|
+
completedAt: null
|
|
42475
|
+
});
|
|
42476
|
+
if (result.conflict)
|
|
42477
|
+
throw new ApiError("idempotency.conflict", "Changed purge request.");
|
|
42478
|
+
if (result.operation.state !== "succeeded")
|
|
42479
|
+
this.run(result.operation).catch(() => {});
|
|
42480
|
+
return result.operation;
|
|
42481
|
+
}
|
|
42482
|
+
async retry() {
|
|
42483
|
+
const operations = await this.context.deps.store.listIncompleteOperations();
|
|
42484
|
+
await Promise.all(operations.filter((operation) => operation.kind === "purge").map((operation) => this.run(operation)));
|
|
42485
|
+
return (await this.context.deps.store.listIncompleteOperations()).filter((operation) => operation.kind === "purge").length;
|
|
42486
|
+
}
|
|
42487
|
+
run(operation) {
|
|
42488
|
+
const workspaceId = operation.workspaceId;
|
|
42489
|
+
if (!workspaceId)
|
|
42490
|
+
return Promise.resolve();
|
|
42491
|
+
const active = this.running.get(workspaceId);
|
|
42492
|
+
if (active)
|
|
42493
|
+
return active;
|
|
42494
|
+
const task = this.attempt(operation, workspaceId).finally(() => this.running.delete(workspaceId));
|
|
42495
|
+
this.running.set(workspaceId, task);
|
|
42496
|
+
return task;
|
|
42497
|
+
}
|
|
42498
|
+
async attempt(operation, workspaceId) {
|
|
42499
|
+
const { store, scheduler, driver, hub } = this.context.deps;
|
|
42500
|
+
let reason = "purge_termination_unresolved";
|
|
42501
|
+
try {
|
|
42502
|
+
await store.updateOperation(operation.id, { state: "running", reasonCode: null, attemptCount: operation.attemptCount + 1 }, this.context.now());
|
|
42503
|
+
await scheduler.drain();
|
|
42504
|
+
const workspace = await store.getWorkspace(workspaceId);
|
|
42505
|
+
if (!workspace)
|
|
42506
|
+
throw new Error("Workspace missing");
|
|
42507
|
+
if (await this.hasActiveCopies(workspaceId, workspace.state === "queued")) {
|
|
42508
|
+
reason = "purge_operation_in_progress";
|
|
42509
|
+
throw new Error(reason);
|
|
42510
|
+
}
|
|
42511
|
+
if (workspace.state === "preserving") {
|
|
42512
|
+
reason = "purge_operation_in_progress";
|
|
42513
|
+
throw new Error(reason);
|
|
42514
|
+
}
|
|
42515
|
+
if (driver.kind === "kubernetes" && (workspace.launchAttempts > 0 || workspace.providerRef)) {
|
|
42516
|
+
await stopWorkspaceProvider(store, driver, workspace, 1, this.context.now(), false);
|
|
42517
|
+
const stopped = await store.getWorkspace(workspaceId);
|
|
42518
|
+
if (!stopped?.providerRef?.terminationEvidence)
|
|
42519
|
+
throw new Error(reason);
|
|
42520
|
+
}
|
|
42521
|
+
hub.close(workspaceId);
|
|
42522
|
+
if (!isTerminal(workspace.state)) {
|
|
42523
|
+
if (workspace.state === "queued") {
|
|
42524
|
+
await store.transition(workspaceId, {
|
|
42525
|
+
from: ["queued"],
|
|
42526
|
+
to: "canceled",
|
|
42527
|
+
reason: "canceled_by_caller",
|
|
42528
|
+
at: this.context.now()
|
|
42529
|
+
});
|
|
42530
|
+
} else {
|
|
42531
|
+
await scheduler.finalize(workspace, "canceled", "canceled_by_caller", this.context.now());
|
|
42532
|
+
if (!(await store.getWorkspace(workspaceId))?.terminalAt)
|
|
42533
|
+
throw new Error(reason);
|
|
42534
|
+
}
|
|
42535
|
+
}
|
|
42536
|
+
const current = await store.getWorkspace(workspaceId);
|
|
42537
|
+
if (!current)
|
|
42538
|
+
throw new Error("Workspace missing");
|
|
42539
|
+
await stopWorkspaceProvider(store, driver, current, 1, this.context.now());
|
|
42540
|
+
const providers = (await driver.list()).filter((provider) => provider.workspaceId === workspaceId);
|
|
42541
|
+
if (providers.length)
|
|
42542
|
+
throw new Error(reason);
|
|
42543
|
+
reason = "purge_storage_unavailable";
|
|
42544
|
+
await driver.purgeInput(workspaceId);
|
|
42545
|
+
await purgeStorage(this.context, workspace);
|
|
42546
|
+
await store.purgeWorkspaceContent(workspaceId, this.context.now());
|
|
42547
|
+
const done = this.context.now();
|
|
42548
|
+
await store.updateOperation(operation.id, { state: "succeeded", reasonCode: null, completedAt: done }, done);
|
|
42549
|
+
} catch (error51) {
|
|
42550
|
+
if (error51 instanceof PurgeOwnershipError)
|
|
42551
|
+
reason = "purge_ownership_unresolved";
|
|
42552
|
+
await store.updateOperation(operation.id, { state: "pending", reasonCode: reason, completedAt: null }, this.context.now());
|
|
42553
|
+
}
|
|
42554
|
+
}
|
|
42555
|
+
async hasActiveCopies(workspaceId, queued) {
|
|
42556
|
+
const operations = await this.context.deps.store.listIncompleteOperations();
|
|
42557
|
+
let active = false;
|
|
42558
|
+
for (const operation of operations) {
|
|
42559
|
+
if (operation.kind === "purge")
|
|
42560
|
+
continue;
|
|
42561
|
+
if (operation.kind === "restore" && operation.resultWorkspaceId) {
|
|
42562
|
+
const target = await this.context.deps.store.getWorkspace(operation.resultWorkspaceId);
|
|
42563
|
+
if (target && isTerminal(target.state)) {
|
|
42564
|
+
await this.context.failOperation(operation.id, "restore_failed");
|
|
42565
|
+
continue;
|
|
42566
|
+
}
|
|
42567
|
+
}
|
|
42568
|
+
if (queued && operation.kind === "restore" && operation.resultWorkspaceId === workspaceId) {
|
|
42569
|
+
await this.context.failOperation(operation.id, "canceled_by_caller");
|
|
42570
|
+
} else if (operation.workspaceId === workspaceId || operation.resultWorkspaceId === workspaceId) {
|
|
42571
|
+
active = true;
|
|
42572
|
+
}
|
|
42573
|
+
}
|
|
42574
|
+
return active;
|
|
42575
|
+
}
|
|
42576
|
+
}
|
|
42577
|
+
|
|
42578
|
+
// ../server/src/persistence/persistence-restore.ts
|
|
42579
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
42580
|
+
|
|
41928
42581
|
// ../server/src/workspaces/service.ts
|
|
41929
|
-
import { randomUUID as
|
|
42582
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
41930
42583
|
function templateAuthorized(principal, templateName) {
|
|
41931
42584
|
return principal.templateNames.includes("*") || principal.templateNames.includes(templateName);
|
|
41932
42585
|
}
|
|
@@ -42041,7 +42694,7 @@ class WorkspaceService {
|
|
|
42041
42694
|
this.validateCreateInput(template, body);
|
|
42042
42695
|
const now = this.now();
|
|
42043
42696
|
const result = await store.insertWorkspace({
|
|
42044
|
-
id:
|
|
42697
|
+
id: randomUUID17(),
|
|
42045
42698
|
principalId: principal.id,
|
|
42046
42699
|
externalId: body.external_id,
|
|
42047
42700
|
idempotencyKey,
|
|
@@ -42150,9 +42803,9 @@ class PersistenceRestoreService {
|
|
|
42150
42803
|
}
|
|
42151
42804
|
this.context.storageDriver();
|
|
42152
42805
|
const now = this.context.now();
|
|
42153
|
-
const resultWorkspaceId =
|
|
42806
|
+
const resultWorkspaceId = randomUUID18();
|
|
42154
42807
|
const operationResult = await this.context.insertOperation({
|
|
42155
|
-
id:
|
|
42808
|
+
id: randomUUID18(),
|
|
42156
42809
|
principalId: principal.id,
|
|
42157
42810
|
kind: "restore",
|
|
42158
42811
|
state: "pending",
|
|
@@ -42213,7 +42866,7 @@ class PersistenceRestoreService {
|
|
|
42213
42866
|
}
|
|
42214
42867
|
await this.context.deps.store.updateOperation(operationResult.operation.id, { resultWorkspaceId: inserted.workspace.id }, now);
|
|
42215
42868
|
await this.context.deps.store.appendEvent(checkpoint.workspaceId, "workspace.restore_queued", {
|
|
42216
|
-
id:
|
|
42869
|
+
id: randomUUID18(),
|
|
42217
42870
|
type: "workspace.restore_queued",
|
|
42218
42871
|
occurred_at: now.toISOString(),
|
|
42219
42872
|
origin_workspace_id: checkpoint.workspaceId,
|
|
@@ -42232,6 +42885,9 @@ class PersistenceRestoreService {
|
|
|
42232
42885
|
class PersistenceService {
|
|
42233
42886
|
constructor(deps) {
|
|
42234
42887
|
this.context = new PersistenceContext(deps);
|
|
42888
|
+
const purge = new PersistencePurgeService(this.context);
|
|
42889
|
+
this.purge = purge.purge.bind(purge);
|
|
42890
|
+
this.retryPurges = purge.retry.bind(purge);
|
|
42235
42891
|
this.preserveRunner = new PersistencePreserveRunner(this.context);
|
|
42236
42892
|
this.preservePersistence = new PreservePersistenceService(this.context, this.preserveRunner);
|
|
42237
42893
|
this.checkpoint = new PersistenceCheckpointService(this.context);
|
|
@@ -42248,6 +42904,8 @@ class PersistenceService {
|
|
|
42248
42904
|
this.delete = this.maintenance.delete.bind(this.maintenance);
|
|
42249
42905
|
this.restore = this.restoreService.restore.bind(this.restoreService);
|
|
42250
42906
|
}
|
|
42907
|
+
purge;
|
|
42908
|
+
retryPurges;
|
|
42251
42909
|
restoreService;
|
|
42252
42910
|
maintenance;
|
|
42253
42911
|
checkpoint;
|
|
@@ -42598,6 +43256,10 @@ class DockerDriver {
|
|
|
42598
43256
|
await rm(this.egressInputPath(workspaceId), { force: true });
|
|
42599
43257
|
}
|
|
42600
43258
|
}
|
|
43259
|
+
async purgeInput(workspaceId) {
|
|
43260
|
+
await rm(this.inputPath(workspaceId), { force: true });
|
|
43261
|
+
await rm(this.egressInputPath(workspaceId), { force: true });
|
|
43262
|
+
}
|
|
42601
43263
|
async cleanupInput(workspaceId) {
|
|
42602
43264
|
await rm(this.inputPath(workspaceId), { force: true });
|
|
42603
43265
|
}
|
|
@@ -42893,7 +43555,7 @@ async function restoreCheckpointContent(checkpointRoot, target, manifest) {
|
|
|
42893
43555
|
}
|
|
42894
43556
|
|
|
42895
43557
|
// ../drivers/src/filesystem/filesystem-inventory.ts
|
|
42896
|
-
import {
|
|
43558
|
+
import { readdir as readdir3, readFile, rm as rm3 } from "fs/promises";
|
|
42897
43559
|
import { join as join6, resolve as resolve8 } from "path";
|
|
42898
43560
|
var STORAGE_METADATA_FILE = ".pocketcoder-storage.json";
|
|
42899
43561
|
var CHECKPOINT_METADATA_FILE = ".pocketcoder-checkpoint.json";
|
|
@@ -42921,15 +43583,13 @@ async function discoverStorage(rootDirectory) {
|
|
|
42921
43583
|
}
|
|
42922
43584
|
async function discoverCheckpoints(rootDirectory) {
|
|
42923
43585
|
const result = [];
|
|
42924
|
-
for (const
|
|
43586
|
+
for (const entry of await readdir3(rootDirectory)) {
|
|
43587
|
+
const id = entry.replace(/^\.creating-/, "");
|
|
42925
43588
|
if (!/^[0-9a-f-]{36}$/i.test(id))
|
|
42926
43589
|
continue;
|
|
42927
43590
|
const root = childOf(rootDirectory, id);
|
|
42928
|
-
|
|
42929
|
-
const handle = await open(join6(root, CHECKPOINT_METADATA_FILE), "r");
|
|
42930
|
-
await handle.close();
|
|
43591
|
+
if (!result.some((row) => row.checkpointId === id))
|
|
42931
43592
|
result.push({ checkpointId: id, ref: { kind: "filesystem", id, root } });
|
|
42932
|
-
} catch {}
|
|
42933
43593
|
}
|
|
42934
43594
|
return result;
|
|
42935
43595
|
}
|
|
@@ -43159,6 +43819,7 @@ class FilesystemStorageDriver {
|
|
|
43159
43819
|
async deleteCheckpoint(ref) {
|
|
43160
43820
|
const checkpoint = this.checkpointRef(ref);
|
|
43161
43821
|
await deleteCheckpointRoot(checkpoint.root);
|
|
43822
|
+
await deleteCheckpointRoot(join7(this.checkpointRoot, `.creating-${checkpoint.id}`));
|
|
43162
43823
|
}
|
|
43163
43824
|
async listStorage() {
|
|
43164
43825
|
await this.initRoots();
|
|
@@ -43253,6 +43914,232 @@ function discoveredWarmRuntimes(output, kind, namespace) {
|
|
|
43253
43914
|
});
|
|
43254
43915
|
}
|
|
43255
43916
|
|
|
43917
|
+
// ../drivers/src/kubernetes/kubernetes-evidence.ts
|
|
43918
|
+
var EVIDENCE_FINALIZER = "pocketcoder.dev/termination-evidence";
|
|
43919
|
+
var EVIDENCE_ANNOTATION = "pocketcoder.dev/termination-evidence";
|
|
43920
|
+
var NODE_ANNOTATION = "pocketcoder.dev/termination-node";
|
|
43921
|
+
async function patch(run, kind, name2, value) {
|
|
43922
|
+
await run(["patch", kind, name2, "--type=merge", "-p", JSON.stringify(value)]);
|
|
43923
|
+
}
|
|
43924
|
+
async function podsFor(run, name2) {
|
|
43925
|
+
const result = JSON.parse(await run(["get", "pods", "-l", `job-name=${name2}`, "-o", "json"]));
|
|
43926
|
+
return result.items;
|
|
43927
|
+
}
|
|
43928
|
+
function owned(pod, uid) {
|
|
43929
|
+
return pod.metadata.ownerReferences?.some((owner) => owner.uid === uid && owner.kind === "Job" && owner.controller);
|
|
43930
|
+
}
|
|
43931
|
+
function stopped(pod) {
|
|
43932
|
+
const groups = [
|
|
43933
|
+
[pod.spec.containers, pod.status?.containerStatuses],
|
|
43934
|
+
[pod.spec.initContainers, pod.status?.initContainerStatuses],
|
|
43935
|
+
[pod.spec.ephemeralContainers, pod.status?.ephemeralContainerStatuses]
|
|
43936
|
+
];
|
|
43937
|
+
if (!pod.spec.containers?.length)
|
|
43938
|
+
return false;
|
|
43939
|
+
if (!pod.spec.nodeName && pod.metadata.deletionTimestamp)
|
|
43940
|
+
return groups.every(([, statuses]) => !statuses?.length);
|
|
43941
|
+
return groups.every(([spec, statuses]) => (spec?.length ?? 0) === (statuses?.length ?? 0) && (spec ?? []).every((container) => {
|
|
43942
|
+
const status = statuses?.find((candidate) => candidate.name === container.name);
|
|
43943
|
+
return status?.containerID && status.state.terminated?.containerID === status.containerID && !["ContainerStatusUnknown", "NodeLost"].includes(status.state.terminated?.reason ?? "") && status.state.terminated?.finishedAt && Number.isInteger(status.state.terminated.exitCode);
|
|
43944
|
+
}));
|
|
43945
|
+
}
|
|
43946
|
+
function podEvidence(pod) {
|
|
43947
|
+
return {
|
|
43948
|
+
metadata: {
|
|
43949
|
+
uid: pod.metadata.uid,
|
|
43950
|
+
ownerReferences: pod.metadata.ownerReferences,
|
|
43951
|
+
deletionTimestamp: pod.metadata.deletionTimestamp
|
|
43952
|
+
},
|
|
43953
|
+
spec: {
|
|
43954
|
+
nodeName: pod.spec.nodeName,
|
|
43955
|
+
containers: pod.spec.containers?.map(({ name: name2 }) => ({ name: name2 })),
|
|
43956
|
+
initContainers: pod.spec.initContainers?.map(({ name: name2 }) => ({ name: name2 })),
|
|
43957
|
+
ephemeralContainers: pod.spec.ephemeralContainers?.map(({ name: name2 }) => ({ name: name2 }))
|
|
43958
|
+
},
|
|
43959
|
+
status: {
|
|
43960
|
+
containerStatuses: statusEvidence(pod.status?.containerStatuses),
|
|
43961
|
+
initContainerStatuses: statusEvidence(pod.status?.initContainerStatuses),
|
|
43962
|
+
ephemeralContainerStatuses: statusEvidence(pod.status?.ephemeralContainerStatuses)
|
|
43963
|
+
}
|
|
43964
|
+
};
|
|
43965
|
+
}
|
|
43966
|
+
function statusEvidence(statuses) {
|
|
43967
|
+
return statuses?.map((status) => ({
|
|
43968
|
+
name: status.name,
|
|
43969
|
+
containerID: status.containerID,
|
|
43970
|
+
state: {
|
|
43971
|
+
terminated: {
|
|
43972
|
+
exitCode: status.state.terminated?.exitCode,
|
|
43973
|
+
finishedAt: status.state.terminated?.finishedAt,
|
|
43974
|
+
containerID: status.state.terminated?.containerID,
|
|
43975
|
+
reason: status.state.terminated?.reason
|
|
43976
|
+
}
|
|
43977
|
+
}
|
|
43978
|
+
}));
|
|
43979
|
+
}
|
|
43980
|
+
async function updatePod(run, pod, update) {
|
|
43981
|
+
const uid = pod.metadata.uid;
|
|
43982
|
+
const owner = pod.metadata.ownerReferences?.find((item) => item.kind === "Job" && item.controller)?.uid;
|
|
43983
|
+
for (let attempt = 0;; attempt++) {
|
|
43984
|
+
try {
|
|
43985
|
+
return await update(pod);
|
|
43986
|
+
} catch (error51) {
|
|
43987
|
+
if (attempt >= 4 || !(error51 instanceof Error) || !error51.message.includes("Error from server (Conflict)"))
|
|
43988
|
+
throw error51;
|
|
43989
|
+
await Bun.sleep(25 * (attempt + 1));
|
|
43990
|
+
pod = JSON.parse(await run(["get", "pod", pod.metadata.name, "-o", "json"]));
|
|
43991
|
+
if (!uid || pod.metadata.uid !== uid || !owner || !owned(pod, owner))
|
|
43992
|
+
throw new Error("Termination provider changed");
|
|
43993
|
+
}
|
|
43994
|
+
}
|
|
43995
|
+
}
|
|
43996
|
+
async function retain(run, pod) {
|
|
43997
|
+
return updatePod(run, pod, (current) => retainCurrent(run, current));
|
|
43998
|
+
}
|
|
43999
|
+
async function retainCurrent(run, pod) {
|
|
44000
|
+
if (!pod.metadata.name || !pod.metadata.uid)
|
|
44001
|
+
throw new Error("Termination evidence unavailable");
|
|
44002
|
+
if (!pod.spec.nodeName) {
|
|
44003
|
+
await patch(run, "pod", pod.metadata.name, {
|
|
44004
|
+
metadata: {
|
|
44005
|
+
uid: pod.metadata.uid,
|
|
44006
|
+
resourceVersion: pod.metadata.resourceVersion,
|
|
44007
|
+
finalizers: [...new Set([...pod.metadata.finalizers ?? [], EVIDENCE_FINALIZER])]
|
|
44008
|
+
}
|
|
44009
|
+
});
|
|
44010
|
+
return null;
|
|
44011
|
+
}
|
|
44012
|
+
const cached2 = pod.metadata.annotations?.[NODE_ANNOTATION];
|
|
44013
|
+
const node = cached2 ? JSON.parse(cached2) : JSON.parse(await run(["get", "node", pod.spec.nodeName, "-o", "json"]));
|
|
44014
|
+
if (!node.metadata.uid || !node.spec.providerID)
|
|
44015
|
+
throw new Error("Termination node identity unavailable");
|
|
44016
|
+
const identity = {
|
|
44017
|
+
metadata: { uid: node.metadata.uid, deletionTimestamp: node.metadata.deletionTimestamp },
|
|
44018
|
+
spec: { providerID: node.spec.providerID },
|
|
44019
|
+
status: { conditions: node.status?.conditions?.filter((condition) => condition.type === "Ready") }
|
|
44020
|
+
};
|
|
44021
|
+
await patch(run, "pod", pod.metadata.name, {
|
|
44022
|
+
metadata: {
|
|
44023
|
+
uid: pod.metadata.uid,
|
|
44024
|
+
resourceVersion: pod.metadata.resourceVersion,
|
|
44025
|
+
finalizers: [...new Set([...pod.metadata.finalizers ?? [], EVIDENCE_FINALIZER])],
|
|
44026
|
+
annotations: { [NODE_ANNOTATION]: JSON.stringify(identity) }
|
|
44027
|
+
}
|
|
44028
|
+
});
|
|
44029
|
+
return identity;
|
|
44030
|
+
}
|
|
44031
|
+
async function retainNodeIdentities(run, name2, jobUid) {
|
|
44032
|
+
const pods = await podsFor(run, name2);
|
|
44033
|
+
if (pods.some((pod) => !owned(pod, jobUid)))
|
|
44034
|
+
throw new Error("Termination provider changed");
|
|
44035
|
+
for (const pod of pods) {
|
|
44036
|
+
if (pod.spec.nodeName && !pod.metadata.annotations?.[NODE_ANNOTATION])
|
|
44037
|
+
await retain(run, pod);
|
|
44038
|
+
}
|
|
44039
|
+
}
|
|
44040
|
+
async function retainNodes(run, pods, nodes) {
|
|
44041
|
+
for (const pod of pods) {
|
|
44042
|
+
if (pod.spec.nodeName && nodes[pod.spec.nodeName] && pod.metadata.finalizers?.includes(EVIDENCE_FINALIZER))
|
|
44043
|
+
continue;
|
|
44044
|
+
const node = await retain(run, pod);
|
|
44045
|
+
if (pod.spec.nodeName)
|
|
44046
|
+
nodes[pod.spec.nodeName] = node;
|
|
44047
|
+
}
|
|
44048
|
+
}
|
|
44049
|
+
async function releasePods(run, name2, jobUid) {
|
|
44050
|
+
for (const pod of await podsFor(run, name2)) {
|
|
44051
|
+
if (!owned(pod, jobUid) || !pod.metadata.finalizers?.includes(EVIDENCE_FINALIZER))
|
|
44052
|
+
continue;
|
|
44053
|
+
await updatePod(run, pod, (current) => patch(run, "pod", current.metadata.name, {
|
|
44054
|
+
metadata: {
|
|
44055
|
+
uid: current.metadata.uid,
|
|
44056
|
+
resourceVersion: current.metadata.resourceVersion,
|
|
44057
|
+
finalizers: current.metadata.finalizers?.filter((item) => item !== EVIDENCE_FINALIZER) ?? []
|
|
44058
|
+
}
|
|
44059
|
+
}));
|
|
44060
|
+
}
|
|
44061
|
+
}
|
|
44062
|
+
async function stoppedJob(run, name2, uid, deadline) {
|
|
44063
|
+
while (true) {
|
|
44064
|
+
const job = JSON.parse(await run(["get", "job", name2, "-o", "json"]));
|
|
44065
|
+
if (job.metadata.uid !== uid)
|
|
44066
|
+
throw new Error("Termination provider changed");
|
|
44067
|
+
const conditions = job.status?.conditions ?? [];
|
|
44068
|
+
const terminal = conditions.some((item) => ["Complete", "Failed"].includes(item.type) && item.status === "True");
|
|
44069
|
+
const suspended = job.spec.suspend && conditions.some((item) => item.type === "Suspended" && item.status === "True");
|
|
44070
|
+
if (terminal || suspended)
|
|
44071
|
+
return job;
|
|
44072
|
+
if (Date.now() >= deadline)
|
|
44073
|
+
throw new Error("Job suspension unconfirmed");
|
|
44074
|
+
await Bun.sleep(100);
|
|
44075
|
+
}
|
|
44076
|
+
}
|
|
44077
|
+
async function captureTermination(run, name2, graceSeconds) {
|
|
44078
|
+
const output = await run(["get", "job", name2, "--ignore-not-found", "-o", "json"]);
|
|
44079
|
+
if (!output)
|
|
44080
|
+
return;
|
|
44081
|
+
const job = JSON.parse(output);
|
|
44082
|
+
if (!job.metadata.uid)
|
|
44083
|
+
throw new Error("Termination evidence unavailable");
|
|
44084
|
+
if (job.metadata.annotations?.[EVIDENCE_ANNOTATION]) {
|
|
44085
|
+
await releasePods(run, name2, job.metadata.uid);
|
|
44086
|
+
return;
|
|
44087
|
+
}
|
|
44088
|
+
const initial = await podsFor(run, name2);
|
|
44089
|
+
if (!initial.length || initial.some((pod) => !owned(pod, job.metadata.uid)))
|
|
44090
|
+
throw new Error("Termination evidence unavailable");
|
|
44091
|
+
const nodes = {};
|
|
44092
|
+
await retainNodes(run, initial, nodes);
|
|
44093
|
+
await patch(run, "job", name2, { metadata: { uid: job.metadata.uid }, spec: { suspend: true } });
|
|
44094
|
+
const confirmed = await stoppedJob(run, name2, job.metadata.uid, Date.now() + (graceSeconds + 5) * 1000);
|
|
44095
|
+
const retained = await podsFor(run, name2);
|
|
44096
|
+
if (retained.length !== initial.length || retained.some((pod) => !initial.some((old) => old.metadata.uid === pod.metadata.uid)))
|
|
44097
|
+
throw new Error("Termination provider changed");
|
|
44098
|
+
await run([
|
|
44099
|
+
"delete",
|
|
44100
|
+
"pod",
|
|
44101
|
+
"-l",
|
|
44102
|
+
`job-name=${name2}`,
|
|
44103
|
+
`--grace-period=${graceSeconds}`,
|
|
44104
|
+
"--wait=false",
|
|
44105
|
+
"--ignore-not-found"
|
|
44106
|
+
]);
|
|
44107
|
+
const deadline = Date.now() + (graceSeconds + 5) * 1000;
|
|
44108
|
+
while (true) {
|
|
44109
|
+
const pods = await podsFor(run, name2);
|
|
44110
|
+
if (pods.length !== retained.length || pods.some((pod) => !retained.some((old) => old.metadata.uid === pod.metadata.uid)))
|
|
44111
|
+
throw new Error("Termination provider disappeared without evidence");
|
|
44112
|
+
await retainNodes(run, pods, nodes);
|
|
44113
|
+
if (pods.every(stopped)) {
|
|
44114
|
+
const proof = {
|
|
44115
|
+
job: {
|
|
44116
|
+
metadata: { uid: job.metadata.uid, labels: job.metadata.labels },
|
|
44117
|
+
spec: { suspend: confirmed.spec.suspend },
|
|
44118
|
+
status: { conditions: confirmed.status?.conditions }
|
|
44119
|
+
},
|
|
44120
|
+
pods: pods.map(podEvidence),
|
|
44121
|
+
nodes
|
|
44122
|
+
};
|
|
44123
|
+
await patch(run, "job", name2, {
|
|
44124
|
+
metadata: { uid: job.metadata.uid, annotations: { [EVIDENCE_ANNOTATION]: JSON.stringify(proof) } }
|
|
44125
|
+
});
|
|
44126
|
+
await releasePods(run, name2, job.metadata.uid);
|
|
44127
|
+
return;
|
|
44128
|
+
}
|
|
44129
|
+
if (Date.now() >= deadline)
|
|
44130
|
+
throw new Error("Termination evidence unavailable");
|
|
44131
|
+
await Bun.sleep(100);
|
|
44132
|
+
}
|
|
44133
|
+
}
|
|
44134
|
+
async function readTerminationEvidence(run, name2) {
|
|
44135
|
+
const output = await run(["get", "job", name2, "--ignore-not-found", "-o", "json"]);
|
|
44136
|
+
if (!output)
|
|
44137
|
+
return null;
|
|
44138
|
+
const job = JSON.parse(output);
|
|
44139
|
+
const value = job.metadata.annotations?.[EVIDENCE_ANNOTATION];
|
|
44140
|
+
return value ? JSON.parse(value) : null;
|
|
44141
|
+
}
|
|
44142
|
+
|
|
43256
44143
|
// ../drivers/src/kubernetes/kubernetes-scheduling.ts
|
|
43257
44144
|
function schedulingFields(options) {
|
|
43258
44145
|
return {
|
|
@@ -43401,7 +44288,7 @@ function workspaceJobManifest(launch, name2, inputSecret, egressSecret, options)
|
|
|
43401
44288
|
backoffLimit: 0,
|
|
43402
44289
|
ttlSecondsAfterFinished: 3600,
|
|
43403
44290
|
template: {
|
|
43404
|
-
metadata: { labels, annotations },
|
|
44291
|
+
metadata: { labels, annotations, ...options.podFinalizers ? { finalizers: options.podFinalizers } : {} },
|
|
43405
44292
|
spec: {
|
|
43406
44293
|
restartPolicy: "Never",
|
|
43407
44294
|
automountServiceAccountToken: false,
|
|
@@ -43468,7 +44355,7 @@ function warmJobManifest(launch, name2, inputSecret, egressSecret, options) {
|
|
|
43468
44355
|
backoffLimit: 0,
|
|
43469
44356
|
ttlSecondsAfterFinished: 3600,
|
|
43470
44357
|
template: {
|
|
43471
|
-
metadata: { labels, annotations },
|
|
44358
|
+
metadata: { labels, annotations, ...options.podFinalizers ? { finalizers: options.podFinalizers } : {} },
|
|
43472
44359
|
spec: {
|
|
43473
44360
|
restartPolicy: "Never",
|
|
43474
44361
|
automountServiceAccountToken: false,
|
|
@@ -43518,6 +44405,22 @@ function warmJobManifest(launch, name2, inputSecret, egressSecret, options) {
|
|
|
43518
44405
|
};
|
|
43519
44406
|
}
|
|
43520
44407
|
|
|
44408
|
+
// ../drivers/src/kubernetes/kubernetes-stop.ts
|
|
44409
|
+
async function stopKubernetesJob(run, name2, graceSeconds) {
|
|
44410
|
+
const job = await run(["get", "job", name2, "--ignore-not-found", "-o", "name"]);
|
|
44411
|
+
if (job)
|
|
44412
|
+
await run(["patch", "job", name2, "--type=merge", "-p", '{"spec":{"suspend":true}}']);
|
|
44413
|
+
await run([
|
|
44414
|
+
"delete",
|
|
44415
|
+
"pod",
|
|
44416
|
+
"-l",
|
|
44417
|
+
`job-name=${name2}`,
|
|
44418
|
+
`--grace-period=${graceSeconds}`,
|
|
44419
|
+
"--wait=true",
|
|
44420
|
+
"--ignore-not-found"
|
|
44421
|
+
]);
|
|
44422
|
+
}
|
|
44423
|
+
|
|
43521
44424
|
// ../drivers/src/kubernetes/kubernetes.ts
|
|
43522
44425
|
class KubernetesDriver {
|
|
43523
44426
|
kind = "kubernetes";
|
|
@@ -43529,7 +44432,9 @@ class KubernetesDriver {
|
|
|
43529
44432
|
imagePullPolicy;
|
|
43530
44433
|
egress;
|
|
43531
44434
|
sidecarsSupported = false;
|
|
44435
|
+
captureEvidence;
|
|
43532
44436
|
constructor(options = {}) {
|
|
44437
|
+
this.captureEvidence = options.captureTerminationEvidence ?? false;
|
|
43533
44438
|
this.namespace = options.namespace ?? "default";
|
|
43534
44439
|
if (!isKubernetesName(this.namespace)) {
|
|
43535
44440
|
throw new Error("namespace must be a Kubernetes resource name");
|
|
@@ -43597,6 +44502,7 @@ class KubernetesDriver {
|
|
|
43597
44502
|
nodeSelector: this.nodeSelector,
|
|
43598
44503
|
tolerations: this.tolerations,
|
|
43599
44504
|
imagePullPolicy: this.imagePullPolicy,
|
|
44505
|
+
...this.captureEvidence ? { podFinalizers: [EVIDENCE_FINALIZER] } : {},
|
|
43600
44506
|
egressImage: this.egress.egressImage
|
|
43601
44507
|
});
|
|
43602
44508
|
try {
|
|
@@ -43650,6 +44556,7 @@ class KubernetesDriver {
|
|
|
43650
44556
|
nodeSelector: this.nodeSelector,
|
|
43651
44557
|
tolerations: this.tolerations,
|
|
43652
44558
|
imagePullPolicy: this.imagePullPolicy,
|
|
44559
|
+
...this.captureEvidence ? { podFinalizers: [EVIDENCE_FINALIZER] } : {},
|
|
43653
44560
|
egressImage: this.egress.egressImage
|
|
43654
44561
|
});
|
|
43655
44562
|
try {
|
|
@@ -43672,38 +44579,36 @@ class KubernetesDriver {
|
|
|
43672
44579
|
}
|
|
43673
44580
|
}
|
|
43674
44581
|
async inspect(ref) {
|
|
43675
|
-
|
|
43676
|
-
|
|
43677
|
-
|
|
43678
|
-
|
|
43679
|
-
|
|
43680
|
-
|
|
43681
|
-
|
|
43682
|
-
|
|
43683
|
-
|
|
43684
|
-
};
|
|
43685
|
-
} catch {
|
|
44582
|
+
const output = await kubectl(this.kubectlBin, this.namespace, [
|
|
44583
|
+
"get",
|
|
44584
|
+
"job",
|
|
44585
|
+
ref.id,
|
|
44586
|
+
"--ignore-not-found",
|
|
44587
|
+
"-o",
|
|
44588
|
+
"json"
|
|
44589
|
+
]);
|
|
44590
|
+
if (!output)
|
|
43686
44591
|
return { exists: false, running: false, exitCode: null };
|
|
43687
|
-
|
|
44592
|
+
const job = JSON.parse(output);
|
|
44593
|
+
if (this.captureEvidence) {
|
|
44594
|
+
if (!job.metadata?.uid)
|
|
44595
|
+
throw new Error("Termination evidence unavailable");
|
|
44596
|
+
await retainNodeIdentities((args) => kubectl(this.kubectlBin, this.namespace, args), ref.id, job.metadata.uid);
|
|
44597
|
+
}
|
|
44598
|
+
const running = (job.status?.active ?? 0) > 0;
|
|
44599
|
+
const completedExitCode = job.status?.succeeded ? 0 : 1;
|
|
44600
|
+
return {
|
|
44601
|
+
exists: true,
|
|
44602
|
+
running,
|
|
44603
|
+
exitCode: running || !(job.status?.succeeded || job.status?.failed) ? null : completedExitCode
|
|
44604
|
+
};
|
|
43688
44605
|
}
|
|
43689
44606
|
async stop(ref, graceSeconds) {
|
|
43690
|
-
|
|
43691
|
-
|
|
43692
|
-
|
|
43693
|
-
|
|
43694
|
-
|
|
43695
|
-
"-p",
|
|
43696
|
-
'{"spec":{"suspend":true}}'
|
|
43697
|
-
]).catch(() => {});
|
|
43698
|
-
await kubectl(this.kubectlBin, this.namespace, [
|
|
43699
|
-
"delete",
|
|
43700
|
-
"pod",
|
|
43701
|
-
"-l",
|
|
43702
|
-
`job-name=${ref.id}`,
|
|
43703
|
-
`--grace-period=${graceSeconds}`,
|
|
43704
|
-
"--wait=true",
|
|
43705
|
-
"--ignore-not-found"
|
|
43706
|
-
]).catch(() => {});
|
|
44607
|
+
if (this.captureEvidence) {
|
|
44608
|
+
await captureTermination((args) => kubectl(this.kubectlBin, this.namespace, args), ref.id, graceSeconds);
|
|
44609
|
+
return;
|
|
44610
|
+
}
|
|
44611
|
+
await stopKubernetesJob((args) => kubectl(this.kubectlBin, this.namespace, args), ref.id, graceSeconds);
|
|
43707
44612
|
}
|
|
43708
44613
|
async remove(ref) {
|
|
43709
44614
|
await kubectl(this.kubectlBin, this.namespace, [
|
|
@@ -43711,19 +44616,31 @@ class KubernetesDriver {
|
|
|
43711
44616
|
"job",
|
|
43712
44617
|
ref.id,
|
|
43713
44618
|
"--ignore-not-found",
|
|
44619
|
+
"--cascade=foreground",
|
|
43714
44620
|
"--wait=true"
|
|
43715
|
-
])
|
|
44621
|
+
]);
|
|
43716
44622
|
const inputSecret = typeof ref.inputSecret === "string" ? ref.inputSecret : `${ref.id}-input`;
|
|
43717
|
-
await kubectl(this.kubectlBin, this.namespace, ["delete", "secret", inputSecret, "--ignore-not-found"])
|
|
44623
|
+
await kubectl(this.kubectlBin, this.namespace, ["delete", "secret", inputSecret, "--ignore-not-found"]);
|
|
43718
44624
|
if (typeof ref.egressSecret === "string") {
|
|
43719
|
-
await kubectl(this.kubectlBin, this.namespace, [
|
|
43720
|
-
"delete",
|
|
43721
|
-
"secret",
|
|
43722
|
-
ref.egressSecret,
|
|
43723
|
-
"--ignore-not-found"
|
|
43724
|
-
]).catch(() => {});
|
|
44625
|
+
await kubectl(this.kubectlBin, this.namespace, ["delete", "secret", ref.egressSecret, "--ignore-not-found"]);
|
|
43725
44626
|
}
|
|
43726
44627
|
}
|
|
44628
|
+
async terminationEvidence(ref) {
|
|
44629
|
+
if (!this.captureEvidence)
|
|
44630
|
+
return null;
|
|
44631
|
+
return readTerminationEvidence((args) => kubectl(this.kubectlBin, this.namespace, args), ref.id);
|
|
44632
|
+
}
|
|
44633
|
+
async purgeInput(workspaceId) {
|
|
44634
|
+
const name2 = resourceName(workspaceId);
|
|
44635
|
+
await kubectl(this.kubectlBin, this.namespace, [
|
|
44636
|
+
"delete",
|
|
44637
|
+
"secret",
|
|
44638
|
+
`${name2}-input`,
|
|
44639
|
+
`${name2}-egress`,
|
|
44640
|
+
"--ignore-not-found",
|
|
44641
|
+
"--wait=true"
|
|
44642
|
+
]);
|
|
44643
|
+
}
|
|
43727
44644
|
async cleanupInput(workspaceId) {
|
|
43728
44645
|
await kubectl(this.kubectlBin, this.namespace, [
|
|
43729
44646
|
"delete",
|
|
@@ -44248,6 +45165,7 @@ function loadConfig(env2 = process.env) {
|
|
|
44248
45165
|
}
|
|
44249
45166
|
return {
|
|
44250
45167
|
listenHost: env2.POCKETCODER_HOST ?? "127.0.0.1",
|
|
45168
|
+
launchPolicy: launchPolicyConfig(env2),
|
|
44251
45169
|
listenPort,
|
|
44252
45170
|
storeKind,
|
|
44253
45171
|
databaseUrl,
|
|
@@ -44276,8 +45194,7 @@ function loadConfig(env2 = process.env) {
|
|
|
44276
45194
|
}
|
|
44277
45195
|
|
|
44278
45196
|
// ../memory-store/src/modules/auth/memory-store-auth.ts
|
|
44279
|
-
import { randomUUID as
|
|
44280
|
-
|
|
45197
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
44281
45198
|
class MemoryAuthStore {
|
|
44282
45199
|
context;
|
|
44283
45200
|
constructor(context) {
|
|
@@ -44288,7 +45205,7 @@ class MemoryAuthStore {
|
|
|
44288
45205
|
throw new Error(`principal exists: ${name2}`);
|
|
44289
45206
|
}
|
|
44290
45207
|
const row = {
|
|
44291
|
-
id:
|
|
45208
|
+
id: randomUUID19(),
|
|
44292
45209
|
name: name2,
|
|
44293
45210
|
scopes,
|
|
44294
45211
|
templateNames,
|
|
@@ -44301,6 +45218,21 @@ class MemoryAuthStore {
|
|
|
44301
45218
|
async getPrincipalByName(name2) {
|
|
44302
45219
|
return this.context.principals.find((p) => p.name === name2) ?? null;
|
|
44303
45220
|
}
|
|
45221
|
+
async getPrincipal(id) {
|
|
45222
|
+
return this.context.principals.find((row) => row.id === id) ?? null;
|
|
45223
|
+
}
|
|
45224
|
+
async listMachineKeys(principalId, filter) {
|
|
45225
|
+
return this.context.keys.filter((row) => row.principalId === principalId && (!filter.cursor || row.id > filter.cursor) && (!filter.requestId || row.issuanceRequestId === filter.requestId)).sort((a, b) => a.id.localeCompare(b.id)).slice(0, filter.limit).map((row) => ({ ...row }));
|
|
45226
|
+
}
|
|
45227
|
+
async revokePrincipalKeys(principalId, at) {
|
|
45228
|
+
const principal = this.context.principals.find((row) => row.id === principalId);
|
|
45229
|
+
if (!principal)
|
|
45230
|
+
return;
|
|
45231
|
+
principal.disabledAt = at;
|
|
45232
|
+
for (const key of this.context.keys)
|
|
45233
|
+
if (key.principalId === principalId)
|
|
45234
|
+
key.revokedAt ??= at;
|
|
45235
|
+
}
|
|
44304
45236
|
async listPrincipals() {
|
|
44305
45237
|
return this.context.principals.map((p) => ({ ...p }));
|
|
44306
45238
|
}
|
|
@@ -44319,7 +45251,32 @@ class MemoryAuthStore {
|
|
|
44319
45251
|
}
|
|
44320
45252
|
}
|
|
44321
45253
|
async insertMachineKey(row) {
|
|
45254
|
+
await this.issueMachineKey({
|
|
45255
|
+
...row,
|
|
45256
|
+
issuanceRequestId: row.issuanceRequestId ?? null,
|
|
45257
|
+
issuanceRequestDigest: row.issuanceRequestDigest ?? null,
|
|
45258
|
+
managedPrincipalIds: row.managedPrincipalIds ?? []
|
|
45259
|
+
});
|
|
45260
|
+
}
|
|
45261
|
+
async issueMachineKey(row) {
|
|
45262
|
+
const principal = this.context.principals.find((principal2) => principal2.id === row.principalId);
|
|
45263
|
+
if (!principal)
|
|
45264
|
+
throw new ApiError("auth.invalid_key", "Unknown principal.");
|
|
45265
|
+
const existing = row.issuanceRequestId ? this.context.keys.find((key) => key.principalId === row.principalId && key.issuanceRequestId === row.issuanceRequestId) : null;
|
|
45266
|
+
if (existing)
|
|
45267
|
+
return {
|
|
45268
|
+
key: { ...existing },
|
|
45269
|
+
created: false,
|
|
45270
|
+
conflict: existing.issuanceRequestDigest !== row.issuanceRequestDigest
|
|
45271
|
+
};
|
|
45272
|
+
if (principal.disabledAt)
|
|
45273
|
+
throw new ApiError("auth.disabled_principal", "This principal is disabled.");
|
|
45274
|
+
if (row.issuanceRequestId && row.expiresAt && row.expiresAt <= new Date)
|
|
45275
|
+
throw new ApiError("validation.invalid", "Key expiry must be in the future.");
|
|
45276
|
+
if (row.scopes.some((scope) => !principal.scopes.includes("admin") && !principal.scopes.includes(scope)))
|
|
45277
|
+
throw new ApiError("auth.missing_scope", "Key scopes exceed the principal's authority.");
|
|
44322
45278
|
this.context.keys.push({ ...row });
|
|
45279
|
+
return { key: { ...row }, created: true, conflict: false };
|
|
44323
45280
|
}
|
|
44324
45281
|
async getMachineKeyWithPrincipal(keyId) {
|
|
44325
45282
|
const key = this.context.keys.find((k) => k.id === keyId);
|
|
@@ -44345,7 +45302,7 @@ class MemoryAuthStore {
|
|
|
44345
45302
|
}
|
|
44346
45303
|
|
|
44347
45304
|
// ../memory-store/src/state/memory-store-base.ts
|
|
44348
|
-
import { randomUUID as
|
|
45305
|
+
import { randomUUID as randomUUID20 } from "crypto";
|
|
44349
45306
|
var ACTIVE_STATES = [
|
|
44350
45307
|
"provisioning",
|
|
44351
45308
|
"connected",
|
|
@@ -44419,7 +45376,7 @@ class MemoryState {
|
|
|
44419
45376
|
}
|
|
44420
45377
|
appendHistory(row, from, to, reason, at) {
|
|
44421
45378
|
this.history.push({
|
|
44422
|
-
id:
|
|
45379
|
+
id: randomUUID20(),
|
|
44423
45380
|
workspaceId: row.id,
|
|
44424
45381
|
fromState: from,
|
|
44425
45382
|
toState: to,
|
|
@@ -44428,6 +45385,8 @@ class MemoryState {
|
|
|
44428
45385
|
});
|
|
44429
45386
|
}
|
|
44430
45387
|
appendWorkspaceEvent(row, at) {
|
|
45388
|
+
if (row.purgeRequestedAt)
|
|
45389
|
+
return;
|
|
44431
45390
|
const payload = buildEventEnvelope(row, at);
|
|
44432
45391
|
this.outbox.push({
|
|
44433
45392
|
id: payload.id,
|
|
@@ -44453,6 +45412,8 @@ class MemoryConversationStore {
|
|
|
44453
45412
|
const state = this.context.conversationStates.get(input.workspaceId);
|
|
44454
45413
|
if (state?.status === "deleted")
|
|
44455
45414
|
throw new Error("conversation.deleted");
|
|
45415
|
+
if (this.context.workspaces.get(input.workspaceId)?.purgeRequestedAt)
|
|
45416
|
+
throw new Error("conversation.deleted");
|
|
44456
45417
|
const rows = this.context.conversations.get(input.workspaceId) ?? [];
|
|
44457
45418
|
const existing = rows.find((row) => row.messageId === input.messageId);
|
|
44458
45419
|
if (existing)
|
|
@@ -44528,6 +45489,8 @@ class MemoryLogStore {
|
|
|
44528
45489
|
this.context = context;
|
|
44529
45490
|
}
|
|
44530
45491
|
async appendLogs(workspaceId, entries) {
|
|
45492
|
+
if (this.context.workspaces.get(workspaceId)?.purgeRequestedAt)
|
|
45493
|
+
return;
|
|
44531
45494
|
const list = this.context.logs.get(workspaceId) ?? [];
|
|
44532
45495
|
let bytes = this.context.logBytes.get(workspaceId) ?? 0;
|
|
44533
45496
|
let seq = list.length > 0 ? list[list.length - 1]?.seq ?? 0 : 0;
|
|
@@ -44564,6 +45527,8 @@ class MemoryLogStore {
|
|
|
44564
45527
|
};
|
|
44565
45528
|
}
|
|
44566
45529
|
async appendNetworkEvents(workspaceId, sourceSessionId, events) {
|
|
45530
|
+
if (this.context.workspaces.get(workspaceId)?.purgeRequestedAt)
|
|
45531
|
+
return;
|
|
44567
45532
|
const rows = this.context.networkEvents.get(workspaceId) ?? [];
|
|
44568
45533
|
const seen = new Set(rows.map((row) => `${row.sourceSessionId}:${row.source_seq}`));
|
|
44569
45534
|
const workspace = this.context.workspaces.get(workspaceId);
|
|
@@ -44585,7 +45550,7 @@ class MemoryLogStore {
|
|
|
44585
45550
|
}
|
|
44586
45551
|
|
|
44587
45552
|
// ../memory-store/src/modules/outbox/memory-store-outbox.ts
|
|
44588
|
-
import { randomUUID as
|
|
45553
|
+
import { randomUUID as randomUUID21 } from "crypto";
|
|
44589
45554
|
|
|
44590
45555
|
class MemoryOutboxStore {
|
|
44591
45556
|
context;
|
|
@@ -44616,8 +45581,10 @@ class MemoryOutboxStore {
|
|
|
44616
45581
|
this.context.claimedEvents.delete(id);
|
|
44617
45582
|
}
|
|
44618
45583
|
async appendEvent(workspaceId, eventType, payload, at) {
|
|
45584
|
+
if (this.context.workspaces.get(workspaceId)?.purgeRequestedAt)
|
|
45585
|
+
return;
|
|
44619
45586
|
this.context.outbox.push({
|
|
44620
|
-
id:
|
|
45587
|
+
id: randomUUID21(),
|
|
44621
45588
|
workspaceId,
|
|
44622
45589
|
eventType,
|
|
44623
45590
|
payload,
|
|
@@ -44630,6 +45597,52 @@ class MemoryOutboxStore {
|
|
|
44630
45597
|
}
|
|
44631
45598
|
}
|
|
44632
45599
|
|
|
45600
|
+
// ../memory-store/src/modules/persistence/memory-store-content.ts
|
|
45601
|
+
function createContentPurge2(context) {
|
|
45602
|
+
return {
|
|
45603
|
+
async listWorkspaceStorage(workspaceId) {
|
|
45604
|
+
return [...context.storage.values()].filter((row) => row.workspaceId === workspaceId).map((row) => ({ ...row }));
|
|
45605
|
+
},
|
|
45606
|
+
async purgeWorkspaceContent(workspaceId, at) {
|
|
45607
|
+
const workspace = context.workspaces.get(workspaceId);
|
|
45608
|
+
if (!workspace?.purgeRequestedAt)
|
|
45609
|
+
throw new Error("Purge has not been admitted");
|
|
45610
|
+
context.logs.delete(workspaceId);
|
|
45611
|
+
context.logBytes.delete(workspaceId);
|
|
45612
|
+
context.outputs.delete(workspaceId);
|
|
45613
|
+
context.conversations.delete(workspaceId);
|
|
45614
|
+
context.conversationBytes.delete(workspaceId);
|
|
45615
|
+
context.networkEvents.delete(workspaceId);
|
|
45616
|
+
context.outbox = context.outbox.filter((row) => row.workspaceId !== workspaceId);
|
|
45617
|
+
context.conversationStates.set(workspaceId, {
|
|
45618
|
+
workspaceId,
|
|
45619
|
+
status: "deleted",
|
|
45620
|
+
expiresAt: null,
|
|
45621
|
+
deletedAt: at,
|
|
45622
|
+
updatedAt: at
|
|
45623
|
+
});
|
|
45624
|
+
for (const checkpoint of context.checkpoints.values()) {
|
|
45625
|
+
if (checkpoint.workspaceId === workspaceId)
|
|
45626
|
+
Object.assign(checkpoint, { manifest: null, manifestDigest: null, sourceProvenance: null, label: null });
|
|
45627
|
+
}
|
|
45628
|
+
Object.assign(workspace, {
|
|
45629
|
+
launchInput: null,
|
|
45630
|
+
outputs: {},
|
|
45631
|
+
metadata: {},
|
|
45632
|
+
health: {},
|
|
45633
|
+
failureLogTail: null,
|
|
45634
|
+
failureLogTailTruncated: false,
|
|
45635
|
+
failureLastLogSeq: null,
|
|
45636
|
+
sourceDescriptor: null,
|
|
45637
|
+
resolvedSource: null,
|
|
45638
|
+
registrationDigest: null,
|
|
45639
|
+
reconnectDigest: null,
|
|
45640
|
+
updatedAt: at
|
|
45641
|
+
});
|
|
45642
|
+
}
|
|
45643
|
+
};
|
|
45644
|
+
}
|
|
45645
|
+
|
|
44633
45646
|
// ../memory-store/src/modules/persistence/memory-store-operations.ts
|
|
44634
45647
|
class MemoryOperationStore {
|
|
44635
45648
|
context;
|
|
@@ -44649,6 +45662,12 @@ class MemoryOperationStore {
|
|
|
44649
45662
|
throw new OperationCapacityExceededError;
|
|
44650
45663
|
}
|
|
44651
45664
|
this.assertOperationReferences(row);
|
|
45665
|
+
const workspace = row.workspaceId ? this.context.workspaces.get(row.workspaceId) : null;
|
|
45666
|
+
if (workspace?.purgeRequestedAt && row.kind !== "purge") {
|
|
45667
|
+
throw new ApiError("operation.conflict", "Workspace content is being purged.");
|
|
45668
|
+
}
|
|
45669
|
+
if (workspace && row.kind === "purge")
|
|
45670
|
+
workspace.purgeRequestedAt ??= row.createdAt;
|
|
44652
45671
|
this.context.operations.set(row.id, { ...row });
|
|
44653
45672
|
return { operation: { ...row }, created: true, conflict: false };
|
|
44654
45673
|
}
|
|
@@ -44671,12 +45690,12 @@ class MemoryOperationStore {
|
|
|
44671
45690
|
async listIncompleteOperations() {
|
|
44672
45691
|
return [...this.context.operations.values()].filter((row) => row.state === "pending" || row.state === "running").map((row) => ({ ...row }));
|
|
44673
45692
|
}
|
|
44674
|
-
async updateOperation(id,
|
|
45693
|
+
async updateOperation(id, patch2, at) {
|
|
44675
45694
|
const row = this.context.operations.get(id);
|
|
44676
45695
|
if (!row)
|
|
44677
45696
|
return;
|
|
44678
|
-
this.assertOperationReferences({ ...row, ...
|
|
44679
|
-
Object.assign(row,
|
|
45697
|
+
this.assertOperationReferences({ ...row, ...patch2 });
|
|
45698
|
+
Object.assign(row, patch2);
|
|
44680
45699
|
row.updatedAt = at;
|
|
44681
45700
|
}
|
|
44682
45701
|
async checkpointUsage(principalId) {
|
|
@@ -44693,6 +45712,8 @@ class MemoryOperationStore {
|
|
|
44693
45712
|
const workspace = this.context.workspaces.get(input.workspaceId);
|
|
44694
45713
|
if (!workspace)
|
|
44695
45714
|
throw new Error("workspace not found");
|
|
45715
|
+
if (workspace.purgeRequestedAt)
|
|
45716
|
+
throw new ApiError("operation.conflict", "Workspace content is being purged.");
|
|
44696
45717
|
const list = this.context.outputs.get(input.workspaceId) ?? [];
|
|
44697
45718
|
const row = { ...input, seq: list.length + 1 };
|
|
44698
45719
|
list.push(row);
|
|
@@ -44727,11 +45748,11 @@ class MemoryPersistenceStore {
|
|
|
44727
45748
|
const row = this.context.storage.get(id);
|
|
44728
45749
|
return row ? { ...row } : null;
|
|
44729
45750
|
}
|
|
44730
|
-
async updateWorkspaceStorage(id,
|
|
45751
|
+
async updateWorkspaceStorage(id, patch2, at) {
|
|
44731
45752
|
const row = this.context.storage.get(id);
|
|
44732
45753
|
if (!row)
|
|
44733
45754
|
return;
|
|
44734
|
-
Object.assign(row,
|
|
45755
|
+
Object.assign(row, patch2);
|
|
44735
45756
|
row.updatedAt = at;
|
|
44736
45757
|
}
|
|
44737
45758
|
async insertCheckpoint(row) {
|
|
@@ -44748,24 +45769,24 @@ class MemoryPersistenceStore {
|
|
|
44748
45769
|
async listCheckpoints(principalId, filter = {}) {
|
|
44749
45770
|
return [...this.context.checkpoints.values()].filter((row) => row.principalId === principalId && (!filter.workspaceId || row.workspaceId === filter.workspaceId) && (!filter.state || row.state === filter.state)).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()).map((row) => ({ ...row }));
|
|
44750
45771
|
}
|
|
44751
|
-
async updateCheckpoint(id,
|
|
45772
|
+
async updateCheckpoint(id, patch2, at) {
|
|
44752
45773
|
const row = this.context.checkpoints.get(id);
|
|
44753
45774
|
if (!row)
|
|
44754
45775
|
return;
|
|
44755
45776
|
if (row.state === "ready") {
|
|
44756
45777
|
const mutable = new Set(["state", "reasonCode", "expiresAt", "deletedAt"]);
|
|
44757
|
-
for (const key of Object.keys(
|
|
45778
|
+
for (const key of Object.keys(patch2)) {
|
|
44758
45779
|
if (!mutable.has(key))
|
|
44759
45780
|
throw new Error("ready checkpoints are immutable");
|
|
44760
45781
|
}
|
|
44761
45782
|
}
|
|
44762
|
-
Object.assign(row,
|
|
45783
|
+
Object.assign(row, patch2);
|
|
44763
45784
|
row.updatedAt = at;
|
|
44764
45785
|
}
|
|
44765
45786
|
}
|
|
44766
45787
|
|
|
44767
45788
|
// ../memory-store/src/modules/templates/memory-store-templates.ts
|
|
44768
|
-
import { randomUUID as
|
|
45789
|
+
import { randomUUID as randomUUID22 } from "crypto";
|
|
44769
45790
|
|
|
44770
45791
|
class MemoryTemplateStore {
|
|
44771
45792
|
context;
|
|
@@ -44781,7 +45802,7 @@ class MemoryTemplateStore {
|
|
|
44781
45802
|
return { row: existing, created: false, conflict: false };
|
|
44782
45803
|
}
|
|
44783
45804
|
const row = {
|
|
44784
|
-
id:
|
|
45805
|
+
id: randomUUID22(),
|
|
44785
45806
|
name: input.name,
|
|
44786
45807
|
version: input.version,
|
|
44787
45808
|
digest: input.digest,
|
|
@@ -44887,16 +45908,16 @@ class MemoryWarmPoolStore {
|
|
|
44887
45908
|
async listWarmPoolRuntimes() {
|
|
44888
45909
|
return [...this.context.warmPoolRuntimes.values()].map((row) => ({ ...row }));
|
|
44889
45910
|
}
|
|
44890
|
-
async updateWarmPoolRuntime(id,
|
|
45911
|
+
async updateWarmPoolRuntime(id, patch2, at) {
|
|
44891
45912
|
const row = this.context.warmPoolRuntimes.get(id);
|
|
44892
45913
|
if (!row)
|
|
44893
45914
|
return;
|
|
44894
|
-
Object.assign(row,
|
|
45915
|
+
Object.assign(row, patch2);
|
|
44895
45916
|
row.updatedAt = at;
|
|
44896
45917
|
}
|
|
44897
45918
|
async claimWarmPoolRuntime(claim) {
|
|
44898
45919
|
const workspace = this.context.workspaces.get(claim.workspaceId);
|
|
44899
|
-
if (workspace?.state !== "queued")
|
|
45920
|
+
if (workspace?.state !== "queued" || workspace.purgeRequestedAt)
|
|
44900
45921
|
return null;
|
|
44901
45922
|
const runtime = [...this.context.warmPoolRuntimes.values()].filter((row) => row.state === "ready" && row.templateDigest === claim.templateDigest && row.driverKind === claim.driverKind && row.eligibilityFingerprint === claim.eligibilityFingerprint).sort((a, b) => (a.readyAt?.getTime() ?? 0) - (b.readyAt?.getTime() ?? 0))[0];
|
|
44902
45923
|
if (!runtime?.providerRef)
|
|
@@ -44958,7 +45979,7 @@ class MemoryAdmissionStore {
|
|
|
44958
45979
|
}
|
|
44959
45980
|
async claimWorkspaceAdmission(claim) {
|
|
44960
45981
|
const workspace = this.context.workspaces.get(claim.workspaceId);
|
|
44961
|
-
if (workspace?.state !== "queued")
|
|
45982
|
+
if (workspace?.state !== "queued" || workspace.purgeRequestedAt)
|
|
44962
45983
|
return null;
|
|
44963
45984
|
const counts = this.activeCounts();
|
|
44964
45985
|
if (counts.global >= claim.limits.globalActiveWorkspaces)
|
|
@@ -44981,12 +46002,14 @@ class MemoryAdmissionStore {
|
|
|
44981
46002
|
}
|
|
44982
46003
|
});
|
|
44983
46004
|
}
|
|
44984
|
-
async updateWorkspace(id,
|
|
46005
|
+
async updateWorkspace(id, patch2, at) {
|
|
44985
46006
|
const row = this.context.workspaces.get(id);
|
|
44986
46007
|
if (!row)
|
|
44987
46008
|
return;
|
|
44988
|
-
Object.assign(row,
|
|
44989
|
-
|
|
46009
|
+
Object.assign(row, patch2);
|
|
46010
|
+
if (row.purgeRequestedAt)
|
|
46011
|
+
Object.assign(row, purgedContentPatch());
|
|
46012
|
+
const changed = Object.keys(patch2).some((key) => CHANGE_PATCH_KEYS2.has(key));
|
|
44990
46013
|
if (changed)
|
|
44991
46014
|
row.changeSeq += 1;
|
|
44992
46015
|
row.updatedAt = at;
|
|
@@ -45035,12 +46058,16 @@ class MemoryAdmissionStore {
|
|
|
45035
46058
|
return null;
|
|
45036
46059
|
if (!canTransition(row.state, req.to))
|
|
45037
46060
|
return null;
|
|
46061
|
+
if (row.purgeRequestedAt && ["provisioning", "connected", "ready", "preserving", "queued"].includes(req.to))
|
|
46062
|
+
return null;
|
|
45038
46063
|
const fromState = row.state;
|
|
45039
46064
|
row.state = req.to;
|
|
45040
46065
|
if (req.reason !== undefined)
|
|
45041
46066
|
row.reasonCode = req.reason;
|
|
45042
46067
|
if (req.patch)
|
|
45043
46068
|
Object.assign(row, req.patch);
|
|
46069
|
+
if (row.purgeRequestedAt)
|
|
46070
|
+
Object.assign(row, purgedContentPatch());
|
|
45044
46071
|
row.changeSeq += 1;
|
|
45045
46072
|
row.updatedAt = req.at;
|
|
45046
46073
|
if (isTerminal(req.to)) {
|
|
@@ -45137,6 +46164,7 @@ class MemoryWorkspaceStore {
|
|
|
45137
46164
|
createdAt: row.createdAt,
|
|
45138
46165
|
updatedAt: row.createdAt,
|
|
45139
46166
|
terminalAt: null,
|
|
46167
|
+
purgeRequestedAt: null,
|
|
45140
46168
|
originWorkspaceId: row.originWorkspaceId ?? null,
|
|
45141
46169
|
restoredFromCheckpointId: row.restoredFromCheckpointId ?? null,
|
|
45142
46170
|
sourceDescriptor: row.sourceDescriptor ?? null,
|
|
@@ -45189,6 +46217,9 @@ class MemoryWorkspaceStore {
|
|
|
45189
46217
|
class MemoryStore {
|
|
45190
46218
|
constructor() {
|
|
45191
46219
|
this.context = new MemoryState;
|
|
46220
|
+
const content = createContentPurge2(this.context);
|
|
46221
|
+
this.listWorkspaceStorage = content.listWorkspaceStorage;
|
|
46222
|
+
this.purgeWorkspaceContent = content.purgeWorkspaceContent;
|
|
45192
46223
|
this.persistence = new MemoryPersistenceStore(this.context);
|
|
45193
46224
|
this.operation = new MemoryOperationStore(this.context);
|
|
45194
46225
|
this.template = new MemoryTemplateStore(this.context);
|
|
@@ -45233,6 +46264,10 @@ class MemoryStore {
|
|
|
45233
46264
|
this.listPrincipals = this.auth.listPrincipals.bind(this.auth);
|
|
45234
46265
|
this.updatePrincipal = this.auth.updatePrincipal.bind(this.auth);
|
|
45235
46266
|
this.setPrincipalDisabled = this.auth.setPrincipalDisabled.bind(this.auth);
|
|
46267
|
+
this.getPrincipal = this.auth.getPrincipal.bind(this.auth);
|
|
46268
|
+
this.issueMachineKey = this.auth.issueMachineKey.bind(this.auth);
|
|
46269
|
+
this.listMachineKeys = this.auth.listMachineKeys.bind(this.auth);
|
|
46270
|
+
this.revokePrincipalKeys = this.auth.revokePrincipalKeys.bind(this.auth);
|
|
45236
46271
|
this.insertMachineKey = this.auth.insertMachineKey.bind(this.auth);
|
|
45237
46272
|
this.getMachineKeyWithPrincipal = this.auth.getMachineKeyWithPrincipal.bind(this.auth);
|
|
45238
46273
|
this.revokeMachineKey = this.auth.revokeMachineKey.bind(this.auth);
|
|
@@ -45283,6 +46318,8 @@ class MemoryStore {
|
|
|
45283
46318
|
operation;
|
|
45284
46319
|
persistence;
|
|
45285
46320
|
context;
|
|
46321
|
+
listWorkspaceStorage;
|
|
46322
|
+
purgeWorkspaceContent;
|
|
45286
46323
|
init;
|
|
45287
46324
|
acquireCoordinatorLease;
|
|
45288
46325
|
close;
|
|
@@ -45316,6 +46353,10 @@ class MemoryStore {
|
|
|
45316
46353
|
listPrincipals;
|
|
45317
46354
|
updatePrincipal;
|
|
45318
46355
|
setPrincipalDisabled;
|
|
46356
|
+
getPrincipal;
|
|
46357
|
+
issueMachineKey;
|
|
46358
|
+
listMachineKeys;
|
|
46359
|
+
revokePrincipalKeys;
|
|
45319
46360
|
insertMachineKey;
|
|
45320
46361
|
getMachineKeyWithPrincipal;
|
|
45321
46362
|
revokeMachineKey;
|
|
@@ -45563,10 +46604,10 @@ function mapValues(object2, mapper) {
|
|
|
45563
46604
|
});
|
|
45564
46605
|
return result;
|
|
45565
46606
|
}
|
|
45566
|
-
function omit2(object2,
|
|
46607
|
+
function omit2(object2, keys2) {
|
|
45567
46608
|
const result = {};
|
|
45568
46609
|
Object.entries(object2).forEach(([key, value]) => {
|
|
45569
|
-
if (!
|
|
46610
|
+
if (!keys2.some((keyToOmit) => keyToOmit === key)) {
|
|
45570
46611
|
result[key] = value;
|
|
45571
46612
|
}
|
|
45572
46613
|
});
|
|
@@ -45941,8 +46982,8 @@ class DiscriminatedUnionTransformer {
|
|
|
45941
46982
|
const refId = Metadata.getRefId(obj);
|
|
45942
46983
|
const value = (_a4 = obj.def.shape) === null || _a4 === undefined ? undefined : _a4[discriminator];
|
|
45943
46984
|
if (isZodType(value, "ZodEnum")) {
|
|
45944
|
-
const
|
|
45945
|
-
|
|
46985
|
+
const keys2 = Object.values(value._zod.def.entries).filter(isString);
|
|
46986
|
+
keys2.forEach((enumValue) => {
|
|
45946
46987
|
mapping[enumValue] = generateSchemaRef(refId);
|
|
45947
46988
|
});
|
|
45948
46989
|
return;
|
|
@@ -46069,8 +47110,8 @@ class RecordTransformer {
|
|
|
46069
47110
|
const keyType = zodSchema.keyType;
|
|
46070
47111
|
const propertiesSchema = isAnyZodType(propertiesType) ? mapItem(propertiesType) : {};
|
|
46071
47112
|
if (isZodType(keyType, "ZodEnum")) {
|
|
46072
|
-
const
|
|
46073
|
-
const properties =
|
|
47113
|
+
const keys2 = Object.values(keyType._zod.def.entries).filter(isString);
|
|
47114
|
+
const properties = keys2.reduce((acc, curr) => Object.assign(Object.assign({}, acc), { [curr]: propertiesSchema }), {});
|
|
46074
47115
|
return Object.assign(Object.assign({}, mapNullableType("object")), { properties });
|
|
46075
47116
|
}
|
|
46076
47117
|
return Object.assign(Object.assign({}, mapNullableType("object")), { additionalProperties: propertiesSchema });
|
|
@@ -47372,9 +48413,9 @@ var handleParsingNestedValues = (form, key, value) => {
|
|
|
47372
48413
|
return;
|
|
47373
48414
|
}
|
|
47374
48415
|
let nestedForm = form;
|
|
47375
|
-
const
|
|
47376
|
-
|
|
47377
|
-
if (index2 ===
|
|
48416
|
+
const keys2 = key.split(".");
|
|
48417
|
+
keys2.forEach((key2, index2) => {
|
|
48418
|
+
if (index2 === keys2.length - 1) {
|
|
47378
48419
|
nestedForm[key2] = value;
|
|
47379
48420
|
} else {
|
|
47380
48421
|
if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
|
|
@@ -47410,8 +48451,8 @@ var HonoRequest = class {
|
|
|
47410
48451
|
}
|
|
47411
48452
|
#getAllDecodedParams() {
|
|
47412
48453
|
const decoded = {};
|
|
47413
|
-
const
|
|
47414
|
-
for (const key of
|
|
48454
|
+
const keys2 = Object.keys(this.#matchResult[0][this.routeIndex][1]);
|
|
48455
|
+
for (const key of keys2) {
|
|
47415
48456
|
const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
|
|
47416
48457
|
if (value !== undefined) {
|
|
47417
48458
|
decoded[key] = /\%/.test(value) ? tryDecodeURIComponent(value) : value;
|
|
@@ -48170,9 +49211,9 @@ function buildMatcherFromPreprocessedRoutes(routes) {
|
|
|
48170
49211
|
if (!map2) {
|
|
48171
49212
|
continue;
|
|
48172
49213
|
}
|
|
48173
|
-
const
|
|
48174
|
-
for (let k = 0, len3 =
|
|
48175
|
-
map2[
|
|
49214
|
+
const keys2 = Object.keys(map2);
|
|
49215
|
+
for (let k = 0, len3 = keys2.length;k < len3; k++) {
|
|
49216
|
+
map2[keys2[k]] = paramReplacementMap[map2[keys2[k]]];
|
|
48176
49217
|
}
|
|
48177
49218
|
}
|
|
48178
49219
|
}
|
|
@@ -48975,10 +50016,10 @@ var createBunWebSocket = () => ({
|
|
|
48975
50016
|
});
|
|
48976
50017
|
|
|
48977
50018
|
// ../server/src/http/middleware.ts
|
|
48978
|
-
import { randomUUID as
|
|
50019
|
+
import { randomUUID as randomUUID23 } from "crypto";
|
|
48979
50020
|
var requestId = async (c, next) => {
|
|
48980
50021
|
const candidate = c.req.header("x-request-id");
|
|
48981
|
-
const id = candidate && /^[A-Za-z0-9._-]{1,128}$/.test(candidate) ? candidate :
|
|
50022
|
+
const id = candidate && /^[A-Za-z0-9._-]{1,128}$/.test(candidate) ? candidate : randomUUID23();
|
|
48982
50023
|
c.set("requestId", id);
|
|
48983
50024
|
c.header("x-request-id", id);
|
|
48984
50025
|
await next();
|
|
@@ -49025,6 +50066,7 @@ function machineAuth(store, pepper) {
|
|
|
49025
50066
|
c.set("principal", found.principal);
|
|
49026
50067
|
c.set("scopes", effectiveScopes);
|
|
49027
50068
|
c.set("keyId", found.key.id);
|
|
50069
|
+
c.set("managedPrincipalIds", found.key.managedPrincipalIds);
|
|
49028
50070
|
store.touchMachineKey(found.key.id, now).catch(() => {});
|
|
49029
50071
|
await next();
|
|
49030
50072
|
};
|
|
@@ -49039,7 +50081,7 @@ function requireScope(scope) {
|
|
|
49039
50081
|
}
|
|
49040
50082
|
function errorHandler2(logger) {
|
|
49041
50083
|
return (err, c) => {
|
|
49042
|
-
const id = c.get("requestId") ??
|
|
50084
|
+
const id = c.get("requestId") ?? randomUUID23();
|
|
49043
50085
|
if (err instanceof ApiError) {
|
|
49044
50086
|
return c.json(errorEnvelope(err.code, err.message, id, err.details), err.status);
|
|
49045
50087
|
}
|
|
@@ -49141,8 +50183,219 @@ function registerAdministrationRoutes({
|
|
|
49141
50183
|
});
|
|
49142
50184
|
}
|
|
49143
50185
|
|
|
50186
|
+
// ../../node_modules/.bun/hono@4.12.32/node_modules/hono/dist/middleware/body-limit/index.js
|
|
50187
|
+
var ERROR_MESSAGE = "Payload Too Large";
|
|
50188
|
+
var bodyLimit = (options) => {
|
|
50189
|
+
const onError = options.onError || (() => {
|
|
50190
|
+
const res = new Response(ERROR_MESSAGE, {
|
|
50191
|
+
status: 413
|
|
50192
|
+
});
|
|
50193
|
+
throw new HTTPException(413, { res });
|
|
50194
|
+
});
|
|
50195
|
+
const maxSize = options.maxSize;
|
|
50196
|
+
return async function bodyLimit2(c, next) {
|
|
50197
|
+
if (!c.req.raw.body) {
|
|
50198
|
+
return next();
|
|
50199
|
+
}
|
|
50200
|
+
const hasTransferEncoding = c.req.raw.headers.has("transfer-encoding");
|
|
50201
|
+
const hasContentLength = c.req.raw.headers.has("content-length");
|
|
50202
|
+
if (hasContentLength && !hasTransferEncoding) {
|
|
50203
|
+
const contentLength = parseInt(c.req.raw.headers.get("content-length") || "0", 10);
|
|
50204
|
+
return contentLength > maxSize ? onError(c) : next();
|
|
50205
|
+
}
|
|
50206
|
+
let size = 0;
|
|
50207
|
+
const chunks = [];
|
|
50208
|
+
const rawReader = c.req.raw.body.getReader();
|
|
50209
|
+
for (;; ) {
|
|
50210
|
+
const { done, value } = await rawReader.read();
|
|
50211
|
+
if (done) {
|
|
50212
|
+
break;
|
|
50213
|
+
}
|
|
50214
|
+
size += value.length;
|
|
50215
|
+
if (size > maxSize) {
|
|
50216
|
+
return onError(c);
|
|
50217
|
+
}
|
|
50218
|
+
chunks.push(value);
|
|
50219
|
+
}
|
|
50220
|
+
const requestInit = {
|
|
50221
|
+
body: new ReadableStream({
|
|
50222
|
+
start(controller) {
|
|
50223
|
+
for (const chunk of chunks) {
|
|
50224
|
+
controller.enqueue(chunk);
|
|
50225
|
+
}
|
|
50226
|
+
controller.close();
|
|
50227
|
+
}
|
|
50228
|
+
}),
|
|
50229
|
+
duplex: "half"
|
|
50230
|
+
};
|
|
50231
|
+
c.req.raw = new Request(c.req.raw, requestInit);
|
|
50232
|
+
return next();
|
|
50233
|
+
};
|
|
50234
|
+
};
|
|
50235
|
+
|
|
50236
|
+
// ../server/src/administration/principal-authority.ts
|
|
50237
|
+
async function managedPrincipal(context, store, id) {
|
|
50238
|
+
if (!context.get("managedPrincipalIds").includes(id))
|
|
50239
|
+
throw new ApiError("principal.not_found", "Unknown principal.");
|
|
50240
|
+
const principal = await store.getPrincipal(id);
|
|
50241
|
+
if (!principal)
|
|
50242
|
+
throw new ApiError("principal.not_found", "Unknown principal.");
|
|
50243
|
+
return principal;
|
|
50244
|
+
}
|
|
50245
|
+
|
|
50246
|
+
// ../server/src/administration/keys-routes.ts
|
|
50247
|
+
var principalParams = exports_external.object({ principalId: exports_external.uuid() });
|
|
50248
|
+
var revokedSchema = exports_external.object({ revoked: exports_external.literal(true) });
|
|
50249
|
+
function registerKeyRoutes(app, store, pepper) {
|
|
50250
|
+
app.use("/v1/principals/*", bodyLimit({
|
|
50251
|
+
maxSize: 16384,
|
|
50252
|
+
onError: () => {
|
|
50253
|
+
throw new ApiError("validation.invalid", "Request body exceeds 16384 bytes.");
|
|
50254
|
+
}
|
|
50255
|
+
}));
|
|
50256
|
+
app.openapi(createRoute({
|
|
50257
|
+
method: "get",
|
|
50258
|
+
path: "/v1/principals/{principalId}/keys",
|
|
50259
|
+
operationId: "listPrincipalKeys",
|
|
50260
|
+
tags: ["Keys"],
|
|
50261
|
+
middleware: [requireScope("keys:read")],
|
|
50262
|
+
request: {
|
|
50263
|
+
params: principalParams,
|
|
50264
|
+
query: exports_external.object({
|
|
50265
|
+
limit: exports_external.coerce.number().int().min(1).max(100).default(50),
|
|
50266
|
+
cursor: exports_external.uuid().optional(),
|
|
50267
|
+
request_id: exports_external.string().min(1).max(128).optional()
|
|
50268
|
+
})
|
|
50269
|
+
},
|
|
50270
|
+
responses: {
|
|
50271
|
+
200: {
|
|
50272
|
+
description: "Authoritative key metadata",
|
|
50273
|
+
content: { "application/json": { schema: KeyListResponseSchema } }
|
|
50274
|
+
},
|
|
50275
|
+
...COMMON_ERROR_RESPONSES
|
|
50276
|
+
}
|
|
50277
|
+
}), async (c) => {
|
|
50278
|
+
const principal = await managedPrincipal(c, store, c.req.valid("param").principalId);
|
|
50279
|
+
const query = c.req.valid("query");
|
|
50280
|
+
const keys2 = await store.listMachineKeys(principal.id, {
|
|
50281
|
+
limit: query.limit + 1,
|
|
50282
|
+
...query.cursor ? { cursor: query.cursor } : {},
|
|
50283
|
+
...query.request_id ? { requestId: query.request_id } : {}
|
|
50284
|
+
});
|
|
50285
|
+
const items = keys2.slice(0, query.limit).map((key) => keyResource(key, principal));
|
|
50286
|
+
return c.json({ items, next_cursor: keys2.length > query.limit ? items.at(-1)?.id ?? null : null }, 200);
|
|
50287
|
+
});
|
|
50288
|
+
app.openapi(createRoute({
|
|
50289
|
+
method: "post",
|
|
50290
|
+
path: "/v1/principals/{principalId}/keys",
|
|
50291
|
+
operationId: "issuePrincipalKey",
|
|
50292
|
+
tags: ["Keys"],
|
|
50293
|
+
middleware: [requireScope("keys:write")],
|
|
50294
|
+
request: {
|
|
50295
|
+
params: principalParams,
|
|
50296
|
+
body: { content: { "application/json": { schema: KeyIssueRequestSchema } } }
|
|
50297
|
+
},
|
|
50298
|
+
responses: {
|
|
50299
|
+
200: {
|
|
50300
|
+
description: "Reconciled issuance; secret unavailable",
|
|
50301
|
+
content: { "application/json": { schema: KeyIssueResponseSchema } }
|
|
50302
|
+
},
|
|
50303
|
+
201: {
|
|
50304
|
+
description: "Issued key; secret shown once",
|
|
50305
|
+
content: { "application/json": { schema: KeyIssueResponseSchema } }
|
|
50306
|
+
},
|
|
50307
|
+
...COMMON_ERROR_RESPONSES
|
|
50308
|
+
}
|
|
50309
|
+
}), async (c) => {
|
|
50310
|
+
const principal = await managedPrincipal(c, store, c.req.valid("param").principalId);
|
|
50311
|
+
const result = await issuePrincipalKey(store, pepper, principal, c.req.valid("json"));
|
|
50312
|
+
return c.json({ key: result.key, token: result.token }, result.created ? 201 : 200);
|
|
50313
|
+
});
|
|
50314
|
+
app.openapi(createRoute({
|
|
50315
|
+
method: "delete",
|
|
50316
|
+
path: "/v1/principals/{principalId}/keys/{keyId}",
|
|
50317
|
+
operationId: "revokePrincipalKey",
|
|
50318
|
+
tags: ["Keys"],
|
|
50319
|
+
middleware: [requireScope("keys:write")],
|
|
50320
|
+
request: { params: principalParams.extend({ keyId: exports_external.uuid() }) },
|
|
50321
|
+
responses: {
|
|
50322
|
+
200: { description: "Key revoked", content: { "application/json": { schema: revokedSchema } } },
|
|
50323
|
+
...COMMON_ERROR_RESPONSES
|
|
50324
|
+
}
|
|
50325
|
+
}), async (c) => {
|
|
50326
|
+
const { principalId, keyId } = c.req.valid("param");
|
|
50327
|
+
await managedPrincipal(c, store, principalId);
|
|
50328
|
+
const found = await store.getMachineKeyWithPrincipal(keyId);
|
|
50329
|
+
if (!found || found.key.principalId !== principalId)
|
|
50330
|
+
throw new ApiError("key.not_found", "Unknown key.");
|
|
50331
|
+
await store.revokeMachineKey(keyId, new Date);
|
|
50332
|
+
return c.json({ revoked: true }, 200);
|
|
50333
|
+
});
|
|
50334
|
+
app.openapi(createRoute({
|
|
50335
|
+
method: "delete",
|
|
50336
|
+
path: "/v1/principals/{principalId}/keys",
|
|
50337
|
+
operationId: "revokeAllPrincipalKeys",
|
|
50338
|
+
tags: ["Keys"],
|
|
50339
|
+
middleware: [requireScope("keys:write")],
|
|
50340
|
+
request: { params: principalParams },
|
|
50341
|
+
responses: {
|
|
50342
|
+
200: {
|
|
50343
|
+
description: "Principal disabled and all keys revoked atomically",
|
|
50344
|
+
content: { "application/json": { schema: revokedSchema } }
|
|
50345
|
+
},
|
|
50346
|
+
...COMMON_ERROR_RESPONSES
|
|
50347
|
+
}
|
|
50348
|
+
}), async (c) => {
|
|
50349
|
+
const principal = await managedPrincipal(c, store, c.req.valid("param").principalId);
|
|
50350
|
+
await store.revokePrincipalKeys(principal.id, new Date);
|
|
50351
|
+
return c.json({ revoked: true }, 200);
|
|
50352
|
+
});
|
|
50353
|
+
}
|
|
50354
|
+
|
|
50355
|
+
// ../server/src/administration/recovery-routes.ts
|
|
50356
|
+
function registerOperatorRecoveryRoutes(app, store, persistence2) {
|
|
50357
|
+
const responses = {
|
|
50358
|
+
202: {
|
|
50359
|
+
description: "Recovery purge accepted",
|
|
50360
|
+
content: { "application/json": { schema: OperationResourceSchema } }
|
|
50361
|
+
},
|
|
50362
|
+
...COMMON_ERROR_RESPONSES
|
|
50363
|
+
};
|
|
50364
|
+
app.openapi(createRoute({
|
|
50365
|
+
method: "post",
|
|
50366
|
+
path: "/v1/principals/{principalId}/workspaces/{id}/purge",
|
|
50367
|
+
operationId: "recoverWorkspacePurge",
|
|
50368
|
+
tags: ["Recovery"],
|
|
50369
|
+
middleware: [requireScope("workspaces:recover")],
|
|
50370
|
+
request: {
|
|
50371
|
+
params: exports_external.object({ principalId: exports_external.uuid(), id: exports_external.uuid() }),
|
|
50372
|
+
headers: IdempotencyHeadersSchema,
|
|
50373
|
+
body: { content: { "application/json": { schema: exports_external.strictObject({}) } } }
|
|
50374
|
+
},
|
|
50375
|
+
responses
|
|
50376
|
+
}), async (c) => {
|
|
50377
|
+
const principal = await managedPrincipal(c, store, c.req.valid("param").principalId);
|
|
50378
|
+
return c.json(toOperationResource(await persistence2.purge(principal, c.req.valid("param").id, c.req.valid("header")["Idempotency-Key"])), 202);
|
|
50379
|
+
});
|
|
50380
|
+
app.openapi(createRoute({
|
|
50381
|
+
method: "get",
|
|
50382
|
+
path: "/v1/principals/{principalId}/operations/{id}",
|
|
50383
|
+
operationId: "getRecoveryOperation",
|
|
50384
|
+
tags: ["Recovery"],
|
|
50385
|
+
middleware: [requireScope("workspaces:recover")],
|
|
50386
|
+
request: { params: exports_external.object({ principalId: exports_external.uuid(), id: exports_external.uuid() }) },
|
|
50387
|
+
responses: { 200: responses[202], ...COMMON_ERROR_RESPONSES }
|
|
50388
|
+
}), async (c) => {
|
|
50389
|
+
const principal = await managedPrincipal(c, store, c.req.valid("param").principalId);
|
|
50390
|
+
const operation = await store.getOperation(c.req.valid("param").id);
|
|
50391
|
+
if (!operation || operation.principalId !== principal.id || operation.kind !== "purge")
|
|
50392
|
+
throw new ApiError("workspace.not_found", "Unknown operation.");
|
|
50393
|
+
return c.json(toOperationResource(operation), 200);
|
|
50394
|
+
});
|
|
50395
|
+
}
|
|
50396
|
+
|
|
49144
50397
|
// ../server/src/attachments/attachments.ts
|
|
49145
|
-
import { randomUUID as
|
|
50398
|
+
import { randomUUID as randomUUID24 } from "crypto";
|
|
49146
50399
|
var ACK_TIMEOUT_MS = 30000;
|
|
49147
50400
|
var RESULT_TIMEOUT_MS = 30000;
|
|
49148
50401
|
var RESOLVE_TIMEOUT_MS = 1e4;
|
|
@@ -49283,6 +50536,8 @@ function attachmentUploadHandler(deps) {
|
|
|
49283
50536
|
const id = c.req.param("id") ?? "";
|
|
49284
50537
|
const attachmentId = (c.req.param("attachmentId") ?? "").toLowerCase();
|
|
49285
50538
|
const row = await deps.service.getOwned(principal, id);
|
|
50539
|
+
if (row.purgeRequestedAt)
|
|
50540
|
+
throw new ApiError("operation.conflict", "Workspace content is being purged.");
|
|
49286
50541
|
if (isTerminal(row.state)) {
|
|
49287
50542
|
throw new ApiError("workspace.terminal", "This workspace has ended.");
|
|
49288
50543
|
}
|
|
@@ -49294,7 +50549,7 @@ function attachmentUploadHandler(deps) {
|
|
|
49294
50549
|
}
|
|
49295
50550
|
const headers = uploadHeaders(c);
|
|
49296
50551
|
const conn = liveConnection(deps.hub, id);
|
|
49297
|
-
const operationId =
|
|
50552
|
+
const operationId = randomUUID24();
|
|
49298
50553
|
deps.hub.openAttachment(conn, operationId);
|
|
49299
50554
|
try {
|
|
49300
50555
|
const result = await streamUpload(deps.hub, conn, operationId, c.req.raw.body, headers, attachmentId);
|
|
@@ -49319,7 +50574,7 @@ function abandonUpload(hub, workspaceId, conn, operationId, error51) {
|
|
|
49319
50574
|
hub.send(conn, "attachment_abort", { operation_id: operationId, reason });
|
|
49320
50575
|
}
|
|
49321
50576
|
async function resolveDescriptors(hub, conn, attachmentIds) {
|
|
49322
|
-
const operationId =
|
|
50577
|
+
const operationId = randomUUID24();
|
|
49323
50578
|
hub.openAttachment(conn, operationId);
|
|
49324
50579
|
try {
|
|
49325
50580
|
hub.send(conn, "attachment_resolve", {
|
|
@@ -49373,7 +50628,7 @@ ${attachmentManifest(descriptors)}`;
|
|
|
49373
50628
|
}
|
|
49374
50629
|
|
|
49375
50630
|
// ../server/src/control-channel/hub.ts
|
|
49376
|
-
import { randomUUID as
|
|
50631
|
+
import { randomUUID as randomUUID25 } from "crypto";
|
|
49377
50632
|
|
|
49378
50633
|
// ../server/src/relay/relay-stream-channel.ts
|
|
49379
50634
|
class RelayStreamRegistry {
|
|
@@ -49857,12 +51112,12 @@ class Hub {
|
|
|
49857
51112
|
relay(workspaceId, request) {
|
|
49858
51113
|
const conn = this.byWorkspace.get(workspaceId);
|
|
49859
51114
|
if (!conn?.registered) {
|
|
49860
|
-
return Promise.resolve({ request_id:
|
|
51115
|
+
return Promise.resolve({ request_id: randomUUID25(), headers: {}, error_code: "unreachable" });
|
|
49861
51116
|
}
|
|
49862
51117
|
if (conn.inflight.size + conn.streams.size >= MAX_INFLIGHT_RELAY) {
|
|
49863
|
-
return Promise.resolve({ request_id:
|
|
51118
|
+
return Promise.resolve({ request_id: randomUUID25(), headers: {}, error_code: "deadline" });
|
|
49864
51119
|
}
|
|
49865
|
-
const requestId2 =
|
|
51120
|
+
const requestId2 = randomUUID25();
|
|
49866
51121
|
return new Promise((resolve11) => {
|
|
49867
51122
|
const timer = setTimeout(() => {
|
|
49868
51123
|
conn.inflight.delete(requestId2);
|
|
@@ -49874,7 +51129,7 @@ class Hub {
|
|
|
49874
51129
|
}
|
|
49875
51130
|
relayStream(workspaceId, request, maxResponseBytes) {
|
|
49876
51131
|
const conn = this.byWorkspace.get(workspaceId);
|
|
49877
|
-
const requestId2 =
|
|
51132
|
+
const requestId2 = randomUUID25();
|
|
49878
51133
|
if (!conn?.registered) {
|
|
49879
51134
|
return Promise.resolve({ request_id: requestId2, headers: {}, error_code: "unreachable" });
|
|
49880
51135
|
}
|
|
@@ -50088,7 +51343,7 @@ async function authenticateConnection(deps, credentials) {
|
|
|
50088
51343
|
return { error: "Missing workspace credentials." };
|
|
50089
51344
|
}
|
|
50090
51345
|
const row = await deps.store.getWorkspace(credentials.workspaceId);
|
|
50091
|
-
if (!row || isTerminal(row.state))
|
|
51346
|
+
if (!row || row.purgeRequestedAt || isTerminal(row.state))
|
|
50092
51347
|
return { error: "Unknown workspace." };
|
|
50093
51348
|
if (sourceCredentialReference2(row) && credentials.protocolVersion < SOURCE_CREDENTIAL_MIN_PROTOCOL_VERSION) {
|
|
50094
51349
|
return { error: "Source credentials require agent protocol version 6." };
|
|
@@ -50534,7 +51789,7 @@ function agentWsEvents(deps) {
|
|
|
50534
51789
|
};
|
|
50535
51790
|
}
|
|
50536
51791
|
// ../server/src/conversations/conversations-routes.ts
|
|
50537
|
-
import { randomUUID as
|
|
51792
|
+
import { randomUUID as randomUUID26 } from "crypto";
|
|
50538
51793
|
|
|
50539
51794
|
// ../server/src/http/pagination.ts
|
|
50540
51795
|
function encodeCursor(scope, value) {
|
|
@@ -50648,7 +51903,7 @@ function registerConversationRoutes({
|
|
|
50648
51903
|
await store.deleteConversation(workspace.id, at);
|
|
50649
51904
|
if (previous?.status !== "deleted") {
|
|
50650
51905
|
await store.appendEvent(workspace.id, "workspace.conversation_deleted", {
|
|
50651
|
-
id:
|
|
51906
|
+
id: randomUUID26(),
|
|
50652
51907
|
type: "workspace.conversation_deleted",
|
|
50653
51908
|
occurred_at: at.toISOString(),
|
|
50654
51909
|
workspace_id: workspace.id
|
|
@@ -50846,7 +52101,9 @@ var HEALTHY_CHECKS = {
|
|
|
50846
52101
|
database: "ok",
|
|
50847
52102
|
schema: "ok",
|
|
50848
52103
|
reconciliation: "ok",
|
|
50849
|
-
coordinator: "ok"
|
|
52104
|
+
coordinator: "ok",
|
|
52105
|
+
"policy-reconciliation": "disabled",
|
|
52106
|
+
cleanup: "ok"
|
|
50850
52107
|
};
|
|
50851
52108
|
|
|
50852
52109
|
class Readiness {
|
|
@@ -51090,6 +52347,40 @@ function registerCheckpointRoutes(deps) {
|
|
|
51090
52347
|
});
|
|
51091
52348
|
}
|
|
51092
52349
|
|
|
52350
|
+
// ../server/src/persistence/purge-routes.ts
|
|
52351
|
+
function registerPurgeRoutes(app, persistence2) {
|
|
52352
|
+
app.openapi(createRoute({
|
|
52353
|
+
method: "post",
|
|
52354
|
+
path: "/v1/workspaces/{id}/purge",
|
|
52355
|
+
operationId: "purgeWorkspace",
|
|
52356
|
+
tags: ["Workspaces"],
|
|
52357
|
+
middleware: [
|
|
52358
|
+
requireScope("workspaces:purge"),
|
|
52359
|
+
bodyLimit({
|
|
52360
|
+
maxSize: 1024,
|
|
52361
|
+
onError: () => {
|
|
52362
|
+
throw new ApiError("validation.invalid", "Purge requires an empty JSON object.");
|
|
52363
|
+
}
|
|
52364
|
+
})
|
|
52365
|
+
],
|
|
52366
|
+
request: {
|
|
52367
|
+
params: exports_external.object({ id: exports_external.uuid() }),
|
|
52368
|
+
headers: IdempotencyHeadersSchema,
|
|
52369
|
+
body: { content: { "application/json": { schema: exports_external.strictObject({}) } } }
|
|
52370
|
+
},
|
|
52371
|
+
responses: {
|
|
52372
|
+
202: {
|
|
52373
|
+
description: "Durable purge accepted",
|
|
52374
|
+
content: { "application/json": { schema: OperationResourceSchema } }
|
|
52375
|
+
},
|
|
52376
|
+
...COMMON_ERROR_RESPONSES
|
|
52377
|
+
}
|
|
52378
|
+
}), async (c) => {
|
|
52379
|
+
const operation = await persistence2.purge(c.get("principal"), c.req.valid("param").id, c.req.valid("header")["Idempotency-Key"]);
|
|
52380
|
+
return c.json(toOperationResource(operation), 202);
|
|
52381
|
+
});
|
|
52382
|
+
}
|
|
52383
|
+
|
|
51093
52384
|
// ../server/src/persistence/recovery-routes.ts
|
|
51094
52385
|
function registerRecoveryRoutes({
|
|
51095
52386
|
app,
|
|
@@ -51201,6 +52492,14 @@ function registerRecoveryRoutes({
|
|
|
51201
52492
|
|
|
51202
52493
|
// ../server/src/relay/relay.ts
|
|
51203
52494
|
var SAFE_RESPONSE_HEADERS = ["content-type", "cache-control"];
|
|
52495
|
+
function assertRelayReady(row) {
|
|
52496
|
+
if (row.purgeRequestedAt)
|
|
52497
|
+
throw new ApiError("operation.conflict", "Workspace content is being purged.");
|
|
52498
|
+
if (isTerminal(row.state))
|
|
52499
|
+
throw new ApiError("workspace.terminal", "This workspace has ended.");
|
|
52500
|
+
if (row.state !== "ready")
|
|
52501
|
+
throw new ApiError("workspace.not_ready", "Workspace is not ready.");
|
|
52502
|
+
}
|
|
51204
52503
|
function requestPath(c, prefix) {
|
|
51205
52504
|
const rawPath = c.req.path.startsWith(prefix) ? c.req.path.slice(prefix.length) : "";
|
|
51206
52505
|
return rawPath === "" ? "/" : rawPath;
|
|
@@ -51299,12 +52598,7 @@ function relayHandler(deps, options) {
|
|
|
51299
52598
|
const id = c.req.param("id") ?? "";
|
|
51300
52599
|
const serviceName = options?.service ?? c.req.param("service") ?? "";
|
|
51301
52600
|
const row = await deps.service.getOwned(principal, id);
|
|
51302
|
-
|
|
51303
|
-
throw new ApiError("workspace.terminal", "This workspace has ended.");
|
|
51304
|
-
}
|
|
51305
|
-
if (row.state !== "ready") {
|
|
51306
|
-
throw new ApiError("workspace.not_ready", "Workspace is not ready.");
|
|
51307
|
-
}
|
|
52601
|
+
assertRelayReady(row);
|
|
51308
52602
|
const prefix = options?.pathPrefix?.(id) ?? `/v1/workspaces/${id}/services/${serviceName}`;
|
|
51309
52603
|
const path = requestPath(c, prefix);
|
|
51310
52604
|
const method = c.req.method;
|
|
@@ -51316,6 +52610,8 @@ function relayHandler(deps, options) {
|
|
|
51316
52610
|
let bodyB64 = await requestBody(c, method, match2.route.maxRequestBytes);
|
|
51317
52611
|
if (options?.transformBodyB64)
|
|
51318
52612
|
bodyB64 = await options.transformBodyB64(c, bodyB64);
|
|
52613
|
+
if ((await deps.store.getWorkspace(id))?.purgeRequestedAt)
|
|
52614
|
+
throw new ApiError("operation.conflict", "Workspace content is being purged.");
|
|
51319
52615
|
if (!deps.hub.isConnected(id)) {
|
|
51320
52616
|
throw new ApiError("workspace.disconnected", "The workspace supervisor is not currently connected.");
|
|
51321
52617
|
}
|
|
@@ -51425,7 +52721,7 @@ function registerCatalogRoutes({ app, store }) {
|
|
|
51425
52721
|
}
|
|
51426
52722
|
|
|
51427
52723
|
// ../server/src/terminals/terminal-ws.ts
|
|
51428
|
-
import { randomUUID as
|
|
52724
|
+
import { randomUUID as randomUUID27 } from "crypto";
|
|
51429
52725
|
var SessionIdSchema = exports_external.uuid();
|
|
51430
52726
|
function terminalConnectValidator(deps) {
|
|
51431
52727
|
return async (c, next) => {
|
|
@@ -51438,6 +52734,8 @@ function terminalConnectValidator(deps) {
|
|
|
51438
52734
|
async function terminalCapability(deps, c) {
|
|
51439
52735
|
const workspaceId = c.req.param("id") ?? "";
|
|
51440
52736
|
const workspace = await deps.service.getOwned(c.get("principal"), workspaceId);
|
|
52737
|
+
if (workspace.purgeRequestedAt)
|
|
52738
|
+
throw new ApiError("operation.conflict", "Workspace content is being purged.");
|
|
51441
52739
|
if (isTerminal(workspace.state)) {
|
|
51442
52740
|
throw new ApiError("workspace.terminal", "This workspace has ended.");
|
|
51443
52741
|
}
|
|
@@ -51466,7 +52764,7 @@ async function terminalSession(deps, c, workspaceId, maxSessions) {
|
|
|
51466
52764
|
if (c.req.header("upgrade")?.toLowerCase() !== "websocket") {
|
|
51467
52765
|
throw new ApiError("validation.invalid", "WebSocket upgrade required.");
|
|
51468
52766
|
}
|
|
51469
|
-
const sessionId = parsedSessionId?.data ??
|
|
52767
|
+
const sessionId = parsedSessionId?.data ?? randomUUID27();
|
|
51470
52768
|
if (requested) {
|
|
51471
52769
|
const session2 = await deps.store.getTerminalSession(sessionId);
|
|
51472
52770
|
if (!session2 || session2.workspaceId !== workspaceId) {
|
|
@@ -51769,6 +53067,7 @@ function buildServer(deps) {
|
|
|
51769
53067
|
}) : undefined;
|
|
51770
53068
|
const persistenceHolder = {};
|
|
51771
53069
|
const scheduler = new Scheduler({
|
|
53070
|
+
...deps.authorizeLaunch ? { authorizeLaunch: deps.authorizeLaunch } : {},
|
|
51772
53071
|
store,
|
|
51773
53072
|
driver,
|
|
51774
53073
|
...deps.storageDriver ? { storageDriver: deps.storageDriver } : {},
|
|
@@ -51869,9 +53168,12 @@ function buildServer(deps) {
|
|
|
51869
53168
|
});
|
|
51870
53169
|
app.use("/v1/*", machineAuth(store, pepper));
|
|
51871
53170
|
registerCatalogRoutes({ app, store });
|
|
53171
|
+
registerKeyRoutes(app, store, pepper);
|
|
53172
|
+
registerOperatorRecoveryRoutes(app, store, persistence2);
|
|
51872
53173
|
registerAdministrationRoutes({ app, persistence: persistence2, warmPool });
|
|
51873
53174
|
registerCheckpointRoutes({ app, store, service, persistence: persistence2 });
|
|
51874
53175
|
registerRecoveryRoutes({ app, store, service, persistence: persistence2 });
|
|
53176
|
+
registerPurgeRoutes(app, persistence2);
|
|
51875
53177
|
registerConversationRoutes({ app, store, service });
|
|
51876
53178
|
registerWorkspaceRoutes({ app, store, service });
|
|
51877
53179
|
registerDiagnosticRoutes({ app, store, service });
|
|
@@ -51907,6 +53209,98 @@ function buildServer(deps) {
|
|
|
51907
53209
|
};
|
|
51908
53210
|
}
|
|
51909
53211
|
|
|
53212
|
+
// ../server/src/lifecycle/policy-reconciliation.ts
|
|
53213
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
53214
|
+
|
|
53215
|
+
class PolicyReconciliation {
|
|
53216
|
+
store;
|
|
53217
|
+
url;
|
|
53218
|
+
token;
|
|
53219
|
+
active;
|
|
53220
|
+
constructor(store, url2, token) {
|
|
53221
|
+
this.store = store;
|
|
53222
|
+
this.url = url2;
|
|
53223
|
+
this.token = token;
|
|
53224
|
+
}
|
|
53225
|
+
async post(path, body) {
|
|
53226
|
+
try {
|
|
53227
|
+
const response = await fetch(`${this.url.replace(/\/$/, "")}/${path}`, {
|
|
53228
|
+
method: "POST",
|
|
53229
|
+
redirect: "error",
|
|
53230
|
+
signal: AbortSignal.timeout(5000),
|
|
53231
|
+
headers: { authorization: `Bearer ${this.token}`, "content-type": "application/json" },
|
|
53232
|
+
body: JSON.stringify(body)
|
|
53233
|
+
});
|
|
53234
|
+
if (!response.ok)
|
|
53235
|
+
throw new Error("policy response");
|
|
53236
|
+
return await response.json();
|
|
53237
|
+
} catch {
|
|
53238
|
+
throw new Error("Policy reconciliation unavailable");
|
|
53239
|
+
}
|
|
53240
|
+
}
|
|
53241
|
+
tick() {
|
|
53242
|
+
this.active ??= this.reconcile().finally(() => {
|
|
53243
|
+
this.active = undefined;
|
|
53244
|
+
});
|
|
53245
|
+
return this.active;
|
|
53246
|
+
}
|
|
53247
|
+
async drain() {
|
|
53248
|
+
await this.active?.catch(() => {});
|
|
53249
|
+
}
|
|
53250
|
+
async reconcile() {
|
|
53251
|
+
const pending = await this.post("pending", {});
|
|
53252
|
+
if (typeof pending !== "object" || pending === null || !("workspace_ids" in pending) || !Array.isArray(pending.workspace_ids)) {
|
|
53253
|
+
throw new Error("Invalid policy reconciliation response");
|
|
53254
|
+
}
|
|
53255
|
+
let failed = false;
|
|
53256
|
+
for (const id of pending.workspace_ids) {
|
|
53257
|
+
try {
|
|
53258
|
+
if (typeof id !== "string")
|
|
53259
|
+
throw new Error("Invalid workspace identity");
|
|
53260
|
+
const row = await this.store.getWorkspace(id);
|
|
53261
|
+
if (!row)
|
|
53262
|
+
throw new Error("Missing reconciliation workspace");
|
|
53263
|
+
const ref = row.providerRef;
|
|
53264
|
+
const result = await this.post("reconcile", {
|
|
53265
|
+
workspace_id: row.id,
|
|
53266
|
+
principal_id: row.principalId,
|
|
53267
|
+
external_id: row.externalId,
|
|
53268
|
+
template_name: row.templateName,
|
|
53269
|
+
launch_mode: row.launchMode,
|
|
53270
|
+
metadata: row.metadata,
|
|
53271
|
+
state: row.state,
|
|
53272
|
+
reason_code: row.reasonCode,
|
|
53273
|
+
launch_attempts: row.launchAttempts,
|
|
53274
|
+
provider_kind: row.providerKind,
|
|
53275
|
+
provider_ref: ref ? {
|
|
53276
|
+
id: ref.id,
|
|
53277
|
+
namespace: ref.namespace,
|
|
53278
|
+
poolRuntimeId: ref.poolRuntimeId,
|
|
53279
|
+
terminationEvidence: ref.terminationEvidence
|
|
53280
|
+
} : null
|
|
53281
|
+
});
|
|
53282
|
+
if (typeof result !== "object" || result === null || !("workspace_id" in result) || result.workspace_id !== row.id || !("released" in result) || typeof result.released !== "boolean") {
|
|
53283
|
+
throw new Error("Invalid policy reconciliation response");
|
|
53284
|
+
}
|
|
53285
|
+
if ("reason" in result && ["termination-unproven", "provider-unavailable"].includes(String(result.reason)))
|
|
53286
|
+
failed = true;
|
|
53287
|
+
} catch {
|
|
53288
|
+
failed = true;
|
|
53289
|
+
}
|
|
53290
|
+
}
|
|
53291
|
+
if (failed)
|
|
53292
|
+
throw new Error("Policy reconciliation unavailable or termination unproven");
|
|
53293
|
+
}
|
|
53294
|
+
}
|
|
53295
|
+
function loadPolicyReconciliation(store, config2) {
|
|
53296
|
+
if (!config2)
|
|
53297
|
+
return;
|
|
53298
|
+
const token = readFileSync5(config2.tokenFile, "utf8").trim();
|
|
53299
|
+
if (token.length < 24 || /\s/.test(token))
|
|
53300
|
+
throw new Error("Invalid launch policy control token");
|
|
53301
|
+
return new PolicyReconciliation(store, config2.url, token);
|
|
53302
|
+
}
|
|
53303
|
+
|
|
51910
53304
|
// ../server/src/lifecycle/lifecycle.ts
|
|
51911
53305
|
var defaultLog = (message) => console.log(`[pocketcoder-server] ${message}`);
|
|
51912
53306
|
async function initializeStore(config2, log) {
|
|
@@ -51944,6 +53338,7 @@ function createWorkspaceDriver(config2) {
|
|
|
51944
53338
|
return new KubernetesDriver({
|
|
51945
53339
|
...egress,
|
|
51946
53340
|
namespace: config2.kubernetesNamespace,
|
|
53341
|
+
captureTerminationEvidence: Boolean(config2.launchPolicy),
|
|
51947
53342
|
nodeSelector: config2.kubernetesNodeSelector ?? undefined,
|
|
51948
53343
|
tolerations: config2.kubernetesTolerations,
|
|
51949
53344
|
...config2.kubernetesServiceAccount ? { serviceAccountName: config2.kubernetesServiceAccount } : {}
|
|
@@ -52006,6 +53401,7 @@ function startExclusiveTimer(intervalMs, task, errorContext, log) {
|
|
|
52006
53401
|
}
|
|
52007
53402
|
async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
52008
53403
|
const log = options.log ?? defaultLog;
|
|
53404
|
+
const authorizeLaunch = loadLaunchPolicy(config2.launchPolicy);
|
|
52009
53405
|
const logger = createStructuredLogger((record3) => log(JSON.stringify(record3)));
|
|
52010
53406
|
const metrics = new RuntimeMetrics;
|
|
52011
53407
|
log(`config: ${JSON.stringify(configSummary(config2))}`);
|
|
@@ -52021,7 +53417,11 @@ async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
|
52021
53417
|
const storageDriver = createStorageDriver(config2);
|
|
52022
53418
|
const secretResolver = createSecretResolver(config2);
|
|
52023
53419
|
const readiness = new Readiness({ reconciliation: "pending" }, metrics);
|
|
53420
|
+
const policyReconciliation = loadPolicyReconciliation(store, config2.launchPolicy);
|
|
53421
|
+
if (policyReconciliation)
|
|
53422
|
+
readiness.set("policy-reconciliation", "pending");
|
|
52024
53423
|
const { app, websocket: websocket2, scheduler, persistence: persistence2, warmPool } = buildServer({
|
|
53424
|
+
...authorizeLaunch ? { authorizeLaunch } : {},
|
|
52025
53425
|
store,
|
|
52026
53426
|
driver,
|
|
52027
53427
|
...storageDriver ? { storageDriver } : {},
|
|
@@ -52048,6 +53448,9 @@ async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
|
52048
53448
|
const schedulerTimer = startExclusiveTimer(config2.schedulerIntervalMs, async () => {
|
|
52049
53449
|
try {
|
|
52050
53450
|
await scheduler.tick();
|
|
53451
|
+
const pendingPurges = await persistence2.retryPurges();
|
|
53452
|
+
readiness.set("cleanup", pendingPurges > 0 ? "pending" : "ok");
|
|
53453
|
+
metrics.observe("purge.pending", pendingPurges);
|
|
52051
53454
|
readiness.set("coordinator", "ok");
|
|
52052
53455
|
} catch (error51) {
|
|
52053
53456
|
readiness.set("coordinator", "failed");
|
|
@@ -52055,6 +53458,15 @@ async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
|
52055
53458
|
}
|
|
52056
53459
|
}, "scheduler tick failed", log);
|
|
52057
53460
|
const outboxTimer = startExclusiveTimer(config2.outboxIntervalMs, () => outbox2.tick(), "outbox tick failed", log);
|
|
53461
|
+
const policyTimer = policyReconciliation ? startExclusiveTimer(config2.schedulerIntervalMs, async () => {
|
|
53462
|
+
try {
|
|
53463
|
+
await policyReconciliation.tick();
|
|
53464
|
+
readiness.set("policy-reconciliation", "ok");
|
|
53465
|
+
} catch (error51) {
|
|
53466
|
+
readiness.set("policy-reconciliation", "failed");
|
|
53467
|
+
throw error51;
|
|
53468
|
+
}
|
|
53469
|
+
}, "policy reconciliation failed", log) : null;
|
|
52058
53470
|
const warmPoolTimer = warmPool && warmPoolContinuously ? startExclusiveTimer(config2.schedulerIntervalMs, () => warmPool.reconcile(), "warm pool reconciliation failed", log) : null;
|
|
52059
53471
|
const retentionTimer = startExclusiveTimer(60000, async () => {
|
|
52060
53472
|
const { deleted, skipped } = await persistence2.pruneExpired();
|
|
@@ -52075,6 +53487,9 @@ async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
|
52075
53487
|
clearInterval(schedulerTimer);
|
|
52076
53488
|
clearInterval(outboxTimer);
|
|
52077
53489
|
clearInterval(retentionTimer);
|
|
53490
|
+
if (policyTimer)
|
|
53491
|
+
clearInterval(policyTimer);
|
|
53492
|
+
await policyReconciliation?.drain();
|
|
52078
53493
|
if (warmPoolTimer)
|
|
52079
53494
|
clearInterval(warmPoolTimer);
|
|
52080
53495
|
throw error51;
|
|
@@ -52096,6 +53511,9 @@ async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
|
52096
53511
|
clearInterval(schedulerTimer);
|
|
52097
53512
|
clearInterval(outboxTimer);
|
|
52098
53513
|
clearInterval(retentionTimer);
|
|
53514
|
+
if (policyTimer)
|
|
53515
|
+
clearInterval(policyTimer);
|
|
53516
|
+
await policyReconciliation?.drain();
|
|
52099
53517
|
if (warmPoolTimer)
|
|
52100
53518
|
clearInterval(warmPoolTimer);
|
|
52101
53519
|
await server.stop(true);
|
|
@@ -52146,7 +53564,7 @@ function stateFromJson(value) {
|
|
|
52146
53564
|
}
|
|
52147
53565
|
function readState() {
|
|
52148
53566
|
try {
|
|
52149
|
-
return stateFromJson(JSON.parse(
|
|
53567
|
+
return stateFromJson(JSON.parse(readFileSync6(statePath(), "utf8")));
|
|
52150
53568
|
} catch {
|
|
52151
53569
|
return null;
|
|
52152
53570
|
}
|
|
@@ -52154,7 +53572,7 @@ function readState() {
|
|
|
52154
53572
|
function writeState(state) {
|
|
52155
53573
|
const path = statePath();
|
|
52156
53574
|
mkdirSync(dirname7(path), { recursive: true, mode: 448 });
|
|
52157
|
-
const temporary = `${path}.${process.pid}.${
|
|
53575
|
+
const temporary = `${path}.${process.pid}.${randomUUID28()}.tmp`;
|
|
52158
53576
|
writeFileSync(temporary, `${JSON.stringify(state, null, 2)}
|
|
52159
53577
|
`, { mode: 384 });
|
|
52160
53578
|
renameSync(temporary, path);
|
|
@@ -52226,7 +53644,7 @@ function selfInvocation(args) {
|
|
|
52226
53644
|
}
|
|
52227
53645
|
function logTail(path) {
|
|
52228
53646
|
try {
|
|
52229
|
-
const content =
|
|
53647
|
+
const content = readFileSync6(path, "utf8");
|
|
52230
53648
|
return content.slice(Math.max(0, content.length - 4000)).trim();
|
|
52231
53649
|
} catch {
|
|
52232
53650
|
return "";
|
|
@@ -52263,7 +53681,7 @@ async function startManagedServer(options) {
|
|
|
52263
53681
|
if (existing)
|
|
52264
53682
|
removeState();
|
|
52265
53683
|
const config2 = loadConfig();
|
|
52266
|
-
const instanceToken =
|
|
53684
|
+
const instanceToken = randomUUID28();
|
|
52267
53685
|
const state = {
|
|
52268
53686
|
version: 1,
|
|
52269
53687
|
pid: 0,
|
|
@@ -52442,7 +53860,7 @@ function addStorageCommands(parser2) {
|
|
|
52442
53860
|
}
|
|
52443
53861
|
|
|
52444
53862
|
// src/commands/templates/list.ts
|
|
52445
|
-
function
|
|
53863
|
+
function addListCommand5(parser2) {
|
|
52446
53864
|
return addAction(parser2, "list", "List authorized template versions through the REST API", (command2) => command2.option("json", { type: "boolean", description: "Print JSON" }), async (flags) => {
|
|
52447
53865
|
const items = await controlPlaneClient().templates.list();
|
|
52448
53866
|
if (flags.json)
|
|
@@ -52535,7 +53953,7 @@ function addValidateCommand(parser2) {
|
|
|
52535
53953
|
|
|
52536
53954
|
// src/commands/templates/index.ts
|
|
52537
53955
|
function addTemplateCommands(parser2) {
|
|
52538
|
-
return addResource(parser2, "templates", "Validate and inspect templates", (commands) => addListDatabaseCommand(
|
|
53956
|
+
return addResource(parser2, "templates", "Validate and inspect templates", (commands) => addListDatabaseCommand(addListCommand5(addRenderCommand(addValidateCommand(commands)))));
|
|
52539
53957
|
}
|
|
52540
53958
|
|
|
52541
53959
|
// src/commands/workspaces/chat-session.ts
|
|
@@ -52567,8 +53985,8 @@ async function waitForAgentInput(id, options, deps) {
|
|
|
52567
53985
|
}
|
|
52568
53986
|
|
|
52569
53987
|
// src/commands/workspaces/attachments.ts
|
|
52570
|
-
import { randomUUID as
|
|
52571
|
-
import { readFileSync as
|
|
53988
|
+
import { randomUUID as randomUUID29 } from "crypto";
|
|
53989
|
+
import { readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
52572
53990
|
import { basename as basename2, extname as extname2 } from "path";
|
|
52573
53991
|
var MEDIA_TYPES = {
|
|
52574
53992
|
".csv": "text/csv",
|
|
@@ -52600,12 +54018,12 @@ async function uploadAttachments(api3, workspaceId, paths, log) {
|
|
|
52600
54018
|
const name2 = basename2(path);
|
|
52601
54019
|
let bytes;
|
|
52602
54020
|
try {
|
|
52603
|
-
bytes =
|
|
54021
|
+
bytes = readFileSync7(path);
|
|
52604
54022
|
} catch {
|
|
52605
54023
|
throw new Error(`could not read attachment file: ${path}`);
|
|
52606
54024
|
}
|
|
52607
54025
|
log(`uploading ${name2} (${bytes.byteLength} bytes)`);
|
|
52608
|
-
const id =
|
|
54026
|
+
const id = randomUUID29();
|
|
52609
54027
|
const response = await api3(`/v1/workspaces/${workspaceId}/attachments/${id}`, {
|
|
52610
54028
|
method: "PUT",
|
|
52611
54029
|
headers: {
|
|
@@ -52673,7 +54091,7 @@ function fileFlags(value) {
|
|
|
52673
54091
|
}
|
|
52674
54092
|
|
|
52675
54093
|
// src/commands/workspaces/chat-cursor.ts
|
|
52676
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
54094
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
|
|
52677
54095
|
import { homedir as homedir2 } from "os";
|
|
52678
54096
|
import { dirname as dirname8, join as join10 } from "path";
|
|
52679
54097
|
function cursorFile() {
|
|
@@ -52682,7 +54100,7 @@ function cursorFile() {
|
|
|
52682
54100
|
}
|
|
52683
54101
|
function readCursors(path) {
|
|
52684
54102
|
try {
|
|
52685
|
-
return JSON.parse(
|
|
54103
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
52686
54104
|
} catch {
|
|
52687
54105
|
return {};
|
|
52688
54106
|
}
|
|
@@ -53050,7 +54468,7 @@ function addChatCommand(parser2) {
|
|
|
53050
54468
|
}
|
|
53051
54469
|
|
|
53052
54470
|
// src/commands/workspaces/create.ts
|
|
53053
|
-
import { randomUUID as
|
|
54471
|
+
import { randomUUID as randomUUID30 } from "crypto";
|
|
53054
54472
|
|
|
53055
54473
|
// src/commands/workspaces/launch-input.ts
|
|
53056
54474
|
function parseLaunchInput(value, fail2) {
|
|
@@ -53157,7 +54575,7 @@ async function waitForReady(initial, flags, { client, fail: failCommand }) {
|
|
|
53157
54575
|
}
|
|
53158
54576
|
async function createWorkspace(flags, deps) {
|
|
53159
54577
|
const template = required2(flags, "template", deps.fail);
|
|
53160
|
-
const externalId = typeof flags["external-id"] === "string" ? flags["external-id"] : `pcd-${
|
|
54578
|
+
const externalId = typeof flags["external-id"] === "string" ? flags["external-id"] : `pcd-${randomUUID30()}`;
|
|
53161
54579
|
const launchInput = parseLaunchInput(flags.input, deps.fail);
|
|
53162
54580
|
const created = await deps.client.workspaces.create({
|
|
53163
54581
|
externalId,
|
|
@@ -53226,7 +54644,7 @@ async function listWorkspaces(flags) {
|
|
|
53226
54644
|
else
|
|
53227
54645
|
printWorkspaces(items);
|
|
53228
54646
|
}
|
|
53229
|
-
function
|
|
54647
|
+
function addListCommand6(parser2) {
|
|
53230
54648
|
return addAction(parser2, "list", "List workspaces", (command2) => command2.option("active", { type: "boolean", description: "Only show nonterminal workspaces" }).option("state", { type: "string", description: "Filter by state" }).option("template", { type: "string", description: "Filter by template name" }).option("external-id", { type: "string", description: "Filter by external ID" }).option("limit", { type: "string", description: "Maximum number of workspaces" }).option("json", { type: "boolean", description: "Print JSON" }), listWorkspaces);
|
|
53231
54649
|
}
|
|
53232
54650
|
|
|
@@ -53269,14 +54687,29 @@ function addOutputsCommand(parser2) {
|
|
|
53269
54687
|
}
|
|
53270
54688
|
|
|
53271
54689
|
// src/commands/workspaces/preserve.ts
|
|
53272
|
-
import { randomUUID as
|
|
54690
|
+
import { randomUUID as randomUUID31 } from "crypto";
|
|
53273
54691
|
function addPreserveCommand(parser2) {
|
|
53274
54692
|
return addAction(parser2, "preserve", "Stop and checkpoint a persistence-enabled workspace", (command2) => command2.option("id", { type: "string", demandOption: true, description: "Workspace ID" }).option("retention", { type: "string" }).option("label", { type: "string" }), async (flags) => {
|
|
53275
54693
|
const id = need(flags, "id");
|
|
53276
54694
|
const result = await controlPlaneClient().workspaces.preserve(id, {
|
|
53277
54695
|
...typeof flags.retention === "string" ? { retention: flags.retention } : {},
|
|
53278
54696
|
...typeof flags.label === "string" ? { label: flags.label } : {}
|
|
53279
|
-
}, `preserve-${id}-${
|
|
54697
|
+
}, `preserve-${id}-${randomUUID31()}`);
|
|
54698
|
+
console.log(JSON.stringify(result, null, 2));
|
|
54699
|
+
});
|
|
54700
|
+
}
|
|
54701
|
+
|
|
54702
|
+
// src/commands/workspaces/purge.ts
|
|
54703
|
+
function addPurgeCommand(parser2) {
|
|
54704
|
+
return addAction(parser2, "purge", "Purge owned workspace content and return a durable operation", (command2) => command2.option("id", { type: "string", demandOption: true, description: "Workspace ID" }).option("request-id", {
|
|
54705
|
+
type: "string",
|
|
54706
|
+
demandOption: true,
|
|
54707
|
+
description: "Stable request ID; use a new ID for backup replay"
|
|
54708
|
+
}).option("principal-id", { type: "string", description: "Explicit target for delegated operator recovery" }), async (flags) => {
|
|
54709
|
+
const client = controlPlaneClient();
|
|
54710
|
+
const id = need(flags, "id");
|
|
54711
|
+
const key = need(flags, "request-id");
|
|
54712
|
+
const result = typeof flags["principal-id"] === "string" ? await client.recovery.purge(flags["principal-id"], id, key) : await client.workspaces.purge(id, key);
|
|
53280
54713
|
console.log(JSON.stringify(result, null, 2));
|
|
53281
54714
|
});
|
|
53282
54715
|
}
|
|
@@ -53451,7 +54884,7 @@ function addTerminalSessionsCommand(parser2) {
|
|
|
53451
54884
|
// src/commands/workspaces/index.ts
|
|
53452
54885
|
function addWorkspaceCommands(parser2) {
|
|
53453
54886
|
const actions = [
|
|
53454
|
-
|
|
54887
|
+
addListCommand6,
|
|
53455
54888
|
addCreateCommand2,
|
|
53456
54889
|
addGetCommand2,
|
|
53457
54890
|
addLogsCommand,
|
|
@@ -53460,6 +54893,7 @@ function addWorkspaceCommands(parser2) {
|
|
|
53460
54893
|
addTerminalCommand,
|
|
53461
54894
|
addCancelCommand,
|
|
53462
54895
|
addPreserveCommand,
|
|
54896
|
+
addPurgeCommand,
|
|
53463
54897
|
addRestoreCommand,
|
|
53464
54898
|
addRecreateCommand,
|
|
53465
54899
|
addOutputsCommand,
|