@pstdio/pocketcoder-cli 0.7.2 → 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);
|
|
@@ -21248,6 +21262,17 @@ function sqlValues(values) {
|
|
|
21248
21262
|
}
|
|
21249
21263
|
var timestamptz = (name2) => timestamp2(name2, { withTimezone: true, mode: "date" });
|
|
21250
21264
|
|
|
21265
|
+
// ../db/src/schema/structured-json.ts
|
|
21266
|
+
var structuredJson = customType2({
|
|
21267
|
+
dataType: () => "jsonb",
|
|
21268
|
+
codec: "jsonb",
|
|
21269
|
+
fromDriver: (value) => typeof value === "string" ? JSON.parse(value) : value
|
|
21270
|
+
});
|
|
21271
|
+
function structuredJsonValue(column) {
|
|
21272
|
+
return sql`CASE WHEN jsonb_typeof(${column}) = 'string'
|
|
21273
|
+
THEN (${column} #>> '{}')::jsonb ELSE ${column} END`;
|
|
21274
|
+
}
|
|
21275
|
+
|
|
21251
21276
|
// ../db/src/schema/access.ts
|
|
21252
21277
|
function createAccessTables(table2 = pgTable) {
|
|
21253
21278
|
const templates2 = table2("templates", {
|
|
@@ -21256,7 +21281,7 @@ function createAccessTables(table2 = pgTable) {
|
|
|
21256
21281
|
version: text2("version").notNull(),
|
|
21257
21282
|
digest: text2("digest").notNull().unique(),
|
|
21258
21283
|
description: text2("description"),
|
|
21259
|
-
spec:
|
|
21284
|
+
spec: structuredJson("spec").$type().notNull(),
|
|
21260
21285
|
status: text2("status").$type().notNull(),
|
|
21261
21286
|
createdAt: timestamptz("created_at").notNull(),
|
|
21262
21287
|
retiredAt: timestamptz("retired_at")
|
|
@@ -21273,6 +21298,9 @@ function createAccessTables(table2 = pgTable) {
|
|
|
21273
21298
|
createdAt: timestamptz("created_at").notNull()
|
|
21274
21299
|
});
|
|
21275
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([]),
|
|
21276
21304
|
id: uuid("id").primaryKey(),
|
|
21277
21305
|
principalId: uuid("principal_id").notNull().references(() => principals.id),
|
|
21278
21306
|
secretDigest: bytea2("secret_digest").$type().notNull(),
|
|
@@ -21281,7 +21309,7 @@ function createAccessTables(table2 = pgTable) {
|
|
|
21281
21309
|
expiresAt: timestamptz("expires_at"),
|
|
21282
21310
|
revokedAt: timestamptz("revoked_at"),
|
|
21283
21311
|
lastUsedAt: timestamptz("last_used_at")
|
|
21284
|
-
});
|
|
21312
|
+
}, (table3) => [unique().on(table3.principalId, table3.issuanceRequestId)]);
|
|
21285
21313
|
return { templates: templates2, principals, machineKeys };
|
|
21286
21314
|
}
|
|
21287
21315
|
|
|
@@ -35629,6 +35657,58 @@ var AttachmentResolvedPayload = exports_external.object({
|
|
|
35629
35657
|
descriptors: exports_external.array(AttachmentDescriptorSchema).optional(),
|
|
35630
35658
|
missing_id: exports_external.uuid().optional()
|
|
35631
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() });
|
|
35632
35712
|
// ../contracts/src/common/canonical.ts
|
|
35633
35713
|
import { createHash as createHash2 } from "crypto";
|
|
35634
35714
|
function canonicalJson(value) {
|
|
@@ -35684,6 +35764,8 @@ var ERROR_CODES = {
|
|
|
35684
35764
|
"auth.invalid_key": 401,
|
|
35685
35765
|
"auth.missing_scope": 403,
|
|
35686
35766
|
"auth.disabled_principal": 403,
|
|
35767
|
+
"principal.not_found": 404,
|
|
35768
|
+
"key.not_found": 404,
|
|
35687
35769
|
"validation.invalid": 400,
|
|
35688
35770
|
"idempotency.conflict": 409,
|
|
35689
35771
|
"capacity.queue_full": 429,
|
|
@@ -35771,33 +35853,6 @@ function CursorQuerySchema(maxLimit, defaultLimit) {
|
|
|
35771
35853
|
limit: exports_external.coerce.number().int().positive().max(maxLimit).default(defaultLimit)
|
|
35772
35854
|
});
|
|
35773
35855
|
}
|
|
35774
|
-
// ../contracts/src/common/scopes.ts
|
|
35775
|
-
var SCOPES = [
|
|
35776
|
-
"templates:read",
|
|
35777
|
-
"workspaces:create",
|
|
35778
|
-
"workspaces:read",
|
|
35779
|
-
"workspaces:cancel",
|
|
35780
|
-
"workspaces:preserve",
|
|
35781
|
-
"workspaces:restore",
|
|
35782
|
-
"checkpoints:read",
|
|
35783
|
-
"checkpoints:delete",
|
|
35784
|
-
"outputs:read",
|
|
35785
|
-
"conversations:read",
|
|
35786
|
-
"conversations:delete",
|
|
35787
|
-
"services:relay",
|
|
35788
|
-
"attachments:write",
|
|
35789
|
-
"logs:read",
|
|
35790
|
-
"network:read",
|
|
35791
|
-
"terminal:attach",
|
|
35792
|
-
"terminal:read",
|
|
35793
|
-
"admin"
|
|
35794
|
-
];
|
|
35795
|
-
function isScope(value) {
|
|
35796
|
-
return SCOPES.includes(value);
|
|
35797
|
-
}
|
|
35798
|
-
function hasScope(granted, required2) {
|
|
35799
|
-
return granted.includes(required2) || granted.includes("admin");
|
|
35800
|
-
}
|
|
35801
35856
|
// ../contracts/src/conversations/conversation.ts
|
|
35802
35857
|
var CONVERSATION_ROLES = ["user", "assistant", "system", "tool"];
|
|
35803
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");
|
|
@@ -35924,7 +35979,7 @@ var CHECKPOINT_STATES = ["creating", "ready", "failed", "deleting", "deleted"];
|
|
|
35924
35979
|
function isCheckpointState(value) {
|
|
35925
35980
|
return CHECKPOINT_STATES.some((state) => state === value);
|
|
35926
35981
|
}
|
|
35927
|
-
var OPERATION_KINDS = ["preserve", "restore", "verify", "delete"];
|
|
35982
|
+
var OPERATION_KINDS = ["preserve", "restore", "verify", "delete", "purge"];
|
|
35928
35983
|
var OPERATION_STATES = ["pending", "running", "succeeded", "failed"];
|
|
35929
35984
|
var STORAGE_STATES = [
|
|
35930
35985
|
"allocating",
|
|
@@ -37314,7 +37369,7 @@ function createActivityTables(table2 = pgTable, { workspaces: workspaces2 }) {
|
|
|
37314
37369
|
role: text2("role").$type().notNull(),
|
|
37315
37370
|
content: text2("content").notNull(),
|
|
37316
37371
|
occurredAt: timestamptz("occurred_at").notNull(),
|
|
37317
|
-
metadata:
|
|
37372
|
+
metadata: structuredJson("metadata").$type().notNull().default({}),
|
|
37318
37373
|
createdAt: timestamptz("created_at").notNull()
|
|
37319
37374
|
}, (table3) => [
|
|
37320
37375
|
primaryKey({ columns: [table3.workspaceId, table3.seq] }),
|
|
@@ -37368,9 +37423,9 @@ function createPersistenceTables(table2 = pgTable, { principals }, { workspaces:
|
|
|
37368
37423
|
workspaceId: uuid("workspace_id").notNull().references(() => workspaces2.id),
|
|
37369
37424
|
principalId: uuid("principal_id").notNull().references(() => principals.id),
|
|
37370
37425
|
providerKind: text2("provider_kind").notNull(),
|
|
37371
|
-
providerRef:
|
|
37426
|
+
providerRef: structuredJson("provider_ref").$type().notNull(),
|
|
37372
37427
|
state: text2("state").$type().notNull(),
|
|
37373
|
-
mountManifest:
|
|
37428
|
+
mountManifest: structuredJson("mount_manifest").$type().notNull(),
|
|
37374
37429
|
logicalBytes: bigint2("logical_bytes", { mode: "number" }),
|
|
37375
37430
|
fileCount: bigint2("file_count", { mode: "number" }),
|
|
37376
37431
|
retainedUntil: timestamptz("retained_until"),
|
|
@@ -37393,11 +37448,11 @@ function createPersistenceTables(table2 = pgTable, { principals }, { workspaces:
|
|
|
37393
37448
|
state: text2("state").$type().notNull(),
|
|
37394
37449
|
reasonCode: text2("reason_code").$type(),
|
|
37395
37450
|
providerKind: text2("provider_kind").notNull(),
|
|
37396
|
-
providerRef:
|
|
37397
|
-
templateSnapshot:
|
|
37451
|
+
providerRef: structuredJson("provider_ref").$type(),
|
|
37452
|
+
templateSnapshot: structuredJson("template_snapshot").$type().notNull(),
|
|
37398
37453
|
templateDigest: text2("template_digest").notNull(),
|
|
37399
|
-
sourceProvenance:
|
|
37400
|
-
manifest:
|
|
37454
|
+
sourceProvenance: structuredJson("source_provenance").$type(),
|
|
37455
|
+
manifest: structuredJson("manifest").$type(),
|
|
37401
37456
|
manifestDigest: text2("manifest_digest"),
|
|
37402
37457
|
logicalBytes: bigint2("logical_bytes", { mode: "number" }),
|
|
37403
37458
|
storedBytes: bigint2("stored_bytes", { mode: "number" }),
|
|
@@ -37452,7 +37507,7 @@ function createWorkspaceTables(table2 = pgTable, { principals, machineKeys, temp
|
|
|
37452
37507
|
templateName: text2("template_name").notNull(),
|
|
37453
37508
|
templateVersion: text2("template_version").notNull(),
|
|
37454
37509
|
templateDigest: text2("template_digest").notNull(),
|
|
37455
|
-
templateSnapshot:
|
|
37510
|
+
templateSnapshot: structuredJson("template_snapshot").$type().notNull(),
|
|
37456
37511
|
state: text2("state").$type().notNull(),
|
|
37457
37512
|
reasonCode: text2("reason_code").$type(),
|
|
37458
37513
|
agentState: text2("agent_state").$type().notNull().default("unknown"),
|
|
@@ -37463,9 +37518,9 @@ function createWorkspaceTables(table2 = pgTable, { principals, machineKeys, temp
|
|
|
37463
37518
|
failureLogTailTruncated: boolean2("failure_log_tail_truncated").notNull().default(false),
|
|
37464
37519
|
failureLastLogSeq: bigint2("failure_last_log_seq", { mode: "number" }),
|
|
37465
37520
|
terminalIntent: text2("terminal_intent").$type(),
|
|
37466
|
-
launchInput:
|
|
37521
|
+
launchInput: structuredJson("launch_input").$type(),
|
|
37467
37522
|
providerKind: text2("provider_kind"),
|
|
37468
|
-
providerRef:
|
|
37523
|
+
providerRef: structuredJson("provider_ref").$type(),
|
|
37469
37524
|
provisioningMode: text2("provisioning_mode").$type(),
|
|
37470
37525
|
registrationDigest: bytea2("registration_digest").$type(),
|
|
37471
37526
|
registrationExpiresAt: timestamptz("registration_expires_at"),
|
|
@@ -37476,20 +37531,21 @@ function createWorkspaceTables(table2 = pgTable, { principals, machineKeys, temp
|
|
|
37476
37531
|
readyAt: timestamptz("ready_at"),
|
|
37477
37532
|
lastActivityAt: timestamptz("last_activity_at"),
|
|
37478
37533
|
launchAttempts: integer("launch_attempts").notNull().default(0),
|
|
37479
|
-
health:
|
|
37480
|
-
metadata:
|
|
37534
|
+
health: structuredJson("health").$type().notNull().default({}),
|
|
37535
|
+
metadata: structuredJson("metadata").$type().notNull().default({}),
|
|
37481
37536
|
deadlineAt: timestamptz("deadline_at").notNull(),
|
|
37482
37537
|
createdAt: timestamptz("created_at").notNull(),
|
|
37483
37538
|
updatedAt: timestamptz("updated_at").notNull(),
|
|
37484
37539
|
terminalAt: timestamptz("terminal_at"),
|
|
37540
|
+
purgeRequestedAt: timestamptz("purge_requested_at"),
|
|
37485
37541
|
originWorkspaceId: uuid("origin_workspace_id"),
|
|
37486
37542
|
restoredFromCheckpointId: uuid("restored_from_checkpoint_id"),
|
|
37487
|
-
sourceDescriptor:
|
|
37488
|
-
resolvedSource:
|
|
37543
|
+
sourceDescriptor: structuredJson("source_descriptor").$type(),
|
|
37544
|
+
resolvedSource: structuredJson("resolved_source").$type(),
|
|
37489
37545
|
persistenceCapability: text2("persistence_capability").$type().notNull().default("filesystem_only"),
|
|
37490
37546
|
latestCheckpointId: uuid("latest_checkpoint_id"),
|
|
37491
37547
|
launchMode: text2("launch_mode").$type().notNull().default("create"),
|
|
37492
|
-
outputs:
|
|
37548
|
+
outputs: structuredJson("outputs").$type().notNull().default({})
|
|
37493
37549
|
}, (table3) => [
|
|
37494
37550
|
check("workspaces_state_check", sql`${table3.state} IN ${sqlValues(WORKSPACE_STATES)}`),
|
|
37495
37551
|
check("workspaces_reason_code_check", sql`${table3.reasonCode} IS NULL OR ${table3.reasonCode} IN ${sqlValues(REASON_CODES)}`),
|
|
@@ -37552,7 +37608,7 @@ function createWorkspaceTables(table2 = pgTable, { principals, machineKeys, temp
|
|
|
37552
37608
|
driverKind: text2("driver_kind").notNull(),
|
|
37553
37609
|
eligibilityFingerprint: text2("eligibility_fingerprint").notNull(),
|
|
37554
37610
|
state: text2("state").$type().notNull(),
|
|
37555
|
-
providerRef:
|
|
37611
|
+
providerRef: structuredJson("provider_ref").$type(),
|
|
37556
37612
|
enrollmentDigest: bytea2("enrollment_digest").$type(),
|
|
37557
37613
|
enrollmentExpiresAt: timestamptz("enrollment_expires_at"),
|
|
37558
37614
|
workspaceId: uuid("workspace_id").references(() => workspaces2.id),
|
|
@@ -37601,9 +37657,9 @@ var {
|
|
|
37601
37657
|
} = createSchema();
|
|
37602
37658
|
// ../db/src/database/context.ts
|
|
37603
37659
|
var {SQL: SQL6 } = globalThis.Bun;
|
|
37604
|
-
function createDatabaseContext(databaseUrl, schemaName) {
|
|
37660
|
+
function createDatabaseContext(databaseUrl, schemaName, options = {}) {
|
|
37605
37661
|
const schema = assertValidSchema(schemaName);
|
|
37606
|
-
const client = new SQL6(databaseUrl);
|
|
37662
|
+
const client = new SQL6(databaseUrl, options);
|
|
37607
37663
|
return {
|
|
37608
37664
|
schema,
|
|
37609
37665
|
client,
|
|
@@ -37715,9 +37771,63 @@ function requiredRow(row) {
|
|
|
37715
37771
|
return row;
|
|
37716
37772
|
}
|
|
37717
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
|
+
|
|
37718
37819
|
// ../db/src/modules/auth/repository.ts
|
|
37719
|
-
function createAuth(
|
|
37820
|
+
function createAuth(context) {
|
|
37821
|
+
const {
|
|
37822
|
+
db,
|
|
37823
|
+
tables: { principals: principals2, machineKeys: machineKeys2 }
|
|
37824
|
+
} = context;
|
|
37720
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
|
+
},
|
|
37721
37831
|
async createPrincipal(name2, scopes, templateNames) {
|
|
37722
37832
|
const [row] = await db.insert(principals2).values({ id: randomUUID2(), name: name2, scopes, templateNames, createdAt: sql`now()` }).returning();
|
|
37723
37833
|
return requiredRow(row);
|
|
@@ -37736,9 +37846,6 @@ function createAuth({ db, tables: { principals: principals2, machineKeys: machin
|
|
|
37736
37846
|
async setPrincipalDisabled(id, disabled) {
|
|
37737
37847
|
await db.update(principals2).set({ disabledAt: disabled ? sql`now()` : null }).where(eq(principals2.id, id));
|
|
37738
37848
|
},
|
|
37739
|
-
async insertMachineKey(row) {
|
|
37740
|
-
await db.insert(machineKeys2).values(row);
|
|
37741
|
-
},
|
|
37742
37849
|
async getMachineKeyWithPrincipal(keyId) {
|
|
37743
37850
|
const [row] = await db.select({ key: machineKeys2, principal: principals2 }).from(machineKeys2).innerJoin(principals2, eq(principals2.id, machineKeys2.principalId)).where(eq(machineKeys2.id, keyId));
|
|
37744
37851
|
return row ?? null;
|
|
@@ -37753,16 +37860,71 @@ function createAuth({ db, tables: { principals: principals2, machineKeys: machin
|
|
|
37753
37860
|
};
|
|
37754
37861
|
}
|
|
37755
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
|
+
|
|
37756
37916
|
// ../db/src/modules/conversations/repository.ts
|
|
37757
37917
|
var MAX_CONVERSATION_BYTES = 50 * 1024 * 1024;
|
|
37758
37918
|
var MAX_CONVERSATION_MESSAGES = 1e5;
|
|
37759
|
-
function createConversations({
|
|
37760
|
-
|
|
37761
|
-
|
|
37762
|
-
|
|
37919
|
+
function createConversations(context) {
|
|
37920
|
+
const {
|
|
37921
|
+
db,
|
|
37922
|
+
tables: { workspaceConversations: conversations2, workspaceConversationMessages: messages }
|
|
37923
|
+
} = context;
|
|
37763
37924
|
return {
|
|
37764
37925
|
async appendConversationMessage(input) {
|
|
37765
37926
|
return db.transaction(async (tx) => {
|
|
37927
|
+
await requireContentWritable(tx, context.tables, input.workspaceId);
|
|
37766
37928
|
await lock(tx, input.workspaceId, 7081);
|
|
37767
37929
|
const [state] = await tx.select({ status: conversations2.status }).from(conversations2).where(eq(conversations2.workspaceId, input.workspaceId)).for("update");
|
|
37768
37930
|
if (state?.status === "deleted")
|
|
@@ -37816,12 +37978,18 @@ function createConversations({
|
|
|
37816
37978
|
|
|
37817
37979
|
// ../db/src/modules/logs/repository.ts
|
|
37818
37980
|
var MAX_LOG_BYTES = 10 * 1024 * 1024;
|
|
37819
|
-
function createLogs(
|
|
37981
|
+
function createLogs(context) {
|
|
37982
|
+
const {
|
|
37983
|
+
db,
|
|
37984
|
+
tables: { workspaceLogs: logs2 }
|
|
37985
|
+
} = context;
|
|
37820
37986
|
return {
|
|
37821
37987
|
async appendLogs(workspaceId, entries) {
|
|
37822
37988
|
if (entries.length === 0)
|
|
37823
37989
|
return;
|
|
37824
37990
|
await db.transaction(async (tx) => {
|
|
37991
|
+
if (!await contentWritable(tx, context.tables, workspaceId))
|
|
37992
|
+
return;
|
|
37825
37993
|
await lock(tx, workspaceId, 7080);
|
|
37826
37994
|
const [stats] = await tx.select({
|
|
37827
37995
|
maxSeq: sql`coalesce(max(${logs2.seq}),0)`.mapWith(Number),
|
|
@@ -37861,12 +38029,18 @@ function createLogs({ db, tables: { workspaceLogs: logs2 } }) {
|
|
|
37861
38029
|
}
|
|
37862
38030
|
|
|
37863
38031
|
// ../db/src/modules/network-audit/repository.ts
|
|
37864
|
-
function createNetworkAudit(
|
|
38032
|
+
function createNetworkAudit(context) {
|
|
38033
|
+
const {
|
|
38034
|
+
db,
|
|
38035
|
+
tables: { workspaceNetworkEvents: events, workspaces: workspaces3 }
|
|
38036
|
+
} = context;
|
|
37865
38037
|
return {
|
|
37866
38038
|
async appendNetworkEvents(workspaceId, sourceSessionId, inputs) {
|
|
37867
38039
|
if (inputs.length === 0)
|
|
37868
38040
|
return;
|
|
37869
38041
|
await db.transaction(async (tx) => {
|
|
38042
|
+
if (!await contentWritable(tx, context.tables, workspaceId))
|
|
38043
|
+
return;
|
|
37870
38044
|
await lock(tx, workspaceId, 7348);
|
|
37871
38045
|
for (const event of inputs) {
|
|
37872
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)));
|
|
@@ -37908,7 +38082,11 @@ function createNetworkAudit({ db, tables: { workspaceNetworkEvents: events, work
|
|
|
37908
38082
|
// ../db/src/modules/outbox/repository.ts
|
|
37909
38083
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
37910
38084
|
var CLAIM_LEASE_MS = 60000;
|
|
37911
|
-
function createOutbox(
|
|
38085
|
+
function createOutbox(context) {
|
|
38086
|
+
const {
|
|
38087
|
+
db,
|
|
38088
|
+
tables: { eventOutbox: outbox2 }
|
|
38089
|
+
} = context;
|
|
37912
38090
|
return {
|
|
37913
38091
|
async claimDueEvents(now, limit) {
|
|
37914
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 });
|
|
@@ -37922,29 +38100,38 @@ function createOutbox({ db, tables: { eventOutbox: outbox2 } }) {
|
|
|
37922
38100
|
await db.update(outbox2).set({ attemptCount: sql`${outbox2.attemptCount}+1`, lastErrorCode: errorCode, nextAttemptAt }).where(eq(outbox2.id, id));
|
|
37923
38101
|
},
|
|
37924
38102
|
async appendEvent(workspaceId, eventType, payload, at) {
|
|
37925
|
-
await db.
|
|
37926
|
-
|
|
37927
|
-
|
|
37928
|
-
|
|
37929
|
-
|
|
37930
|
-
|
|
37931
|
-
|
|
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
|
+
});
|
|
37932
38114
|
});
|
|
37933
38115
|
}
|
|
37934
38116
|
};
|
|
37935
38117
|
}
|
|
37936
38118
|
|
|
37937
38119
|
// ../db/src/modules/outputs/repository.ts
|
|
37938
|
-
function createOutputs(
|
|
38120
|
+
function createOutputs(context) {
|
|
38121
|
+
const {
|
|
38122
|
+
db,
|
|
38123
|
+
tables: { workspaceOutputs: outputs2, workspaces: workspaces3 }
|
|
38124
|
+
} = context;
|
|
37939
38125
|
return {
|
|
37940
38126
|
async appendOutput(input) {
|
|
37941
38127
|
return db.transaction(async (tx) => {
|
|
38128
|
+
await requireContentWritable(tx, context.tables, input.workspaceId);
|
|
37942
38129
|
await lock(tx, input.workspaceId, 7081);
|
|
37943
38130
|
const [latest] = await tx.select({ seq: sql`coalesce(max(${outputs2.seq}),0)`.mapWith(Number) }).from(outputs2).where(eq(outputs2.workspaceId, input.workspaceId));
|
|
37944
38131
|
const row = { ...input, seq: requiredRow(latest).seq + 1 };
|
|
37945
38132
|
await tx.insert(outputs2).values({ ...row, value: row.value === null ? sql`'null'::jsonb` : row.value });
|
|
37946
38133
|
await tx.update(workspaces3).set({
|
|
37947
|
-
outputs: sql`${workspaces3.outputs} || jsonb_build_object(${input.name}::text, ${sql.param(input.value, outputs2.value)}::jsonb)`,
|
|
38134
|
+
outputs: sql`${structuredJsonValue(workspaces3.outputs)} || jsonb_build_object(${input.name}::text, ${sql.param(input.value, outputs2.value)}::jsonb)`,
|
|
37948
38135
|
updatedAt: input.occurredAt
|
|
37949
38136
|
}).where(eq(workspaces3.id, input.workspaceId));
|
|
37950
38137
|
return row;
|
|
@@ -37986,7 +38173,7 @@ function createCheckpoints({ db, tables: { workspaceCheckpoints: checkpoints } }
|
|
|
37986
38173
|
function createOperations({
|
|
37987
38174
|
db,
|
|
37988
38175
|
schema,
|
|
37989
|
-
tables: { workspaceOperations: operations, workspaceCheckpoints: checkpoints }
|
|
38176
|
+
tables: { workspaceOperations: operations, workspaceCheckpoints: checkpoints, workspaces: workspaces3 }
|
|
37990
38177
|
}) {
|
|
37991
38178
|
const incomplete = inArray(operations.state, ["pending", "running"]);
|
|
37992
38179
|
async function countIncomplete(tx = db) {
|
|
@@ -38000,6 +38187,15 @@ function createOperations({
|
|
|
38000
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)));
|
|
38001
38188
|
if (existing)
|
|
38002
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
|
+
}
|
|
38003
38199
|
if (options.maxIncompleteOperations !== undefined && await countIncomplete(tx) >= options.maxIncompleteOperations)
|
|
38004
38200
|
throw new OperationCapacityExceededError;
|
|
38005
38201
|
const [row] = await tx.insert(operations).values(input).returning();
|
|
@@ -38118,6 +38314,152 @@ function createTerminals({ db, tables: { workspaceTerminalSessions: sessions } }
|
|
|
38118
38314
|
|
|
38119
38315
|
// ../db/src/modules/workspaces/events.ts
|
|
38120
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
|
+
}
|
|
38121
38463
|
// ../runtime-core/src/observability/metrics.ts
|
|
38122
38464
|
function metricKey(name2, labels = {}) {
|
|
38123
38465
|
const entries = Object.entries(labels).sort(([left2], [right2]) => left2.localeCompare(right2));
|
|
@@ -38213,6 +38555,25 @@ class OutboxDispatcher {
|
|
|
38213
38555
|
await this.deps.store.markEventFailed(id, errorCode, new Date(this.now().getTime() + backoff));
|
|
38214
38556
|
}
|
|
38215
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
|
+
|
|
38216
38577
|
// ../runtime-core/src/reconciliation/reconciliation-metrics.ts
|
|
38217
38578
|
async function measureReconciliation(metrics, kind, skipped, reconcile) {
|
|
38218
38579
|
const startedAt = performance.now();
|
|
@@ -38267,8 +38628,7 @@ async function reconcileDeletion(deps, operation, checkpoint, now) {
|
|
|
38267
38628
|
async function cleanupProvider(deps, workspace) {
|
|
38268
38629
|
if (!workspace?.providerRef)
|
|
38269
38630
|
return;
|
|
38270
|
-
await deps.driver
|
|
38271
|
-
await deps.driver.remove(workspace.providerRef).catch(() => {});
|
|
38631
|
+
await stopWorkspaceProvider(deps.store, deps.driver, workspace, 1, deps.now?.() ?? new Date);
|
|
38272
38632
|
}
|
|
38273
38633
|
async function completePreserve(deps, operation, checkpoint, workspace, now) {
|
|
38274
38634
|
await cleanupProvider(deps, workspace);
|
|
@@ -38303,13 +38663,26 @@ async function failPreserve(deps, operation, context, now) {
|
|
|
38303
38663
|
}
|
|
38304
38664
|
await failOperation(deps, operation, "checkpoint_failed", now);
|
|
38305
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
|
+
}
|
|
38306
38678
|
async function reconcileOperation(deps, operation, now) {
|
|
38307
|
-
|
|
38679
|
+
if (operation.kind === "purge")
|
|
38680
|
+
return;
|
|
38308
38681
|
if (operation.kind === "restore") {
|
|
38309
|
-
|
|
38310
|
-
await failOperation(deps, operation, "restore_failed", now);
|
|
38682
|
+
await reconcileRestore(deps, operation, now);
|
|
38311
38683
|
return;
|
|
38312
38684
|
}
|
|
38685
|
+
const context = await loadOperationContext(deps, operation);
|
|
38313
38686
|
if (!context.checkpoint) {
|
|
38314
38687
|
await failOperation(deps, operation, "checkpoint_storage_lost", now);
|
|
38315
38688
|
return;
|
|
@@ -38361,22 +38734,13 @@ async function reconcilePersistence(deps) {
|
|
|
38361
38734
|
}
|
|
38362
38735
|
// ../runtime-core/src/reconciliation/reconcile.ts
|
|
38363
38736
|
async function reconcileProviderRow(deps, row, found, now) {
|
|
38364
|
-
if (row.state === "queued")
|
|
38737
|
+
if (row.state === "queued" || row.state === "terminating")
|
|
38365
38738
|
return;
|
|
38366
38739
|
const mismatched = found && found.templateDigest !== row.templateDigest;
|
|
38367
38740
|
if (mismatched) {
|
|
38368
38741
|
deps.log?.(`reconcile: template digest mismatch for ${row.id}; failing workspace`);
|
|
38369
38742
|
}
|
|
38370
38743
|
const lost = !found || mismatched;
|
|
38371
|
-
if (lost && row.state === "terminating") {
|
|
38372
|
-
await deps.store.transition(row.id, {
|
|
38373
|
-
from: ["terminating"],
|
|
38374
|
-
to: row.terminalIntent ?? "failed",
|
|
38375
|
-
reason: row.reasonCode ?? "provider_lost",
|
|
38376
|
-
at: now
|
|
38377
|
-
});
|
|
38378
|
-
return;
|
|
38379
|
-
}
|
|
38380
38744
|
if (lost && (row.state === "connected" || row.state === "ready")) {
|
|
38381
38745
|
await settleLostStorage(deps, row, now);
|
|
38382
38746
|
await deps.store.transition(row.id, {
|
|
@@ -38499,95 +38863,6 @@ async function loadTemplateDir(store, dir) {
|
|
|
38499
38863
|
}
|
|
38500
38864
|
return result;
|
|
38501
38865
|
}
|
|
38502
|
-
// ../auth/src/index.ts
|
|
38503
|
-
import { createHmac, randomBytes, randomUUID as randomUUID5, timingSafeEqual } from "crypto";
|
|
38504
|
-
var KEY_PREFIX = "pkt";
|
|
38505
|
-
function issueMachineKey(pepper) {
|
|
38506
|
-
const id = randomUUID5();
|
|
38507
|
-
const secret = randomBytes(32).toString("base64url");
|
|
38508
|
-
return {
|
|
38509
|
-
id,
|
|
38510
|
-
token: `${KEY_PREFIX}_${id}_${secret}`,
|
|
38511
|
-
secretDigest: digestSecret(pepper, id, secret)
|
|
38512
|
-
};
|
|
38513
|
-
}
|
|
38514
|
-
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_-]+)$/;
|
|
38515
|
-
function parseMachineKey(token) {
|
|
38516
|
-
const match = KEY_RE.exec(token);
|
|
38517
|
-
if (!match) {
|
|
38518
|
-
return null;
|
|
38519
|
-
}
|
|
38520
|
-
return { id: match[1], secret: match[2] };
|
|
38521
|
-
}
|
|
38522
|
-
function digestSecret(pepper, keyId, secret) {
|
|
38523
|
-
return new Uint8Array(createHmac("sha256", pepper).update(`${keyId}:${secret}`).digest());
|
|
38524
|
-
}
|
|
38525
|
-
function verifySecret(pepper, keyId, secret, storedDigest) {
|
|
38526
|
-
const computed = digestSecret(pepper, keyId, secret);
|
|
38527
|
-
if (computed.length !== storedDigest.length) {
|
|
38528
|
-
return false;
|
|
38529
|
-
}
|
|
38530
|
-
return timingSafeEqual(computed, storedDigest);
|
|
38531
|
-
}
|
|
38532
|
-
function generateOpaqueSecret() {
|
|
38533
|
-
return randomBytes(32).toString("base64url");
|
|
38534
|
-
}
|
|
38535
|
-
function digestOpaque(pepper, secret) {
|
|
38536
|
-
return new Uint8Array(createHmac("sha256", pepper).update(secret).digest());
|
|
38537
|
-
}
|
|
38538
|
-
function verifyOpaque(pepper, secret, storedDigest) {
|
|
38539
|
-
const computed = digestOpaque(pepper, secret);
|
|
38540
|
-
if (computed.length !== storedDigest.length) {
|
|
38541
|
-
return false;
|
|
38542
|
-
}
|
|
38543
|
-
return timingSafeEqual(computed, storedDigest);
|
|
38544
|
-
}
|
|
38545
|
-
function signEvent(signingKey, timestamp4, body) {
|
|
38546
|
-
const mac3 = createHmac("sha256", signingKey).update(`${timestamp4}.${body}`).digest("hex");
|
|
38547
|
-
return `sha256=${mac3}`;
|
|
38548
|
-
}
|
|
38549
|
-
var EGRESS_TOKEN_PREFIX = "pce1";
|
|
38550
|
-
var EGRESS_TOKEN_DOMAIN = "pocketcoder-egress-audit-v1\x00";
|
|
38551
|
-
function issueEgressAuditToken(signingKey, subject) {
|
|
38552
|
-
const payload = Buffer.from(JSON.stringify({
|
|
38553
|
-
v: 1,
|
|
38554
|
-
aud: "egress-audit",
|
|
38555
|
-
kind: subject.kind,
|
|
38556
|
-
id: subject.id,
|
|
38557
|
-
exp: subject.expiresAt.getTime()
|
|
38558
|
-
})).toString("base64url");
|
|
38559
|
-
const signature = createHmac("sha256", signingKey).update(`${EGRESS_TOKEN_DOMAIN}${payload}`).digest("base64url");
|
|
38560
|
-
return `${EGRESS_TOKEN_PREFIX}.${payload}.${signature}`;
|
|
38561
|
-
}
|
|
38562
|
-
function verifyEgressAuditToken(signingKey, token, now = new Date) {
|
|
38563
|
-
const [prefix, payload, signature, extra] = token.split(".");
|
|
38564
|
-
if (prefix !== EGRESS_TOKEN_PREFIX || !payload || !signature || extra)
|
|
38565
|
-
return null;
|
|
38566
|
-
const expected = createHmac("sha256", signingKey).update(`${EGRESS_TOKEN_DOMAIN}${payload}`).digest("base64url");
|
|
38567
|
-
const actualBytes = Buffer.from(signature);
|
|
38568
|
-
const expectedBytes = Buffer.from(expected);
|
|
38569
|
-
if (actualBytes.length !== expectedBytes.length || !timingSafeEqual(actualBytes, expectedBytes)) {
|
|
38570
|
-
return null;
|
|
38571
|
-
}
|
|
38572
|
-
try {
|
|
38573
|
-
const value = JSON.parse(Buffer.from(payload, "base64url").toString());
|
|
38574
|
-
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()) {
|
|
38575
|
-
return null;
|
|
38576
|
-
}
|
|
38577
|
-
return { kind: value.kind, id: value.id };
|
|
38578
|
-
} catch {
|
|
38579
|
-
return null;
|
|
38580
|
-
}
|
|
38581
|
-
}
|
|
38582
|
-
var REDACT_PATTERNS = [/pkt_[0-9a-f-]{36}_[A-Za-z0-9_-]+/g, /(authorization:?\s*bearer\s+)\S+/gi];
|
|
38583
|
-
function redact(text6) {
|
|
38584
|
-
let out = text6;
|
|
38585
|
-
for (const pattern of REDACT_PATTERNS) {
|
|
38586
|
-
out = out.replace(pattern, "[redacted]");
|
|
38587
|
-
}
|
|
38588
|
-
return out;
|
|
38589
|
-
}
|
|
38590
|
-
|
|
38591
38866
|
// ../runtime-core/src/scheduler/scheduler-base.ts
|
|
38592
38867
|
var FAILURE_LOG_TAIL_BYTES = 16 * 1024;
|
|
38593
38868
|
function decodeFailureLogTail(content, truncated) {
|
|
@@ -38774,8 +39049,20 @@ class SchedulerAdmission {
|
|
|
38774
39049
|
return;
|
|
38775
39050
|
}
|
|
38776
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
|
+
}
|
|
38777
39062
|
async launch(row) {
|
|
38778
39063
|
const { store, driver, secrets } = this.context.deps;
|
|
39064
|
+
if (!await this.authorized(row))
|
|
39065
|
+
return false;
|
|
38779
39066
|
const now = this.context.now();
|
|
38780
39067
|
const secret = secrets.generate();
|
|
38781
39068
|
const registrationDigest = secrets.digest(secret);
|
|
@@ -38940,24 +39227,28 @@ class SchedulerLifecycle {
|
|
|
38940
39227
|
connections.signal(row.id, "TERM");
|
|
38941
39228
|
}
|
|
38942
39229
|
if (updated.providerRef) {
|
|
38943
|
-
this.
|
|
39230
|
+
this.finalize(updated, terminalState, reason, this.context.now()).catch((err) => this.context.report(`terminate.${row.id}`, err));
|
|
38944
39231
|
} else {
|
|
38945
39232
|
await this.finalize(updated, terminalState, reason, at);
|
|
38946
39233
|
}
|
|
38947
39234
|
return updated;
|
|
38948
39235
|
}
|
|
38949
|
-
async finalize(row, terminalState, reason, at
|
|
39236
|
+
async finalize(row, terminalState, reason, at) {
|
|
38950
39237
|
const { store, driver, connections } = this.context.deps;
|
|
39238
|
+
const retainStorage = terminalState === "failed" && row.templateSnapshot.spec.persistence.checkpoint.onFailure === "retain-for-recovery";
|
|
38951
39239
|
if (row.providerRef) {
|
|
38952
39240
|
try {
|
|
38953
|
-
await
|
|
38954
|
-
await driver.remove({
|
|
38955
|
-
kind: row.providerKind ?? "",
|
|
38956
|
-
id: "",
|
|
38957
|
-
...row.providerRef
|
|
38958
|
-
});
|
|
39241
|
+
await stopWorkspaceProvider(store, driver, row, this.context.graceSeconds(row), at);
|
|
38959
39242
|
} catch (err) {
|
|
38960
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;
|
|
38961
39252
|
}
|
|
38962
39253
|
}
|
|
38963
39254
|
if (retainStorage) {
|
|
@@ -38997,16 +39288,12 @@ class SchedulerLifecycle {
|
|
|
38997
39288
|
}
|
|
38998
39289
|
});
|
|
38999
39290
|
}
|
|
39000
|
-
async stopAndRemove(ref, graceSeconds) {
|
|
39001
|
-
await this.context.deps.driver.stop(ref, graceSeconds);
|
|
39002
|
-
await this.context.deps.driver.remove(ref);
|
|
39003
|
-
}
|
|
39004
39291
|
async fail(row, reason, at) {
|
|
39005
39292
|
const action = row.templateSnapshot.spec.persistence.checkpoint.onFailure;
|
|
39006
39293
|
if (action === "preserve" && ["connected", "ready"].includes(row.state) && await this.requestPolicyPreserve(row, "failure")) {
|
|
39007
39294
|
return;
|
|
39008
39295
|
}
|
|
39009
|
-
await this.finalize(row, "failed", reason, at
|
|
39296
|
+
await this.finalize(row, "failed", reason, at);
|
|
39010
39297
|
}
|
|
39011
39298
|
async handleProcessExit(row, exitCode, at) {
|
|
39012
39299
|
if (exitCode === 0 && row.templateSnapshot.spec.persistence.checkpoint.onCleanExit === "preserve" && await this.requestPolicyPreserve(row, "clean_exit")) {
|
|
@@ -39051,6 +39338,8 @@ class SchedulerSweep {
|
|
|
39051
39338
|
for (const row of rows) {
|
|
39052
39339
|
try {
|
|
39053
39340
|
await this.sweepRow(row, now);
|
|
39341
|
+
if (row.providerRef)
|
|
39342
|
+
await this.context.deps.driver.inspect(row.providerRef);
|
|
39054
39343
|
} catch (err) {
|
|
39055
39344
|
this.context.report(`sweep.${row.id}`, err);
|
|
39056
39345
|
}
|
|
@@ -39098,9 +39387,12 @@ class SchedulerSweep {
|
|
|
39098
39387
|
}
|
|
39099
39388
|
async sweepTerminating(row, now) {
|
|
39100
39389
|
const stuckMs = 4 * this.context.timeoutMs(row, "terminateGrace") + 5000;
|
|
39101
|
-
|
|
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)
|
|
39102
39393
|
return;
|
|
39103
|
-
|
|
39394
|
+
const terminalState = row.terminalIntent ?? "failed";
|
|
39395
|
+
await this.lifecycle.finalize(row, terminalState, row.reasonCode, now);
|
|
39104
39396
|
}
|
|
39105
39397
|
async sweepRow(row, now) {
|
|
39106
39398
|
switch (row.state) {
|
|
@@ -39111,8 +39403,8 @@ class SchedulerSweep {
|
|
|
39111
39403
|
if (row.registrationExpiresAt && now >= row.registrationExpiresAt) {
|
|
39112
39404
|
if (row.provisioningMode === "warm" && row.launchAttempts < this.context.deps.limits.maxLaunchAttempts) {
|
|
39113
39405
|
if (row.providerRef) {
|
|
39114
|
-
await this.context.deps.driver.stop(row.providerRef, 1)
|
|
39115
|
-
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);
|
|
39116
39408
|
}
|
|
39117
39409
|
await this.context.deps.store.transition(row.id, {
|
|
39118
39410
|
from: ["provisioning"],
|
|
@@ -39170,6 +39462,9 @@ class Scheduler {
|
|
|
39170
39462
|
});
|
|
39171
39463
|
return this.context.activeTick;
|
|
39172
39464
|
}
|
|
39465
|
+
async drain() {
|
|
39466
|
+
await this.context.activeTick;
|
|
39467
|
+
}
|
|
39173
39468
|
async runTick() {
|
|
39174
39469
|
await this.sweeper.sweep();
|
|
39175
39470
|
await this.admission.admit();
|
|
@@ -39465,6 +39760,8 @@ async function appendTransition(context, tx, row, from, reason, at) {
|
|
|
39465
39760
|
reasonCode: reason,
|
|
39466
39761
|
occurredAt: at
|
|
39467
39762
|
});
|
|
39763
|
+
if (row.purgeRequestedAt)
|
|
39764
|
+
return;
|
|
39468
39765
|
const payload = buildEventEnvelope(row, at);
|
|
39469
39766
|
await tx.insert(context.tables.eventOutbox).values({
|
|
39470
39767
|
id: payload.id,
|
|
@@ -39509,7 +39806,7 @@ function createWarmPools(context) {
|
|
|
39509
39806
|
async claimWarmPoolRuntime(claim) {
|
|
39510
39807
|
const result = await db.transaction(async (tx) => {
|
|
39511
39808
|
const [current] = await tx.select().from(workspaces3).where(eq(workspaces3.id, claim.workspaceId)).for("update");
|
|
39512
|
-
if (current?.state !== "queued")
|
|
39809
|
+
if (current?.state !== "queued" || current.purgeRequestedAt)
|
|
39513
39810
|
return null;
|
|
39514
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 });
|
|
39515
39812
|
if (!runtime)
|
|
@@ -39575,7 +39872,7 @@ function createAdmission(context) {
|
|
|
39575
39872
|
const workspace = await db.transaction(async (tx) => {
|
|
39576
39873
|
await lock(tx, `${schema}:workspace-admission`, 7351);
|
|
39577
39874
|
const [current] = await tx.select().from(workspaces3).where(eq(workspaces3.id, claim.workspaceId)).for("update");
|
|
39578
|
-
if (current?.state !== "queued")
|
|
39875
|
+
if (current?.state !== "queued" || current.purgeRequestedAt)
|
|
39579
39876
|
return null;
|
|
39580
39877
|
const counts = await countActive(tx);
|
|
39581
39878
|
if (counts.global >= claim.limits.globalActiveWorkspaces)
|
|
@@ -39664,7 +39961,7 @@ function createWorkspaces(context) {
|
|
|
39664
39961
|
const cursor = db.select({ createdAt: workspaces3.createdAt, id: workspaces3.id }).from(workspaces3).where(and(eq(workspaces3.id, filter.cursor), eq(workspaces3.principalId, principalId)));
|
|
39665
39962
|
cursorFilter = sql`(${workspaces3.createdAt}, ${workspaces3.id}) < (${cursor})`;
|
|
39666
39963
|
}
|
|
39667
|
-
const rows = await db.select().from(workspaces3).where(and(eq(workspaces3.principalId, principalId), filter.externalId ? eq(workspaces3.externalId, filter.externalId) : undefined, filter.state ? eq(workspaces3.state, filter.state) : undefined, filter.template ? eq(workspaces3.templateName, filter.template) : undefined, filter.metadata ? sql`${workspaces3.metadata} @> ${sql.param(filter.metadata, workspaces3.metadata)}::jsonb` : undefined, filter.createdAfter ? gte(workspaces3.createdAt, filter.createdAfter) : undefined, filter.createdBefore ? lt(workspaces3.createdAt, filter.createdBefore) : undefined, cursorFilter)).orderBy(desc2(workspaces3.createdAt), desc2(workspaces3.id)).limit(filter.limit);
|
|
39964
|
+
const rows = await db.select().from(workspaces3).where(and(eq(workspaces3.principalId, principalId), filter.externalId ? eq(workspaces3.externalId, filter.externalId) : undefined, filter.state ? eq(workspaces3.state, filter.state) : undefined, filter.template ? eq(workspaces3.templateName, filter.template) : undefined, filter.metadata ? sql`${structuredJsonValue(workspaces3.metadata)} @> ${sql.param(filter.metadata, workspaces3.metadata)}::jsonb` : undefined, filter.createdAfter ? gte(workspaces3.createdAt, filter.createdAfter) : undefined, filter.createdBefore ? lt(workspaces3.createdAt, filter.createdBefore) : undefined, cursorFilter)).orderBy(desc2(workspaces3.createdAt), desc2(workspaces3.id)).limit(filter.limit);
|
|
39668
39965
|
return rows.map(workspaceFromRow);
|
|
39669
39966
|
}
|
|
39670
39967
|
};
|
|
@@ -39695,7 +39992,15 @@ function createTransitions(context) {
|
|
|
39695
39992
|
return {
|
|
39696
39993
|
async updateWorkspace(id, patch, at) {
|
|
39697
39994
|
const bumpsChange = Object.keys(patch).some((key) => CHANGE_PATCH_KEYS.has(key));
|
|
39698
|
-
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
|
+
});
|
|
39699
40004
|
if (bumpsChange)
|
|
39700
40005
|
notifyChange(context, id);
|
|
39701
40006
|
},
|
|
@@ -39704,9 +40009,12 @@ function createTransitions(context) {
|
|
|
39704
40009
|
const [current] = await tx.select().from(workspaces3).where(eq(workspaces3.id, id)).for("update");
|
|
39705
40010
|
if (!current || !req.from.includes(current.state) || !canTransition(current.state, req.to))
|
|
39706
40011
|
return null;
|
|
40012
|
+
if (current.purgeRequestedAt && ["provisioning", "connected", "ready", "preserving", "queued"].includes(req.to))
|
|
40013
|
+
return null;
|
|
39707
40014
|
const terminal = isTerminal(req.to);
|
|
39708
40015
|
const [row] = await tx.update(workspaces3).set({
|
|
39709
40016
|
...req.patch,
|
|
40017
|
+
...current.purgeRequestedAt ? purgedContentPatch() : {},
|
|
39710
40018
|
state: req.to,
|
|
39711
40019
|
reasonCode: req.reason,
|
|
39712
40020
|
updatedAt: req.at,
|
|
@@ -39737,9 +40045,12 @@ function createTransitions(context) {
|
|
|
39737
40045
|
|
|
39738
40046
|
// ../db/src/store.ts
|
|
39739
40047
|
class PostgresStore {
|
|
39740
|
-
constructor(databaseUrl, schema = "pocketcoder") {
|
|
39741
|
-
const context = createDatabaseContext(databaseUrl, schema);
|
|
40048
|
+
constructor(databaseUrl, schema = "pocketcoder", options = {}) {
|
|
40049
|
+
const context = createDatabaseContext(databaseUrl, schema, options);
|
|
39742
40050
|
const lifecycle2 = createLifecycle(context);
|
|
40051
|
+
const content = createContentPurge(context);
|
|
40052
|
+
this.listWorkspaceStorage = content.listWorkspaceStorage;
|
|
40053
|
+
this.purgeWorkspaceContent = content.purgeWorkspaceContent;
|
|
39743
40054
|
this.init = lifecycle2.init;
|
|
39744
40055
|
this.close = lifecycle2.close;
|
|
39745
40056
|
this.acquireCoordinatorLease = lifecycle2.acquireCoordinatorLease;
|
|
@@ -39754,6 +40065,10 @@ class PostgresStore {
|
|
|
39754
40065
|
this.listPrincipals = auth2.listPrincipals;
|
|
39755
40066
|
this.updatePrincipal = auth2.updatePrincipal;
|
|
39756
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;
|
|
39757
40072
|
this.insertMachineKey = auth2.insertMachineKey;
|
|
39758
40073
|
this.getMachineKeyWithPrincipal = auth2.getMachineKeyWithPrincipal;
|
|
39759
40074
|
this.revokeMachineKey = auth2.revokeMachineKey;
|
|
@@ -39826,6 +40141,8 @@ class PostgresStore {
|
|
|
39826
40141
|
this.appendEvent = outbox2.appendEvent;
|
|
39827
40142
|
this.waitForWorkspaceChange = createWorkspaceWaiter(context, workspaces3.getWorkspace);
|
|
39828
40143
|
}
|
|
40144
|
+
listWorkspaceStorage;
|
|
40145
|
+
purgeWorkspaceContent;
|
|
39829
40146
|
init;
|
|
39830
40147
|
close;
|
|
39831
40148
|
acquireCoordinatorLease;
|
|
@@ -39838,6 +40155,10 @@ class PostgresStore {
|
|
|
39838
40155
|
listPrincipals;
|
|
39839
40156
|
updatePrincipal;
|
|
39840
40157
|
setPrincipalDisabled;
|
|
40158
|
+
getPrincipal;
|
|
40159
|
+
issueMachineKey;
|
|
40160
|
+
listMachineKeys;
|
|
40161
|
+
revokePrincipalKeys;
|
|
39841
40162
|
insertMachineKey;
|
|
39842
40163
|
getMachineKeyWithPrincipal;
|
|
39843
40164
|
revokeMachineKey;
|
|
@@ -40163,6 +40484,51 @@ class OutputsApi extends WorkspaceCursorApi {
|
|
|
40163
40484
|
}
|
|
40164
40485
|
}
|
|
40165
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
|
+
|
|
40166
40532
|
// ../sdk/src/resources/templates/templates.ts
|
|
40167
40533
|
class TemplatesApi {
|
|
40168
40534
|
transport;
|
|
@@ -40372,6 +40738,9 @@ class WorkspacesApi {
|
|
|
40372
40738
|
preserve(id, input, key, options = {}) {
|
|
40373
40739
|
return this.jsonOperation(`/v1/workspaces/${encodeURIComponent(id)}/preserve`, input, key, PreserveResponseSchema, options);
|
|
40374
40740
|
}
|
|
40741
|
+
purge(id, key, options = {}) {
|
|
40742
|
+
return this.jsonOperation(`/v1/workspaces/${encodeURIComponent(id)}/purge`, {}, key, OperationResourceSchema, options);
|
|
40743
|
+
}
|
|
40375
40744
|
recreate(id, input, key, options = {}) {
|
|
40376
40745
|
return this.jsonOperation(`/v1/workspaces/${encodeURIComponent(id)}/recreate`, input, key, RestoreResponseSchema, options);
|
|
40377
40746
|
}
|
|
@@ -40533,6 +40902,8 @@ class PocketCoderClient {
|
|
|
40533
40902
|
outputs;
|
|
40534
40903
|
administration;
|
|
40535
40904
|
terminals;
|
|
40905
|
+
keys;
|
|
40906
|
+
recovery;
|
|
40536
40907
|
constructor(config2, fetchImpl = fetch) {
|
|
40537
40908
|
this.transport = new PocketCoderTransport(config2, fetchImpl);
|
|
40538
40909
|
this.templates = new TemplatesApi(this.transport);
|
|
@@ -40547,6 +40918,8 @@ class PocketCoderClient {
|
|
|
40547
40918
|
this.outputs = new OutputsApi(this.transport);
|
|
40548
40919
|
this.administration = new AdministrationApi(this.transport);
|
|
40549
40920
|
this.terminals = new TerminalsApi(this.transport);
|
|
40921
|
+
this.keys = new KeysApi(this.transport);
|
|
40922
|
+
this.recovery = new RecoveryApi(this.transport);
|
|
40550
40923
|
}
|
|
40551
40924
|
raw(path, init = {}) {
|
|
40552
40925
|
return this.transport.raw(path, init);
|
|
@@ -40792,7 +41165,7 @@ async function api2(path, init = {}) {
|
|
|
40792
41165
|
}
|
|
40793
41166
|
async function withStore(fn) {
|
|
40794
41167
|
const { url: url2, schema } = dbConfig();
|
|
40795
|
-
const store = new PostgresStore(url2, schema);
|
|
41168
|
+
const store = new PostgresStore(url2, schema, { max: 1 });
|
|
40796
41169
|
try {
|
|
40797
41170
|
return await fn(store);
|
|
40798
41171
|
} finally {
|
|
@@ -41051,66 +41424,103 @@ async function printFailureTail(workspaceId) {
|
|
|
41051
41424
|
} catch {}
|
|
41052
41425
|
}
|
|
41053
41426
|
|
|
41427
|
+
// src/commands/keys/issue.ts
|
|
41428
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
41429
|
+
|
|
41054
41430
|
// src/commands/scopes.ts
|
|
41055
41431
|
function parseScopes(value) {
|
|
41056
|
-
|
|
41057
|
-
for (const item of items)
|
|
41432
|
+
return valueList(value).map((item) => {
|
|
41058
41433
|
if (!isScope(item))
|
|
41059
41434
|
fail(`unknown scope: ${item}`);
|
|
41060
|
-
|
|
41435
|
+
return item;
|
|
41436
|
+
});
|
|
41061
41437
|
}
|
|
41062
41438
|
|
|
41063
41439
|
// src/commands/keys/issue.ts
|
|
41064
41440
|
function addIssueCommand(parser2) {
|
|
41065
|
-
return addAction(parser2, "issue", "Issue a machine key", (command2) => command2.option("principal", {
|
|
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", {
|
|
41066
41442
|
type: "string",
|
|
41067
|
-
|
|
41068
|
-
|
|
41069
|
-
}).option("scopes", {
|
|
41070
|
-
type: "string",
|
|
41071
|
-
description: "Comma-separated scopes; defaults to the principal scopes"
|
|
41072
|
-
}).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", {
|
|
41073
41445
|
type: "string",
|
|
41074
41446
|
default: "never",
|
|
41075
|
-
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;
|
|
41076
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
|
+
}
|
|
41077
41477
|
const pepper = process.env.POCKETCODER_AUTH_PEPPER;
|
|
41078
41478
|
if (!pepper)
|
|
41079
|
-
fail("POCKETCODER_AUTH_PEPPER is required to issue keys");
|
|
41479
|
+
fail("POCKETCODER_AUTH_PEPPER is required to issue keys locally");
|
|
41080
41480
|
await withStore(async (store) => {
|
|
41081
41481
|
const name2 = need(flags, "principal");
|
|
41082
41482
|
const principal = await store.getPrincipalByName(name2);
|
|
41083
41483
|
if (!principal)
|
|
41084
41484
|
fail(`unknown principal: ${name2}`);
|
|
41085
|
-
const
|
|
41086
|
-
const
|
|
41087
|
-
|
|
41088
|
-
|
|
41089
|
-
}
|
|
41090
|
-
const
|
|
41091
|
-
|
|
41092
|
-
|
|
41093
|
-
|
|
41094
|
-
|
|
41095
|
-
|
|
41096
|
-
|
|
41097
|
-
expiresAt,
|
|
41098
|
-
revokedAt: null,
|
|
41099
|
-
lastUsedAt: null
|
|
41100
|
-
});
|
|
41101
|
-
console.log("machine key (shown once, store it now):");
|
|
41102
|
-
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
|
+
}
|
|
41103
41497
|
});
|
|
41104
41498
|
});
|
|
41105
41499
|
}
|
|
41106
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
|
+
|
|
41107
41513
|
// src/commands/keys/revoke.ts
|
|
41108
41514
|
function addRevokeCommand(parser2) {
|
|
41109
|
-
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", {
|
|
41110
41516
|
type: "string",
|
|
41111
41517
|
demandOption: true,
|
|
41112
41518
|
description: "Machine key ID"
|
|
41113
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
|
+
}
|
|
41114
41524
|
await withStore(async (store) => {
|
|
41115
41525
|
const id = need(flags, "id");
|
|
41116
41526
|
const revoked = await store.revokeMachineKey(id, new Date);
|
|
@@ -41119,13 +41529,20 @@ function addRevokeCommand(parser2) {
|
|
|
41119
41529
|
});
|
|
41120
41530
|
}
|
|
41121
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
|
+
|
|
41122
41539
|
// src/commands/keys/index.ts
|
|
41123
41540
|
function addKeyCommands(parser2) {
|
|
41124
|
-
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));
|
|
41125
41542
|
}
|
|
41126
41543
|
|
|
41127
41544
|
// src/commands/pools/list.ts
|
|
41128
|
-
function
|
|
41545
|
+
function addListCommand3(parser2) {
|
|
41129
41546
|
return addAction(parser2, "list", "List warm pool inventory and metrics", (command2) => command2.option("json", { type: "boolean", description: "Print JSON" }), async (flags) => {
|
|
41130
41547
|
const body = await controlPlaneClient().administration.warmPools();
|
|
41131
41548
|
if (flags.json)
|
|
@@ -41142,7 +41559,7 @@ function addListCommand2(parser2) {
|
|
|
41142
41559
|
|
|
41143
41560
|
// src/commands/pools/index.ts
|
|
41144
41561
|
function addPoolCommands(parser2) {
|
|
41145
|
-
return addResource(parser2, "pools", "Inspect operator-managed warm capacity",
|
|
41562
|
+
return addResource(parser2, "pools", "Inspect operator-managed warm capacity", addListCommand3);
|
|
41146
41563
|
}
|
|
41147
41564
|
|
|
41148
41565
|
// src/commands/principals/options.ts
|
|
@@ -41168,7 +41585,7 @@ function addCreateCommand(parser2) {
|
|
|
41168
41585
|
}
|
|
41169
41586
|
|
|
41170
41587
|
// src/commands/principals/list.ts
|
|
41171
|
-
function
|
|
41588
|
+
function addListCommand4(parser2) {
|
|
41172
41589
|
return addAction(parser2, "list", "List principals", unchanged, async () => {
|
|
41173
41590
|
await withStore(async (store) => {
|
|
41174
41591
|
for (const principal of await store.listPrincipals()) {
|
|
@@ -41197,21 +41614,72 @@ function addUpdateCommand(parser2) {
|
|
|
41197
41614
|
|
|
41198
41615
|
// src/commands/principals/index.ts
|
|
41199
41616
|
function addPrincipalCommands(parser2) {
|
|
41200
|
-
return addResource(parser2, "principals", "Manage principals", (commands) =>
|
|
41617
|
+
return addResource(parser2, "principals", "Manage principals", (commands) => addListCommand4(addUpdateCommand(addCreateCommand(commands))));
|
|
41201
41618
|
}
|
|
41202
41619
|
|
|
41203
41620
|
// src/commands/server/process.ts
|
|
41204
41621
|
import { spawn, spawnSync } from "child_process";
|
|
41205
|
-
import { createHash as createHash4, randomUUID as
|
|
41206
|
-
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";
|
|
41207
41624
|
import { homedir } from "os";
|
|
41208
41625
|
import { dirname as dirname7, join as join8, resolve as resolve11 } from "path";
|
|
41209
41626
|
|
|
41210
41627
|
// ../server/src/config/config.ts
|
|
41211
41628
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
41212
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
|
+
|
|
41213
41681
|
// ../server/src/persistence/persistence-base.ts
|
|
41214
|
-
import { randomUUID as
|
|
41682
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
41215
41683
|
var DEFAULT_PERSISTENCE_LIMITS = {
|
|
41216
41684
|
maxRetainedBytes: 500 * 1024 ** 3,
|
|
41217
41685
|
maxRetainedBytesPerPrincipal: 100 * 1024 ** 3,
|
|
@@ -41354,7 +41822,7 @@ class PersistenceContext {
|
|
|
41354
41822
|
async emitCheckpointEvent(type, row) {
|
|
41355
41823
|
const at = this.now();
|
|
41356
41824
|
await this.deps.store.appendEvent(row.workspaceId, type, {
|
|
41357
|
-
id:
|
|
41825
|
+
id: randomUUID12(),
|
|
41358
41826
|
type,
|
|
41359
41827
|
occurred_at: at.toISOString(),
|
|
41360
41828
|
checkpoint: {
|
|
@@ -41370,7 +41838,7 @@ class PersistenceContext {
|
|
|
41370
41838
|
}
|
|
41371
41839
|
}
|
|
41372
41840
|
// ../server/src/persistence/persistence-checkpoints.ts
|
|
41373
|
-
import { randomUUID as
|
|
41841
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
41374
41842
|
class PersistenceCheckpointService {
|
|
41375
41843
|
context;
|
|
41376
41844
|
constructor(context) {
|
|
@@ -41389,7 +41857,7 @@ class PersistenceCheckpointService {
|
|
|
41389
41857
|
}
|
|
41390
41858
|
const now = this.context.now();
|
|
41391
41859
|
const inserted = await this.context.insertOperation({
|
|
41392
|
-
id:
|
|
41860
|
+
id: randomUUID13(),
|
|
41393
41861
|
principalId: principal.id,
|
|
41394
41862
|
kind: "verify",
|
|
41395
41863
|
state: "running",
|
|
@@ -41468,7 +41936,7 @@ class PersistenceCheckpointService {
|
|
|
41468
41936
|
});
|
|
41469
41937
|
const at = this.context.now();
|
|
41470
41938
|
await this.context.deps.store.appendEvent(workspaceId, "workspace.output_published", {
|
|
41471
|
-
id:
|
|
41939
|
+
id: randomUUID13(),
|
|
41472
41940
|
type: "workspace.output_published",
|
|
41473
41941
|
occurred_at: at.toISOString(),
|
|
41474
41942
|
workspace_id: workspaceId,
|
|
@@ -41479,7 +41947,44 @@ class PersistenceCheckpointService {
|
|
|
41479
41947
|
}
|
|
41480
41948
|
|
|
41481
41949
|
// ../server/src/persistence/persistence-maintenance.ts
|
|
41482
|
-
import { randomUUID as
|
|
41950
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
41951
|
+
|
|
41952
|
+
// ../server/src/persistence/persistence-cleanup.ts
|
|
41953
|
+
async function cleanupPreservedStorage(context, storageId) {
|
|
41954
|
+
const { store, driver } = context.deps;
|
|
41955
|
+
const storage = await store.getStorage(storageId);
|
|
41956
|
+
if (storage?.state !== "retained")
|
|
41957
|
+
return;
|
|
41958
|
+
if (storage.lastErrorCode && storage.lastErrorCode !== "storage_cleanup_failed")
|
|
41959
|
+
return;
|
|
41960
|
+
const workspace = await store.getWorkspace(storage.workspaceId);
|
|
41961
|
+
if (workspace?.state !== "preserved" || !workspace.terminalAt)
|
|
41962
|
+
return;
|
|
41963
|
+
const checkpoints = (await store.listCheckpoints(storage.principalId)).filter((checkpoint) => checkpoint.storageId === storage.id);
|
|
41964
|
+
if (checkpoints.length === 0 || checkpoints.some((checkpoint) => checkpoint.state !== "deleted"))
|
|
41965
|
+
return;
|
|
41966
|
+
const checkpointIds = new Set(checkpoints.map((checkpoint) => checkpoint.id));
|
|
41967
|
+
const operations = await store.listIncompleteOperations();
|
|
41968
|
+
if (operations.some((operation) => operation.workspaceId === workspace.id || operation.resultWorkspaceId === workspace.id || operation.checkpointId !== null && checkpointIds.has(operation.checkpointId)))
|
|
41969
|
+
return;
|
|
41970
|
+
try {
|
|
41971
|
+
if ((await driver.list()).some((provider) => provider.workspaceId === workspace.id))
|
|
41972
|
+
return;
|
|
41973
|
+
await context.storageDriver().deleteStorage(storage.providerRef);
|
|
41974
|
+
const now = context.now();
|
|
41975
|
+
await store.updateWorkspaceStorage(storage.id, {
|
|
41976
|
+
state: "deleted",
|
|
41977
|
+
deletedAt: now,
|
|
41978
|
+
retainedUntil: null,
|
|
41979
|
+
lastErrorCode: null
|
|
41980
|
+
}, now);
|
|
41981
|
+
} catch (error51) {
|
|
41982
|
+
await store.updateWorkspaceStorage(storage.id, { lastErrorCode: "storage_cleanup_failed" }, context.now());
|
|
41983
|
+
context.deps.log?.(`storage cleanup ${storage.id}: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
41984
|
+
}
|
|
41985
|
+
}
|
|
41986
|
+
|
|
41987
|
+
// ../server/src/persistence/persistence-maintenance.ts
|
|
41483
41988
|
class PersistenceMaintenanceService {
|
|
41484
41989
|
context;
|
|
41485
41990
|
constructor(context) {
|
|
@@ -41531,6 +42036,10 @@ class PersistenceMaintenanceService {
|
|
|
41531
42036
|
this.context.deps.log?.(`retention ${checkpoint.id}: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
41532
42037
|
}
|
|
41533
42038
|
}
|
|
42039
|
+
const deletedCheckpoints = await this.context.deps.store.listCheckpoints(principal.id, { state: "deleted" });
|
|
42040
|
+
for (const storageId of new Set(deletedCheckpoints.map((checkpoint) => checkpoint.storageId))) {
|
|
42041
|
+
await cleanupPreservedStorage(this.context, storageId);
|
|
42042
|
+
}
|
|
41534
42043
|
}
|
|
41535
42044
|
return { deleted, skipped, transcriptsDeleted };
|
|
41536
42045
|
}
|
|
@@ -41557,8 +42066,14 @@ class PersistenceMaintenanceService {
|
|
|
41557
42066
|
async delete(principal, checkpointId, idempotencyKey) {
|
|
41558
42067
|
const requestDigest = digestOf({ checkpoint_id: checkpointId });
|
|
41559
42068
|
const replay = await this.context.replayedOperation(principal.id, "delete", idempotencyKey, requestDigest, "Changed delete request.");
|
|
41560
|
-
if (replay)
|
|
42069
|
+
if (replay) {
|
|
42070
|
+
if (replay.state === "succeeded") {
|
|
42071
|
+
const checkpoint2 = await this.context.deps.store.getCheckpoint(checkpointId);
|
|
42072
|
+
if (checkpoint2)
|
|
42073
|
+
await cleanupPreservedStorage(this.context, checkpoint2.storageId);
|
|
42074
|
+
}
|
|
41561
42075
|
return replay;
|
|
42076
|
+
}
|
|
41562
42077
|
const checkpoint = await this.context.getCheckpointOwned(principal, checkpointId);
|
|
41563
42078
|
const activeRestore = (await this.context.deps.store.listIncompleteOperations()).some((operation) => operation.kind === "restore" && operation.checkpointId === checkpoint.id);
|
|
41564
42079
|
if (activeRestore) {
|
|
@@ -41566,7 +42081,7 @@ class PersistenceMaintenanceService {
|
|
|
41566
42081
|
}
|
|
41567
42082
|
const now = this.context.now();
|
|
41568
42083
|
const inserted = await this.context.insertOperation({
|
|
41569
|
-
id:
|
|
42084
|
+
id: randomUUID14(),
|
|
41570
42085
|
principalId: principal.id,
|
|
41571
42086
|
kind: "delete",
|
|
41572
42087
|
state: "running",
|
|
@@ -41602,12 +42117,13 @@ class PersistenceMaintenanceService {
|
|
|
41602
42117
|
} catch {
|
|
41603
42118
|
await this.context.failOperation(inserted.operation.id, "checkpoint_failed");
|
|
41604
42119
|
}
|
|
42120
|
+
await cleanupPreservedStorage(this.context, checkpoint.storageId);
|
|
41605
42121
|
return await this.context.deps.store.getOperation(inserted.operation.id) ?? inserted.operation;
|
|
41606
42122
|
}
|
|
41607
42123
|
}
|
|
41608
42124
|
|
|
41609
42125
|
// ../server/src/persistence/persistence-preserve.ts
|
|
41610
|
-
import { randomUUID as
|
|
42126
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
41611
42127
|
class PreservePersistenceService {
|
|
41612
42128
|
context;
|
|
41613
42129
|
preserveRunner;
|
|
@@ -41633,8 +42149,8 @@ class PreservePersistenceService {
|
|
|
41633
42149
|
throw new ApiError("validation.invalid", "retention may not exceed the template checkpoint policy");
|
|
41634
42150
|
}
|
|
41635
42151
|
this.context.storageDriver();
|
|
41636
|
-
const checkpointId =
|
|
41637
|
-
const operationId =
|
|
42152
|
+
const checkpointId = randomUUID15();
|
|
42153
|
+
const operationId = randomUUID15();
|
|
41638
42154
|
const now = this.context.now();
|
|
41639
42155
|
const operationResult = await this.context.insertOperation({
|
|
41640
42156
|
id: operationId,
|
|
@@ -41750,11 +42266,7 @@ class PersistencePreserveRunner {
|
|
|
41750
42266
|
const quiesced = native || hook ? await this.context.deps.hub.prepareCheckpoint(workspace.id, operationId, deadlineMs) : false;
|
|
41751
42267
|
if (!workspace.providerRef)
|
|
41752
42268
|
return quiesced;
|
|
41753
|
-
await this.context.deps.driver.
|
|
41754
|
-
kind: workspace.providerKind ?? "",
|
|
41755
|
-
id: "",
|
|
41756
|
-
...workspace.providerRef
|
|
41757
|
-
}, 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);
|
|
41758
42270
|
return quiesced;
|
|
41759
42271
|
}
|
|
41760
42272
|
async createSnapshot(workspace, checkpoint, storage) {
|
|
@@ -41863,11 +42375,211 @@ class PersistencePreserveRunner {
|
|
|
41863
42375
|
}
|
|
41864
42376
|
}
|
|
41865
42377
|
|
|
41866
|
-
// ../server/src/persistence/persistence-
|
|
42378
|
+
// ../server/src/persistence/persistence-purge.ts
|
|
41867
42379
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
41868
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
|
+
|
|
41869
42581
|
// ../server/src/workspaces/service.ts
|
|
41870
|
-
import { randomUUID as
|
|
42582
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
41871
42583
|
function templateAuthorized(principal, templateName) {
|
|
41872
42584
|
return principal.templateNames.includes("*") || principal.templateNames.includes(templateName);
|
|
41873
42585
|
}
|
|
@@ -41982,7 +42694,7 @@ class WorkspaceService {
|
|
|
41982
42694
|
this.validateCreateInput(template, body);
|
|
41983
42695
|
const now = this.now();
|
|
41984
42696
|
const result = await store.insertWorkspace({
|
|
41985
|
-
id:
|
|
42697
|
+
id: randomUUID17(),
|
|
41986
42698
|
principalId: principal.id,
|
|
41987
42699
|
externalId: body.external_id,
|
|
41988
42700
|
idempotencyKey,
|
|
@@ -42091,9 +42803,9 @@ class PersistenceRestoreService {
|
|
|
42091
42803
|
}
|
|
42092
42804
|
this.context.storageDriver();
|
|
42093
42805
|
const now = this.context.now();
|
|
42094
|
-
const resultWorkspaceId =
|
|
42806
|
+
const resultWorkspaceId = randomUUID18();
|
|
42095
42807
|
const operationResult = await this.context.insertOperation({
|
|
42096
|
-
id:
|
|
42808
|
+
id: randomUUID18(),
|
|
42097
42809
|
principalId: principal.id,
|
|
42098
42810
|
kind: "restore",
|
|
42099
42811
|
state: "pending",
|
|
@@ -42154,7 +42866,7 @@ class PersistenceRestoreService {
|
|
|
42154
42866
|
}
|
|
42155
42867
|
await this.context.deps.store.updateOperation(operationResult.operation.id, { resultWorkspaceId: inserted.workspace.id }, now);
|
|
42156
42868
|
await this.context.deps.store.appendEvent(checkpoint.workspaceId, "workspace.restore_queued", {
|
|
42157
|
-
id:
|
|
42869
|
+
id: randomUUID18(),
|
|
42158
42870
|
type: "workspace.restore_queued",
|
|
42159
42871
|
occurred_at: now.toISOString(),
|
|
42160
42872
|
origin_workspace_id: checkpoint.workspaceId,
|
|
@@ -42173,6 +42885,9 @@ class PersistenceRestoreService {
|
|
|
42173
42885
|
class PersistenceService {
|
|
42174
42886
|
constructor(deps) {
|
|
42175
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);
|
|
42176
42891
|
this.preserveRunner = new PersistencePreserveRunner(this.context);
|
|
42177
42892
|
this.preservePersistence = new PreservePersistenceService(this.context, this.preserveRunner);
|
|
42178
42893
|
this.checkpoint = new PersistenceCheckpointService(this.context);
|
|
@@ -42189,6 +42904,8 @@ class PersistenceService {
|
|
|
42189
42904
|
this.delete = this.maintenance.delete.bind(this.maintenance);
|
|
42190
42905
|
this.restore = this.restoreService.restore.bind(this.restoreService);
|
|
42191
42906
|
}
|
|
42907
|
+
purge;
|
|
42908
|
+
retryPurges;
|
|
42192
42909
|
restoreService;
|
|
42193
42910
|
maintenance;
|
|
42194
42911
|
checkpoint;
|
|
@@ -42539,6 +43256,10 @@ class DockerDriver {
|
|
|
42539
43256
|
await rm(this.egressInputPath(workspaceId), { force: true });
|
|
42540
43257
|
}
|
|
42541
43258
|
}
|
|
43259
|
+
async purgeInput(workspaceId) {
|
|
43260
|
+
await rm(this.inputPath(workspaceId), { force: true });
|
|
43261
|
+
await rm(this.egressInputPath(workspaceId), { force: true });
|
|
43262
|
+
}
|
|
42542
43263
|
async cleanupInput(workspaceId) {
|
|
42543
43264
|
await rm(this.inputPath(workspaceId), { force: true });
|
|
42544
43265
|
}
|
|
@@ -42834,7 +43555,7 @@ async function restoreCheckpointContent(checkpointRoot, target, manifest) {
|
|
|
42834
43555
|
}
|
|
42835
43556
|
|
|
42836
43557
|
// ../drivers/src/filesystem/filesystem-inventory.ts
|
|
42837
|
-
import {
|
|
43558
|
+
import { readdir as readdir3, readFile, rm as rm3 } from "fs/promises";
|
|
42838
43559
|
import { join as join6, resolve as resolve8 } from "path";
|
|
42839
43560
|
var STORAGE_METADATA_FILE = ".pocketcoder-storage.json";
|
|
42840
43561
|
var CHECKPOINT_METADATA_FILE = ".pocketcoder-checkpoint.json";
|
|
@@ -42862,15 +43583,13 @@ async function discoverStorage(rootDirectory) {
|
|
|
42862
43583
|
}
|
|
42863
43584
|
async function discoverCheckpoints(rootDirectory) {
|
|
42864
43585
|
const result = [];
|
|
42865
|
-
for (const
|
|
43586
|
+
for (const entry of await readdir3(rootDirectory)) {
|
|
43587
|
+
const id = entry.replace(/^\.creating-/, "");
|
|
42866
43588
|
if (!/^[0-9a-f-]{36}$/i.test(id))
|
|
42867
43589
|
continue;
|
|
42868
43590
|
const root = childOf(rootDirectory, id);
|
|
42869
|
-
|
|
42870
|
-
const handle = await open(join6(root, CHECKPOINT_METADATA_FILE), "r");
|
|
42871
|
-
await handle.close();
|
|
43591
|
+
if (!result.some((row) => row.checkpointId === id))
|
|
42872
43592
|
result.push({ checkpointId: id, ref: { kind: "filesystem", id, root } });
|
|
42873
|
-
} catch {}
|
|
42874
43593
|
}
|
|
42875
43594
|
return result;
|
|
42876
43595
|
}
|
|
@@ -43100,6 +43819,7 @@ class FilesystemStorageDriver {
|
|
|
43100
43819
|
async deleteCheckpoint(ref) {
|
|
43101
43820
|
const checkpoint = this.checkpointRef(ref);
|
|
43102
43821
|
await deleteCheckpointRoot(checkpoint.root);
|
|
43822
|
+
await deleteCheckpointRoot(join7(this.checkpointRoot, `.creating-${checkpoint.id}`));
|
|
43103
43823
|
}
|
|
43104
43824
|
async listStorage() {
|
|
43105
43825
|
await this.initRoots();
|
|
@@ -43194,6 +43914,232 @@ function discoveredWarmRuntimes(output, kind, namespace) {
|
|
|
43194
43914
|
});
|
|
43195
43915
|
}
|
|
43196
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
|
+
|
|
43197
44143
|
// ../drivers/src/kubernetes/kubernetes-scheduling.ts
|
|
43198
44144
|
function schedulingFields(options) {
|
|
43199
44145
|
return {
|
|
@@ -43342,7 +44288,7 @@ function workspaceJobManifest(launch, name2, inputSecret, egressSecret, options)
|
|
|
43342
44288
|
backoffLimit: 0,
|
|
43343
44289
|
ttlSecondsAfterFinished: 3600,
|
|
43344
44290
|
template: {
|
|
43345
|
-
metadata: { labels, annotations },
|
|
44291
|
+
metadata: { labels, annotations, ...options.podFinalizers ? { finalizers: options.podFinalizers } : {} },
|
|
43346
44292
|
spec: {
|
|
43347
44293
|
restartPolicy: "Never",
|
|
43348
44294
|
automountServiceAccountToken: false,
|
|
@@ -43409,7 +44355,7 @@ function warmJobManifest(launch, name2, inputSecret, egressSecret, options) {
|
|
|
43409
44355
|
backoffLimit: 0,
|
|
43410
44356
|
ttlSecondsAfterFinished: 3600,
|
|
43411
44357
|
template: {
|
|
43412
|
-
metadata: { labels, annotations },
|
|
44358
|
+
metadata: { labels, annotations, ...options.podFinalizers ? { finalizers: options.podFinalizers } : {} },
|
|
43413
44359
|
spec: {
|
|
43414
44360
|
restartPolicy: "Never",
|
|
43415
44361
|
automountServiceAccountToken: false,
|
|
@@ -43459,6 +44405,22 @@ function warmJobManifest(launch, name2, inputSecret, egressSecret, options) {
|
|
|
43459
44405
|
};
|
|
43460
44406
|
}
|
|
43461
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
|
+
|
|
43462
44424
|
// ../drivers/src/kubernetes/kubernetes.ts
|
|
43463
44425
|
class KubernetesDriver {
|
|
43464
44426
|
kind = "kubernetes";
|
|
@@ -43470,7 +44432,9 @@ class KubernetesDriver {
|
|
|
43470
44432
|
imagePullPolicy;
|
|
43471
44433
|
egress;
|
|
43472
44434
|
sidecarsSupported = false;
|
|
44435
|
+
captureEvidence;
|
|
43473
44436
|
constructor(options = {}) {
|
|
44437
|
+
this.captureEvidence = options.captureTerminationEvidence ?? false;
|
|
43474
44438
|
this.namespace = options.namespace ?? "default";
|
|
43475
44439
|
if (!isKubernetesName(this.namespace)) {
|
|
43476
44440
|
throw new Error("namespace must be a Kubernetes resource name");
|
|
@@ -43538,6 +44502,7 @@ class KubernetesDriver {
|
|
|
43538
44502
|
nodeSelector: this.nodeSelector,
|
|
43539
44503
|
tolerations: this.tolerations,
|
|
43540
44504
|
imagePullPolicy: this.imagePullPolicy,
|
|
44505
|
+
...this.captureEvidence ? { podFinalizers: [EVIDENCE_FINALIZER] } : {},
|
|
43541
44506
|
egressImage: this.egress.egressImage
|
|
43542
44507
|
});
|
|
43543
44508
|
try {
|
|
@@ -43591,6 +44556,7 @@ class KubernetesDriver {
|
|
|
43591
44556
|
nodeSelector: this.nodeSelector,
|
|
43592
44557
|
tolerations: this.tolerations,
|
|
43593
44558
|
imagePullPolicy: this.imagePullPolicy,
|
|
44559
|
+
...this.captureEvidence ? { podFinalizers: [EVIDENCE_FINALIZER] } : {},
|
|
43594
44560
|
egressImage: this.egress.egressImage
|
|
43595
44561
|
});
|
|
43596
44562
|
try {
|
|
@@ -43613,38 +44579,36 @@ class KubernetesDriver {
|
|
|
43613
44579
|
}
|
|
43614
44580
|
}
|
|
43615
44581
|
async inspect(ref) {
|
|
43616
|
-
|
|
43617
|
-
|
|
43618
|
-
|
|
43619
|
-
|
|
43620
|
-
|
|
43621
|
-
|
|
43622
|
-
|
|
43623
|
-
|
|
43624
|
-
|
|
43625
|
-
};
|
|
43626
|
-
} 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)
|
|
43627
44591
|
return { exists: false, running: false, exitCode: null };
|
|
43628
|
-
|
|
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
|
+
};
|
|
43629
44605
|
}
|
|
43630
44606
|
async stop(ref, graceSeconds) {
|
|
43631
|
-
|
|
43632
|
-
|
|
43633
|
-
|
|
43634
|
-
|
|
43635
|
-
|
|
43636
|
-
"-p",
|
|
43637
|
-
'{"spec":{"suspend":true}}'
|
|
43638
|
-
]).catch(() => {});
|
|
43639
|
-
await kubectl(this.kubectlBin, this.namespace, [
|
|
43640
|
-
"delete",
|
|
43641
|
-
"pod",
|
|
43642
|
-
"-l",
|
|
43643
|
-
`job-name=${ref.id}`,
|
|
43644
|
-
`--grace-period=${graceSeconds}`,
|
|
43645
|
-
"--wait=true",
|
|
43646
|
-
"--ignore-not-found"
|
|
43647
|
-
]).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);
|
|
43648
44612
|
}
|
|
43649
44613
|
async remove(ref) {
|
|
43650
44614
|
await kubectl(this.kubectlBin, this.namespace, [
|
|
@@ -43652,19 +44616,31 @@ class KubernetesDriver {
|
|
|
43652
44616
|
"job",
|
|
43653
44617
|
ref.id,
|
|
43654
44618
|
"--ignore-not-found",
|
|
44619
|
+
"--cascade=foreground",
|
|
43655
44620
|
"--wait=true"
|
|
43656
|
-
])
|
|
44621
|
+
]);
|
|
43657
44622
|
const inputSecret = typeof ref.inputSecret === "string" ? ref.inputSecret : `${ref.id}-input`;
|
|
43658
|
-
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"]);
|
|
43659
44624
|
if (typeof ref.egressSecret === "string") {
|
|
43660
|
-
await kubectl(this.kubectlBin, this.namespace, [
|
|
43661
|
-
"delete",
|
|
43662
|
-
"secret",
|
|
43663
|
-
ref.egressSecret,
|
|
43664
|
-
"--ignore-not-found"
|
|
43665
|
-
]).catch(() => {});
|
|
44625
|
+
await kubectl(this.kubectlBin, this.namespace, ["delete", "secret", ref.egressSecret, "--ignore-not-found"]);
|
|
43666
44626
|
}
|
|
43667
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
|
+
}
|
|
43668
44644
|
async cleanupInput(workspaceId) {
|
|
43669
44645
|
await kubectl(this.kubectlBin, this.namespace, [
|
|
43670
44646
|
"delete",
|
|
@@ -44189,6 +45165,7 @@ function loadConfig(env2 = process.env) {
|
|
|
44189
45165
|
}
|
|
44190
45166
|
return {
|
|
44191
45167
|
listenHost: env2.POCKETCODER_HOST ?? "127.0.0.1",
|
|
45168
|
+
launchPolicy: launchPolicyConfig(env2),
|
|
44192
45169
|
listenPort,
|
|
44193
45170
|
storeKind,
|
|
44194
45171
|
databaseUrl,
|
|
@@ -44217,8 +45194,7 @@ function loadConfig(env2 = process.env) {
|
|
|
44217
45194
|
}
|
|
44218
45195
|
|
|
44219
45196
|
// ../memory-store/src/modules/auth/memory-store-auth.ts
|
|
44220
|
-
import { randomUUID as
|
|
44221
|
-
|
|
45197
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
44222
45198
|
class MemoryAuthStore {
|
|
44223
45199
|
context;
|
|
44224
45200
|
constructor(context) {
|
|
@@ -44229,7 +45205,7 @@ class MemoryAuthStore {
|
|
|
44229
45205
|
throw new Error(`principal exists: ${name2}`);
|
|
44230
45206
|
}
|
|
44231
45207
|
const row = {
|
|
44232
|
-
id:
|
|
45208
|
+
id: randomUUID19(),
|
|
44233
45209
|
name: name2,
|
|
44234
45210
|
scopes,
|
|
44235
45211
|
templateNames,
|
|
@@ -44242,6 +45218,21 @@ class MemoryAuthStore {
|
|
|
44242
45218
|
async getPrincipalByName(name2) {
|
|
44243
45219
|
return this.context.principals.find((p) => p.name === name2) ?? null;
|
|
44244
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
|
+
}
|
|
44245
45236
|
async listPrincipals() {
|
|
44246
45237
|
return this.context.principals.map((p) => ({ ...p }));
|
|
44247
45238
|
}
|
|
@@ -44260,7 +45251,32 @@ class MemoryAuthStore {
|
|
|
44260
45251
|
}
|
|
44261
45252
|
}
|
|
44262
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.");
|
|
44263
45278
|
this.context.keys.push({ ...row });
|
|
45279
|
+
return { key: { ...row }, created: true, conflict: false };
|
|
44264
45280
|
}
|
|
44265
45281
|
async getMachineKeyWithPrincipal(keyId) {
|
|
44266
45282
|
const key = this.context.keys.find((k) => k.id === keyId);
|
|
@@ -44286,7 +45302,7 @@ class MemoryAuthStore {
|
|
|
44286
45302
|
}
|
|
44287
45303
|
|
|
44288
45304
|
// ../memory-store/src/state/memory-store-base.ts
|
|
44289
|
-
import { randomUUID as
|
|
45305
|
+
import { randomUUID as randomUUID20 } from "crypto";
|
|
44290
45306
|
var ACTIVE_STATES = [
|
|
44291
45307
|
"provisioning",
|
|
44292
45308
|
"connected",
|
|
@@ -44360,7 +45376,7 @@ class MemoryState {
|
|
|
44360
45376
|
}
|
|
44361
45377
|
appendHistory(row, from, to, reason, at) {
|
|
44362
45378
|
this.history.push({
|
|
44363
|
-
id:
|
|
45379
|
+
id: randomUUID20(),
|
|
44364
45380
|
workspaceId: row.id,
|
|
44365
45381
|
fromState: from,
|
|
44366
45382
|
toState: to,
|
|
@@ -44369,6 +45385,8 @@ class MemoryState {
|
|
|
44369
45385
|
});
|
|
44370
45386
|
}
|
|
44371
45387
|
appendWorkspaceEvent(row, at) {
|
|
45388
|
+
if (row.purgeRequestedAt)
|
|
45389
|
+
return;
|
|
44372
45390
|
const payload = buildEventEnvelope(row, at);
|
|
44373
45391
|
this.outbox.push({
|
|
44374
45392
|
id: payload.id,
|
|
@@ -44394,6 +45412,8 @@ class MemoryConversationStore {
|
|
|
44394
45412
|
const state = this.context.conversationStates.get(input.workspaceId);
|
|
44395
45413
|
if (state?.status === "deleted")
|
|
44396
45414
|
throw new Error("conversation.deleted");
|
|
45415
|
+
if (this.context.workspaces.get(input.workspaceId)?.purgeRequestedAt)
|
|
45416
|
+
throw new Error("conversation.deleted");
|
|
44397
45417
|
const rows = this.context.conversations.get(input.workspaceId) ?? [];
|
|
44398
45418
|
const existing = rows.find((row) => row.messageId === input.messageId);
|
|
44399
45419
|
if (existing)
|
|
@@ -44469,6 +45489,8 @@ class MemoryLogStore {
|
|
|
44469
45489
|
this.context = context;
|
|
44470
45490
|
}
|
|
44471
45491
|
async appendLogs(workspaceId, entries) {
|
|
45492
|
+
if (this.context.workspaces.get(workspaceId)?.purgeRequestedAt)
|
|
45493
|
+
return;
|
|
44472
45494
|
const list = this.context.logs.get(workspaceId) ?? [];
|
|
44473
45495
|
let bytes = this.context.logBytes.get(workspaceId) ?? 0;
|
|
44474
45496
|
let seq = list.length > 0 ? list[list.length - 1]?.seq ?? 0 : 0;
|
|
@@ -44505,6 +45527,8 @@ class MemoryLogStore {
|
|
|
44505
45527
|
};
|
|
44506
45528
|
}
|
|
44507
45529
|
async appendNetworkEvents(workspaceId, sourceSessionId, events) {
|
|
45530
|
+
if (this.context.workspaces.get(workspaceId)?.purgeRequestedAt)
|
|
45531
|
+
return;
|
|
44508
45532
|
const rows = this.context.networkEvents.get(workspaceId) ?? [];
|
|
44509
45533
|
const seen = new Set(rows.map((row) => `${row.sourceSessionId}:${row.source_seq}`));
|
|
44510
45534
|
const workspace = this.context.workspaces.get(workspaceId);
|
|
@@ -44526,7 +45550,7 @@ class MemoryLogStore {
|
|
|
44526
45550
|
}
|
|
44527
45551
|
|
|
44528
45552
|
// ../memory-store/src/modules/outbox/memory-store-outbox.ts
|
|
44529
|
-
import { randomUUID as
|
|
45553
|
+
import { randomUUID as randomUUID21 } from "crypto";
|
|
44530
45554
|
|
|
44531
45555
|
class MemoryOutboxStore {
|
|
44532
45556
|
context;
|
|
@@ -44557,8 +45581,10 @@ class MemoryOutboxStore {
|
|
|
44557
45581
|
this.context.claimedEvents.delete(id);
|
|
44558
45582
|
}
|
|
44559
45583
|
async appendEvent(workspaceId, eventType, payload, at) {
|
|
45584
|
+
if (this.context.workspaces.get(workspaceId)?.purgeRequestedAt)
|
|
45585
|
+
return;
|
|
44560
45586
|
this.context.outbox.push({
|
|
44561
|
-
id:
|
|
45587
|
+
id: randomUUID21(),
|
|
44562
45588
|
workspaceId,
|
|
44563
45589
|
eventType,
|
|
44564
45590
|
payload,
|
|
@@ -44571,6 +45597,52 @@ class MemoryOutboxStore {
|
|
|
44571
45597
|
}
|
|
44572
45598
|
}
|
|
44573
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
|
+
|
|
44574
45646
|
// ../memory-store/src/modules/persistence/memory-store-operations.ts
|
|
44575
45647
|
class MemoryOperationStore {
|
|
44576
45648
|
context;
|
|
@@ -44590,6 +45662,12 @@ class MemoryOperationStore {
|
|
|
44590
45662
|
throw new OperationCapacityExceededError;
|
|
44591
45663
|
}
|
|
44592
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;
|
|
44593
45671
|
this.context.operations.set(row.id, { ...row });
|
|
44594
45672
|
return { operation: { ...row }, created: true, conflict: false };
|
|
44595
45673
|
}
|
|
@@ -44612,12 +45690,12 @@ class MemoryOperationStore {
|
|
|
44612
45690
|
async listIncompleteOperations() {
|
|
44613
45691
|
return [...this.context.operations.values()].filter((row) => row.state === "pending" || row.state === "running").map((row) => ({ ...row }));
|
|
44614
45692
|
}
|
|
44615
|
-
async updateOperation(id,
|
|
45693
|
+
async updateOperation(id, patch2, at) {
|
|
44616
45694
|
const row = this.context.operations.get(id);
|
|
44617
45695
|
if (!row)
|
|
44618
45696
|
return;
|
|
44619
|
-
this.assertOperationReferences({ ...row, ...
|
|
44620
|
-
Object.assign(row,
|
|
45697
|
+
this.assertOperationReferences({ ...row, ...patch2 });
|
|
45698
|
+
Object.assign(row, patch2);
|
|
44621
45699
|
row.updatedAt = at;
|
|
44622
45700
|
}
|
|
44623
45701
|
async checkpointUsage(principalId) {
|
|
@@ -44634,6 +45712,8 @@ class MemoryOperationStore {
|
|
|
44634
45712
|
const workspace = this.context.workspaces.get(input.workspaceId);
|
|
44635
45713
|
if (!workspace)
|
|
44636
45714
|
throw new Error("workspace not found");
|
|
45715
|
+
if (workspace.purgeRequestedAt)
|
|
45716
|
+
throw new ApiError("operation.conflict", "Workspace content is being purged.");
|
|
44637
45717
|
const list = this.context.outputs.get(input.workspaceId) ?? [];
|
|
44638
45718
|
const row = { ...input, seq: list.length + 1 };
|
|
44639
45719
|
list.push(row);
|
|
@@ -44668,11 +45748,11 @@ class MemoryPersistenceStore {
|
|
|
44668
45748
|
const row = this.context.storage.get(id);
|
|
44669
45749
|
return row ? { ...row } : null;
|
|
44670
45750
|
}
|
|
44671
|
-
async updateWorkspaceStorage(id,
|
|
45751
|
+
async updateWorkspaceStorage(id, patch2, at) {
|
|
44672
45752
|
const row = this.context.storage.get(id);
|
|
44673
45753
|
if (!row)
|
|
44674
45754
|
return;
|
|
44675
|
-
Object.assign(row,
|
|
45755
|
+
Object.assign(row, patch2);
|
|
44676
45756
|
row.updatedAt = at;
|
|
44677
45757
|
}
|
|
44678
45758
|
async insertCheckpoint(row) {
|
|
@@ -44689,24 +45769,24 @@ class MemoryPersistenceStore {
|
|
|
44689
45769
|
async listCheckpoints(principalId, filter = {}) {
|
|
44690
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 }));
|
|
44691
45771
|
}
|
|
44692
|
-
async updateCheckpoint(id,
|
|
45772
|
+
async updateCheckpoint(id, patch2, at) {
|
|
44693
45773
|
const row = this.context.checkpoints.get(id);
|
|
44694
45774
|
if (!row)
|
|
44695
45775
|
return;
|
|
44696
45776
|
if (row.state === "ready") {
|
|
44697
45777
|
const mutable = new Set(["state", "reasonCode", "expiresAt", "deletedAt"]);
|
|
44698
|
-
for (const key of Object.keys(
|
|
45778
|
+
for (const key of Object.keys(patch2)) {
|
|
44699
45779
|
if (!mutable.has(key))
|
|
44700
45780
|
throw new Error("ready checkpoints are immutable");
|
|
44701
45781
|
}
|
|
44702
45782
|
}
|
|
44703
|
-
Object.assign(row,
|
|
45783
|
+
Object.assign(row, patch2);
|
|
44704
45784
|
row.updatedAt = at;
|
|
44705
45785
|
}
|
|
44706
45786
|
}
|
|
44707
45787
|
|
|
44708
45788
|
// ../memory-store/src/modules/templates/memory-store-templates.ts
|
|
44709
|
-
import { randomUUID as
|
|
45789
|
+
import { randomUUID as randomUUID22 } from "crypto";
|
|
44710
45790
|
|
|
44711
45791
|
class MemoryTemplateStore {
|
|
44712
45792
|
context;
|
|
@@ -44722,7 +45802,7 @@ class MemoryTemplateStore {
|
|
|
44722
45802
|
return { row: existing, created: false, conflict: false };
|
|
44723
45803
|
}
|
|
44724
45804
|
const row = {
|
|
44725
|
-
id:
|
|
45805
|
+
id: randomUUID22(),
|
|
44726
45806
|
name: input.name,
|
|
44727
45807
|
version: input.version,
|
|
44728
45808
|
digest: input.digest,
|
|
@@ -44828,16 +45908,16 @@ class MemoryWarmPoolStore {
|
|
|
44828
45908
|
async listWarmPoolRuntimes() {
|
|
44829
45909
|
return [...this.context.warmPoolRuntimes.values()].map((row) => ({ ...row }));
|
|
44830
45910
|
}
|
|
44831
|
-
async updateWarmPoolRuntime(id,
|
|
45911
|
+
async updateWarmPoolRuntime(id, patch2, at) {
|
|
44832
45912
|
const row = this.context.warmPoolRuntimes.get(id);
|
|
44833
45913
|
if (!row)
|
|
44834
45914
|
return;
|
|
44835
|
-
Object.assign(row,
|
|
45915
|
+
Object.assign(row, patch2);
|
|
44836
45916
|
row.updatedAt = at;
|
|
44837
45917
|
}
|
|
44838
45918
|
async claimWarmPoolRuntime(claim) {
|
|
44839
45919
|
const workspace = this.context.workspaces.get(claim.workspaceId);
|
|
44840
|
-
if (workspace?.state !== "queued")
|
|
45920
|
+
if (workspace?.state !== "queued" || workspace.purgeRequestedAt)
|
|
44841
45921
|
return null;
|
|
44842
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];
|
|
44843
45923
|
if (!runtime?.providerRef)
|
|
@@ -44899,7 +45979,7 @@ class MemoryAdmissionStore {
|
|
|
44899
45979
|
}
|
|
44900
45980
|
async claimWorkspaceAdmission(claim) {
|
|
44901
45981
|
const workspace = this.context.workspaces.get(claim.workspaceId);
|
|
44902
|
-
if (workspace?.state !== "queued")
|
|
45982
|
+
if (workspace?.state !== "queued" || workspace.purgeRequestedAt)
|
|
44903
45983
|
return null;
|
|
44904
45984
|
const counts = this.activeCounts();
|
|
44905
45985
|
if (counts.global >= claim.limits.globalActiveWorkspaces)
|
|
@@ -44922,12 +46002,14 @@ class MemoryAdmissionStore {
|
|
|
44922
46002
|
}
|
|
44923
46003
|
});
|
|
44924
46004
|
}
|
|
44925
|
-
async updateWorkspace(id,
|
|
46005
|
+
async updateWorkspace(id, patch2, at) {
|
|
44926
46006
|
const row = this.context.workspaces.get(id);
|
|
44927
46007
|
if (!row)
|
|
44928
46008
|
return;
|
|
44929
|
-
Object.assign(row,
|
|
44930
|
-
|
|
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));
|
|
44931
46013
|
if (changed)
|
|
44932
46014
|
row.changeSeq += 1;
|
|
44933
46015
|
row.updatedAt = at;
|
|
@@ -44976,12 +46058,16 @@ class MemoryAdmissionStore {
|
|
|
44976
46058
|
return null;
|
|
44977
46059
|
if (!canTransition(row.state, req.to))
|
|
44978
46060
|
return null;
|
|
46061
|
+
if (row.purgeRequestedAt && ["provisioning", "connected", "ready", "preserving", "queued"].includes(req.to))
|
|
46062
|
+
return null;
|
|
44979
46063
|
const fromState = row.state;
|
|
44980
46064
|
row.state = req.to;
|
|
44981
46065
|
if (req.reason !== undefined)
|
|
44982
46066
|
row.reasonCode = req.reason;
|
|
44983
46067
|
if (req.patch)
|
|
44984
46068
|
Object.assign(row, req.patch);
|
|
46069
|
+
if (row.purgeRequestedAt)
|
|
46070
|
+
Object.assign(row, purgedContentPatch());
|
|
44985
46071
|
row.changeSeq += 1;
|
|
44986
46072
|
row.updatedAt = req.at;
|
|
44987
46073
|
if (isTerminal(req.to)) {
|
|
@@ -45078,6 +46164,7 @@ class MemoryWorkspaceStore {
|
|
|
45078
46164
|
createdAt: row.createdAt,
|
|
45079
46165
|
updatedAt: row.createdAt,
|
|
45080
46166
|
terminalAt: null,
|
|
46167
|
+
purgeRequestedAt: null,
|
|
45081
46168
|
originWorkspaceId: row.originWorkspaceId ?? null,
|
|
45082
46169
|
restoredFromCheckpointId: row.restoredFromCheckpointId ?? null,
|
|
45083
46170
|
sourceDescriptor: row.sourceDescriptor ?? null,
|
|
@@ -45130,6 +46217,9 @@ class MemoryWorkspaceStore {
|
|
|
45130
46217
|
class MemoryStore {
|
|
45131
46218
|
constructor() {
|
|
45132
46219
|
this.context = new MemoryState;
|
|
46220
|
+
const content = createContentPurge2(this.context);
|
|
46221
|
+
this.listWorkspaceStorage = content.listWorkspaceStorage;
|
|
46222
|
+
this.purgeWorkspaceContent = content.purgeWorkspaceContent;
|
|
45133
46223
|
this.persistence = new MemoryPersistenceStore(this.context);
|
|
45134
46224
|
this.operation = new MemoryOperationStore(this.context);
|
|
45135
46225
|
this.template = new MemoryTemplateStore(this.context);
|
|
@@ -45174,6 +46264,10 @@ class MemoryStore {
|
|
|
45174
46264
|
this.listPrincipals = this.auth.listPrincipals.bind(this.auth);
|
|
45175
46265
|
this.updatePrincipal = this.auth.updatePrincipal.bind(this.auth);
|
|
45176
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);
|
|
45177
46271
|
this.insertMachineKey = this.auth.insertMachineKey.bind(this.auth);
|
|
45178
46272
|
this.getMachineKeyWithPrincipal = this.auth.getMachineKeyWithPrincipal.bind(this.auth);
|
|
45179
46273
|
this.revokeMachineKey = this.auth.revokeMachineKey.bind(this.auth);
|
|
@@ -45224,6 +46318,8 @@ class MemoryStore {
|
|
|
45224
46318
|
operation;
|
|
45225
46319
|
persistence;
|
|
45226
46320
|
context;
|
|
46321
|
+
listWorkspaceStorage;
|
|
46322
|
+
purgeWorkspaceContent;
|
|
45227
46323
|
init;
|
|
45228
46324
|
acquireCoordinatorLease;
|
|
45229
46325
|
close;
|
|
@@ -45257,6 +46353,10 @@ class MemoryStore {
|
|
|
45257
46353
|
listPrincipals;
|
|
45258
46354
|
updatePrincipal;
|
|
45259
46355
|
setPrincipalDisabled;
|
|
46356
|
+
getPrincipal;
|
|
46357
|
+
issueMachineKey;
|
|
46358
|
+
listMachineKeys;
|
|
46359
|
+
revokePrincipalKeys;
|
|
45260
46360
|
insertMachineKey;
|
|
45261
46361
|
getMachineKeyWithPrincipal;
|
|
45262
46362
|
revokeMachineKey;
|
|
@@ -45504,10 +46604,10 @@ function mapValues(object2, mapper) {
|
|
|
45504
46604
|
});
|
|
45505
46605
|
return result;
|
|
45506
46606
|
}
|
|
45507
|
-
function omit2(object2,
|
|
46607
|
+
function omit2(object2, keys2) {
|
|
45508
46608
|
const result = {};
|
|
45509
46609
|
Object.entries(object2).forEach(([key, value]) => {
|
|
45510
|
-
if (!
|
|
46610
|
+
if (!keys2.some((keyToOmit) => keyToOmit === key)) {
|
|
45511
46611
|
result[key] = value;
|
|
45512
46612
|
}
|
|
45513
46613
|
});
|
|
@@ -45882,8 +46982,8 @@ class DiscriminatedUnionTransformer {
|
|
|
45882
46982
|
const refId = Metadata.getRefId(obj);
|
|
45883
46983
|
const value = (_a4 = obj.def.shape) === null || _a4 === undefined ? undefined : _a4[discriminator];
|
|
45884
46984
|
if (isZodType(value, "ZodEnum")) {
|
|
45885
|
-
const
|
|
45886
|
-
|
|
46985
|
+
const keys2 = Object.values(value._zod.def.entries).filter(isString);
|
|
46986
|
+
keys2.forEach((enumValue) => {
|
|
45887
46987
|
mapping[enumValue] = generateSchemaRef(refId);
|
|
45888
46988
|
});
|
|
45889
46989
|
return;
|
|
@@ -46010,8 +47110,8 @@ class RecordTransformer {
|
|
|
46010
47110
|
const keyType = zodSchema.keyType;
|
|
46011
47111
|
const propertiesSchema = isAnyZodType(propertiesType) ? mapItem(propertiesType) : {};
|
|
46012
47112
|
if (isZodType(keyType, "ZodEnum")) {
|
|
46013
|
-
const
|
|
46014
|
-
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 }), {});
|
|
46015
47115
|
return Object.assign(Object.assign({}, mapNullableType("object")), { properties });
|
|
46016
47116
|
}
|
|
46017
47117
|
return Object.assign(Object.assign({}, mapNullableType("object")), { additionalProperties: propertiesSchema });
|
|
@@ -47313,9 +48413,9 @@ var handleParsingNestedValues = (form, key, value) => {
|
|
|
47313
48413
|
return;
|
|
47314
48414
|
}
|
|
47315
48415
|
let nestedForm = form;
|
|
47316
|
-
const
|
|
47317
|
-
|
|
47318
|
-
if (index2 ===
|
|
48416
|
+
const keys2 = key.split(".");
|
|
48417
|
+
keys2.forEach((key2, index2) => {
|
|
48418
|
+
if (index2 === keys2.length - 1) {
|
|
47319
48419
|
nestedForm[key2] = value;
|
|
47320
48420
|
} else {
|
|
47321
48421
|
if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
|
|
@@ -47351,8 +48451,8 @@ var HonoRequest = class {
|
|
|
47351
48451
|
}
|
|
47352
48452
|
#getAllDecodedParams() {
|
|
47353
48453
|
const decoded = {};
|
|
47354
|
-
const
|
|
47355
|
-
for (const key of
|
|
48454
|
+
const keys2 = Object.keys(this.#matchResult[0][this.routeIndex][1]);
|
|
48455
|
+
for (const key of keys2) {
|
|
47356
48456
|
const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
|
|
47357
48457
|
if (value !== undefined) {
|
|
47358
48458
|
decoded[key] = /\%/.test(value) ? tryDecodeURIComponent(value) : value;
|
|
@@ -48111,9 +49211,9 @@ function buildMatcherFromPreprocessedRoutes(routes) {
|
|
|
48111
49211
|
if (!map2) {
|
|
48112
49212
|
continue;
|
|
48113
49213
|
}
|
|
48114
|
-
const
|
|
48115
|
-
for (let k = 0, len3 =
|
|
48116
|
-
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]]];
|
|
48117
49217
|
}
|
|
48118
49218
|
}
|
|
48119
49219
|
}
|
|
@@ -48916,10 +50016,10 @@ var createBunWebSocket = () => ({
|
|
|
48916
50016
|
});
|
|
48917
50017
|
|
|
48918
50018
|
// ../server/src/http/middleware.ts
|
|
48919
|
-
import { randomUUID as
|
|
50019
|
+
import { randomUUID as randomUUID23 } from "crypto";
|
|
48920
50020
|
var requestId = async (c, next) => {
|
|
48921
50021
|
const candidate = c.req.header("x-request-id");
|
|
48922
|
-
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();
|
|
48923
50023
|
c.set("requestId", id);
|
|
48924
50024
|
c.header("x-request-id", id);
|
|
48925
50025
|
await next();
|
|
@@ -48966,6 +50066,7 @@ function machineAuth(store, pepper) {
|
|
|
48966
50066
|
c.set("principal", found.principal);
|
|
48967
50067
|
c.set("scopes", effectiveScopes);
|
|
48968
50068
|
c.set("keyId", found.key.id);
|
|
50069
|
+
c.set("managedPrincipalIds", found.key.managedPrincipalIds);
|
|
48969
50070
|
store.touchMachineKey(found.key.id, now).catch(() => {});
|
|
48970
50071
|
await next();
|
|
48971
50072
|
};
|
|
@@ -48980,7 +50081,7 @@ function requireScope(scope) {
|
|
|
48980
50081
|
}
|
|
48981
50082
|
function errorHandler2(logger) {
|
|
48982
50083
|
return (err, c) => {
|
|
48983
|
-
const id = c.get("requestId") ??
|
|
50084
|
+
const id = c.get("requestId") ?? randomUUID23();
|
|
48984
50085
|
if (err instanceof ApiError) {
|
|
48985
50086
|
return c.json(errorEnvelope(err.code, err.message, id, err.details), err.status);
|
|
48986
50087
|
}
|
|
@@ -49082,8 +50183,219 @@ function registerAdministrationRoutes({
|
|
|
49082
50183
|
});
|
|
49083
50184
|
}
|
|
49084
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
|
+
|
|
49085
50397
|
// ../server/src/attachments/attachments.ts
|
|
49086
|
-
import { randomUUID as
|
|
50398
|
+
import { randomUUID as randomUUID24 } from "crypto";
|
|
49087
50399
|
var ACK_TIMEOUT_MS = 30000;
|
|
49088
50400
|
var RESULT_TIMEOUT_MS = 30000;
|
|
49089
50401
|
var RESOLVE_TIMEOUT_MS = 1e4;
|
|
@@ -49224,6 +50536,8 @@ function attachmentUploadHandler(deps) {
|
|
|
49224
50536
|
const id = c.req.param("id") ?? "";
|
|
49225
50537
|
const attachmentId = (c.req.param("attachmentId") ?? "").toLowerCase();
|
|
49226
50538
|
const row = await deps.service.getOwned(principal, id);
|
|
50539
|
+
if (row.purgeRequestedAt)
|
|
50540
|
+
throw new ApiError("operation.conflict", "Workspace content is being purged.");
|
|
49227
50541
|
if (isTerminal(row.state)) {
|
|
49228
50542
|
throw new ApiError("workspace.terminal", "This workspace has ended.");
|
|
49229
50543
|
}
|
|
@@ -49235,7 +50549,7 @@ function attachmentUploadHandler(deps) {
|
|
|
49235
50549
|
}
|
|
49236
50550
|
const headers = uploadHeaders(c);
|
|
49237
50551
|
const conn = liveConnection(deps.hub, id);
|
|
49238
|
-
const operationId =
|
|
50552
|
+
const operationId = randomUUID24();
|
|
49239
50553
|
deps.hub.openAttachment(conn, operationId);
|
|
49240
50554
|
try {
|
|
49241
50555
|
const result = await streamUpload(deps.hub, conn, operationId, c.req.raw.body, headers, attachmentId);
|
|
@@ -49260,7 +50574,7 @@ function abandonUpload(hub, workspaceId, conn, operationId, error51) {
|
|
|
49260
50574
|
hub.send(conn, "attachment_abort", { operation_id: operationId, reason });
|
|
49261
50575
|
}
|
|
49262
50576
|
async function resolveDescriptors(hub, conn, attachmentIds) {
|
|
49263
|
-
const operationId =
|
|
50577
|
+
const operationId = randomUUID24();
|
|
49264
50578
|
hub.openAttachment(conn, operationId);
|
|
49265
50579
|
try {
|
|
49266
50580
|
hub.send(conn, "attachment_resolve", {
|
|
@@ -49314,7 +50628,7 @@ ${attachmentManifest(descriptors)}`;
|
|
|
49314
50628
|
}
|
|
49315
50629
|
|
|
49316
50630
|
// ../server/src/control-channel/hub.ts
|
|
49317
|
-
import { randomUUID as
|
|
50631
|
+
import { randomUUID as randomUUID25 } from "crypto";
|
|
49318
50632
|
|
|
49319
50633
|
// ../server/src/relay/relay-stream-channel.ts
|
|
49320
50634
|
class RelayStreamRegistry {
|
|
@@ -49798,12 +51112,12 @@ class Hub {
|
|
|
49798
51112
|
relay(workspaceId, request) {
|
|
49799
51113
|
const conn = this.byWorkspace.get(workspaceId);
|
|
49800
51114
|
if (!conn?.registered) {
|
|
49801
|
-
return Promise.resolve({ request_id:
|
|
51115
|
+
return Promise.resolve({ request_id: randomUUID25(), headers: {}, error_code: "unreachable" });
|
|
49802
51116
|
}
|
|
49803
51117
|
if (conn.inflight.size + conn.streams.size >= MAX_INFLIGHT_RELAY) {
|
|
49804
|
-
return Promise.resolve({ request_id:
|
|
51118
|
+
return Promise.resolve({ request_id: randomUUID25(), headers: {}, error_code: "deadline" });
|
|
49805
51119
|
}
|
|
49806
|
-
const requestId2 =
|
|
51120
|
+
const requestId2 = randomUUID25();
|
|
49807
51121
|
return new Promise((resolve11) => {
|
|
49808
51122
|
const timer = setTimeout(() => {
|
|
49809
51123
|
conn.inflight.delete(requestId2);
|
|
@@ -49815,7 +51129,7 @@ class Hub {
|
|
|
49815
51129
|
}
|
|
49816
51130
|
relayStream(workspaceId, request, maxResponseBytes) {
|
|
49817
51131
|
const conn = this.byWorkspace.get(workspaceId);
|
|
49818
|
-
const requestId2 =
|
|
51132
|
+
const requestId2 = randomUUID25();
|
|
49819
51133
|
if (!conn?.registered) {
|
|
49820
51134
|
return Promise.resolve({ request_id: requestId2, headers: {}, error_code: "unreachable" });
|
|
49821
51135
|
}
|
|
@@ -50029,7 +51343,7 @@ async function authenticateConnection(deps, credentials) {
|
|
|
50029
51343
|
return { error: "Missing workspace credentials." };
|
|
50030
51344
|
}
|
|
50031
51345
|
const row = await deps.store.getWorkspace(credentials.workspaceId);
|
|
50032
|
-
if (!row || isTerminal(row.state))
|
|
51346
|
+
if (!row || row.purgeRequestedAt || isTerminal(row.state))
|
|
50033
51347
|
return { error: "Unknown workspace." };
|
|
50034
51348
|
if (sourceCredentialReference2(row) && credentials.protocolVersion < SOURCE_CREDENTIAL_MIN_PROTOCOL_VERSION) {
|
|
50035
51349
|
return { error: "Source credentials require agent protocol version 6." };
|
|
@@ -50475,7 +51789,7 @@ function agentWsEvents(deps) {
|
|
|
50475
51789
|
};
|
|
50476
51790
|
}
|
|
50477
51791
|
// ../server/src/conversations/conversations-routes.ts
|
|
50478
|
-
import { randomUUID as
|
|
51792
|
+
import { randomUUID as randomUUID26 } from "crypto";
|
|
50479
51793
|
|
|
50480
51794
|
// ../server/src/http/pagination.ts
|
|
50481
51795
|
function encodeCursor(scope, value) {
|
|
@@ -50589,7 +51903,7 @@ function registerConversationRoutes({
|
|
|
50589
51903
|
await store.deleteConversation(workspace.id, at);
|
|
50590
51904
|
if (previous?.status !== "deleted") {
|
|
50591
51905
|
await store.appendEvent(workspace.id, "workspace.conversation_deleted", {
|
|
50592
|
-
id:
|
|
51906
|
+
id: randomUUID26(),
|
|
50593
51907
|
type: "workspace.conversation_deleted",
|
|
50594
51908
|
occurred_at: at.toISOString(),
|
|
50595
51909
|
workspace_id: workspace.id
|
|
@@ -50787,7 +52101,9 @@ var HEALTHY_CHECKS = {
|
|
|
50787
52101
|
database: "ok",
|
|
50788
52102
|
schema: "ok",
|
|
50789
52103
|
reconciliation: "ok",
|
|
50790
|
-
coordinator: "ok"
|
|
52104
|
+
coordinator: "ok",
|
|
52105
|
+
"policy-reconciliation": "disabled",
|
|
52106
|
+
cleanup: "ok"
|
|
50791
52107
|
};
|
|
50792
52108
|
|
|
50793
52109
|
class Readiness {
|
|
@@ -51031,6 +52347,40 @@ function registerCheckpointRoutes(deps) {
|
|
|
51031
52347
|
});
|
|
51032
52348
|
}
|
|
51033
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
|
+
|
|
51034
52384
|
// ../server/src/persistence/recovery-routes.ts
|
|
51035
52385
|
function registerRecoveryRoutes({
|
|
51036
52386
|
app,
|
|
@@ -51142,6 +52492,14 @@ function registerRecoveryRoutes({
|
|
|
51142
52492
|
|
|
51143
52493
|
// ../server/src/relay/relay.ts
|
|
51144
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
|
+
}
|
|
51145
52503
|
function requestPath(c, prefix) {
|
|
51146
52504
|
const rawPath = c.req.path.startsWith(prefix) ? c.req.path.slice(prefix.length) : "";
|
|
51147
52505
|
return rawPath === "" ? "/" : rawPath;
|
|
@@ -51240,12 +52598,7 @@ function relayHandler(deps, options) {
|
|
|
51240
52598
|
const id = c.req.param("id") ?? "";
|
|
51241
52599
|
const serviceName = options?.service ?? c.req.param("service") ?? "";
|
|
51242
52600
|
const row = await deps.service.getOwned(principal, id);
|
|
51243
|
-
|
|
51244
|
-
throw new ApiError("workspace.terminal", "This workspace has ended.");
|
|
51245
|
-
}
|
|
51246
|
-
if (row.state !== "ready") {
|
|
51247
|
-
throw new ApiError("workspace.not_ready", "Workspace is not ready.");
|
|
51248
|
-
}
|
|
52601
|
+
assertRelayReady(row);
|
|
51249
52602
|
const prefix = options?.pathPrefix?.(id) ?? `/v1/workspaces/${id}/services/${serviceName}`;
|
|
51250
52603
|
const path = requestPath(c, prefix);
|
|
51251
52604
|
const method = c.req.method;
|
|
@@ -51257,6 +52610,8 @@ function relayHandler(deps, options) {
|
|
|
51257
52610
|
let bodyB64 = await requestBody(c, method, match2.route.maxRequestBytes);
|
|
51258
52611
|
if (options?.transformBodyB64)
|
|
51259
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.");
|
|
51260
52615
|
if (!deps.hub.isConnected(id)) {
|
|
51261
52616
|
throw new ApiError("workspace.disconnected", "The workspace supervisor is not currently connected.");
|
|
51262
52617
|
}
|
|
@@ -51366,7 +52721,7 @@ function registerCatalogRoutes({ app, store }) {
|
|
|
51366
52721
|
}
|
|
51367
52722
|
|
|
51368
52723
|
// ../server/src/terminals/terminal-ws.ts
|
|
51369
|
-
import { randomUUID as
|
|
52724
|
+
import { randomUUID as randomUUID27 } from "crypto";
|
|
51370
52725
|
var SessionIdSchema = exports_external.uuid();
|
|
51371
52726
|
function terminalConnectValidator(deps) {
|
|
51372
52727
|
return async (c, next) => {
|
|
@@ -51379,6 +52734,8 @@ function terminalConnectValidator(deps) {
|
|
|
51379
52734
|
async function terminalCapability(deps, c) {
|
|
51380
52735
|
const workspaceId = c.req.param("id") ?? "";
|
|
51381
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.");
|
|
51382
52739
|
if (isTerminal(workspace.state)) {
|
|
51383
52740
|
throw new ApiError("workspace.terminal", "This workspace has ended.");
|
|
51384
52741
|
}
|
|
@@ -51407,7 +52764,7 @@ async function terminalSession(deps, c, workspaceId, maxSessions) {
|
|
|
51407
52764
|
if (c.req.header("upgrade")?.toLowerCase() !== "websocket") {
|
|
51408
52765
|
throw new ApiError("validation.invalid", "WebSocket upgrade required.");
|
|
51409
52766
|
}
|
|
51410
|
-
const sessionId = parsedSessionId?.data ??
|
|
52767
|
+
const sessionId = parsedSessionId?.data ?? randomUUID27();
|
|
51411
52768
|
if (requested) {
|
|
51412
52769
|
const session2 = await deps.store.getTerminalSession(sessionId);
|
|
51413
52770
|
if (!session2 || session2.workspaceId !== workspaceId) {
|
|
@@ -51710,6 +53067,7 @@ function buildServer(deps) {
|
|
|
51710
53067
|
}) : undefined;
|
|
51711
53068
|
const persistenceHolder = {};
|
|
51712
53069
|
const scheduler = new Scheduler({
|
|
53070
|
+
...deps.authorizeLaunch ? { authorizeLaunch: deps.authorizeLaunch } : {},
|
|
51713
53071
|
store,
|
|
51714
53072
|
driver,
|
|
51715
53073
|
...deps.storageDriver ? { storageDriver: deps.storageDriver } : {},
|
|
@@ -51810,9 +53168,12 @@ function buildServer(deps) {
|
|
|
51810
53168
|
});
|
|
51811
53169
|
app.use("/v1/*", machineAuth(store, pepper));
|
|
51812
53170
|
registerCatalogRoutes({ app, store });
|
|
53171
|
+
registerKeyRoutes(app, store, pepper);
|
|
53172
|
+
registerOperatorRecoveryRoutes(app, store, persistence2);
|
|
51813
53173
|
registerAdministrationRoutes({ app, persistence: persistence2, warmPool });
|
|
51814
53174
|
registerCheckpointRoutes({ app, store, service, persistence: persistence2 });
|
|
51815
53175
|
registerRecoveryRoutes({ app, store, service, persistence: persistence2 });
|
|
53176
|
+
registerPurgeRoutes(app, persistence2);
|
|
51816
53177
|
registerConversationRoutes({ app, store, service });
|
|
51817
53178
|
registerWorkspaceRoutes({ app, store, service });
|
|
51818
53179
|
registerDiagnosticRoutes({ app, store, service });
|
|
@@ -51848,6 +53209,98 @@ function buildServer(deps) {
|
|
|
51848
53209
|
};
|
|
51849
53210
|
}
|
|
51850
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
|
+
|
|
51851
53304
|
// ../server/src/lifecycle/lifecycle.ts
|
|
51852
53305
|
var defaultLog = (message) => console.log(`[pocketcoder-server] ${message}`);
|
|
51853
53306
|
async function initializeStore(config2, log) {
|
|
@@ -51885,6 +53338,7 @@ function createWorkspaceDriver(config2) {
|
|
|
51885
53338
|
return new KubernetesDriver({
|
|
51886
53339
|
...egress,
|
|
51887
53340
|
namespace: config2.kubernetesNamespace,
|
|
53341
|
+
captureTerminationEvidence: Boolean(config2.launchPolicy),
|
|
51888
53342
|
nodeSelector: config2.kubernetesNodeSelector ?? undefined,
|
|
51889
53343
|
tolerations: config2.kubernetesTolerations,
|
|
51890
53344
|
...config2.kubernetesServiceAccount ? { serviceAccountName: config2.kubernetesServiceAccount } : {}
|
|
@@ -51947,6 +53401,7 @@ function startExclusiveTimer(intervalMs, task, errorContext, log) {
|
|
|
51947
53401
|
}
|
|
51948
53402
|
async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
51949
53403
|
const log = options.log ?? defaultLog;
|
|
53404
|
+
const authorizeLaunch = loadLaunchPolicy(config2.launchPolicy);
|
|
51950
53405
|
const logger = createStructuredLogger((record3) => log(JSON.stringify(record3)));
|
|
51951
53406
|
const metrics = new RuntimeMetrics;
|
|
51952
53407
|
log(`config: ${JSON.stringify(configSummary(config2))}`);
|
|
@@ -51962,7 +53417,11 @@ async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
|
51962
53417
|
const storageDriver = createStorageDriver(config2);
|
|
51963
53418
|
const secretResolver = createSecretResolver(config2);
|
|
51964
53419
|
const readiness = new Readiness({ reconciliation: "pending" }, metrics);
|
|
53420
|
+
const policyReconciliation = loadPolicyReconciliation(store, config2.launchPolicy);
|
|
53421
|
+
if (policyReconciliation)
|
|
53422
|
+
readiness.set("policy-reconciliation", "pending");
|
|
51965
53423
|
const { app, websocket: websocket2, scheduler, persistence: persistence2, warmPool } = buildServer({
|
|
53424
|
+
...authorizeLaunch ? { authorizeLaunch } : {},
|
|
51966
53425
|
store,
|
|
51967
53426
|
driver,
|
|
51968
53427
|
...storageDriver ? { storageDriver } : {},
|
|
@@ -51989,6 +53448,9 @@ async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
|
51989
53448
|
const schedulerTimer = startExclusiveTimer(config2.schedulerIntervalMs, async () => {
|
|
51990
53449
|
try {
|
|
51991
53450
|
await scheduler.tick();
|
|
53451
|
+
const pendingPurges = await persistence2.retryPurges();
|
|
53452
|
+
readiness.set("cleanup", pendingPurges > 0 ? "pending" : "ok");
|
|
53453
|
+
metrics.observe("purge.pending", pendingPurges);
|
|
51992
53454
|
readiness.set("coordinator", "ok");
|
|
51993
53455
|
} catch (error51) {
|
|
51994
53456
|
readiness.set("coordinator", "failed");
|
|
@@ -51996,6 +53458,15 @@ async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
|
51996
53458
|
}
|
|
51997
53459
|
}, "scheduler tick failed", log);
|
|
51998
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;
|
|
51999
53470
|
const warmPoolTimer = warmPool && warmPoolContinuously ? startExclusiveTimer(config2.schedulerIntervalMs, () => warmPool.reconcile(), "warm pool reconciliation failed", log) : null;
|
|
52000
53471
|
const retentionTimer = startExclusiveTimer(60000, async () => {
|
|
52001
53472
|
const { deleted, skipped } = await persistence2.pruneExpired();
|
|
@@ -52016,6 +53487,9 @@ async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
|
52016
53487
|
clearInterval(schedulerTimer);
|
|
52017
53488
|
clearInterval(outboxTimer);
|
|
52018
53489
|
clearInterval(retentionTimer);
|
|
53490
|
+
if (policyTimer)
|
|
53491
|
+
clearInterval(policyTimer);
|
|
53492
|
+
await policyReconciliation?.drain();
|
|
52019
53493
|
if (warmPoolTimer)
|
|
52020
53494
|
clearInterval(warmPoolTimer);
|
|
52021
53495
|
throw error51;
|
|
@@ -52024,8 +53498,7 @@ async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
|
52024
53498
|
const url2 = `http://${healthHost}:${server.port}`;
|
|
52025
53499
|
log(`listening on http://${config2.listenHost}:${server.port}`);
|
|
52026
53500
|
log(`workspaces reach this server at ${config2.workspaceServerUrl}`);
|
|
52027
|
-
|
|
52028
|
-
warmPool.reconcile().catch((error51) => log(`warm pool initial reconcile failed: ${String(error51)}`));
|
|
53501
|
+
const initialWarmPool = warmPool?.reconcile().catch((error51) => log(`warm pool initial reconcile failed: ${String(error51)}`));
|
|
52029
53502
|
let stopPromise = null;
|
|
52030
53503
|
return {
|
|
52031
53504
|
config: config2,
|
|
@@ -52038,9 +53511,13 @@ async function startPocketCoderServer(config2 = loadConfig(), options = {}) {
|
|
|
52038
53511
|
clearInterval(schedulerTimer);
|
|
52039
53512
|
clearInterval(outboxTimer);
|
|
52040
53513
|
clearInterval(retentionTimer);
|
|
53514
|
+
if (policyTimer)
|
|
53515
|
+
clearInterval(policyTimer);
|
|
53516
|
+
await policyReconciliation?.drain();
|
|
52041
53517
|
if (warmPoolTimer)
|
|
52042
53518
|
clearInterval(warmPoolTimer);
|
|
52043
53519
|
await server.stop(true);
|
|
53520
|
+
await initialWarmPool;
|
|
52044
53521
|
await store.close();
|
|
52045
53522
|
})();
|
|
52046
53523
|
return stopPromise;
|
|
@@ -52087,7 +53564,7 @@ function stateFromJson(value) {
|
|
|
52087
53564
|
}
|
|
52088
53565
|
function readState() {
|
|
52089
53566
|
try {
|
|
52090
|
-
return stateFromJson(JSON.parse(
|
|
53567
|
+
return stateFromJson(JSON.parse(readFileSync6(statePath(), "utf8")));
|
|
52091
53568
|
} catch {
|
|
52092
53569
|
return null;
|
|
52093
53570
|
}
|
|
@@ -52095,7 +53572,7 @@ function readState() {
|
|
|
52095
53572
|
function writeState(state) {
|
|
52096
53573
|
const path = statePath();
|
|
52097
53574
|
mkdirSync(dirname7(path), { recursive: true, mode: 448 });
|
|
52098
|
-
const temporary = `${path}.${process.pid}.${
|
|
53575
|
+
const temporary = `${path}.${process.pid}.${randomUUID28()}.tmp`;
|
|
52099
53576
|
writeFileSync(temporary, `${JSON.stringify(state, null, 2)}
|
|
52100
53577
|
`, { mode: 384 });
|
|
52101
53578
|
renameSync(temporary, path);
|
|
@@ -52167,7 +53644,7 @@ function selfInvocation(args) {
|
|
|
52167
53644
|
}
|
|
52168
53645
|
function logTail(path) {
|
|
52169
53646
|
try {
|
|
52170
|
-
const content =
|
|
53647
|
+
const content = readFileSync6(path, "utf8");
|
|
52171
53648
|
return content.slice(Math.max(0, content.length - 4000)).trim();
|
|
52172
53649
|
} catch {
|
|
52173
53650
|
return "";
|
|
@@ -52204,7 +53681,7 @@ async function startManagedServer(options) {
|
|
|
52204
53681
|
if (existing)
|
|
52205
53682
|
removeState();
|
|
52206
53683
|
const config2 = loadConfig();
|
|
52207
|
-
const instanceToken =
|
|
53684
|
+
const instanceToken = randomUUID28();
|
|
52208
53685
|
const state = {
|
|
52209
53686
|
version: 1,
|
|
52210
53687
|
pid: 0,
|
|
@@ -52383,7 +53860,7 @@ function addStorageCommands(parser2) {
|
|
|
52383
53860
|
}
|
|
52384
53861
|
|
|
52385
53862
|
// src/commands/templates/list.ts
|
|
52386
|
-
function
|
|
53863
|
+
function addListCommand5(parser2) {
|
|
52387
53864
|
return addAction(parser2, "list", "List authorized template versions through the REST API", (command2) => command2.option("json", { type: "boolean", description: "Print JSON" }), async (flags) => {
|
|
52388
53865
|
const items = await controlPlaneClient().templates.list();
|
|
52389
53866
|
if (flags.json)
|
|
@@ -52476,7 +53953,7 @@ function addValidateCommand(parser2) {
|
|
|
52476
53953
|
|
|
52477
53954
|
// src/commands/templates/index.ts
|
|
52478
53955
|
function addTemplateCommands(parser2) {
|
|
52479
|
-
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)))));
|
|
52480
53957
|
}
|
|
52481
53958
|
|
|
52482
53959
|
// src/commands/workspaces/chat-session.ts
|
|
@@ -52508,8 +53985,8 @@ async function waitForAgentInput(id, options, deps) {
|
|
|
52508
53985
|
}
|
|
52509
53986
|
|
|
52510
53987
|
// src/commands/workspaces/attachments.ts
|
|
52511
|
-
import { randomUUID as
|
|
52512
|
-
import { readFileSync as
|
|
53988
|
+
import { randomUUID as randomUUID29 } from "crypto";
|
|
53989
|
+
import { readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
52513
53990
|
import { basename as basename2, extname as extname2 } from "path";
|
|
52514
53991
|
var MEDIA_TYPES = {
|
|
52515
53992
|
".csv": "text/csv",
|
|
@@ -52541,12 +54018,12 @@ async function uploadAttachments(api3, workspaceId, paths, log) {
|
|
|
52541
54018
|
const name2 = basename2(path);
|
|
52542
54019
|
let bytes;
|
|
52543
54020
|
try {
|
|
52544
|
-
bytes =
|
|
54021
|
+
bytes = readFileSync7(path);
|
|
52545
54022
|
} catch {
|
|
52546
54023
|
throw new Error(`could not read attachment file: ${path}`);
|
|
52547
54024
|
}
|
|
52548
54025
|
log(`uploading ${name2} (${bytes.byteLength} bytes)`);
|
|
52549
|
-
const id =
|
|
54026
|
+
const id = randomUUID29();
|
|
52550
54027
|
const response = await api3(`/v1/workspaces/${workspaceId}/attachments/${id}`, {
|
|
52551
54028
|
method: "PUT",
|
|
52552
54029
|
headers: {
|
|
@@ -52614,7 +54091,7 @@ function fileFlags(value) {
|
|
|
52614
54091
|
}
|
|
52615
54092
|
|
|
52616
54093
|
// src/commands/workspaces/chat-cursor.ts
|
|
52617
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
54094
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
|
|
52618
54095
|
import { homedir as homedir2 } from "os";
|
|
52619
54096
|
import { dirname as dirname8, join as join10 } from "path";
|
|
52620
54097
|
function cursorFile() {
|
|
@@ -52623,7 +54100,7 @@ function cursorFile() {
|
|
|
52623
54100
|
}
|
|
52624
54101
|
function readCursors(path) {
|
|
52625
54102
|
try {
|
|
52626
|
-
return JSON.parse(
|
|
54103
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
52627
54104
|
} catch {
|
|
52628
54105
|
return {};
|
|
52629
54106
|
}
|
|
@@ -52991,7 +54468,7 @@ function addChatCommand(parser2) {
|
|
|
52991
54468
|
}
|
|
52992
54469
|
|
|
52993
54470
|
// src/commands/workspaces/create.ts
|
|
52994
|
-
import { randomUUID as
|
|
54471
|
+
import { randomUUID as randomUUID30 } from "crypto";
|
|
52995
54472
|
|
|
52996
54473
|
// src/commands/workspaces/launch-input.ts
|
|
52997
54474
|
function parseLaunchInput(value, fail2) {
|
|
@@ -53098,7 +54575,7 @@ async function waitForReady(initial, flags, { client, fail: failCommand }) {
|
|
|
53098
54575
|
}
|
|
53099
54576
|
async function createWorkspace(flags, deps) {
|
|
53100
54577
|
const template = required2(flags, "template", deps.fail);
|
|
53101
|
-
const externalId = typeof flags["external-id"] === "string" ? flags["external-id"] : `pcd-${
|
|
54578
|
+
const externalId = typeof flags["external-id"] === "string" ? flags["external-id"] : `pcd-${randomUUID30()}`;
|
|
53102
54579
|
const launchInput = parseLaunchInput(flags.input, deps.fail);
|
|
53103
54580
|
const created = await deps.client.workspaces.create({
|
|
53104
54581
|
externalId,
|
|
@@ -53167,7 +54644,7 @@ async function listWorkspaces(flags) {
|
|
|
53167
54644
|
else
|
|
53168
54645
|
printWorkspaces(items);
|
|
53169
54646
|
}
|
|
53170
|
-
function
|
|
54647
|
+
function addListCommand6(parser2) {
|
|
53171
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);
|
|
53172
54649
|
}
|
|
53173
54650
|
|
|
@@ -53210,14 +54687,29 @@ function addOutputsCommand(parser2) {
|
|
|
53210
54687
|
}
|
|
53211
54688
|
|
|
53212
54689
|
// src/commands/workspaces/preserve.ts
|
|
53213
|
-
import { randomUUID as
|
|
54690
|
+
import { randomUUID as randomUUID31 } from "crypto";
|
|
53214
54691
|
function addPreserveCommand(parser2) {
|
|
53215
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) => {
|
|
53216
54693
|
const id = need(flags, "id");
|
|
53217
54694
|
const result = await controlPlaneClient().workspaces.preserve(id, {
|
|
53218
54695
|
...typeof flags.retention === "string" ? { retention: flags.retention } : {},
|
|
53219
54696
|
...typeof flags.label === "string" ? { label: flags.label } : {}
|
|
53220
|
-
}, `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);
|
|
53221
54713
|
console.log(JSON.stringify(result, null, 2));
|
|
53222
54714
|
});
|
|
53223
54715
|
}
|
|
@@ -53392,7 +54884,7 @@ function addTerminalSessionsCommand(parser2) {
|
|
|
53392
54884
|
// src/commands/workspaces/index.ts
|
|
53393
54885
|
function addWorkspaceCommands(parser2) {
|
|
53394
54886
|
const actions = [
|
|
53395
|
-
|
|
54887
|
+
addListCommand6,
|
|
53396
54888
|
addCreateCommand2,
|
|
53397
54889
|
addGetCommand2,
|
|
53398
54890
|
addLogsCommand,
|
|
@@ -53401,6 +54893,7 @@ function addWorkspaceCommands(parser2) {
|
|
|
53401
54893
|
addTerminalCommand,
|
|
53402
54894
|
addCancelCommand,
|
|
53403
54895
|
addPreserveCommand,
|
|
54896
|
+
addPurgeCommand,
|
|
53404
54897
|
addRestoreCommand,
|
|
53405
54898
|
addRecreateCommand,
|
|
53406
54899
|
addOutputsCommand,
|