@hasna/skills 0.8.12 → 0.9.1
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/README.md +11 -4
- package/bin/index.js +183 -13
- package/bin/mcp.js +248 -154
- package/bin/migrate.js +100 -10
- package/bin/server.js +596 -51
- package/bin/worker.js +283 -20
- package/dist/cli/commands/registry.d.ts +1 -0
- package/dist/index.js +88 -9
- package/dist/lib/cloud-executions.d.ts +20 -0
- package/dist/lib/registry-types.d.ts +3 -0
- package/dist/lib/remote-client.d.ts +11 -0
- package/dist/lib/revision.d.ts +3 -0
- package/dist/sdk/execution/admission.d.ts +2 -1
- package/dist/sdk/execution/types.d.ts +9 -0
- package/dist/sdk/index.d.ts +1 -0
- package/dist/sdk/index.js +1267 -498
- package/dist/server/runtime-policy.d.ts +4 -2
- package/dist/server/runtime-pure-contract.d.ts +51 -0
- package/dist/server/runtime-store.d.ts +4 -0
- package/dist/server/runtime-worker.d.ts +2 -1
- package/dist/server/selection-store.d.ts +9 -1
- package/dist/server/skills-api.d.ts +2 -1
- package/dist/server/sqlite-store.d.ts +4 -1
- package/dist/server/store.d.ts +6 -1
- package/dist/server/types.d.ts +27 -0
- package/migrations/postgres/0010_skill_archive_lifecycle.sql +5 -0
- package/migrations/sqlite/0010_skill_archive_lifecycle.sql +5 -0
- package/package.json +1 -1
package/dist/sdk/index.js
CHANGED
|
@@ -32370,9 +32370,196 @@ var cachedClient;
|
|
|
32370
32370
|
var init_event_sink = __esm(() => {
|
|
32371
32371
|
init_dist();
|
|
32372
32372
|
});
|
|
32373
|
+
// package.json
|
|
32374
|
+
var package_default = {
|
|
32375
|
+
name: "@hasna/skills",
|
|
32376
|
+
version: "0.9.1",
|
|
32377
|
+
description: "Skills library for AI coding agents",
|
|
32378
|
+
type: "module",
|
|
32379
|
+
bin: {
|
|
32380
|
+
skills: "bin/index.js",
|
|
32381
|
+
"skills-mcp": "bin/mcp.js",
|
|
32382
|
+
"skills-serve": "bin/server.js",
|
|
32383
|
+
"skills-server": "bin/server.js",
|
|
32384
|
+
"skills-worker": "bin/worker.js",
|
|
32385
|
+
"skills-migrate": "bin/migrate.js"
|
|
32386
|
+
},
|
|
32387
|
+
exports: {
|
|
32388
|
+
".": {
|
|
32389
|
+
import: "./dist/index.js",
|
|
32390
|
+
types: "./dist/index.d.ts"
|
|
32391
|
+
},
|
|
32392
|
+
"./storage": {
|
|
32393
|
+
import: "./dist/storage.js",
|
|
32394
|
+
types: "./dist/storage.d.ts"
|
|
32395
|
+
},
|
|
32396
|
+
"./sdk": {
|
|
32397
|
+
import: "./dist/sdk/index.js",
|
|
32398
|
+
types: "./dist/sdk/index.d.ts"
|
|
32399
|
+
},
|
|
32400
|
+
"./admin-contract": {
|
|
32401
|
+
import: "./dist/admin-contract.js",
|
|
32402
|
+
types: "./dist/admin-contract.d.ts"
|
|
32403
|
+
}
|
|
32404
|
+
},
|
|
32405
|
+
files: [
|
|
32406
|
+
"dist/",
|
|
32407
|
+
"!dist/**/*.test.d.ts",
|
|
32408
|
+
"!dist/**/*.fixture.d.ts",
|
|
32409
|
+
"!dist/test-preload.d.ts",
|
|
32410
|
+
"!dist/platform",
|
|
32411
|
+
"bin/",
|
|
32412
|
+
"migrations/",
|
|
32413
|
+
"docs/skill-standard.md",
|
|
32414
|
+
"docs/plugin-admission.md",
|
|
32415
|
+
"schemas/",
|
|
32416
|
+
"LICENSE",
|
|
32417
|
+
"README.md",
|
|
32418
|
+
"!dist/**/*test-utils.d.ts",
|
|
32419
|
+
"!dist/**/*fixtures.d.ts"
|
|
32420
|
+
],
|
|
32421
|
+
main: "./dist/index.js",
|
|
32422
|
+
types: "./dist/index.d.ts",
|
|
32423
|
+
scripts: {
|
|
32424
|
+
clean: "rm -rf bin/ dist/",
|
|
32425
|
+
build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
32426
|
+
"build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
32427
|
+
test: "bun test --timeout 30000",
|
|
32428
|
+
dev: "bun run ./src/cli/index.tsx",
|
|
32429
|
+
"dev:watch": "bun --watch run ./src/cli/index.tsx",
|
|
32430
|
+
"dev:mcp": "bun --watch run ./src/mcp/index.ts",
|
|
32431
|
+
"dev:server": "bun --watch run ./src/server/index.ts",
|
|
32432
|
+
"dev:worker": "bun --watch run ./src/server/worker.ts",
|
|
32433
|
+
"docker:check": "bash scripts/docker-build-check.sh",
|
|
32434
|
+
migrate: "bun run ./src/server/migrate.ts",
|
|
32435
|
+
typecheck: "tsc --noEmit",
|
|
32436
|
+
"verify:release": "bun run scripts/release-guard.ts",
|
|
32437
|
+
"verify:consumer-types": "bun run scripts/consumer-types.ts",
|
|
32438
|
+
prepack: "bun run verify:generated && bun run verify:release && bun run verify:consumer-types",
|
|
32439
|
+
prepublishOnly: "bun run verify:producer && bun run typecheck && bun run test",
|
|
32440
|
+
"verify:producer": "bun run scripts/release-dependencies.ts",
|
|
32441
|
+
"verify:generated": "HASNA_SKILLS_RELEASE_ARTIFACT_TEST=1 bun test src/lib/release-artifacts.test.ts",
|
|
32442
|
+
"verify:consumer-credentials": "bun run scripts/consumer-credentials.ts"
|
|
32443
|
+
},
|
|
32444
|
+
keywords: [
|
|
32445
|
+
"skills",
|
|
32446
|
+
"ai",
|
|
32447
|
+
"agent",
|
|
32448
|
+
"cli",
|
|
32449
|
+
"typescript",
|
|
32450
|
+
"bun",
|
|
32451
|
+
"claude",
|
|
32452
|
+
"codex",
|
|
32453
|
+
"gemini",
|
|
32454
|
+
"mcp",
|
|
32455
|
+
"model-context-protocol",
|
|
32456
|
+
"open-source",
|
|
32457
|
+
"skills-library",
|
|
32458
|
+
"automation"
|
|
32459
|
+
],
|
|
32460
|
+
author: "Hasna",
|
|
32461
|
+
license: "Apache-2.0",
|
|
32462
|
+
devDependencies: {
|
|
32463
|
+
"@hasna/contracts": "1.1.0",
|
|
32464
|
+
"@types/bun": "1.3.14",
|
|
32465
|
+
"@types/node": "25.2.3",
|
|
32466
|
+
"@types/react": "^18.2.0",
|
|
32467
|
+
"bun-types": "1.3.14",
|
|
32468
|
+
"react-devtools-core": "^7.0.1",
|
|
32469
|
+
typescript: "^5",
|
|
32470
|
+
yaml: "^2.9.0"
|
|
32471
|
+
},
|
|
32472
|
+
dependencies: {
|
|
32473
|
+
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
32474
|
+
"@aws-sdk/client-s3": "^3.1079.0",
|
|
32475
|
+
"@hasna/events": "0.1.18",
|
|
32476
|
+
"@hasna/secrets": "0.4.2",
|
|
32477
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
32478
|
+
chalk: "^5.3.0",
|
|
32479
|
+
commander: "^12.1.0",
|
|
32480
|
+
ink: "^5.0.1",
|
|
32481
|
+
"ink-select-input": "^6.0.0",
|
|
32482
|
+
"ink-spinner": "^5.0.0",
|
|
32483
|
+
"ink-text-input": "^6.0.0",
|
|
32484
|
+
react: "^18.2.0",
|
|
32485
|
+
zod: "^4.3.6"
|
|
32486
|
+
},
|
|
32487
|
+
engines: {
|
|
32488
|
+
bun: ">=1.0.0"
|
|
32489
|
+
},
|
|
32490
|
+
publishConfig: {
|
|
32491
|
+
registry: "https://registry.npmjs.org",
|
|
32492
|
+
access: "public"
|
|
32493
|
+
},
|
|
32494
|
+
repository: {
|
|
32495
|
+
type: "git",
|
|
32496
|
+
url: "https://github.com/hasna/apps.git",
|
|
32497
|
+
directory: "apps/skills"
|
|
32498
|
+
},
|
|
32499
|
+
homepage: "https://github.com/hasna/apps/tree/main/apps/skills",
|
|
32500
|
+
bugs: {
|
|
32501
|
+
url: "https://github.com/hasna/apps/issues"
|
|
32502
|
+
}
|
|
32503
|
+
};
|
|
32373
32504
|
|
|
32374
|
-
// src/lib/
|
|
32375
|
-
import {
|
|
32505
|
+
// src/lib/remote-files.ts
|
|
32506
|
+
import { createHash } from "crypto";
|
|
32507
|
+
var MAX_REMOTE_FILE_BYTES = 64 * 1024 * 1024;
|
|
32508
|
+
function describeRemoteFiles(files) {
|
|
32509
|
+
if (files.length > 10)
|
|
32510
|
+
throw new Error("At most 10 input files are supported");
|
|
32511
|
+
const names = new Set;
|
|
32512
|
+
let total = 0;
|
|
32513
|
+
return files.map((file) => {
|
|
32514
|
+
if (typeof file.name !== "string" || file.name.length === 0 || file.name.length > 255 || /[/\\\x00-\x1f\x7f]/.test(file.name) || /[\ud800-\udfff]/u.test(file.name) || file.name === "." || file.name === ".." || names.has(file.name))
|
|
32515
|
+
throw new Error("Input file names must be unique safe basenames");
|
|
32516
|
+
names.add(file.name);
|
|
32517
|
+
total += file.bytes.byteLength;
|
|
32518
|
+
if (file.bytes.byteLength > 20 * 1024 * 1024 || total > 50 * 1024 * 1024)
|
|
32519
|
+
throw new Error("Input files exceed the supported size limit");
|
|
32520
|
+
return { name: file.name, sizeBytes: file.bytes.byteLength, sha256: sha256(file.bytes), contentType: file.contentType ?? "application/octet-stream" };
|
|
32521
|
+
});
|
|
32522
|
+
}
|
|
32523
|
+
function sha256(bytes) {
|
|
32524
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
32525
|
+
}
|
|
32526
|
+
async function readBoundedResponse(response, maximum) {
|
|
32527
|
+
if (!Number.isSafeInteger(maximum) || maximum < 0 || maximum > MAX_REMOTE_FILE_BYTES)
|
|
32528
|
+
throw new Error("Invalid artifact size limit");
|
|
32529
|
+
const length = response.headers.get("content-length");
|
|
32530
|
+
if (length && (!/^\d+$/.test(length) || Number(length) > maximum)) {
|
|
32531
|
+
await response.body?.cancel();
|
|
32532
|
+
throw new Error("Artifact exceeds its declared size limit");
|
|
32533
|
+
}
|
|
32534
|
+
const reader = response.body?.getReader();
|
|
32535
|
+
if (!reader)
|
|
32536
|
+
return new Uint8Array;
|
|
32537
|
+
const chunks = [];
|
|
32538
|
+
let size = 0;
|
|
32539
|
+
try {
|
|
32540
|
+
while (true) {
|
|
32541
|
+
const next = await reader.read();
|
|
32542
|
+
if (next.done)
|
|
32543
|
+
break;
|
|
32544
|
+
size += next.value.byteLength;
|
|
32545
|
+
if (size > maximum)
|
|
32546
|
+
throw new Error("Artifact exceeds its declared size limit");
|
|
32547
|
+
chunks.push(next.value);
|
|
32548
|
+
}
|
|
32549
|
+
} catch (error) {
|
|
32550
|
+
await reader.cancel().catch(() => {});
|
|
32551
|
+
throw error;
|
|
32552
|
+
} finally {
|
|
32553
|
+
reader.releaseLock();
|
|
32554
|
+
}
|
|
32555
|
+
const bytes = new Uint8Array(size);
|
|
32556
|
+
let offset = 0;
|
|
32557
|
+
for (const chunk of chunks) {
|
|
32558
|
+
bytes.set(chunk, offset);
|
|
32559
|
+
offset += chunk.byteLength;
|
|
32560
|
+
}
|
|
32561
|
+
return bytes;
|
|
32562
|
+
}
|
|
32376
32563
|
|
|
32377
32564
|
// node_modules/@hasna/contracts/dist/client/transport.js
|
|
32378
32565
|
import { isIP } from "net";
|
|
@@ -33591,64 +33778,152 @@ class MissingSkillsFleetError extends Error {
|
|
|
33591
33778
|
}
|
|
33592
33779
|
}
|
|
33593
33780
|
|
|
33594
|
-
// src/lib/
|
|
33595
|
-
|
|
33596
|
-
|
|
33597
|
-
|
|
33598
|
-
|
|
33599
|
-
|
|
33600
|
-
|
|
33601
|
-
let total = 0;
|
|
33602
|
-
return files.map((file) => {
|
|
33603
|
-
if (typeof file.name !== "string" || file.name.length === 0 || file.name.length > 255 || /[/\\\x00-\x1f\x7f]/.test(file.name) || /[\ud800-\udfff]/u.test(file.name) || file.name === "." || file.name === ".." || names.has(file.name))
|
|
33604
|
-
throw new Error("Input file names must be unique safe basenames");
|
|
33605
|
-
names.add(file.name);
|
|
33606
|
-
total += file.bytes.byteLength;
|
|
33607
|
-
if (file.bytes.byteLength > 20 * 1024 * 1024 || total > 50 * 1024 * 1024)
|
|
33608
|
-
throw new Error("Input files exceed the supported size limit");
|
|
33609
|
-
return { name: file.name, sizeBytes: file.bytes.byteLength, sha256: sha256(file.bytes), contentType: file.contentType ?? "application/octet-stream" };
|
|
33610
|
-
});
|
|
33611
|
-
}
|
|
33612
|
-
function sha256(bytes) {
|
|
33613
|
-
return createHash("sha256").update(bytes).digest("hex");
|
|
33614
|
-
}
|
|
33615
|
-
async function readBoundedResponse(response, maximum) {
|
|
33616
|
-
if (!Number.isSafeInteger(maximum) || maximum < 0 || maximum > MAX_REMOTE_FILE_BYTES)
|
|
33617
|
-
throw new Error("Invalid artifact size limit");
|
|
33618
|
-
const length = response.headers.get("content-length");
|
|
33619
|
-
if (length && (!/^\d+$/.test(length) || Number(length) > maximum)) {
|
|
33620
|
-
await response.body?.cancel();
|
|
33621
|
-
throw new Error("Artifact exceeds its declared size limit");
|
|
33781
|
+
// src/lib/cloud-executions.ts
|
|
33782
|
+
class CloudExecutionClient {
|
|
33783
|
+
apiOrigin;
|
|
33784
|
+
apiKey;
|
|
33785
|
+
constructor(apiOrigin, apiKey) {
|
|
33786
|
+
this.apiOrigin = apiOrigin;
|
|
33787
|
+
this.apiKey = apiKey;
|
|
33622
33788
|
}
|
|
33623
|
-
|
|
33624
|
-
|
|
33625
|
-
|
|
33626
|
-
|
|
33627
|
-
|
|
33628
|
-
|
|
33629
|
-
|
|
33630
|
-
|
|
33631
|
-
|
|
33789
|
+
static async configured() {
|
|
33790
|
+
const c = await resolveSkillsConnection();
|
|
33791
|
+
if (!c)
|
|
33792
|
+
throw Error("Cloud execution requires a configured Skills API credential");
|
|
33793
|
+
return new CloudExecutionClient(c.apiOrigin, c.apiKey);
|
|
33794
|
+
}
|
|
33795
|
+
async request(route, method = "GET", value) {
|
|
33796
|
+
const response = await fetch(skillsApiRequestUrl(this.apiOrigin, "/api/v1/executions/" + route), {
|
|
33797
|
+
method,
|
|
33798
|
+
headers: {
|
|
33799
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
33800
|
+
"User-Agent": `hasna-skills/${package_default.version}`,
|
|
33801
|
+
...value === undefined ? {} : { "content-type": "application/json" }
|
|
33802
|
+
},
|
|
33803
|
+
...value === undefined ? {} : { body: JSON.stringify(value) },
|
|
33804
|
+
redirect: "error",
|
|
33805
|
+
signal: AbortSignal.timeout(15000)
|
|
33806
|
+
});
|
|
33807
|
+
if (!response.ok) {
|
|
33808
|
+
response.body?.cancel();
|
|
33809
|
+
throw Error(`Skills cloud execution refused: HTTP ${response.status}`);
|
|
33810
|
+
}
|
|
33811
|
+
return response;
|
|
33812
|
+
}
|
|
33813
|
+
async eligibility(slug, version, bundleDigest) {
|
|
33814
|
+
if (!/^[a-z0-9-]+$/.test(slug) || !/^\d+\.\d+\.\d+$/.test(version) || bundleDigest !== undefined && !/^(sha256:)?[a-f0-9]{64}$/.test(bundleDigest))
|
|
33815
|
+
throw Error("Cloud eligibility requires an exact skill version and valid digest");
|
|
33816
|
+
const query = new URLSearchParams({ version, ...bundleDigest ? { bundleDigest } : {} });
|
|
33817
|
+
const v = await jsonBody(await this.request(`${slug}/eligibility?${query}`));
|
|
33818
|
+
if (!v || v.contractVersion !== 1 || typeof v.eligible !== "boolean" || v.skill !== slug || v.version !== version || !/^[a-f0-9]{64}$/.test(v.bundleDigest) || bundleDigest && bundleDigest.replace(/^sha256:/, "") !== v.bundleDigest || v.eligible && (!v.executionContract || !["pdf.v1", "regex-test.v1"].includes(v.executionContract.id)))
|
|
33819
|
+
throw Error("Invalid cloud eligibility receipt");
|
|
33820
|
+
return v;
|
|
33821
|
+
}
|
|
33822
|
+
async submit(slug, version, input, key, selection) {
|
|
33823
|
+
if (!/^[a-z0-9-]+$/.test(slug) || !/^\d+\.\d+\.\d+$/.test(version))
|
|
33824
|
+
throw Error("Cloud execution requires skill@exact-version");
|
|
33825
|
+
if (!/^[A-Za-z0-9._:-]{1,128}$/.test(key))
|
|
33826
|
+
throw Error("Invalid idempotency key");
|
|
33827
|
+
if (selection?.authority && selection.authority !== skillsApiRequestUrl(this.apiOrigin, "/api/v1/").replace(/\/$/, ""))
|
|
33828
|
+
throw Error("Selected skill belongs to another Skills authority");
|
|
33829
|
+
const execution = parseExecution(await jsonBody(await this.request(slug, "POST", {
|
|
33830
|
+
version,
|
|
33831
|
+
input,
|
|
33832
|
+
idempotencyKey: key,
|
|
33833
|
+
...selection ? {
|
|
33834
|
+
bundleDigest: selection.bundleDigest,
|
|
33835
|
+
workspaceId: selection.workspaceId
|
|
33836
|
+
} : {}
|
|
33837
|
+
})));
|
|
33838
|
+
if (execution.skill !== slug || execution.version !== version || selection && execution.bundleDigest.replace(/^sha256:/, "") !== selection.bundleDigest.replace(/^sha256:/, ""))
|
|
33839
|
+
throw Error("Cloud execution receipt does not match the selected exact skill");
|
|
33840
|
+
return execution;
|
|
33841
|
+
}
|
|
33842
|
+
async get(id) {
|
|
33843
|
+
return parseExecution(await jsonBody(await this.request(runId(id))));
|
|
33844
|
+
}
|
|
33845
|
+
async logs(id) {
|
|
33846
|
+
return jsonBody(await this.request(runId(id) + "/logs"));
|
|
33847
|
+
}
|
|
33848
|
+
async artifacts(id) {
|
|
33849
|
+
return (await this.get(id)).artifacts;
|
|
33850
|
+
}
|
|
33851
|
+
async download(id, name) {
|
|
33852
|
+
if (!["document.pdf", "document.html"].includes(name))
|
|
33853
|
+
throw Error("Invalid cloud artifact name");
|
|
33854
|
+
const response = await this.request(runId(id) + "/artifacts/" + encodeURIComponent(name));
|
|
33855
|
+
if (!response.body)
|
|
33856
|
+
throw Error("Cloud artifact body is missing");
|
|
33857
|
+
const reader = response.body.getReader();
|
|
33858
|
+
const chunks = [];
|
|
33859
|
+
let size = 0;
|
|
33860
|
+
for (;; ) {
|
|
33861
|
+
const r2 = await reader.read();
|
|
33862
|
+
if (r2.done)
|
|
33632
33863
|
break;
|
|
33633
|
-
size +=
|
|
33634
|
-
if (size >
|
|
33635
|
-
|
|
33636
|
-
|
|
33864
|
+
size += r2.value.length;
|
|
33865
|
+
if (size > 2000000) {
|
|
33866
|
+
await reader.cancel();
|
|
33867
|
+
throw Error("Cloud artifact exceeds limit");
|
|
33868
|
+
}
|
|
33869
|
+
chunks.push(r2.value);
|
|
33637
33870
|
}
|
|
33638
|
-
|
|
33639
|
-
await reader.cancel().catch(() => {});
|
|
33640
|
-
throw error;
|
|
33641
|
-
} finally {
|
|
33642
|
-
reader.releaseLock();
|
|
33871
|
+
return Buffer.concat(chunks);
|
|
33643
33872
|
}
|
|
33644
|
-
|
|
33645
|
-
|
|
33646
|
-
|
|
33647
|
-
|
|
33648
|
-
|
|
33873
|
+
async cancel(id) {
|
|
33874
|
+
return parseExecution(await jsonBody(await this.request(runId(id) + "/cancel", "POST", {})));
|
|
33875
|
+
}
|
|
33876
|
+
async wait(id, options = {}) {
|
|
33877
|
+
const timeout = options.timeoutMs ?? 300000, interval = options.intervalMs ?? 1000;
|
|
33878
|
+
if (!Number.isFinite(timeout) || timeout < 1 || timeout > 1200000 || !Number.isFinite(interval) || interval < 100 || interval > 60000)
|
|
33879
|
+
throw Error("Invalid cloud polling bounds");
|
|
33880
|
+
const until = Date.now() + timeout;
|
|
33881
|
+
for (;; ) {
|
|
33882
|
+
const run = await this.get(id);
|
|
33883
|
+
if (["succeeded", "failed", "cancelled"].includes(run.status))
|
|
33884
|
+
return run;
|
|
33885
|
+
if (Date.now() >= until)
|
|
33886
|
+
throw Error("Cloud execution is still running; poll the returned execution id");
|
|
33887
|
+
await Bun.sleep(interval);
|
|
33888
|
+
}
|
|
33889
|
+
}
|
|
33890
|
+
}
|
|
33891
|
+
function runId(id) {
|
|
33892
|
+
if (!/^run_[a-z0-9_]+$/.test(id))
|
|
33893
|
+
throw Error("Invalid cloud execution id");
|
|
33894
|
+
return id;
|
|
33895
|
+
}
|
|
33896
|
+
function parseExecution(value) {
|
|
33897
|
+
const v = value;
|
|
33898
|
+
if (!v || v.contractVersion !== 1 || v.target !== "cloud" || ![
|
|
33899
|
+
"admitted",
|
|
33900
|
+
"leased",
|
|
33901
|
+
"running",
|
|
33902
|
+
"succeeded",
|
|
33903
|
+
"failed",
|
|
33904
|
+
"cancelled"
|
|
33905
|
+
].includes(v.status) || typeof v.skill !== "string" || typeof v.version !== "string" || !Array.isArray(v.artifacts))
|
|
33906
|
+
throw Error("Unsupported cloud execution response");
|
|
33907
|
+
runId(v.id);
|
|
33908
|
+
for (const field of [
|
|
33909
|
+
"bundleDigest",
|
|
33910
|
+
"inputDigest",
|
|
33911
|
+
"runtimeImageDigest"
|
|
33912
|
+
])
|
|
33913
|
+
if (typeof v[field] !== "string" || !/^(sha256:)?[a-f0-9]{64}$/.test(v[field]))
|
|
33914
|
+
throw Error("Invalid cloud execution integrity receipt");
|
|
33915
|
+
return v;
|
|
33916
|
+
}
|
|
33917
|
+
async function jsonBody(response) {
|
|
33918
|
+
const bytes = await readBoundedResponse(response, 1e5);
|
|
33919
|
+
try {
|
|
33920
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
33921
|
+
} catch {
|
|
33922
|
+
throw Error("Invalid cloud execution JSON response");
|
|
33649
33923
|
}
|
|
33650
|
-
return bytes;
|
|
33651
33924
|
}
|
|
33925
|
+
// src/lib/execution-grant-client.ts
|
|
33926
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
33652
33927
|
|
|
33653
33928
|
// src/lib/selection-cache.ts
|
|
33654
33929
|
import { createHash as createHash3, randomUUID } from "crypto";
|
|
@@ -36701,138 +36976,6 @@ async function resolveExecutionGrant(input) {
|
|
|
36701
36976
|
}
|
|
36702
36977
|
return value;
|
|
36703
36978
|
}
|
|
36704
|
-
// package.json
|
|
36705
|
-
var package_default = {
|
|
36706
|
-
name: "@hasna/skills",
|
|
36707
|
-
version: "0.8.12",
|
|
36708
|
-
description: "Skills library for AI coding agents",
|
|
36709
|
-
type: "module",
|
|
36710
|
-
bin: {
|
|
36711
|
-
skills: "bin/index.js",
|
|
36712
|
-
"skills-mcp": "bin/mcp.js",
|
|
36713
|
-
"skills-serve": "bin/server.js",
|
|
36714
|
-
"skills-server": "bin/server.js",
|
|
36715
|
-
"skills-worker": "bin/worker.js",
|
|
36716
|
-
"skills-migrate": "bin/migrate.js"
|
|
36717
|
-
},
|
|
36718
|
-
exports: {
|
|
36719
|
-
".": {
|
|
36720
|
-
import: "./dist/index.js",
|
|
36721
|
-
types: "./dist/index.d.ts"
|
|
36722
|
-
},
|
|
36723
|
-
"./storage": {
|
|
36724
|
-
import: "./dist/storage.js",
|
|
36725
|
-
types: "./dist/storage.d.ts"
|
|
36726
|
-
},
|
|
36727
|
-
"./sdk": {
|
|
36728
|
-
import: "./dist/sdk/index.js",
|
|
36729
|
-
types: "./dist/sdk/index.d.ts"
|
|
36730
|
-
},
|
|
36731
|
-
"./admin-contract": {
|
|
36732
|
-
import: "./dist/admin-contract.js",
|
|
36733
|
-
types: "./dist/admin-contract.d.ts"
|
|
36734
|
-
}
|
|
36735
|
-
},
|
|
36736
|
-
files: [
|
|
36737
|
-
"dist/",
|
|
36738
|
-
"!dist/**/*.test.d.ts",
|
|
36739
|
-
"!dist/**/*.fixture.d.ts",
|
|
36740
|
-
"!dist/test-preload.d.ts",
|
|
36741
|
-
"!dist/platform",
|
|
36742
|
-
"bin/",
|
|
36743
|
-
"migrations/",
|
|
36744
|
-
"docs/skill-standard.md",
|
|
36745
|
-
"docs/plugin-admission.md",
|
|
36746
|
-
"schemas/",
|
|
36747
|
-
"LICENSE",
|
|
36748
|
-
"README.md",
|
|
36749
|
-
"!dist/**/*test-utils.d.ts",
|
|
36750
|
-
"!dist/**/*fixtures.d.ts"
|
|
36751
|
-
],
|
|
36752
|
-
main: "./dist/index.js",
|
|
36753
|
-
types: "./dist/index.d.ts",
|
|
36754
|
-
scripts: {
|
|
36755
|
-
clean: "rm -rf bin/ dist/",
|
|
36756
|
-
build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
36757
|
-
"build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
36758
|
-
test: "bun test --timeout 30000",
|
|
36759
|
-
dev: "bun run ./src/cli/index.tsx",
|
|
36760
|
-
"dev:watch": "bun --watch run ./src/cli/index.tsx",
|
|
36761
|
-
"dev:mcp": "bun --watch run ./src/mcp/index.ts",
|
|
36762
|
-
"dev:server": "bun --watch run ./src/server/index.ts",
|
|
36763
|
-
"dev:worker": "bun --watch run ./src/server/worker.ts",
|
|
36764
|
-
"docker:check": "bash scripts/docker-build-check.sh",
|
|
36765
|
-
migrate: "bun run ./src/server/migrate.ts",
|
|
36766
|
-
typecheck: "tsc --noEmit",
|
|
36767
|
-
"verify:release": "bun run scripts/release-guard.ts",
|
|
36768
|
-
"verify:consumer-types": "bun run scripts/consumer-types.ts",
|
|
36769
|
-
prepack: "bun run verify:generated && bun run verify:release && bun run verify:consumer-types",
|
|
36770
|
-
prepublishOnly: "bun run verify:producer && bun run typecheck && bun run test",
|
|
36771
|
-
"verify:producer": "bun run scripts/release-dependencies.ts",
|
|
36772
|
-
"verify:generated": "HASNA_SKILLS_RELEASE_ARTIFACT_TEST=1 bun test src/lib/release-artifacts.test.ts",
|
|
36773
|
-
"verify:consumer-credentials": "bun run scripts/consumer-credentials.ts"
|
|
36774
|
-
},
|
|
36775
|
-
keywords: [
|
|
36776
|
-
"skills",
|
|
36777
|
-
"ai",
|
|
36778
|
-
"agent",
|
|
36779
|
-
"cli",
|
|
36780
|
-
"typescript",
|
|
36781
|
-
"bun",
|
|
36782
|
-
"claude",
|
|
36783
|
-
"codex",
|
|
36784
|
-
"gemini",
|
|
36785
|
-
"mcp",
|
|
36786
|
-
"model-context-protocol",
|
|
36787
|
-
"open-source",
|
|
36788
|
-
"skills-library",
|
|
36789
|
-
"automation"
|
|
36790
|
-
],
|
|
36791
|
-
author: "Hasna",
|
|
36792
|
-
license: "Apache-2.0",
|
|
36793
|
-
devDependencies: {
|
|
36794
|
-
"@hasna/contracts": "1.1.0",
|
|
36795
|
-
"@types/bun": "1.3.14",
|
|
36796
|
-
"@types/node": "25.2.3",
|
|
36797
|
-
"@types/react": "^18.2.0",
|
|
36798
|
-
"bun-types": "1.3.14",
|
|
36799
|
-
"react-devtools-core": "^7.0.1",
|
|
36800
|
-
typescript: "^5",
|
|
36801
|
-
yaml: "^2.9.0"
|
|
36802
|
-
},
|
|
36803
|
-
dependencies: {
|
|
36804
|
-
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
36805
|
-
"@aws-sdk/client-s3": "^3.1079.0",
|
|
36806
|
-
"@hasna/events": "0.1.18",
|
|
36807
|
-
"@hasna/secrets": "0.4.2",
|
|
36808
|
-
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
36809
|
-
chalk: "^5.3.0",
|
|
36810
|
-
commander: "^12.1.0",
|
|
36811
|
-
ink: "^5.0.1",
|
|
36812
|
-
"ink-select-input": "^6.0.0",
|
|
36813
|
-
"ink-spinner": "^5.0.0",
|
|
36814
|
-
"ink-text-input": "^6.0.0",
|
|
36815
|
-
react: "^18.2.0",
|
|
36816
|
-
zod: "^4.3.6"
|
|
36817
|
-
},
|
|
36818
|
-
engines: {
|
|
36819
|
-
bun: ">=1.0.0"
|
|
36820
|
-
},
|
|
36821
|
-
publishConfig: {
|
|
36822
|
-
registry: "https://registry.npmjs.org",
|
|
36823
|
-
access: "public"
|
|
36824
|
-
},
|
|
36825
|
-
repository: {
|
|
36826
|
-
type: "git",
|
|
36827
|
-
url: "https://github.com/hasna/apps.git",
|
|
36828
|
-
directory: "apps/skills"
|
|
36829
|
-
},
|
|
36830
|
-
homepage: "https://github.com/hasna/apps/tree/main/apps/skills",
|
|
36831
|
-
bugs: {
|
|
36832
|
-
url: "https://github.com/hasna/apps/issues"
|
|
36833
|
-
}
|
|
36834
|
-
};
|
|
36835
|
-
|
|
36836
36979
|
// src/lib/profile-client.ts
|
|
36837
36980
|
function object(value) {
|
|
36838
36981
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -41726,6 +41869,7 @@ function readManifest(skillPath, fallbackName, normalizeName) {
|
|
|
41726
41869
|
const version = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
|
|
41727
41870
|
const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
|
|
41728
41871
|
const commands = parseManifestCommands(jsonManifest) ?? (kind === "instruction" ? [] : inferPackageCommands(pkg, name)) ?? [];
|
|
41872
|
+
const runtime = parseManifestRuntime(jsonManifest) ?? inferRuntimeFromCommands(commands);
|
|
41729
41873
|
return {
|
|
41730
41874
|
$schema: stringField(jsonManifest, "$schema") ?? PORTABLE_SKILL_SCHEMA,
|
|
41731
41875
|
standard: stringField(jsonManifest, "standard") ?? PORTABLE_SKILL_STANDARD,
|
|
@@ -41738,7 +41882,7 @@ function readManifest(skillPath, fallbackName, normalizeName) {
|
|
|
41738
41882
|
...kind ? { kind } : {},
|
|
41739
41883
|
inputs: kind === "instruction" ? parseManifestInputs(jsonManifest) ?? [] : parseManifestInputs(jsonManifest) ?? DEFAULT_INPUTS,
|
|
41740
41884
|
commands,
|
|
41741
|
-
...
|
|
41885
|
+
...runtime ? { runtime } : {},
|
|
41742
41886
|
...parseManifestProvenance(jsonManifest) ? { provenance: parseManifestProvenance(jsonManifest) } : {}
|
|
41743
41887
|
};
|
|
41744
41888
|
}
|
|
@@ -42257,6 +42401,12 @@ function inferPackageCommands(pkg, fallbackName) {
|
|
|
42257
42401
|
}
|
|
42258
42402
|
return;
|
|
42259
42403
|
}
|
|
42404
|
+
function inferRuntimeFromCommands(commands) {
|
|
42405
|
+
const entrypoint = commands.find((command) => command.entry)?.entry;
|
|
42406
|
+
if (!entrypoint || !entrypoint.toLowerCase().endsWith(".py"))
|
|
42407
|
+
return;
|
|
42408
|
+
return { ...defaultRuntimeContract(entrypoint), runtime: "python3" };
|
|
42409
|
+
}
|
|
42260
42410
|
function readJsonObject(path) {
|
|
42261
42411
|
const parsed = JSON.parse(readFileSync15(path, "utf-8"));
|
|
42262
42412
|
if (!isRecord2(parsed))
|
|
@@ -54107,12 +54257,12 @@ class ArtifactStorage {
|
|
|
54107
54257
|
get usesS3() {
|
|
54108
54258
|
return Boolean(this.bucket);
|
|
54109
54259
|
}
|
|
54110
|
-
objectKeyFor(tenantId,
|
|
54260
|
+
objectKeyFor(tenantId, runId2, relativePath) {
|
|
54111
54261
|
const safeRelativePath = relativePath.replace(/^\/+/, "").replace(/\.\.(?:\/|$)/g, "");
|
|
54112
|
-
return `${this.runPrefix}/${tenantId}/${
|
|
54262
|
+
return `${this.runPrefix}/${tenantId}/${runId2}/${safeRelativePath}`;
|
|
54113
54263
|
}
|
|
54114
|
-
quarantineKeyFor(tenantId,
|
|
54115
|
-
return `${this.runPrefix}/quarantine/${tenantId}/${
|
|
54264
|
+
quarantineKeyFor(tenantId, runId2, artifactId) {
|
|
54265
|
+
return `${this.runPrefix}/quarantine/${tenantId}/${runId2}/${artifactId}`;
|
|
54116
54266
|
}
|
|
54117
54267
|
async materialize(run, artifact, body) {
|
|
54118
54268
|
if (!this.bucket) {
|
|
@@ -54363,13 +54513,13 @@ function createCancelService(options) {
|
|
|
54363
54513
|
const governanceStore = options.governanceStore;
|
|
54364
54514
|
const storage = options.storage ?? new ArtifactStorage;
|
|
54365
54515
|
return {
|
|
54366
|
-
async cancel(principal,
|
|
54516
|
+
async cancel(principal, runId2, requestedBy) {
|
|
54367
54517
|
if (!store.transitionRun) {
|
|
54368
|
-
throw new GovernanceError(GOVERNANCE_ERROR_CODES.FENCING_UNSUPPORTED, `cannot cancel run ${
|
|
54518
|
+
throw new GovernanceError(GOVERNANCE_ERROR_CODES.FENCING_UNSUPPORTED, `cannot cancel run ${runId2}: the store has no transitionRun generation fencing`, { gate: "transitionRun" });
|
|
54369
54519
|
}
|
|
54370
|
-
const run = await store.getRun(principal,
|
|
54520
|
+
const run = await store.getRun(principal, runId2);
|
|
54371
54521
|
if (!run)
|
|
54372
|
-
throw new GovernanceError(GOVERNANCE_ERROR_CODES.STALE_LEASE_GENERATION, `run ${
|
|
54522
|
+
throw new GovernanceError(GOVERNANCE_ERROR_CODES.STALE_LEASE_GENERATION, `run ${runId2} not found`, { gate: "run" });
|
|
54373
54523
|
const terminalStatuses = new Set(["succeeded", "failed", "cancelled", "expired", "refunded"]);
|
|
54374
54524
|
if (terminalStatuses.has(run.status)) {
|
|
54375
54525
|
const receipt2 = await governanceStore.appendReceipt({
|
|
@@ -54382,11 +54532,11 @@ function createCancelService(options) {
|
|
|
54382
54532
|
return { run, fencedGeneration: run.leaseGeneration, quarantined: [], receipt: receipt2, alreadyTerminal: true };
|
|
54383
54533
|
}
|
|
54384
54534
|
const fencedGeneration = run.leaseGeneration + 1;
|
|
54385
|
-
const cancelling = await store.transitionRun(
|
|
54535
|
+
const cancelling = await store.transitionRun(runId2, { status: "cancel_requested", leaseGeneration: fencedGeneration }, run.leaseGeneration);
|
|
54386
54536
|
if (!cancelling) {
|
|
54387
|
-
throw new GovernanceError(GOVERNANCE_ERROR_CODES.STALE_LEASE_GENERATION, `run ${
|
|
54537
|
+
throw new GovernanceError(GOVERNANCE_ERROR_CODES.STALE_LEASE_GENERATION, `run ${runId2} changed while cancelling`, { gate: "transition" });
|
|
54388
54538
|
}
|
|
54389
|
-
const artifacts = await store.listArtifacts(principal,
|
|
54539
|
+
const artifacts = await store.listArtifacts(principal, runId2);
|
|
54390
54540
|
const quarantined = [];
|
|
54391
54541
|
for (const artifact of artifacts) {
|
|
54392
54542
|
const quarantineKey = await storage.moveToQuarantine?.(artifact);
|
|
@@ -54403,9 +54553,9 @@ function createCancelService(options) {
|
|
|
54403
54553
|
});
|
|
54404
54554
|
quarantined.push({ ...artifact, ...quarantineKey ? { storageKey: quarantineKey } : {} });
|
|
54405
54555
|
}
|
|
54406
|
-
const cancelled = await store.transitionRun(
|
|
54556
|
+
const cancelled = await store.transitionRun(runId2, { status: "cancelled", completedAt: new Date().toISOString() }, fencedGeneration);
|
|
54407
54557
|
if (!cancelled) {
|
|
54408
|
-
throw new GovernanceError(GOVERNANCE_ERROR_CODES.STALE_LEASE_GENERATION, `run ${
|
|
54558
|
+
throw new GovernanceError(GOVERNANCE_ERROR_CODES.STALE_LEASE_GENERATION, `run ${runId2} moved on while finalising cancellation`, { gate: "transition" });
|
|
54409
54559
|
}
|
|
54410
54560
|
const receipt = await governanceStore.appendReceipt({
|
|
54411
54561
|
kind: "cancel",
|
|
@@ -54623,6 +54773,9 @@ class MemorySkillSelectionStore {
|
|
|
54623
54773
|
async getProfile(p2, id2) {
|
|
54624
54774
|
return structuredClone(this.profiles.get(this.key(p2.orgId, id2)) ?? null);
|
|
54625
54775
|
}
|
|
54776
|
+
async profilesReferencingSkill(p2, slug) {
|
|
54777
|
+
return [...this.profiles.values()].filter((profile2) => profile2.workspaceId === p2.orgId && profile2.selections.some((selection2) => selection2.slug === slug)).map((profile2) => profile2.id).sort();
|
|
54778
|
+
}
|
|
54626
54779
|
async saveProfile(p2, id2, selected, expected) {
|
|
54627
54780
|
const key = this.key(p2.orgId, id2), previous = this.profiles.get(key);
|
|
54628
54781
|
if ((previous?.revision ?? null) !== expected)
|
|
@@ -54660,14 +54813,25 @@ class SqliteSkillSelectionStore {
|
|
|
54660
54813
|
constructor(db) {
|
|
54661
54814
|
this.db = db;
|
|
54662
54815
|
}
|
|
54816
|
+
async profilesReferencingSkill(p2, slug) {
|
|
54817
|
+
const rows = this.db.query("SELECT profile_id,selections_json FROM skills_profiles WHERE org_id=?").all(p2.orgId);
|
|
54818
|
+
return rows.filter((row) => selections(row.selections_json).some((selection2) => selection2.slug === slug)).map((row) => String(row.profile_id)).sort();
|
|
54819
|
+
}
|
|
54663
54820
|
async getProfile(p2, id2) {
|
|
54664
54821
|
const row = this.db.query("SELECT * FROM skills_profiles WHERE org_id=? AND profile_id=?").get(p2.orgId, id2);
|
|
54665
54822
|
return row ? profile(row) : null;
|
|
54666
54823
|
}
|
|
54667
54824
|
async saveProfile(p2, id2, selected, expected) {
|
|
54668
|
-
|
|
54669
|
-
|
|
54670
|
-
|
|
54825
|
+
return this.db.transaction(() => {
|
|
54826
|
+
for (const selection2 of [...selected].sort((a3, b3) => a3.slug.localeCompare(b3.slug))) {
|
|
54827
|
+
const skill = this.db.query("SELECT lifecycle FROM skills_registry WHERE org_id=? AND slug=?").get(p2.orgId, selection2.slug);
|
|
54828
|
+
if (skill?.lifecycle === "archived")
|
|
54829
|
+
return null;
|
|
54830
|
+
}
|
|
54831
|
+
const revision = randomUUID5(), timestamp = new Date().toISOString();
|
|
54832
|
+
const row = expected === null ? this.db.query("INSERT INTO skills_profiles(org_id,profile_id,revision,selections_json,updated_at) VALUES(?,?,?,?,?) ON CONFLICT(org_id,profile_id) DO NOTHING RETURNING *").get(p2.orgId, id2, revision, JSON.stringify(selected), timestamp) : this.db.query("UPDATE skills_profiles SET revision=?,selections_json=?,updated_at=? WHERE org_id=? AND profile_id=? AND revision=? RETURNING *").get(revision, JSON.stringify(selected), timestamp, p2.orgId, id2, expected);
|
|
54833
|
+
return row ? profile(row) : null;
|
|
54834
|
+
}).immediate();
|
|
54671
54835
|
}
|
|
54672
54836
|
async getStationState(p2, id2) {
|
|
54673
54837
|
const row = this.db.query("SELECT * FROM skills_station_state WHERE org_id=? AND actor_id=? AND station_id=?").get(p2.orgId, p2.userId, id2);
|
|
@@ -54686,14 +54850,25 @@ class PostgresSkillSelectionStore {
|
|
|
54686
54850
|
constructor(sql) {
|
|
54687
54851
|
this.sql = sql;
|
|
54688
54852
|
}
|
|
54853
|
+
async profilesReferencingSkill(p2, slug) {
|
|
54854
|
+
const rows = await this.sql`SELECT profile_id,selections_json FROM skills_profiles WHERE org_id=${p2.orgId}`;
|
|
54855
|
+
return rows.filter((row) => selections(row.selections_json).some((selection2) => selection2.slug === slug)).map((row) => String(row.profile_id)).sort();
|
|
54856
|
+
}
|
|
54689
54857
|
async getProfile(p2, id2) {
|
|
54690
54858
|
const rows = await this.sql`SELECT * FROM skills_profiles WHERE org_id=${p2.orgId} AND profile_id=${id2}`;
|
|
54691
54859
|
return rows[0] ? profile(rows[0]) : null;
|
|
54692
54860
|
}
|
|
54693
54861
|
async saveProfile(p2, id2, selected, expected) {
|
|
54694
|
-
|
|
54695
|
-
|
|
54696
|
-
|
|
54862
|
+
return this.sql.begin(async (tx) => {
|
|
54863
|
+
for (const selection2 of [...selected].sort((a3, b3) => a3.slug.localeCompare(b3.slug))) {
|
|
54864
|
+
const rows2 = await tx`SELECT lifecycle FROM skills_registry WHERE org_id=${p2.orgId} AND slug=${selection2.slug} FOR UPDATE`;
|
|
54865
|
+
if (rows2[0]?.lifecycle === "archived")
|
|
54866
|
+
return null;
|
|
54867
|
+
}
|
|
54868
|
+
const revision = randomUUID5(), timestamp = new Date().toISOString();
|
|
54869
|
+
const rows = expected === null ? await tx`INSERT INTO skills_profiles(org_id,profile_id,revision,selections_json,updated_at) VALUES(${p2.orgId},${id2},${revision},${JSON.stringify(selected)}::jsonb,${timestamp}) ON CONFLICT(org_id,profile_id) DO NOTHING RETURNING *` : await tx`UPDATE skills_profiles SET revision=${revision},selections_json=${JSON.stringify(selected)}::jsonb,updated_at=${timestamp} WHERE org_id=${p2.orgId} AND profile_id=${id2} AND revision=${expected} RETURNING *`;
|
|
54870
|
+
return rows[0] ? profile(rows[0]) : null;
|
|
54871
|
+
});
|
|
54697
54872
|
}
|
|
54698
54873
|
async getStationState(p2, id2) {
|
|
54699
54874
|
const rows = await this.sql`SELECT * FROM skills_station_state WHERE org_id=${p2.orgId} AND actor_id=${p2.userId} AND station_id=${id2}`;
|
|
@@ -54814,7 +54989,7 @@ function nowIso() {
|
|
|
54814
54989
|
function normalizeLimit(limit) {
|
|
54815
54990
|
return Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : 0;
|
|
54816
54991
|
}
|
|
54817
|
-
function
|
|
54992
|
+
function runId2() {
|
|
54818
54993
|
return `run_${Date.now().toString(36)}_${randomUUID6().slice(0, 8)}`;
|
|
54819
54994
|
}
|
|
54820
54995
|
function rowToRun(row) {
|
|
@@ -54887,7 +55062,11 @@ function rowToSkill(row) {
|
|
|
54887
55062
|
revisionId: String(row.revision_id ?? ""),
|
|
54888
55063
|
revisionNumber: Number(row.revision_number ?? 0),
|
|
54889
55064
|
...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {},
|
|
54890
|
-
...row.tombstone_purge_after ? { tombstonePurgeAfter: dateString(row.tombstone_purge_after) } : {}
|
|
55065
|
+
...row.tombstone_purge_after ? { tombstonePurgeAfter: dateString(row.tombstone_purge_after) } : {},
|
|
55066
|
+
lifecycle: row.lifecycle === "archived" ? "archived" : "active",
|
|
55067
|
+
...row.archived_at ? { archivedAt: dateString(row.archived_at) } : {},
|
|
55068
|
+
...row.archive_reason ? { archiveReason: String(row.archive_reason) } : {},
|
|
55069
|
+
...row.replacement_slug ? { replacementSlug: String(row.replacement_slug) } : {}
|
|
54891
55070
|
};
|
|
54892
55071
|
}
|
|
54893
55072
|
function rowToPin(row) {
|
|
@@ -54968,10 +55147,10 @@ class StaleLeaseGenerationError extends Error {
|
|
|
54968
55147
|
expectedGeneration;
|
|
54969
55148
|
currentGeneration;
|
|
54970
55149
|
status;
|
|
54971
|
-
constructor(
|
|
54972
|
-
super(`late transition rejected for run ${
|
|
55150
|
+
constructor(runId3, expectedGeneration, currentGeneration, status) {
|
|
55151
|
+
super(`late transition rejected for run ${runId3}: expected lease_generation ${expectedGeneration}, ` + `current ${currentGeneration} (status ${status})`);
|
|
54973
55152
|
this.name = "StaleLeaseGenerationError";
|
|
54974
|
-
this.runId =
|
|
55153
|
+
this.runId = runId3;
|
|
54975
55154
|
this.expectedGeneration = expectedGeneration;
|
|
54976
55155
|
this.currentGeneration = currentGeneration;
|
|
54977
55156
|
this.status = status;
|
|
@@ -55006,11 +55185,22 @@ class SkillVersionExistsError extends Error {
|
|
|
55006
55185
|
}
|
|
55007
55186
|
}
|
|
55008
55187
|
|
|
55188
|
+
class SkillLifecycleConflictError extends Error {
|
|
55189
|
+
slug;
|
|
55190
|
+
profiles;
|
|
55191
|
+
constructor(slug, profiles) {
|
|
55192
|
+
super(`cannot archive '${slug}' while active profiles reference it: ${profiles.join(", ")}`);
|
|
55193
|
+
this.slug = slug;
|
|
55194
|
+
this.profiles = profiles;
|
|
55195
|
+
this.name = "SkillLifecycleConflictError";
|
|
55196
|
+
}
|
|
55197
|
+
}
|
|
55198
|
+
|
|
55009
55199
|
// src/lib/revision.ts
|
|
55010
55200
|
import { createHash as createHash20 } from "crypto";
|
|
55011
55201
|
var REVISION_ID_PATTERN = /^[0-9a-f]{64}$/;
|
|
55012
55202
|
function revisionIdOf(content) {
|
|
55013
|
-
const
|
|
55203
|
+
const canonicalContent = {
|
|
55014
55204
|
slug: content.slug,
|
|
55015
55205
|
displayName: content.displayName,
|
|
55016
55206
|
description: content.description,
|
|
@@ -55022,7 +55212,13 @@ function revisionIdOf(content) {
|
|
|
55022
55212
|
skillMd: content.skillMd ?? null,
|
|
55023
55213
|
bundleSha256: content.bundleSha256 ?? null,
|
|
55024
55214
|
bundleByteSize: content.bundleByteSize ?? null
|
|
55025
|
-
}
|
|
55215
|
+
};
|
|
55216
|
+
if (content.lifecycle === "archived" || content.archiveReason !== undefined || content.replacementSlug !== undefined) {
|
|
55217
|
+
canonicalContent.lifecycle = content.lifecycle ?? "active";
|
|
55218
|
+
canonicalContent.archiveReason = content.archiveReason ?? null;
|
|
55219
|
+
canonicalContent.replacementSlug = content.replacementSlug ?? null;
|
|
55220
|
+
}
|
|
55221
|
+
const canonical4 = JSON.stringify(canonicalContent);
|
|
55026
55222
|
return createHash20("sha256").update(canonical4).digest("hex");
|
|
55027
55223
|
}
|
|
55028
55224
|
function revisionIdOfRecord(record3) {
|
|
@@ -55125,6 +55321,28 @@ class SqliteSkillsStore {
|
|
|
55125
55321
|
scopes: parseScopes(row.scopes_json)
|
|
55126
55322
|
};
|
|
55127
55323
|
}
|
|
55324
|
+
async updateApiKeyScopes(actor, keyId, expectedScopes, addScopes) {
|
|
55325
|
+
const row = this.get("SELECT scopes_json FROM api_keys WHERE id = ? AND org_id = ? AND revoked_at IS NULL LIMIT 1", [keyId, actor.orgId]);
|
|
55326
|
+
if (!row)
|
|
55327
|
+
return { kind: "not_found" };
|
|
55328
|
+
const current = parseScopes(row.scopes_json);
|
|
55329
|
+
if (current.length !== expectedScopes.length || current.some((scope, index) => scope !== expectedScopes[index])) {
|
|
55330
|
+
return { kind: "stale", scopes: current };
|
|
55331
|
+
}
|
|
55332
|
+
const scopes = [...current, ...addScopes.filter((scope) => !current.includes(scope))];
|
|
55333
|
+
const tx = this.db.transaction(() => {
|
|
55334
|
+
const changed = this.db.run("UPDATE api_keys SET scopes_json = ? WHERE id = ? AND org_id = ? AND revoked_at IS NULL AND scopes_json = ?", [JSON.stringify(scopes), keyId, actor.orgId, JSON.stringify(expectedScopes)]);
|
|
55335
|
+
if (changed.changes !== 1)
|
|
55336
|
+
return false;
|
|
55337
|
+
this.db.run("INSERT INTO skills_audit_events (org_id, user_id, api_key_id, action, target_type, target_id, metadata_json) VALUES (?, ?, ?, ?, ?, ?, ?)", [actor.orgId, actor.userId, actor.apiKeyId, "api_key_scopes_added", "api_key", keyId, JSON.stringify({ added: addScopes, scopes })]);
|
|
55338
|
+
return true;
|
|
55339
|
+
});
|
|
55340
|
+
if (!tx()) {
|
|
55341
|
+
const latest = this.get("SELECT scopes_json FROM api_keys WHERE id = ? AND org_id = ? AND revoked_at IS NULL LIMIT 1", [keyId, actor.orgId]);
|
|
55342
|
+
return { kind: "stale", scopes: latest ? parseScopes(latest.scopes_json) : current };
|
|
55343
|
+
}
|
|
55344
|
+
return { kind: "updated", scopes };
|
|
55345
|
+
}
|
|
55128
55346
|
async createRun(input) {
|
|
55129
55347
|
if (input.idempotencyKey) {
|
|
55130
55348
|
const existing = this.get("SELECT * FROM skills_runs WHERE org_id = ? AND idempotency_key = ? LIMIT 1", [input.principal.orgId, input.idempotencyKey]);
|
|
@@ -55134,7 +55352,7 @@ class SqliteSkillsStore {
|
|
|
55134
55352
|
const row = this.get(`INSERT INTO skills_runs (id, org_id, user_id, skill_slug, requested_slug, status, input_json, args_json, idempotency_key, correlation_id, created_at)
|
|
55135
55353
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
55136
55354
|
RETURNING *`, [
|
|
55137
|
-
|
|
55355
|
+
runId2(),
|
|
55138
55356
|
input.principal.orgId,
|
|
55139
55357
|
input.principal.userId,
|
|
55140
55358
|
input.slug,
|
|
@@ -55446,6 +55664,39 @@ class SqliteSkillsStore {
|
|
|
55446
55664
|
const row = this.get("SELECT * FROM skills_registry WHERE org_id = ? AND slug = ? LIMIT 1", [principal.orgId, slug]);
|
|
55447
55665
|
return row ? rowToSkill(row) : null;
|
|
55448
55666
|
}
|
|
55667
|
+
async setSkillLifecycle(principal, slug, patch, expectedRevisionId) {
|
|
55668
|
+
return this.db.transaction(() => {
|
|
55669
|
+
const rowCurrent = this.get("SELECT * FROM skills_registry WHERE org_id=? AND slug=? LIMIT 1", [principal.orgId, slug]);
|
|
55670
|
+
const current = rowCurrent ? rowToSkill(rowCurrent) : null;
|
|
55671
|
+
if (!current || current.tombstonedAt)
|
|
55672
|
+
return null;
|
|
55673
|
+
if (expectedRevisionId !== current.revisionId)
|
|
55674
|
+
throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
|
|
55675
|
+
if (patch.lifecycle === "archived" && current.lifecycle !== "archived") {
|
|
55676
|
+
const profiles = this.db.query("SELECT profile_id,selections_json FROM skills_profiles WHERE org_id=?").all(principal.orgId);
|
|
55677
|
+
const referencing = profiles.filter((profile2) => {
|
|
55678
|
+
const selections2 = typeof profile2.selections_json === "string" ? JSON.parse(profile2.selections_json) : profile2.selections_json;
|
|
55679
|
+
return Array.isArray(selections2) && selections2.some((selection2) => selection2?.slug === slug);
|
|
55680
|
+
}).map((profile2) => String(profile2.profile_id)).sort();
|
|
55681
|
+
if (referencing.length)
|
|
55682
|
+
throw new SkillLifecycleConflictError(slug, referencing);
|
|
55683
|
+
}
|
|
55684
|
+
const next2 = { ...current, lifecycle: patch.lifecycle, updatedAt: nowIso(), ...patch.lifecycle === "archived" ? { archivedAt: current.archivedAt ?? nowIso(), ...patch.reason ? { archiveReason: patch.reason } : {}, ...patch.replacementSlug ? { replacementSlug: patch.replacementSlug } : {} } : {} };
|
|
55685
|
+
if (patch.lifecycle === "active") {
|
|
55686
|
+
delete next2.archivedAt;
|
|
55687
|
+
delete next2.archiveReason;
|
|
55688
|
+
delete next2.replacementSlug;
|
|
55689
|
+
}
|
|
55690
|
+
const row = this.get("UPDATE skills_registry SET lifecycle=?, archived_at=?, archive_reason=?, replacement_slug=?, revision_id=?, revision_number=revision_number+1, updated_at=? WHERE org_id=? AND slug=? AND tombstoned_at IS NULL AND revision_id=? RETURNING *", [next2.lifecycle, next2.archivedAt ?? null, next2.archiveReason ?? null, next2.replacementSlug ?? null, revisionIdOfRecord(next2), next2.updatedAt, principal.orgId, slug, current.revisionId]);
|
|
55691
|
+
if (!row)
|
|
55692
|
+
throw new SkillRevisionConflictError(slug, expectedRevisionId, this.getSkillSync(principal, slug)?.revisionId ?? null);
|
|
55693
|
+
return rowToSkill(row);
|
|
55694
|
+
}).immediate();
|
|
55695
|
+
}
|
|
55696
|
+
getSkillSync(principal, slug) {
|
|
55697
|
+
const row = this.get("SELECT * FROM skills_registry WHERE org_id=? AND slug=? LIMIT 1", [principal.orgId, slug]);
|
|
55698
|
+
return row ? rowToSkill(row) : null;
|
|
55699
|
+
}
|
|
55449
55700
|
async updateSkill(principal, slug, patch, expectedRevisionId) {
|
|
55450
55701
|
const current = await this.getSkill(principal, slug);
|
|
55451
55702
|
if (!current || current.tombstonedAt)
|
|
@@ -55558,13 +55809,13 @@ class SqliteSkillsStore {
|
|
|
55558
55809
|
}
|
|
55559
55810
|
async listTags(principal) {
|
|
55560
55811
|
await this.purgeExpiredTombstones(principal);
|
|
55561
|
-
return this.all("SELECT DISTINCT tag FROM skills_tags WHERE org_id = ? ORDER BY tag ASC", [principal.orgId]).map((row) => String(row.tag));
|
|
55812
|
+
return this.all("SELECT DISTINCT t.tag FROM skills_tags t JOIN skills_registry s ON s.org_id=t.org_id AND s.slug=t.slug WHERE t.org_id = ? AND s.lifecycle='active' ORDER BY t.tag ASC", [principal.orgId]).map((row) => String(row.tag));
|
|
55562
55813
|
}
|
|
55563
55814
|
async listSkillsByTag(principal, tag) {
|
|
55564
55815
|
await this.purgeExpiredTombstones(principal);
|
|
55565
55816
|
return this.all(`SELECT s.* FROM skills_registry s
|
|
55566
55817
|
JOIN skills_tags t ON t.org_id = s.org_id AND t.slug = s.slug
|
|
55567
|
-
WHERE t.org_id = ? AND t.tag = ? AND s.tombstoned_at IS NULL
|
|
55818
|
+
WHERE t.org_id = ? AND t.tag = ? AND s.tombstoned_at IS NULL AND s.lifecycle = 'active'
|
|
55568
55819
|
ORDER BY s.slug ASC`, [principal.orgId, tag]).map(rowToSkill);
|
|
55569
55820
|
}
|
|
55570
55821
|
async listPinsByTag(principal, tag) {
|
|
@@ -55572,7 +55823,7 @@ class SqliteSkillsStore {
|
|
|
55572
55823
|
return this.all(`SELECT p.* FROM skills_pins p
|
|
55573
55824
|
JOIN skills_tags t ON t.org_id = p.org_id AND t.slug = p.slug
|
|
55574
55825
|
JOIN skills_registry s ON s.org_id = p.org_id AND s.slug = p.slug
|
|
55575
|
-
WHERE p.org_id = ? AND p.principal = ? AND t.tag = ? AND s.tombstoned_at IS NULL
|
|
55826
|
+
WHERE p.org_id = ? AND p.principal = ? AND t.tag = ? AND s.tombstoned_at IS NULL AND s.lifecycle = 'active'
|
|
55576
55827
|
ORDER BY p.slug ASC`, [principal.orgId, principal.apiKeyId, tag]).map(rowToPin);
|
|
55577
55828
|
}
|
|
55578
55829
|
async listPublishedSlugs(principal) {
|
|
@@ -55694,8 +55945,8 @@ class MemoryGovernanceStore {
|
|
|
55694
55945
|
this.receipts.push(next2);
|
|
55695
55946
|
return next2;
|
|
55696
55947
|
}
|
|
55697
|
-
async listReceipts(orgId,
|
|
55698
|
-
return this.receipts.filter((receipt) => receipt.orgId === orgId && receipt.runId ===
|
|
55948
|
+
async listReceipts(orgId, runId3) {
|
|
55949
|
+
return this.receipts.filter((receipt) => receipt.orgId === orgId && receipt.runId === runId3);
|
|
55699
55950
|
}
|
|
55700
55951
|
async createReservation(input) {
|
|
55701
55952
|
const estimatedCents = input.estimatedCents;
|
|
@@ -55704,8 +55955,8 @@ class MemoryGovernanceStore {
|
|
|
55704
55955
|
this.reservations.push(reservation);
|
|
55705
55956
|
return reservation;
|
|
55706
55957
|
}
|
|
55707
|
-
async reservationsForRun(orgId,
|
|
55708
|
-
return this.reservations.filter((reservation) => reservation.orgId === orgId && reservation.runId ===
|
|
55958
|
+
async reservationsForRun(orgId, runId3) {
|
|
55959
|
+
return this.reservations.filter((reservation) => reservation.orgId === orgId && reservation.runId === runId3);
|
|
55709
55960
|
}
|
|
55710
55961
|
async reconcileReservation(reservationId2, actualCents, status) {
|
|
55711
55962
|
assertReservationCents(actualCents, "actualCents");
|
|
@@ -55777,8 +56028,8 @@ class SqliteGovernanceStore {
|
|
|
55777
56028
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [next2.id, next2.kind, next2.orgId, next2.runId, next2.artifactId ?? null, next2.requestedBy, JSON.stringify(next2.metadata), next2.createdAt]);
|
|
55778
56029
|
return next2;
|
|
55779
56030
|
}
|
|
55780
|
-
async listReceipts(orgId,
|
|
55781
|
-
const rows = this.db.query("SELECT * FROM skills_lifecycle_receipts WHERE org_id = ? AND run_id = ? ORDER BY created_at ASC, rowid ASC").all(orgId,
|
|
56031
|
+
async listReceipts(orgId, runId3) {
|
|
56032
|
+
const rows = this.db.query("SELECT * FROM skills_lifecycle_receipts WHERE org_id = ? AND run_id = ? ORDER BY created_at ASC, rowid ASC").all(orgId, runId3);
|
|
55782
56033
|
return rows.map((row) => ({
|
|
55783
56034
|
id: String(row.id),
|
|
55784
56035
|
kind: String(row.kind),
|
|
@@ -55798,8 +56049,8 @@ class SqliteGovernanceStore {
|
|
|
55798
56049
|
VALUES (?, ?, ?, ?, ?, ?)`, [reservation.id, reservation.orgId, reservation.runId, reservation.estimatedCents, reservation.status, reservation.createdAt]);
|
|
55799
56050
|
return reservation;
|
|
55800
56051
|
}
|
|
55801
|
-
async reservationsForRun(orgId,
|
|
55802
|
-
const rows = this.db.query("SELECT * FROM skills_credit_reservations WHERE org_id = ? AND run_id = ? ORDER BY created_at ASC, rowid ASC").all(orgId,
|
|
56052
|
+
async reservationsForRun(orgId, runId3) {
|
|
56053
|
+
const rows = this.db.query("SELECT * FROM skills_credit_reservations WHERE org_id = ? AND run_id = ? ORDER BY created_at ASC, rowid ASC").all(orgId, runId3);
|
|
55803
56054
|
return rows.map((row) => this.reservationFrom(row));
|
|
55804
56055
|
}
|
|
55805
56056
|
async reconcileReservation(reservationId2, actualCents, status) {
|
|
@@ -55873,9 +56124,9 @@ class PostgresGovernanceStore {
|
|
|
55873
56124
|
`;
|
|
55874
56125
|
return next2;
|
|
55875
56126
|
}
|
|
55876
|
-
async listReceipts(orgId,
|
|
56127
|
+
async listReceipts(orgId, runId3) {
|
|
55877
56128
|
const rows = await this.sql`
|
|
55878
|
-
SELECT * FROM skills_lifecycle_receipts WHERE org_id = ${orgId} AND run_id = ${
|
|
56129
|
+
SELECT * FROM skills_lifecycle_receipts WHERE org_id = ${orgId} AND run_id = ${runId3} ORDER BY created_at ASC
|
|
55879
56130
|
`;
|
|
55880
56131
|
return rows.map((row) => ({
|
|
55881
56132
|
id: String(row.id),
|
|
@@ -55898,9 +56149,9 @@ class PostgresGovernanceStore {
|
|
|
55898
56149
|
`;
|
|
55899
56150
|
return reservation;
|
|
55900
56151
|
}
|
|
55901
|
-
async reservationsForRun(orgId,
|
|
56152
|
+
async reservationsForRun(orgId, runId3) {
|
|
55902
56153
|
const rows = await this.sql`
|
|
55903
|
-
SELECT * FROM skills_credit_reservations WHERE org_id = ${orgId} AND run_id = ${
|
|
56154
|
+
SELECT * FROM skills_credit_reservations WHERE org_id = ${orgId} AND run_id = ${runId3} ORDER BY created_at ASC
|
|
55904
56155
|
`;
|
|
55905
56156
|
return rows.map((row) => this.reservationFrom(row));
|
|
55906
56157
|
}
|
|
@@ -56016,6 +56267,15 @@ class SkillRequestError extends Error {
|
|
|
56016
56267
|
this.code = code;
|
|
56017
56268
|
}
|
|
56018
56269
|
}
|
|
56270
|
+
function skillLifecyclePatch(body) {
|
|
56271
|
+
if (body.lifecycle !== "active" && body.lifecycle !== "archived")
|
|
56272
|
+
throw new SkillRequestError(400, "INVALID_LIFECYCLE", "lifecycle must be active or archived");
|
|
56273
|
+
if (body.reason !== undefined && (typeof body.reason !== "string" || body.reason.length > 512))
|
|
56274
|
+
throw new SkillRequestError(400, "INVALID_LIFECYCLE", "reason must be at most 512 characters");
|
|
56275
|
+
if (body.replacementSlug !== undefined && (typeof body.replacementSlug !== "string" || !SLUG_PATTERN.test(body.replacementSlug)))
|
|
56276
|
+
throw new SkillRequestError(400, "INVALID_LIFECYCLE", "replacementSlug must be a valid skill slug");
|
|
56277
|
+
return { lifecycle: body.lifecycle, ...body.reason ? { reason: body.reason } : {}, ...body.replacementSlug ? { replacementSlug: body.replacementSlug } : {} };
|
|
56278
|
+
}
|
|
56019
56279
|
function publishedSkillMeta(record3) {
|
|
56020
56280
|
return {
|
|
56021
56281
|
name: record3.slug,
|
|
@@ -56040,7 +56300,11 @@ function publishedPayload(record3, opts = {}) {
|
|
|
56040
56300
|
publishedAt: record3.createdAt,
|
|
56041
56301
|
updatedAt: record3.updatedAt,
|
|
56042
56302
|
revisionId: record3.revisionId,
|
|
56043
|
-
revisionNumber: record3.revisionNumber
|
|
56303
|
+
revisionNumber: record3.revisionNumber,
|
|
56304
|
+
lifecycle: record3.lifecycle,
|
|
56305
|
+
...record3.archivedAt ? { archivedAt: record3.archivedAt } : {},
|
|
56306
|
+
...record3.archiveReason ? { archiveReason: record3.archiveReason } : {},
|
|
56307
|
+
...record3.replacementSlug ? { replacementSlug: record3.replacementSlug } : {}
|
|
56044
56308
|
};
|
|
56045
56309
|
}
|
|
56046
56310
|
function revisionEtag(revisionId) {
|
|
@@ -56080,7 +56344,7 @@ async function tombstoneStatus(store, artifactStorage, principal, record3) {
|
|
|
56080
56344
|
};
|
|
56081
56345
|
}
|
|
56082
56346
|
async function listMergedSkills(store, principal) {
|
|
56083
|
-
return (await store.listSkills(principal)).sort((a3, b3) => a3.slug.localeCompare(b3.slug)).map((record3) => publishedPayload(record3));
|
|
56347
|
+
return (await store.listSkills(principal)).filter((record3) => record3.lifecycle === "active").sort((a3, b3) => a3.slug.localeCompare(b3.slug)).map((record3) => publishedPayload(record3));
|
|
56084
56348
|
}
|
|
56085
56349
|
async function resolvePublishedSkill(store, artifactStorage, principal, slug) {
|
|
56086
56350
|
const record3 = await store.getSkill(principal, slug);
|
|
@@ -56097,7 +56361,7 @@ async function listOrgTags(store, principal) {
|
|
|
56097
56361
|
return [...new Set((await store.listTags(principal)).filter((tag) => tag.trim()))].sort();
|
|
56098
56362
|
}
|
|
56099
56363
|
async function listMergedSkillsByTag(store, principal, tag) {
|
|
56100
|
-
return (await store.listSkillsByTag(principal, tag)).sort((a3, b3) => a3.slug.localeCompare(b3.slug)).map((record3) => publishedPayload(record3));
|
|
56364
|
+
return (await store.listSkillsByTag(principal, tag)).filter((record3) => record3.lifecycle === "active").sort((a3, b3) => a3.slug.localeCompare(b3.slug)).map((record3) => publishedPayload(record3));
|
|
56101
56365
|
}
|
|
56102
56366
|
function skillSummary(skill) {
|
|
56103
56367
|
return {
|
|
@@ -56659,6 +56923,8 @@ async function validatePublished(store, principal, selections2) {
|
|
|
56659
56923
|
const current = await store.getSkill(principal, selection2.slug);
|
|
56660
56924
|
if (!current || current.tombstonedAt)
|
|
56661
56925
|
throw new SkillRequestError(current?.tombstonedAt ? 410 : 404, "SELECTION_UNAVAILABLE", `Selected skill '${selection2.slug}' is unavailable`);
|
|
56926
|
+
if (current.lifecycle === "archived")
|
|
56927
|
+
throw new SkillRequestError(409, "SELECTION_ARCHIVED", `Selected skill '${selection2.slug}' is archived`);
|
|
56662
56928
|
const version2 = await store.getSkillVersion(principal, selection2.slug, selection2.version);
|
|
56663
56929
|
if (!version2 || `sha256:${version2.bundleSha256}` !== selection2.bundleDigest)
|
|
56664
56930
|
throw new SkillRequestError(409, "SELECTION_VERSION_MISMATCH", `Selected version of '${selection2.slug}' does not match its published digest`);
|
|
@@ -56895,8 +57161,8 @@ class RuntimeExecutionStore {
|
|
|
56895
57161
|
this.sqlite?.close();
|
|
56896
57162
|
await this.postgres?.close();
|
|
56897
57163
|
}
|
|
56898
|
-
async job(
|
|
56899
|
-
const row = this.sqlite ? this.sqlite.query("SELECT payload FROM skills_runtime_jobs WHERE run_id=?").get(
|
|
57164
|
+
async job(runId3) {
|
|
57165
|
+
const row = this.sqlite ? this.sqlite.query("SELECT payload FROM skills_runtime_jobs WHERE run_id=?").get(runId3) : (await this.postgres`SELECT payload FROM skills_runtime_jobs WHERE run_id=${runId3}`)[0];
|
|
56900
57166
|
return row ? JSON.parse(String(row.payload)) : null;
|
|
56901
57167
|
}
|
|
56902
57168
|
async mutate(id2, fn) {
|
|
@@ -57229,19 +57495,19 @@ class MemoryRunExecutionStore {
|
|
|
57229
57495
|
}), admission.runId);
|
|
57230
57496
|
return row;
|
|
57231
57497
|
}
|
|
57232
|
-
async getRun(
|
|
57233
|
-
return this.runs.get(
|
|
57498
|
+
async getRun(runId3) {
|
|
57499
|
+
return this.runs.get(runId3) ?? null;
|
|
57234
57500
|
}
|
|
57235
57501
|
async getRunByKey(tenantId, idempotencyKey) {
|
|
57236
|
-
const
|
|
57237
|
-
return
|
|
57502
|
+
const runId3 = this.byKey.get(tenantId + "\x00" + idempotencyKey);
|
|
57503
|
+
return runId3 ? this.runs.get(runId3) ?? null : null;
|
|
57238
57504
|
}
|
|
57239
57505
|
async getRunByDigests(input) {
|
|
57240
|
-
const
|
|
57241
|
-
return
|
|
57506
|
+
const runId3 = this.byDigests.get(digestKey(input));
|
|
57507
|
+
return runId3 ? this.runs.get(runId3) ?? null : null;
|
|
57242
57508
|
}
|
|
57243
|
-
async listAttempts(
|
|
57244
|
-
return Array.from(this.attempts.values()).filter((attempt) => attempt.runId ===
|
|
57509
|
+
async listAttempts(runId3) {
|
|
57510
|
+
return Array.from(this.attempts.values()).filter((attempt) => attempt.runId === runId3).sort((a3, b3) => a3.attemptNumber - b3.attemptNumber);
|
|
57245
57511
|
}
|
|
57246
57512
|
async createAttempt(input) {
|
|
57247
57513
|
const attemptId = `${input.runId}/attempt/${input.attemptNumber}`;
|
|
@@ -57321,7 +57587,7 @@ class MemoryRunExecutionStore {
|
|
|
57321
57587
|
async recordTransition(transition) {
|
|
57322
57588
|
this.transitions.push(transition);
|
|
57323
57589
|
}
|
|
57324
|
-
async markAttemptTerminal(
|
|
57590
|
+
async markAttemptTerminal(runId3, attemptId) {
|
|
57325
57591
|
const attempt = this.attempts.get(attemptId);
|
|
57326
57592
|
if (!attempt)
|
|
57327
57593
|
return null;
|
|
@@ -57333,15 +57599,15 @@ class MemoryRunExecutionStore {
|
|
|
57333
57599
|
this.receipts.set(receipt.runId + "\x00" + receipt.attemptId, receipt);
|
|
57334
57600
|
return receipt;
|
|
57335
57601
|
}
|
|
57336
|
-
async getReceipt(
|
|
57337
|
-
return this.receipts.get(
|
|
57602
|
+
async getReceipt(runId3, attemptId) {
|
|
57603
|
+
return this.receipts.get(runId3 + "\x00" + attemptId) ?? null;
|
|
57338
57604
|
}
|
|
57339
|
-
async finalizeRun(
|
|
57340
|
-
const run = this.runs.get(
|
|
57605
|
+
async finalizeRun(runId3, status, receiptId2) {
|
|
57606
|
+
const run = this.runs.get(runId3);
|
|
57341
57607
|
if (!run)
|
|
57342
57608
|
return null;
|
|
57343
57609
|
for (const [attemptId, attempt] of this.attempts) {
|
|
57344
|
-
if (attempt.runId ===
|
|
57610
|
+
if (attempt.runId === runId3 && attempt.status !== "terminal") {
|
|
57345
57611
|
this.attempts.set(attemptId, { ...attempt, status: "terminal" });
|
|
57346
57612
|
}
|
|
57347
57613
|
}
|
|
@@ -57351,15 +57617,15 @@ class MemoryRunExecutionStore {
|
|
|
57351
57617
|
terminalReceiptId: receiptId2,
|
|
57352
57618
|
updatedAt: new Date().toISOString()
|
|
57353
57619
|
};
|
|
57354
|
-
this.runs.set(
|
|
57620
|
+
this.runs.set(runId3, next2);
|
|
57355
57621
|
return next2;
|
|
57356
57622
|
}
|
|
57357
|
-
async setRunStatus(
|
|
57358
|
-
const run = this.runs.get(
|
|
57623
|
+
async setRunStatus(runId3, status) {
|
|
57624
|
+
const run = this.runs.get(runId3);
|
|
57359
57625
|
if (!run)
|
|
57360
57626
|
return null;
|
|
57361
57627
|
const next2 = { ...run, status, updatedAt: new Date().toISOString() };
|
|
57362
|
-
this.runs.set(
|
|
57628
|
+
this.runs.set(runId3, next2);
|
|
57363
57629
|
return next2;
|
|
57364
57630
|
}
|
|
57365
57631
|
}
|
|
@@ -57377,21 +57643,28 @@ class SqliteRunExecutionStore {
|
|
|
57377
57643
|
this.db = new Database4(path);
|
|
57378
57644
|
this.db.run("PRAGMA journal_mode = WAL");
|
|
57379
57645
|
this.db.run("PRAGMA foreign_keys = ON");
|
|
57380
|
-
this.db.
|
|
57646
|
+
this.db.transaction(() => {
|
|
57647
|
+
this.db.run(SCHEMA_SQL);
|
|
57648
|
+
for (const table of ["execution_runs", "execution_receipts"]) {
|
|
57649
|
+
const columns = this.db.query(`PRAGMA table_info(${table})`).all();
|
|
57650
|
+
if (!columns.some((column) => column.name === "execution_contract_json"))
|
|
57651
|
+
this.db.run(`ALTER TABLE ${table} ADD COLUMN execution_contract_json TEXT`);
|
|
57652
|
+
}
|
|
57653
|
+
}).immediate();
|
|
57381
57654
|
}
|
|
57382
57655
|
async admit(admission) {
|
|
57383
57656
|
this.db.query(`INSERT OR IGNORE INTO execution_runs (
|
|
57384
57657
|
run_id, contract_version, tenant_id, skill_id, skill_version, bundle_digest,
|
|
57385
57658
|
runtime_image_digest, dependency_layer_tag, input_digest, runtime,
|
|
57386
|
-
policy_json, limits_json, idempotency_key, status, created_at, updated_at
|
|
57387
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(admission.runId, admission.contractVersion, admission.tenantId, admission.skillId, admission.skillVersion, admission.bundleDigest, admission.runtimeImageDigest, admission.dependencyLayerTag, admission.inputDigest, admission.runtime, JSON.stringify(admission.policy), JSON.stringify(admission.limits), admission.idempotencyKey, "admitted", admission.createdAt, admission.createdAt);
|
|
57659
|
+
policy_json, limits_json, idempotency_key, status, created_at, updated_at, execution_contract_json
|
|
57660
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(admission.runId, admission.contractVersion, admission.tenantId, admission.skillId, admission.skillVersion, admission.bundleDigest, admission.runtimeImageDigest, admission.dependencyLayerTag, admission.inputDigest, admission.runtime, JSON.stringify(admission.policy), JSON.stringify(admission.limits), admission.idempotencyKey, "admitted", admission.createdAt, admission.createdAt, admission.executionContract ? JSON.stringify(admission.executionContract) : null);
|
|
57388
57661
|
const row = this.readRun(admission.runId);
|
|
57389
57662
|
if (!row)
|
|
57390
57663
|
throw new Error(`admission did not persist run ${admission.runId}`);
|
|
57391
57664
|
return row;
|
|
57392
57665
|
}
|
|
57393
|
-
async getRun(
|
|
57394
|
-
return this.readRun(
|
|
57666
|
+
async getRun(runId3) {
|
|
57667
|
+
return this.readRun(runId3);
|
|
57395
57668
|
}
|
|
57396
57669
|
async getRunByKey(tenantId, idempotencyKey) {
|
|
57397
57670
|
const row = this.db.query(`SELECT * FROM execution_runs WHERE tenant_id = ? AND idempotency_key = ? LIMIT 1`).get(tenantId, idempotencyKey);
|
|
@@ -57403,8 +57676,8 @@ class SqliteRunExecutionStore {
|
|
|
57403
57676
|
LIMIT 1`).get(input.tenantId, input.skillId, input.skillVersion, input.bundleDigest, input.inputDigest);
|
|
57404
57677
|
return row ? rowToRun2(row) : null;
|
|
57405
57678
|
}
|
|
57406
|
-
async listAttempts(
|
|
57407
|
-
const rows = this.db.query(`SELECT * FROM execution_attempts WHERE run_id = ? ORDER BY attempt_number ASC`).all(
|
|
57679
|
+
async listAttempts(runId3) {
|
|
57680
|
+
const rows = this.db.query(`SELECT * FROM execution_attempts WHERE run_id = ? ORDER BY attempt_number ASC`).all(runId3);
|
|
57408
57681
|
return rows.map(rowToAttempt);
|
|
57409
57682
|
}
|
|
57410
57683
|
async createAttempt(input) {
|
|
@@ -57474,8 +57747,8 @@ class SqliteRunExecutionStore {
|
|
|
57474
57747
|
this.db.query(`INSERT INTO execution_transitions (run_id, attempt_id, from_status, to_status, at)
|
|
57475
57748
|
VALUES (?, ?, ?, ?, ?)`).run(transition.runId, transition.attemptId, transition.from, transition.to, transition.at);
|
|
57476
57749
|
}
|
|
57477
|
-
async markAttemptTerminal(
|
|
57478
|
-
this.db.query(`UPDATE execution_attempts SET status = 'terminal' WHERE attempt_id = ? AND run_id = ? AND status != 'terminal'`).run(attemptId,
|
|
57750
|
+
async markAttemptTerminal(runId3, attemptId) {
|
|
57751
|
+
this.db.query(`UPDATE execution_attempts SET status = 'terminal' WHERE attempt_id = ? AND run_id = ? AND status != 'terminal'`).run(attemptId, runId3);
|
|
57479
57752
|
return this.readAttempt(attemptId);
|
|
57480
57753
|
}
|
|
57481
57754
|
async writeReceipt(receipt) {
|
|
@@ -57483,28 +57756,28 @@ class SqliteRunExecutionStore {
|
|
|
57483
57756
|
run_id, attempt_id, lease_generation, client_token, request_digest, started_by,
|
|
57484
57757
|
task_id, launched_at, completed_at, runtime_image_digest, bundle_digest,
|
|
57485
57758
|
dependency_layer_tag, policy_json, limits_json, exit_code, status,
|
|
57486
|
-
artifact_pointers_json, log_pointers_json, cost_cents
|
|
57487
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(receipt.runId, receipt.attemptId, receipt.leaseGeneration, receipt.clientToken, receipt.requestDigest, receipt.startedBy, receipt.taskId, receipt.launchedAt, receipt.completedAt, receipt.runtimeImageDigest, receipt.bundleDigest, receipt.dependencyLayerTag, JSON.stringify(receipt.policy), JSON.stringify(receipt.limits), receipt.exitCode, receipt.status, JSON.stringify(receipt.artifactPointers), JSON.stringify(receipt.logPointers), receipt.costCents);
|
|
57759
|
+
artifact_pointers_json, log_pointers_json, cost_cents, execution_contract_json
|
|
57760
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(receipt.runId, receipt.attemptId, receipt.leaseGeneration, receipt.clientToken, receipt.requestDigest, receipt.startedBy, receipt.taskId, receipt.launchedAt, receipt.completedAt, receipt.runtimeImageDigest, receipt.bundleDigest, receipt.dependencyLayerTag, JSON.stringify(receipt.policy), JSON.stringify(receipt.limits), receipt.exitCode, receipt.status, JSON.stringify(receipt.artifactPointers), JSON.stringify(receipt.logPointers), receipt.costCents, receipt.executionContract ? JSON.stringify(receipt.executionContract) : null);
|
|
57488
57761
|
return receipt;
|
|
57489
57762
|
}
|
|
57490
|
-
async getReceipt(
|
|
57491
|
-
const row = this.db.query(`SELECT * FROM execution_receipts WHERE run_id = ? AND attempt_id = ?`).get(
|
|
57763
|
+
async getReceipt(runId3, attemptId) {
|
|
57764
|
+
const row = this.db.query(`SELECT * FROM execution_receipts WHERE run_id = ? AND attempt_id = ?`).get(runId3, attemptId);
|
|
57492
57765
|
return row ? rowToReceipt(row) : null;
|
|
57493
57766
|
}
|
|
57494
|
-
async finalizeRun(
|
|
57495
|
-
this.db.query(`UPDATE execution_runs SET status = ?, terminal_receipt_id = ?, updated_at = ? WHERE run_id = ?`).run(status, receiptId2, new Date().toISOString(),
|
|
57496
|
-
this.db.query(`UPDATE execution_attempts SET status = 'terminal' WHERE run_id = ? AND status != 'terminal'`).run(
|
|
57497
|
-
return this.readRun(
|
|
57767
|
+
async finalizeRun(runId3, status, receiptId2) {
|
|
57768
|
+
this.db.query(`UPDATE execution_runs SET status = ?, terminal_receipt_id = ?, updated_at = ? WHERE run_id = ?`).run(status, receiptId2, new Date().toISOString(), runId3);
|
|
57769
|
+
this.db.query(`UPDATE execution_attempts SET status = 'terminal' WHERE run_id = ? AND status != 'terminal'`).run(runId3);
|
|
57770
|
+
return this.readRun(runId3);
|
|
57498
57771
|
}
|
|
57499
|
-
async setRunStatus(
|
|
57500
|
-
this.db.query(`UPDATE execution_runs SET status = ?, updated_at = ? WHERE run_id = ?`).run(status, new Date().toISOString(),
|
|
57501
|
-
return this.readRun(
|
|
57772
|
+
async setRunStatus(runId3, status) {
|
|
57773
|
+
this.db.query(`UPDATE execution_runs SET status = ?, updated_at = ? WHERE run_id = ?`).run(status, new Date().toISOString(), runId3);
|
|
57774
|
+
return this.readRun(runId3);
|
|
57502
57775
|
}
|
|
57503
57776
|
async close() {
|
|
57504
57777
|
this.db.close();
|
|
57505
57778
|
}
|
|
57506
|
-
readRun(
|
|
57507
|
-
const row = this.db.query(`SELECT * FROM execution_runs WHERE run_id = ?`).get(
|
|
57779
|
+
readRun(runId3) {
|
|
57780
|
+
const row = this.db.query(`SELECT * FROM execution_runs WHERE run_id = ?`).get(runId3);
|
|
57508
57781
|
return row ? rowToRun2(row) : null;
|
|
57509
57782
|
}
|
|
57510
57783
|
readAttempt(attemptId) {
|
|
@@ -57523,6 +57796,7 @@ function rowToRun2(row) {
|
|
|
57523
57796
|
bundleDigest: row.bundle_digest,
|
|
57524
57797
|
runtimeImageDigest: row.runtime_image_digest,
|
|
57525
57798
|
dependencyLayerTag: row.dependency_layer_tag,
|
|
57799
|
+
...row.execution_contract_json ? { executionContract: JSON.parse(row.execution_contract_json) } : {},
|
|
57526
57800
|
inputDigest: row.input_digest,
|
|
57527
57801
|
runtime: row.runtime,
|
|
57528
57802
|
policy: JSON.parse(row.policy_json),
|
|
@@ -57566,6 +57840,7 @@ function rowToReceipt(row) {
|
|
|
57566
57840
|
runtimeImageDigest: row.runtime_image_digest,
|
|
57567
57841
|
bundleDigest: row.bundle_digest,
|
|
57568
57842
|
dependencyLayerTag: row.dependency_layer_tag,
|
|
57843
|
+
...row.execution_contract_json ? { executionContract: JSON.parse(row.execution_contract_json) } : {},
|
|
57569
57844
|
policy: JSON.parse(row.policy_json),
|
|
57570
57845
|
limits: JSON.parse(row.limits_json),
|
|
57571
57846
|
exitCode: row.exit_code,
|
|
@@ -57585,6 +57860,7 @@ CREATE TABLE IF NOT EXISTS execution_runs (
|
|
|
57585
57860
|
bundle_digest TEXT NOT NULL,
|
|
57586
57861
|
runtime_image_digest TEXT NOT NULL,
|
|
57587
57862
|
dependency_layer_tag TEXT,
|
|
57863
|
+
execution_contract_json TEXT,
|
|
57588
57864
|
input_digest TEXT NOT NULL,
|
|
57589
57865
|
runtime TEXT NOT NULL,
|
|
57590
57866
|
policy_json TEXT NOT NULL,
|
|
@@ -57638,6 +57914,7 @@ CREATE TABLE IF NOT EXISTS execution_receipts (
|
|
|
57638
57914
|
runtime_image_digest TEXT NOT NULL,
|
|
57639
57915
|
bundle_digest TEXT NOT NULL,
|
|
57640
57916
|
dependency_layer_tag TEXT,
|
|
57917
|
+
execution_contract_json TEXT,
|
|
57641
57918
|
policy_json TEXT NOT NULL,
|
|
57642
57919
|
limits_json TEXT NOT NULL,
|
|
57643
57920
|
exit_code INTEGER,
|
|
@@ -57672,6 +57949,7 @@ function createSubmitRunService(options) {
|
|
|
57672
57949
|
const policy = { ...DEFAULT_RUN_POLICY, ...input.policy };
|
|
57673
57950
|
input = {
|
|
57674
57951
|
...input,
|
|
57952
|
+
...input.executionContract ? { executionContract: { ...input.executionContract } } : {},
|
|
57675
57953
|
systemDeps: [...input.systemDeps ?? []],
|
|
57676
57954
|
policy: { ...policy, egressAllowlist: [...policy.egressAllowlist] },
|
|
57677
57955
|
limits: { ...DEFAULT_RUN_LIMITS, ...input.limits }
|
|
@@ -57680,7 +57958,7 @@ function createSubmitRunService(options) {
|
|
|
57680
57958
|
const byKey = await store.getRunByKey(input.tenantId, input.idempotencyKey);
|
|
57681
57959
|
if (byKey)
|
|
57682
57960
|
return { run: ownedAdmission(byKey.admission), created: false };
|
|
57683
|
-
const byDigests = await store.getRunByDigests({
|
|
57961
|
+
const byDigests = input.executionContract ? null : await store.getRunByDigests({
|
|
57684
57962
|
tenantId: input.tenantId,
|
|
57685
57963
|
skillId: input.skillId,
|
|
57686
57964
|
skillVersion: input.skillVersion,
|
|
@@ -57702,6 +57980,7 @@ function createSubmitRunService(options) {
|
|
|
57702
57980
|
bundleDigest: input.bundleDigest,
|
|
57703
57981
|
runtimeImageDigest: image.runtimeImageDigest,
|
|
57704
57982
|
dependencyLayerTag: image.dependencyLayerTag,
|
|
57983
|
+
...input.executionContract ? { executionContract: { ...input.executionContract } } : {},
|
|
57705
57984
|
inputDigest,
|
|
57706
57985
|
runtime: image.runtime,
|
|
57707
57986
|
policy: { ...DEFAULT_RUN_POLICY, ...input.policy },
|
|
@@ -57717,6 +57996,7 @@ function createSubmitRunService(options) {
|
|
|
57717
57996
|
function ownedAdmission(admission) {
|
|
57718
57997
|
return {
|
|
57719
57998
|
...admission,
|
|
57999
|
+
...admission.executionContract ? { executionContract: { ...admission.executionContract } } : {},
|
|
57720
58000
|
policy: { ...admission.policy, egressAllowlist: [...admission.policy.egressAllowlist] },
|
|
57721
58001
|
limits: { ...admission.limits }
|
|
57722
58002
|
};
|
|
@@ -57738,12 +58018,12 @@ var LEGAL_TRANSITIONS = {
|
|
|
57738
58018
|
cancelled: []
|
|
57739
58019
|
};
|
|
57740
58020
|
function createRunStateMachine(store) {
|
|
57741
|
-
async function transition(
|
|
57742
|
-
const run = await store.getRun(
|
|
58021
|
+
async function transition(runId3, from, to, attemptId = null) {
|
|
58022
|
+
const run = await store.getRun(runId3);
|
|
57743
58023
|
if (!run)
|
|
57744
58024
|
return { ok: false, reason: "NO_SUCH_RUN" };
|
|
57745
58025
|
if (attemptId) {
|
|
57746
|
-
const attempt = await findAttempt(store,
|
|
58026
|
+
const attempt = await findAttempt(store, runId3, attemptId);
|
|
57747
58027
|
if (!attempt)
|
|
57748
58028
|
return { ok: false, reason: "NO_SUCH_ATTEMPT" };
|
|
57749
58029
|
}
|
|
@@ -57755,11 +58035,11 @@ function createRunStateMachine(store) {
|
|
|
57755
58035
|
if (!LEGAL_TRANSITIONS[from].includes(to)) {
|
|
57756
58036
|
return { ok: false, reason: "INVALID_TRANSITION" };
|
|
57757
58037
|
}
|
|
57758
|
-
const next2 = await store.setRunStatus(
|
|
58038
|
+
const next2 = await store.setRunStatus(runId3, to);
|
|
57759
58039
|
if (!next2)
|
|
57760
58040
|
return { ok: false, reason: "NO_SUCH_RUN" };
|
|
57761
58041
|
const record3 = {
|
|
57762
|
-
runId:
|
|
58042
|
+
runId: runId3,
|
|
57763
58043
|
attemptId,
|
|
57764
58044
|
from,
|
|
57765
58045
|
to,
|
|
@@ -57796,8 +58076,8 @@ function createRunStateMachine(store) {
|
|
|
57796
58076
|
});
|
|
57797
58077
|
return claimed;
|
|
57798
58078
|
},
|
|
57799
|
-
async start(
|
|
57800
|
-
return transition(
|
|
58079
|
+
async start(runId3, attemptId) {
|
|
58080
|
+
return transition(runId3, "leased", "running", attemptId);
|
|
57801
58081
|
},
|
|
57802
58082
|
async terminate(input) {
|
|
57803
58083
|
const run = await store.getRun(input.runId);
|
|
@@ -57817,26 +58097,26 @@ function createRunStateMachine(store) {
|
|
|
57817
58097
|
await store.markAttemptTerminal(input.runId, input.attemptId);
|
|
57818
58098
|
return { ok: true };
|
|
57819
58099
|
},
|
|
57820
|
-
async cancel(
|
|
57821
|
-
const run = await store.getRun(
|
|
58100
|
+
async cancel(runId3) {
|
|
58101
|
+
const run = await store.getRun(runId3);
|
|
57822
58102
|
if (!run)
|
|
57823
58103
|
return { ok: false, reason: "NO_SUCH_RUN" };
|
|
57824
58104
|
if (isTerminalStatus(run.status))
|
|
57825
58105
|
return { ok: false, reason: "RUN_TERMINAL" };
|
|
57826
|
-
const result = await transition(
|
|
58106
|
+
const result = await transition(runId3, run.status, "cancelled", run.currentAttemptId);
|
|
57827
58107
|
if (!result.ok)
|
|
57828
58108
|
return result;
|
|
57829
58109
|
return { ok: true };
|
|
57830
58110
|
},
|
|
57831
58111
|
transition,
|
|
57832
|
-
async getStatus(
|
|
57833
|
-
const run = await store.getRun(
|
|
58112
|
+
async getStatus(runId3) {
|
|
58113
|
+
const run = await store.getRun(runId3);
|
|
57834
58114
|
return run ? run.status : null;
|
|
57835
58115
|
}
|
|
57836
58116
|
};
|
|
57837
58117
|
}
|
|
57838
|
-
async function findAttempt(store,
|
|
57839
|
-
const attempts = await store.listAttempts(
|
|
58118
|
+
async function findAttempt(store, runId3, attemptId) {
|
|
58119
|
+
const attempts = await store.listAttempts(runId3);
|
|
57840
58120
|
return attempts.find((attempt) => attempt.attemptId === attemptId) ?? null;
|
|
57841
58121
|
}
|
|
57842
58122
|
|
|
@@ -57857,6 +58137,7 @@ function createReceiptService(store) {
|
|
|
57857
58137
|
runtimeImageDigest: input.admission.runtimeImageDigest,
|
|
57858
58138
|
bundleDigest: input.admission.bundleDigest,
|
|
57859
58139
|
dependencyLayerTag: input.admission.dependencyLayerTag,
|
|
58140
|
+
...input.admission.executionContract ? { executionContract: { ...input.admission.executionContract } } : {},
|
|
57860
58141
|
policy: input.admission.policy,
|
|
57861
58142
|
limits: input.admission.limits,
|
|
57862
58143
|
exitCode: null,
|
|
@@ -57885,7 +58166,7 @@ function createReceiptService(store) {
|
|
|
57885
58166
|
await store.finalizeRun(input.runId, input.status, input.attemptId);
|
|
57886
58167
|
return { receipt: written, runId: input.runId, status: input.status };
|
|
57887
58168
|
},
|
|
57888
|
-
get: (
|
|
58169
|
+
get: (runId3, attemptId) => store.getReceipt(runId3, attemptId)
|
|
57889
58170
|
};
|
|
57890
58171
|
}
|
|
57891
58172
|
|
|
@@ -61124,11 +61405,11 @@ function observedTask(states, taskArn) {
|
|
|
61124
61405
|
const state2 = states[0];
|
|
61125
61406
|
return state2.lastStatus === "STOPPED" || LIVE_TASK_STATUSES.has(state2.lastStatus) ? state2 : undefined;
|
|
61126
61407
|
}
|
|
61127
|
-
function clientTokenFor(
|
|
61128
|
-
return createHash24("sha256").update(`${
|
|
61408
|
+
function clientTokenFor(runId3, attemptId) {
|
|
61409
|
+
return createHash24("sha256").update(`${runId3}\x00${attemptId}`).digest("hex").slice(0, CLIENT_TOKEN_BYTES * 2);
|
|
61129
61410
|
}
|
|
61130
|
-
function startedByFor(
|
|
61131
|
-
return `skills-exec/${
|
|
61411
|
+
function startedByFor(runId3, attemptNumber) {
|
|
61412
|
+
return `skills-exec/${runId3}/a${attemptNumber}`;
|
|
61132
61413
|
}
|
|
61133
61414
|
function requestDigestFor(admission, attemptId) {
|
|
61134
61415
|
return createHash24("sha256").update(canonicalJson2({ admission, attemptId })).digest("hex");
|
|
@@ -61173,29 +61454,29 @@ class EcsDispatcher {
|
|
|
61173
61454
|
return { accepted: false, detail: `run already ${outcome.status}` };
|
|
61174
61455
|
}
|
|
61175
61456
|
}
|
|
61176
|
-
async cancel(
|
|
61177
|
-
const run = await this.store.getRun(
|
|
61457
|
+
async cancel(runId3) {
|
|
61458
|
+
const run = await this.store.getRun(runId3);
|
|
61178
61459
|
if (!run)
|
|
61179
61460
|
return { accepted: false, detail: "no such run" };
|
|
61180
61461
|
if (run.status === "succeeded" || run.status === "failed") {
|
|
61181
61462
|
return { accepted: false, detail: `run already ${run.status}` };
|
|
61182
61463
|
}
|
|
61183
|
-
const attempts = await this.store.listAttempts(
|
|
61464
|
+
const attempts = await this.store.listAttempts(runId3);
|
|
61184
61465
|
const current = attempts[attempts.length - 1];
|
|
61185
61466
|
if (!current) {
|
|
61186
61467
|
if (run.status === "cancelled")
|
|
61187
61468
|
return { accepted: true, detail: "already cancelled (no attempt launched)" };
|
|
61188
|
-
const cancelled = await this.stateMachine.cancel(
|
|
61469
|
+
const cancelled = await this.stateMachine.cancel(runId3);
|
|
61189
61470
|
return cancelled.ok ? { accepted: true, detail: "cancelled (no attempt launched)" } : { accepted: false, detail: cancelled.reason };
|
|
61190
61471
|
}
|
|
61191
|
-
if (!this.cancellationReceipt(await this.receipts.get(
|
|
61472
|
+
if (!this.cancellationReceipt(await this.receipts.get(runId3, current.attemptId), run.admission, current)) {
|
|
61192
61473
|
return { accepted: false, detail: "matching cancellation receipt is unavailable or contradictory" };
|
|
61193
61474
|
}
|
|
61194
61475
|
const observation = await this.reconcile(run.admission, current);
|
|
61195
61476
|
if (observation.kind !== "already-launched" && observation.kind !== "previous-terminal") {
|
|
61196
61477
|
return { accepted: false, detail: "task state unknown; cancellation is not yet confirmed" };
|
|
61197
61478
|
}
|
|
61198
|
-
const taskArn = observation.kind === "already-launched" ? observation.taskId : (await this.store.listAttempts(
|
|
61479
|
+
const taskArn = observation.kind === "already-launched" ? observation.taskId : (await this.store.listAttempts(runId3)).find((row) => row.attemptId === current.attemptId)?.taskId ?? null;
|
|
61199
61480
|
if (!taskArn)
|
|
61200
61481
|
return { accepted: false, detail: "stopped task identity is not confirmed" };
|
|
61201
61482
|
if (observation.kind === "already-launched") {
|
|
@@ -61208,7 +61489,7 @@ class EcsDispatcher {
|
|
|
61208
61489
|
}
|
|
61209
61490
|
}
|
|
61210
61491
|
if (run.status !== "cancelled") {
|
|
61211
|
-
const cancelled = await this.stateMachine.cancel(
|
|
61492
|
+
const cancelled = await this.stateMachine.cancel(runId3);
|
|
61212
61493
|
if (!cancelled.ok)
|
|
61213
61494
|
return { accepted: false, detail: cancelled.reason };
|
|
61214
61495
|
}
|
|
@@ -61219,22 +61500,22 @@ class EcsDispatcher {
|
|
|
61219
61500
|
}
|
|
61220
61501
|
return { accepted: true, target: taskArn, detail: "cancelled after confirmed task stop and receipt" };
|
|
61221
61502
|
}
|
|
61222
|
-
async launchAttempt(
|
|
61223
|
-
const run = await this.store.getRun(
|
|
61503
|
+
async launchAttempt(runId3) {
|
|
61504
|
+
const run = await this.store.getRun(runId3);
|
|
61224
61505
|
if (!run)
|
|
61225
61506
|
return { kind: "no-admission" };
|
|
61226
61507
|
if (run.status === "succeeded" || run.status === "failed" || run.status === "cancelled") {
|
|
61227
61508
|
return { kind: "run-terminal", status: run.status };
|
|
61228
61509
|
}
|
|
61229
|
-
const attempts = await this.store.listAttempts(
|
|
61510
|
+
const attempts = await this.store.listAttempts(runId3);
|
|
61230
61511
|
const previous = attempts[attempts.length - 1];
|
|
61231
61512
|
if (previous) {
|
|
61232
61513
|
return this.reconcile(run.admission, previous);
|
|
61233
61514
|
}
|
|
61234
61515
|
const attemptNumber = 1;
|
|
61235
|
-
const attempt = await this.store.createAttempt({ runId:
|
|
61516
|
+
const attempt = await this.store.createAttempt({ runId: runId3, attemptNumber });
|
|
61236
61517
|
const claimed = await this.stateMachine.claim({
|
|
61237
|
-
runId:
|
|
61518
|
+
runId: runId3,
|
|
61238
61519
|
attemptId: attempt.attemptId,
|
|
61239
61520
|
workerId: this.workerId,
|
|
61240
61521
|
expectedLeaseGeneration: 0
|
|
@@ -61242,11 +61523,11 @@ class EcsDispatcher {
|
|
|
61242
61523
|
if (!claimed.ok) {
|
|
61243
61524
|
return { kind: "claim-refused", attemptId: attempt.attemptId, reason: claimed.reason };
|
|
61244
61525
|
}
|
|
61245
|
-
const clientToken = clientTokenFor(
|
|
61246
|
-
const startedBy = startedByFor(
|
|
61526
|
+
const clientToken = clientTokenFor(runId3, attempt.attemptId);
|
|
61527
|
+
const startedBy = startedByFor(runId3, attemptNumber);
|
|
61247
61528
|
const requestDigest = requestDigestFor(run.admission, attempt.attemptId);
|
|
61248
61529
|
const intent = await this.store.recordLaunchIntent({
|
|
61249
|
-
runId:
|
|
61530
|
+
runId: runId3,
|
|
61250
61531
|
attemptId: attempt.attemptId,
|
|
61251
61532
|
clientToken,
|
|
61252
61533
|
requestDigest,
|
|
@@ -61264,11 +61545,11 @@ class EcsDispatcher {
|
|
|
61264
61545
|
try {
|
|
61265
61546
|
result = await this.client.runTask(this.runTaskInput(run.admission, intent.attempt, clientToken, startedBy, requestDigest));
|
|
61266
61547
|
} catch {
|
|
61267
|
-
await this.store.recordLaunchState({ runId:
|
|
61548
|
+
await this.store.recordLaunchState({ runId: runId3, attemptId: attempt.attemptId, launchState: "ambiguous" });
|
|
61268
61549
|
const reconciled = await this.reconcile(run.admission, intent.attempt);
|
|
61269
61550
|
return reconciled;
|
|
61270
61551
|
}
|
|
61271
|
-
await this.store.recordLaunchState({ runId:
|
|
61552
|
+
await this.store.recordLaunchState({ runId: runId3, attemptId: attempt.attemptId, launchState: "launched", taskId: result.taskArn });
|
|
61272
61553
|
return { kind: "launched", attemptId: attempt.attemptId, taskId: result.taskArn };
|
|
61273
61554
|
}
|
|
61274
61555
|
async reconcile(admission, attempt) {
|
|
@@ -61330,7 +61611,7 @@ class EcsDispatcher {
|
|
|
61330
61611
|
}
|
|
61331
61612
|
cancellationReceipt(receipt, admission, attempt) {
|
|
61332
61613
|
try {
|
|
61333
|
-
return !!receipt && receipt.runId === admission.runId && receipt.attemptId === attempt.attemptId && !!attempt.clientToken && receipt.clientToken === attempt.clientToken && !!attempt.requestDigest && receipt.requestDigest === attempt.requestDigest && !!attempt.startedBy && receipt.startedBy === attempt.startedBy && receipt.bundleDigest === admission.bundleDigest && receipt.runtimeImageDigest === admission.runtimeImageDigest && receipt.dependencyLayerTag === admission.dependencyLayerTag && canonicalJson2(receipt.policy) === canonicalJson2(admission.policy) && canonicalJson2(receipt.limits) === canonicalJson2(admission.limits) && (receipt.taskId === null || receipt.taskId === attempt.taskId) && (receipt.status === null && receipt.completedAt === null || receipt.status === "cancelled" && typeof receipt.completedAt === "string" && Number.isFinite(Date.parse(receipt.completedAt)));
|
|
61614
|
+
return !!receipt && receipt.runId === admission.runId && receipt.attemptId === attempt.attemptId && !!attempt.clientToken && receipt.clientToken === attempt.clientToken && !!attempt.requestDigest && receipt.requestDigest === attempt.requestDigest && !!attempt.startedBy && receipt.startedBy === attempt.startedBy && receipt.bundleDigest === admission.bundleDigest && receipt.runtimeImageDigest === admission.runtimeImageDigest && receipt.dependencyLayerTag === admission.dependencyLayerTag && canonicalJson2(receipt.executionContract ?? null) === canonicalJson2(admission.executionContract ?? null) && canonicalJson2(receipt.policy) === canonicalJson2(admission.policy) && canonicalJson2(receipt.limits) === canonicalJson2(admission.limits) && (receipt.taskId === null || receipt.taskId === attempt.taskId) && (receipt.status === null && receipt.completedAt === null || receipt.status === "cancelled" && typeof receipt.completedAt === "string" && Number.isFinite(Date.parse(receipt.completedAt)));
|
|
61334
61615
|
} catch {
|
|
61335
61616
|
return false;
|
|
61336
61617
|
}
|
|
@@ -61427,7 +61708,174 @@ function validText(value) {
|
|
|
61427
61708
|
}
|
|
61428
61709
|
|
|
61429
61710
|
// src/server/runtime-policy.ts
|
|
61430
|
-
import { createHash as
|
|
61711
|
+
import { createHash as createHash26, createHmac as createHmac3, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
61712
|
+
|
|
61713
|
+
// src/server/runtime-pure-contract.ts
|
|
61714
|
+
import { createHash as createHash25 } from "crypto";
|
|
61715
|
+
var PURE_DESCRIPTOR = Object.freeze({
|
|
61716
|
+
id: "regex-test.v1",
|
|
61717
|
+
runtime: "bun",
|
|
61718
|
+
adapter: "skills-input-json.v1",
|
|
61719
|
+
input: Object.freeze({ patternBytes: 512, textBytes: 4096, totalBytes: 8192, flags: "dgimsuvy" }),
|
|
61720
|
+
output: "regex-matches.v1",
|
|
61721
|
+
secrets: "none",
|
|
61722
|
+
egress: "deny",
|
|
61723
|
+
maxDurationMs: 5000,
|
|
61724
|
+
maxMemoryMb: 512,
|
|
61725
|
+
maxCpuUnits: 256,
|
|
61726
|
+
stdoutBytes: 16384,
|
|
61727
|
+
stderrBytes: 8192,
|
|
61728
|
+
artifactsBytes: 0
|
|
61729
|
+
});
|
|
61730
|
+
var PURE_DESCRIPTOR_DIGEST = digestInput(PURE_DESCRIPTOR);
|
|
61731
|
+
var PURE_LIMITS = Object.freeze({
|
|
61732
|
+
maxDurationMs: 5000,
|
|
61733
|
+
maxMemoryMb: 512,
|
|
61734
|
+
maxCpuUnits: 256,
|
|
61735
|
+
maxArtifactsBytes: 0,
|
|
61736
|
+
maxConcurrency: 1
|
|
61737
|
+
});
|
|
61738
|
+
var hash2 = (bytes) => createHash25("sha256").update(bytes).digest("hex");
|
|
61739
|
+
var object5 = (v2) => {
|
|
61740
|
+
if (!v2 || typeof v2 !== "object" || Array.isArray(v2))
|
|
61741
|
+
throw Error("Pure contract requires an object");
|
|
61742
|
+
return v2;
|
|
61743
|
+
};
|
|
61744
|
+
function keys2(v2, allowed) {
|
|
61745
|
+
if (Object.keys(v2).some((k4) => !allowed.includes(k4)))
|
|
61746
|
+
throw Error("Unsupported pure contract field");
|
|
61747
|
+
}
|
|
61748
|
+
function uniqueJson(text2) {
|
|
61749
|
+
const result = JSON.parse(text2);
|
|
61750
|
+
let i4 = 0;
|
|
61751
|
+
const ws = () => {
|
|
61752
|
+
while (/\s/.test(text2[i4] ?? "") && i4 < text2.length)
|
|
61753
|
+
i4++;
|
|
61754
|
+
};
|
|
61755
|
+
const string = () => {
|
|
61756
|
+
const start = i4++;
|
|
61757
|
+
while (i4 < text2.length) {
|
|
61758
|
+
const c4 = text2[i4++];
|
|
61759
|
+
if (c4 === "\\")
|
|
61760
|
+
i4++;
|
|
61761
|
+
else if (c4 === '"')
|
|
61762
|
+
break;
|
|
61763
|
+
}
|
|
61764
|
+
return JSON.parse(text2.slice(start, i4));
|
|
61765
|
+
};
|
|
61766
|
+
const value = (depth) => {
|
|
61767
|
+
if (depth > 32)
|
|
61768
|
+
throw Error("JSON nesting limit exceeded");
|
|
61769
|
+
ws();
|
|
61770
|
+
if (text2[i4] === '"') {
|
|
61771
|
+
string();
|
|
61772
|
+
return;
|
|
61773
|
+
}
|
|
61774
|
+
if (text2[i4] === "{") {
|
|
61775
|
+
i4++;
|
|
61776
|
+
ws();
|
|
61777
|
+
const seen = new Set;
|
|
61778
|
+
if (text2[i4] === "}") {
|
|
61779
|
+
i4++;
|
|
61780
|
+
return;
|
|
61781
|
+
}
|
|
61782
|
+
for (;; ) {
|
|
61783
|
+
ws();
|
|
61784
|
+
const key = string();
|
|
61785
|
+
if (seen.has(key))
|
|
61786
|
+
throw Error("Duplicate JSON key");
|
|
61787
|
+
seen.add(key);
|
|
61788
|
+
ws();
|
|
61789
|
+
i4++;
|
|
61790
|
+
value(depth + 1);
|
|
61791
|
+
ws();
|
|
61792
|
+
if (text2[i4++] === "}")
|
|
61793
|
+
return;
|
|
61794
|
+
}
|
|
61795
|
+
}
|
|
61796
|
+
if (text2[i4] === "[") {
|
|
61797
|
+
i4++;
|
|
61798
|
+
ws();
|
|
61799
|
+
if (text2[i4] === "]") {
|
|
61800
|
+
i4++;
|
|
61801
|
+
return;
|
|
61802
|
+
}
|
|
61803
|
+
for (;; ) {
|
|
61804
|
+
value(depth + 1);
|
|
61805
|
+
ws();
|
|
61806
|
+
if (text2[i4++] === "]")
|
|
61807
|
+
return;
|
|
61808
|
+
}
|
|
61809
|
+
}
|
|
61810
|
+
while (i4 < text2.length && !/[\s,\]}]/.test(text2[i4]))
|
|
61811
|
+
i4++;
|
|
61812
|
+
};
|
|
61813
|
+
value(0);
|
|
61814
|
+
return result;
|
|
61815
|
+
}
|
|
61816
|
+
function pureInput(value) {
|
|
61817
|
+
const v2 = object5(value);
|
|
61818
|
+
keys2(v2, ["pattern", "text", "flags"]);
|
|
61819
|
+
if (typeof v2.pattern !== "string" || Buffer.byteLength(v2.pattern) > 512 || typeof v2.text !== "string" || Buffer.byteLength(v2.text) > 4096 || typeof v2.flags !== "string" || !/^[dgimsuvy]*$/.test(v2.flags) || new Set(v2.flags).size !== v2.flags.length || v2.flags.includes("u") && v2.flags.includes("v") || Buffer.byteLength(JSON.stringify(v2)) > 8192)
|
|
61820
|
+
throw Error("Invalid pure regex input");
|
|
61821
|
+
return { pattern: v2.pattern, text: v2.text, flags: v2.flags };
|
|
61822
|
+
}
|
|
61823
|
+
function assertPureContract(value) {
|
|
61824
|
+
const v2 = object5(value);
|
|
61825
|
+
keys2(v2, ["id", "descriptorDigest", "entrypoint", "entrypointDigest"]);
|
|
61826
|
+
if (v2.id !== PURE_DESCRIPTOR.id || v2.descriptorDigest !== PURE_DESCRIPTOR_DIGEST || typeof v2.entrypoint !== "string" || !/^src\/[a-zA-Z0-9_/-]+\.[cm]?[jt]s$/.test(v2.entrypoint) || v2.entrypoint.includes("//") || typeof v2.entrypointDigest !== "string" || !/^[a-f0-9]{64}$/.test(v2.entrypointDigest))
|
|
61827
|
+
throw Error("Unsupported pure execution contract");
|
|
61828
|
+
}
|
|
61829
|
+
function assertPureAdmission(a4) {
|
|
61830
|
+
assertPureContract(a4.executionContract);
|
|
61831
|
+
if (a4.runtime !== "bun" || a4.dependencyLayerTag !== null || a4.policy.egress !== "deny" || a4.policy.egressAllowlist.length !== 0 || a4.policy.networkByteCap !== 0 || Object.entries(PURE_LIMITS).some(([k4, v2]) => a4.limits[k4] !== v2))
|
|
61832
|
+
throw Error("Pure admission isolation policy mismatch");
|
|
61833
|
+
return a4.executionContract;
|
|
61834
|
+
}
|
|
61835
|
+
async function inspectPureBundle(bytes, contract) {
|
|
61836
|
+
assertPureContract(contract);
|
|
61837
|
+
const { entries } = await inspectSkillBundle(bytes, { limits: {
|
|
61838
|
+
compressedBytes: 1e6,
|
|
61839
|
+
decompressedBytes: 4000000,
|
|
61840
|
+
fileBytes: 1e6,
|
|
61841
|
+
entries: 100,
|
|
61842
|
+
timeoutMs: 5000
|
|
61843
|
+
} });
|
|
61844
|
+
const entry = entries.find((e5) => e5.path === contract.entrypoint);
|
|
61845
|
+
if (!entry || hash2(entry.bytes) !== contract.entrypointDigest)
|
|
61846
|
+
throw Error("Pure entrypoint integrity failed");
|
|
61847
|
+
if (entries.some((e5) => e5.path.split("/").includes("node_modules")))
|
|
61848
|
+
throw Error("Pure bundle cannot supply dependencies");
|
|
61849
|
+
const read2 = (name) => {
|
|
61850
|
+
const entry2 = entries.find((e5) => e5.path === name);
|
|
61851
|
+
if (!entry2)
|
|
61852
|
+
throw Error("Pure bundle manifest missing");
|
|
61853
|
+
return object5(uniqueJson(Buffer.from(entry2.bytes).toString("utf8")));
|
|
61854
|
+
};
|
|
61855
|
+
const manifest = read2("skill.json"), runtime = object5(manifest.runtime), pkg = read2("package.json");
|
|
61856
|
+
if (manifest.kind !== "executable" || runtime.runtime !== "bun" || runtime.entrypoint !== contract.entrypoint || !Array.isArray(runtime.env) || runtime.env.length !== 0 || runtime.system_deps !== undefined && (!Array.isArray(runtime.system_deps) || runtime.system_deps.length !== 0) || runtime.needs_network === true || manifest.needs_network === true)
|
|
61857
|
+
throw Error("Pure bundle requires a credential-free self-contained Bun entrypoint");
|
|
61858
|
+
for (const key of ["dependencies", "optionalDependencies", "peerDependencies", "devDependencies", "scripts"])
|
|
61859
|
+
if (pkg[key] !== undefined && Object.keys(object5(pkg[key])).length)
|
|
61860
|
+
throw Error("Pure bundle cannot install dependencies or lifecycle scripts");
|
|
61861
|
+
return entries;
|
|
61862
|
+
}
|
|
61863
|
+
function validatePureOutput(stdout, input) {
|
|
61864
|
+
if (Buffer.byteLength(stdout) > PURE_DESCRIPTOR.stdoutBytes)
|
|
61865
|
+
throw Error("Pure output limit exceeded");
|
|
61866
|
+
const v2 = object5(uniqueJson(stdout));
|
|
61867
|
+
keys2(v2, ["pattern", "flags", "matches"]);
|
|
61868
|
+
if (v2.pattern !== input.pattern || v2.flags !== input.flags || !Array.isArray(v2.matches))
|
|
61869
|
+
throw Error("Pure output does not match admitted input");
|
|
61870
|
+
for (const value of v2.matches) {
|
|
61871
|
+
const m2 = object5(value);
|
|
61872
|
+
keys2(m2, ["match", "groups", "namedGroups", "index"]);
|
|
61873
|
+
if (typeof m2.match !== "string" || !Number.isInteger(m2.index) || m2.index < 0 || m2.index > input.text.length || input.text.slice(m2.index, m2.index + m2.match.length) !== m2.match || !Array.isArray(m2.groups) || m2.groups.some((x2) => x2 !== null && typeof x2 !== "string") || m2.namedGroups !== null && Object.values(object5(m2.namedGroups)).some((x2) => typeof x2 !== "string"))
|
|
61874
|
+
throw Error("Invalid pure match output");
|
|
61875
|
+
}
|
|
61876
|
+
}
|
|
61877
|
+
|
|
61878
|
+
// src/server/runtime-policy.ts
|
|
61431
61879
|
var RUNTIME_MAX_INPUT_BYTES = 1e5;
|
|
61432
61880
|
var RUNTIME_MAX_BUNDLE_BYTES = 1e6;
|
|
61433
61881
|
var RUNTIME_MAX_ARTIFACT_BYTES = 2000000;
|
|
@@ -61462,8 +61910,25 @@ function readRuntimeConfig(env = process.env) {
|
|
|
61462
61910
|
for (const key of ["subnets", "securityGroups"])
|
|
61463
61911
|
if (!Array.isArray(c4[key]) || !c4[key].length || c4[key].some((v2) => typeof v2 !== "string" || !v2))
|
|
61464
61912
|
throw Error(`Runtime configuration missing ${key}`);
|
|
61465
|
-
if (!Array.isArray(c4.reviewedBundles) || !c4.reviewedBundles.length
|
|
61466
|
-
throw Error("Runtime requires exact reviewed
|
|
61913
|
+
if (!Array.isArray(c4.reviewedBundles) || !c4.reviewedBundles.length)
|
|
61914
|
+
throw Error("Runtime requires exact reviewed versions and bundle digests");
|
|
61915
|
+
const seen = new Set;
|
|
61916
|
+
for (const b4 of c4.reviewedBundles) {
|
|
61917
|
+
if (!b4 || typeof b4.slug !== "string" || !/^[a-z0-9-]+$/.test(b4.slug) || typeof b4.version !== "string" || !/^\d+\.\d+\.\d+$/.test(b4.version) || !/^[a-f0-9]{64}$/.test(b4.sha256))
|
|
61918
|
+
throw Error("Runtime requires exact reviewed versions and bundle digests");
|
|
61919
|
+
if (b4.executionContract) {
|
|
61920
|
+
if (b4.slug === "pdf-generate")
|
|
61921
|
+
throw Error("pdf-generate is reserved for the legacy PDF contract");
|
|
61922
|
+
assertPureContract(b4.executionContract);
|
|
61923
|
+
if (typeof b4.tenantId !== "string" || !b4.tenantId.trim() || b4.imageDigest !== c4.imageDigest)
|
|
61924
|
+
throw Error("Pure runtime review must bind tenant and configured image");
|
|
61925
|
+
} else if (b4.slug !== "pdf-generate")
|
|
61926
|
+
throw Error("Missing reviewed execution contract");
|
|
61927
|
+
const key = JSON.stringify([b4.executionContract ? b4.tenantId : null, b4.slug, b4.version, b4.sha256]);
|
|
61928
|
+
if (seen.has(key))
|
|
61929
|
+
throw Error("Ambiguous runtime review");
|
|
61930
|
+
seen.add(key);
|
|
61931
|
+
}
|
|
61467
61932
|
return c4;
|
|
61468
61933
|
}
|
|
61469
61934
|
function runtimeInput(value) {
|
|
@@ -61499,7 +61964,7 @@ function verifyRuntimeToken(token, key, admission, attempt, now = Date.now()) {
|
|
|
61499
61964
|
const actual = Buffer.from(token);
|
|
61500
61965
|
return actual.length === expected.length && timingSafeEqual2(actual, expected);
|
|
61501
61966
|
}
|
|
61502
|
-
var hashBytes = (bytes) =>
|
|
61967
|
+
var hashBytes = (bytes) => createHash26("sha256").update(bytes).digest("hex");
|
|
61503
61968
|
|
|
61504
61969
|
// src/server/runtime-api.ts
|
|
61505
61970
|
async function createRuntimeService(options) {
|
|
@@ -61575,7 +62040,7 @@ async function body3(request2, max) {
|
|
|
61575
62040
|
}
|
|
61576
62041
|
chunks.push(v2.value);
|
|
61577
62042
|
}
|
|
61578
|
-
return
|
|
62043
|
+
return uniqueJson(Buffer.concat(chunks).toString("utf8"));
|
|
61579
62044
|
}
|
|
61580
62045
|
var scope = (p2, action) => p2.scopes.includes("*") || p2.scopes.includes("runs:*") || p2.scopes.includes(action) || action === "runs:read" && p2.scopes.includes("skills:read");
|
|
61581
62046
|
function publicJob(job) {
|
|
@@ -61589,6 +62054,7 @@ function publicJob(job) {
|
|
|
61589
62054
|
bundleDigest: a4.bundleDigest,
|
|
61590
62055
|
inputDigest: a4.inputDigest,
|
|
61591
62056
|
runtimeImageDigest: a4.runtimeImageDigest,
|
|
62057
|
+
executionContract: a4.executionContract ?? { id: "pdf.v1" },
|
|
61592
62058
|
status: job.execution.status,
|
|
61593
62059
|
createdAt: a4.createdAt,
|
|
61594
62060
|
updatedAt: job.execution.updatedAt,
|
|
@@ -61599,6 +62065,33 @@ function publicJob(job) {
|
|
|
61599
62065
|
artifacts: (job.result?.artifacts ?? []).map(({ base64, ...meta }) => meta)
|
|
61600
62066
|
};
|
|
61601
62067
|
}
|
|
62068
|
+
function findReview(service, tenant, slug, version2, sha2562) {
|
|
62069
|
+
return service.config.reviewedBundles.find((b4) => b4.slug === slug && b4.version === version2 && b4.sha256 === sha2562 && (!b4.executionContract || b4.tenantId === tenant && b4.imageDigest === service.config.imageDigest));
|
|
62070
|
+
}
|
|
62071
|
+
function sameContract(admission, contract, image) {
|
|
62072
|
+
return digestInput(admission.executionContract ?? null) === digestInput(contract ?? null) && (!contract || admission.runtimeImageDigest === image);
|
|
62073
|
+
}
|
|
62074
|
+
|
|
62075
|
+
class RuntimeBundleError extends Error {
|
|
62076
|
+
code;
|
|
62077
|
+
status;
|
|
62078
|
+
constructor(code, status) {
|
|
62079
|
+
super(code);
|
|
62080
|
+
this.code = code;
|
|
62081
|
+
this.status = status;
|
|
62082
|
+
}
|
|
62083
|
+
}
|
|
62084
|
+
async function reviewedBytes(service, principal, digest4) {
|
|
62085
|
+
const bundle2 = await service.productStore.getSkillBundle(principal, digest4);
|
|
62086
|
+
if (!bundle2)
|
|
62087
|
+
throw new RuntimeBundleError("BUNDLE_UNAVAILABLE", 503);
|
|
62088
|
+
if (bundle2.byteSize > RUNTIME_MAX_BUNDLE_BYTES)
|
|
62089
|
+
throw new RuntimeBundleError("BUNDLE_LIMIT", 413);
|
|
62090
|
+
const bytes = await service.artifacts.readBundle(bundle2);
|
|
62091
|
+
if (!bytes || bytes.length > RUNTIME_MAX_BUNDLE_BYTES || hashBytes(bytes) !== digest4)
|
|
62092
|
+
throw new RuntimeBundleError("BUNDLE_INTEGRITY_FAILED", 503);
|
|
62093
|
+
return bytes;
|
|
62094
|
+
}
|
|
61602
62095
|
async function handleRuntimeApiRequest(request2, principal, service) {
|
|
61603
62096
|
const segments = path(request2);
|
|
61604
62097
|
if (segments[0] !== "api" || segments[1] !== "v1" || segments[2] !== "executions")
|
|
@@ -61611,9 +62104,50 @@ async function handleRuntimeApiRequest(request2, principal, service) {
|
|
|
61611
62104
|
if (!id2 || segments.length > 6 || !/^[a-z0-9_-]+$/.test(id2))
|
|
61612
62105
|
return refusal2("INVALID_EXECUTION_PATH", 400);
|
|
61613
62106
|
try {
|
|
62107
|
+
if (request2.method === "GET" && sub === "eligibility" && segments.length === 5) {
|
|
62108
|
+
const query = new URL(request2.url).searchParams;
|
|
62109
|
+
const versionName = query.get("version");
|
|
62110
|
+
if (!versionName || !/^\d+\.\d+\.\d+$/.test(versionName))
|
|
62111
|
+
return refusal2("REVIEWED_VERSION_REQUIRED", 400);
|
|
62112
|
+
const skill = await service.productStore.getSkill(principal, id2);
|
|
62113
|
+
if (skill?.lifecycle === "archived")
|
|
62114
|
+
return refusal2("SKILL_ARCHIVED", 410);
|
|
62115
|
+
const version2 = skill && !skill.tombstonedAt && await service.productStore.getSkillVersion(principal, id2, versionName);
|
|
62116
|
+
if (!version2)
|
|
62117
|
+
return refusal2("SKILL_VERSION_NOT_FOUND", 404);
|
|
62118
|
+
const pinned = query.get("bundleDigest");
|
|
62119
|
+
if (pinned && pinned.replace(/^sha256:/, "") !== version2.bundleSha256)
|
|
62120
|
+
return refusal2("SELECTION_DIGEST_MISMATCH", 409);
|
|
62121
|
+
const review = findReview(service, principal.orgId, id2, versionName, version2.bundleSha256);
|
|
62122
|
+
if (!review)
|
|
62123
|
+
return json3({
|
|
62124
|
+
contractVersion: 1,
|
|
62125
|
+
eligible: false,
|
|
62126
|
+
reason: "BUNDLE_NOT_REVIEWED_FOR_CLOUD",
|
|
62127
|
+
skill: id2,
|
|
62128
|
+
version: versionName,
|
|
62129
|
+
bundleDigest: version2.bundleSha256
|
|
62130
|
+
});
|
|
62131
|
+
if (review.executionContract) {
|
|
62132
|
+
const bytes = await reviewedBytes(service, principal, version2.bundleSha256);
|
|
62133
|
+
await inspectPureBundle(bytes, review.executionContract);
|
|
62134
|
+
}
|
|
62135
|
+
return json3({
|
|
62136
|
+
contractVersion: 1,
|
|
62137
|
+
eligible: true,
|
|
62138
|
+
skill: id2,
|
|
62139
|
+
version: versionName,
|
|
62140
|
+
bundleDigest: version2.bundleSha256,
|
|
62141
|
+
runtimeImageDigest: service.config.imageDigest,
|
|
62142
|
+
executionContract: review.executionContract ?? { id: "pdf.v1" },
|
|
62143
|
+
secrets: "none",
|
|
62144
|
+
egress: "deny",
|
|
62145
|
+
...review.executionContract ? { descriptor: PURE_DESCRIPTOR, limits: PURE_LIMITS } : {}
|
|
62146
|
+
});
|
|
62147
|
+
}
|
|
61614
62148
|
if (request2.method === "POST" && !sub) {
|
|
61615
62149
|
const raw = await body3(request2, 150000);
|
|
61616
|
-
if (
|
|
62150
|
+
if (typeof raw.version !== "string" || !/^\d+\.\d+\.\d+$/.test(raw.version))
|
|
61617
62151
|
return refusal2("REVIEWED_VERSION_REQUIRED", 400);
|
|
61618
62152
|
if (Object.keys(raw).some((k4) => ![
|
|
61619
62153
|
"version",
|
|
@@ -61625,31 +62159,32 @@ async function handleRuntimeApiRequest(request2, principal, service) {
|
|
|
61625
62159
|
return refusal2("UNSUPPORTED_EXECUTION_FIELD", 400);
|
|
61626
62160
|
if (raw.workspaceId !== undefined && raw.workspaceId !== principal.orgId)
|
|
61627
62161
|
return refusal2("SELECTION_WORKSPACE_MISMATCH", 409);
|
|
61628
|
-
const input = runtimeInput(raw.input);
|
|
61629
62162
|
const key = request2.headers.get("idempotency-key") ?? raw.idempotencyKey;
|
|
61630
62163
|
if (typeof key !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(key))
|
|
61631
62164
|
return refusal2("IDEMPOTENCY_KEY_REQUIRED", 400);
|
|
61632
62165
|
const skill = await service.productStore.getSkill(principal, id2);
|
|
61633
62166
|
if (!skill || skill.tombstonedAt)
|
|
61634
62167
|
return refusal2("SKILL_NOT_FOUND", 404);
|
|
62168
|
+
if (skill.lifecycle === "archived")
|
|
62169
|
+
return refusal2("SKILL_ARCHIVED", 410);
|
|
61635
62170
|
const version2 = await service.productStore.getSkillVersion(principal, id2, raw.version);
|
|
61636
62171
|
if (!version2)
|
|
61637
62172
|
return refusal2("SKILL_VERSION_NOT_FOUND", 404);
|
|
61638
62173
|
if (raw.bundleDigest !== undefined && (typeof raw.bundleDigest !== "string" || raw.bundleDigest.replace(/^sha256:/, "") !== version2.bundleSha256))
|
|
61639
62174
|
return refusal2("SELECTION_DIGEST_MISMATCH", 409);
|
|
61640
|
-
|
|
62175
|
+
const review = findReview(service, principal.orgId, id2, raw.version, version2.bundleSha256);
|
|
62176
|
+
if (!review)
|
|
61641
62177
|
return refusal2("BUNDLE_NOT_REVIEWED_FOR_CLOUD", 403);
|
|
62178
|
+
const contract = review.executionContract;
|
|
62179
|
+
if (contract && (raw.workspaceId !== principal.orgId || typeof raw.bundleDigest !== "string"))
|
|
62180
|
+
return refusal2("PURE_SELECTION_REQUIRED", 400);
|
|
62181
|
+
const input = contract ? pureInput(raw.input) : runtimeInput(raw.input);
|
|
61642
62182
|
const existing = await service.store.getRunByKey(principal.orgId, key);
|
|
61643
|
-
if (existing && (existing.admission.skillId !== id2 || existing.admission.skillVersion !== raw.version || existing.admission.bundleDigest !== version2.bundleSha256 || existing.admission.inputDigest !== digestInput(input)))
|
|
62183
|
+
if (existing && (existing.admission.skillId !== id2 || existing.admission.skillVersion !== raw.version || existing.admission.bundleDigest !== version2.bundleSha256 || existing.admission.inputDigest !== digestInput(input) || !sameContract(existing.admission, contract, service.config.imageDigest)))
|
|
61644
62184
|
return refusal2("IDEMPOTENCY_CONFLICT", 409);
|
|
61645
|
-
const
|
|
61646
|
-
if (
|
|
61647
|
-
|
|
61648
|
-
if (bundle2.byteSize > RUNTIME_MAX_BUNDLE_BYTES)
|
|
61649
|
-
return refusal2("BUNDLE_LIMIT", 413);
|
|
61650
|
-
const bytes = await service.artifacts.readBundle(bundle2);
|
|
61651
|
-
if (!bytes || hashBytes(bytes) !== version2.bundleSha256)
|
|
61652
|
-
return refusal2("BUNDLE_INTEGRITY_FAILED", 503);
|
|
62185
|
+
const bytes = await reviewedBytes(service, principal, version2.bundleSha256);
|
|
62186
|
+
if (contract)
|
|
62187
|
+
await inspectPureBundle(bytes, contract);
|
|
61653
62188
|
const admission = createSubmitRunService({
|
|
61654
62189
|
store: service.store,
|
|
61655
62190
|
imageProfiles: createImageProfileRegistry({
|
|
@@ -61671,12 +62206,13 @@ async function handleRuntimeApiRequest(request2, principal, service) {
|
|
|
61671
62206
|
input,
|
|
61672
62207
|
idempotencyKey: key,
|
|
61673
62208
|
runtime: "bun",
|
|
61674
|
-
|
|
62209
|
+
...contract ? { executionContract: contract } : {},
|
|
62210
|
+
limits: contract ? PURE_LIMITS : {
|
|
61675
62211
|
maxDurationMs: RUNTIME_TIMEOUT_MS,
|
|
61676
62212
|
maxArtifactsBytes: RUNTIME_MAX_ARTIFACT_BYTES
|
|
61677
62213
|
}
|
|
61678
62214
|
});
|
|
61679
|
-
if (run.tenantId !== principal.orgId || run.skillId !== id2 || run.skillVersion !== raw.version || run.bundleDigest !== version2.bundleSha256 || run.inputDigest !== digestInput(input))
|
|
62215
|
+
if (run.tenantId !== principal.orgId || run.skillId !== id2 || run.skillVersion !== raw.version || run.bundleDigest !== version2.bundleSha256 || run.inputDigest !== digestInput(input) || !sameContract(run, contract, service.config.imageDigest))
|
|
61680
62216
|
return refusal2("IDEMPOTENCY_CONFLICT", 409);
|
|
61681
62217
|
await service.store.mutate(run.runId, (j4) => {
|
|
61682
62218
|
if (!j4.input) {
|
|
@@ -61768,6 +62304,8 @@ async function handleRuntimeApiRequest(request2, principal, service) {
|
|
|
61768
62304
|
}
|
|
61769
62305
|
return refusal2("EXECUTION_ROUTE_NOT_FOUND", 404);
|
|
61770
62306
|
} catch (error) {
|
|
62307
|
+
if (error instanceof RuntimeBundleError)
|
|
62308
|
+
return refusal2(error.code, error.status);
|
|
61771
62309
|
if (error.message === "Runtime concurrency limit reached")
|
|
61772
62310
|
return refusal2("RUNTIME_CONCURRENCY_LIMIT", 429);
|
|
61773
62311
|
return refusal2("EXECUTION_REQUEST_FAILED", 400, error.message.startsWith("PDF ") ? error.message : "Execution request could not be completed");
|
|
@@ -61798,7 +62336,7 @@ async function handleRuntimeWorkerRequest(request2, service) {
|
|
|
61798
62336
|
}
|
|
61799
62337
|
if (request2.method === "POST" && s2[4] === "complete") {
|
|
61800
62338
|
try {
|
|
61801
|
-
const result = validatedResult(await body3(request2, 3000000));
|
|
62339
|
+
const result = validatedResult(await body3(request2, 3000000), job);
|
|
61802
62340
|
await service.store.mutate(s2[3], (j4) => {
|
|
61803
62341
|
if (j4.execution.status === "cancelled")
|
|
61804
62342
|
throw Error("Execution cancelled");
|
|
@@ -61835,12 +62373,28 @@ async function handleRuntimeWorkerRequest(request2, service) {
|
|
|
61835
62373
|
}
|
|
61836
62374
|
return refusal2("METHOD_NOT_ALLOWED", 405);
|
|
61837
62375
|
}
|
|
61838
|
-
function validatedResult(value) {
|
|
62376
|
+
function validatedResult(value, job) {
|
|
61839
62377
|
if (!value || typeof value !== "object")
|
|
61840
62378
|
throw Error("Invalid result");
|
|
61841
62379
|
const v2 = value;
|
|
61842
62380
|
if (!Number.isInteger(v2.exitCode) || v2.exitCode < 0 || v2.exitCode > 255 || typeof v2.stdout !== "string" || typeof v2.stderr !== "string" || Buffer.byteLength(v2.stdout) > RUNTIME_MAX_LOG_BYTES || Buffer.byteLength(v2.stderr) > RUNTIME_MAX_LOG_BYTES || !Array.isArray(v2.artifacts) || v2.artifacts.length > 2)
|
|
61843
62381
|
throw Error("Invalid result");
|
|
62382
|
+
if (job.execution.admission.executionContract) {
|
|
62383
|
+
assertPureAdmission(job.execution.admission);
|
|
62384
|
+
if (v2.artifacts.length || Buffer.byteLength(v2.stdout) > PURE_DESCRIPTOR.stdoutBytes || Buffer.byteLength(v2.stderr) > PURE_DESCRIPTOR.stderrBytes)
|
|
62385
|
+
throw Error("Invalid pure result limits");
|
|
62386
|
+
if (v2.exitCode === 0)
|
|
62387
|
+
validatePureOutput(v2.stdout, pureInput(job.input));
|
|
62388
|
+
return {
|
|
62389
|
+
exitCode: v2.exitCode,
|
|
62390
|
+
stdout: v2.stdout,
|
|
62391
|
+
stderr: v2.stderr,
|
|
62392
|
+
artifacts: [],
|
|
62393
|
+
...typeof v2.error === "string" ? { error: v2.error.slice(0, 500) } : {}
|
|
62394
|
+
};
|
|
62395
|
+
}
|
|
62396
|
+
if (job.execution.admission.skillId !== "pdf-generate")
|
|
62397
|
+
throw Error("Missing execution contract");
|
|
61844
62398
|
let total = 0;
|
|
61845
62399
|
const seen = new Set;
|
|
61846
62400
|
for (const a4 of v2.artifacts) {
|
|
@@ -62048,8 +62602,22 @@ class MemorySkillsStore {
|
|
|
62048
62602
|
async ensureBootstrapApiKey(token, principal) {
|
|
62049
62603
|
this.addApiKey(token, principal);
|
|
62050
62604
|
}
|
|
62051
|
-
async authenticateApiKeyHash(
|
|
62052
|
-
return this.apiKeys.get(
|
|
62605
|
+
async authenticateApiKeyHash(hash3) {
|
|
62606
|
+
return this.apiKeys.get(hash3) ?? null;
|
|
62607
|
+
}
|
|
62608
|
+
async updateApiKeyScopes(actor, keyId, expectedScopes, addScopes) {
|
|
62609
|
+
for (const [hash3, target] of this.apiKeys) {
|
|
62610
|
+
if (target.apiKeyId !== keyId || target.orgId !== actor.orgId)
|
|
62611
|
+
continue;
|
|
62612
|
+
if (target.scopes.length !== expectedScopes.length || target.scopes.some((scope2, index) => scope2 !== expectedScopes[index])) {
|
|
62613
|
+
return { kind: "stale", scopes: [...target.scopes] };
|
|
62614
|
+
}
|
|
62615
|
+
const scopes = [...target.scopes, ...addScopes.filter((scope2) => !target.scopes.includes(scope2))];
|
|
62616
|
+
const updated = { ...target, scopes };
|
|
62617
|
+
this.apiKeys.set(hash3, updated);
|
|
62618
|
+
return { kind: "updated", scopes };
|
|
62619
|
+
}
|
|
62620
|
+
return { kind: "not_found" };
|
|
62053
62621
|
}
|
|
62054
62622
|
async createRun(input) {
|
|
62055
62623
|
const idemKey = input.idempotencyKey?.trim();
|
|
@@ -62061,7 +62629,7 @@ class MemorySkillsStore {
|
|
|
62061
62629
|
}
|
|
62062
62630
|
const now = nowIso();
|
|
62063
62631
|
const run = {
|
|
62064
|
-
id:
|
|
62632
|
+
id: runId2(),
|
|
62065
62633
|
orgId: input.principal.orgId,
|
|
62066
62634
|
userId: input.principal.userId,
|
|
62067
62635
|
skill: input.slug,
|
|
@@ -62085,8 +62653,8 @@ class MemorySkillsStore {
|
|
|
62085
62653
|
async listRuns(principal, limit) {
|
|
62086
62654
|
return Array.from(this.runs.values()).filter((run) => run.orgId === principal.orgId).sort((a4, b4) => b4.createdAt.localeCompare(a4.createdAt)).slice(0, normalizeLimit(limit));
|
|
62087
62655
|
}
|
|
62088
|
-
async getRun(principal,
|
|
62089
|
-
const run = this.runs.get(
|
|
62656
|
+
async getRun(principal, runId3) {
|
|
62657
|
+
const run = this.runs.get(runId3);
|
|
62090
62658
|
return run && run.orgId === principal.orgId ? run : null;
|
|
62091
62659
|
}
|
|
62092
62660
|
async claimNextRun(_input) {
|
|
@@ -62095,28 +62663,28 @@ class MemorySkillsStore {
|
|
|
62095
62663
|
return null;
|
|
62096
62664
|
return this.patchRun(run.id, { status: "running", startedAt: run.startedAt ?? nowIso(), leaseGeneration: run.leaseGeneration + 1 });
|
|
62097
62665
|
}
|
|
62098
|
-
async updateRun(
|
|
62099
|
-
return this.patchRun(
|
|
62666
|
+
async updateRun(runId3, patch) {
|
|
62667
|
+
return this.patchRun(runId3, patch);
|
|
62100
62668
|
}
|
|
62101
|
-
async transitionRun(
|
|
62102
|
-
const run = this.runs.get(
|
|
62669
|
+
async transitionRun(runId3, patch, expectedGeneration) {
|
|
62670
|
+
const run = this.runs.get(runId3);
|
|
62103
62671
|
if (!run)
|
|
62104
62672
|
return null;
|
|
62105
62673
|
if (run.leaseGeneration !== expectedGeneration) {
|
|
62106
|
-
throw new StaleLeaseGenerationError(
|
|
62674
|
+
throw new StaleLeaseGenerationError(runId3, expectedGeneration, run.leaseGeneration, run.status);
|
|
62107
62675
|
}
|
|
62108
|
-
return this.patchRun(
|
|
62676
|
+
return this.patchRun(runId3, patch);
|
|
62109
62677
|
}
|
|
62110
|
-
async appendLog(
|
|
62111
|
-
const entries = this.logs.get(
|
|
62112
|
-
const log = { runId:
|
|
62678
|
+
async appendLog(runId3, orgId, level, message) {
|
|
62679
|
+
const entries = this.logs.get(runId3) ?? [];
|
|
62680
|
+
const log = { runId: runId3, sequence: entries.length + 1, level, message, createdAt: nowIso() };
|
|
62113
62681
|
entries.push(log);
|
|
62114
|
-
this.logs.set(
|
|
62682
|
+
this.logs.set(runId3, entries);
|
|
62115
62683
|
return log;
|
|
62116
62684
|
}
|
|
62117
|
-
async listLogs(principal,
|
|
62118
|
-
const run = await this.getRun(principal,
|
|
62119
|
-
return run ? [...this.logs.get(
|
|
62685
|
+
async listLogs(principal, runId3) {
|
|
62686
|
+
const run = await this.getRun(principal, runId3);
|
|
62687
|
+
return run ? [...this.logs.get(runId3) ?? []] : [];
|
|
62120
62688
|
}
|
|
62121
62689
|
async addArtifact(artifact) {
|
|
62122
62690
|
const next2 = { ...artifact, createdAt: nowIso() };
|
|
@@ -62125,12 +62693,12 @@ class MemorySkillsStore {
|
|
|
62125
62693
|
this.artifacts.set(artifact.runId, artifacts);
|
|
62126
62694
|
return next2;
|
|
62127
62695
|
}
|
|
62128
|
-
async listArtifacts(principal,
|
|
62129
|
-
const run = await this.getRun(principal,
|
|
62130
|
-
return run ? [...this.artifacts.get(
|
|
62696
|
+
async listArtifacts(principal, runId3) {
|
|
62697
|
+
const run = await this.getRun(principal, runId3);
|
|
62698
|
+
return run ? [...this.artifacts.get(runId3) ?? []] : [];
|
|
62131
62699
|
}
|
|
62132
|
-
async getArtifact(principal,
|
|
62133
|
-
const artifacts = await this.listArtifacts(principal,
|
|
62700
|
+
async getArtifact(principal, runId3, id2) {
|
|
62701
|
+
const artifacts = await this.listArtifacts(principal, runId3);
|
|
62134
62702
|
return artifacts.find((artifact) => artifact.id === id2) ?? null;
|
|
62135
62703
|
}
|
|
62136
62704
|
async publishSkill(input) {
|
|
@@ -62180,7 +62748,11 @@ class MemorySkillsStore {
|
|
|
62180
62748
|
createdAt: previous?.createdAt ?? now,
|
|
62181
62749
|
updatedAt: now,
|
|
62182
62750
|
revisionId: revisionIdOfRecord(recordFieldsOf(input, carriedBundle, carriedSkillMd)),
|
|
62183
|
-
revisionNumber: (previous?.revisionNumber ?? 0) + 1
|
|
62751
|
+
revisionNumber: (previous?.revisionNumber ?? 0) + 1,
|
|
62752
|
+
lifecycle: previous?.lifecycle ?? "active",
|
|
62753
|
+
...previous?.archivedAt ? { archivedAt: previous.archivedAt } : {},
|
|
62754
|
+
...previous?.archiveReason ? { archiveReason: previous.archiveReason } : {},
|
|
62755
|
+
...previous?.replacementSlug ? { replacementSlug: previous.replacementSlug } : {}
|
|
62184
62756
|
};
|
|
62185
62757
|
this.skills.set(key, record3);
|
|
62186
62758
|
if (input.version && versionSha && !this.versions.has(versionKey(input.principal.orgId, input.slug, input.version))) {
|
|
@@ -62216,6 +62788,33 @@ class MemorySkillsStore {
|
|
|
62216
62788
|
const skill = this.skills.get(skillKey(principal.orgId, slug));
|
|
62217
62789
|
return skill && skill.orgId === principal.orgId ? skill : null;
|
|
62218
62790
|
}
|
|
62791
|
+
async setSkillLifecycle(principal, slug, patch, expectedRevisionId) {
|
|
62792
|
+
const current = await this.getSkill(principal, slug);
|
|
62793
|
+
if (!current || current.tombstonedAt)
|
|
62794
|
+
return null;
|
|
62795
|
+
if (expectedRevisionId !== current.revisionId)
|
|
62796
|
+
throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
|
|
62797
|
+
if (patch.lifecycle === "archived" && current.lifecycle !== "archived") {
|
|
62798
|
+
const profiles = await this.selectionStore.profilesReferencingSkill(principal, slug);
|
|
62799
|
+
if (profiles.length)
|
|
62800
|
+
throw new SkillLifecycleConflictError(slug, profiles);
|
|
62801
|
+
}
|
|
62802
|
+
const next2 = {
|
|
62803
|
+
...current,
|
|
62804
|
+
lifecycle: patch.lifecycle,
|
|
62805
|
+
updatedAt: nowIso(),
|
|
62806
|
+
revisionId: revisionIdOfRecord({ ...current, lifecycle: patch.lifecycle, archiveReason: patch.reason, replacementSlug: patch.replacementSlug }),
|
|
62807
|
+
revisionNumber: current.revisionNumber + 1,
|
|
62808
|
+
...patch.lifecycle === "archived" ? { archivedAt: current.archivedAt ?? nowIso(), ...patch.reason ? { archiveReason: patch.reason } : {}, ...patch.replacementSlug ? { replacementSlug: patch.replacementSlug } : {} } : {}
|
|
62809
|
+
};
|
|
62810
|
+
if (patch.lifecycle === "active") {
|
|
62811
|
+
delete next2.archivedAt;
|
|
62812
|
+
delete next2.archiveReason;
|
|
62813
|
+
delete next2.replacementSlug;
|
|
62814
|
+
}
|
|
62815
|
+
this.skills.set(skillKey(principal.orgId, slug), next2);
|
|
62816
|
+
return next2;
|
|
62817
|
+
}
|
|
62219
62818
|
async updateSkill(principal, slug, patch, expectedRevisionId) {
|
|
62220
62819
|
const current = await this.getSkill(principal, slug);
|
|
62221
62820
|
if (!current || current.tombstonedAt)
|
|
@@ -62286,7 +62885,7 @@ class MemorySkillsStore {
|
|
|
62286
62885
|
await this.purgeExpiredTombstones(principal);
|
|
62287
62886
|
const tags = new Set;
|
|
62288
62887
|
for (const skill of this.skills.values()) {
|
|
62289
|
-
if (skill.orgId !== principal.orgId)
|
|
62888
|
+
if (skill.orgId !== principal.orgId || skill.lifecycle !== "active")
|
|
62290
62889
|
continue;
|
|
62291
62890
|
for (const tag of skill.tags) {
|
|
62292
62891
|
if (tag.trim())
|
|
@@ -62297,13 +62896,13 @@ class MemorySkillsStore {
|
|
|
62297
62896
|
}
|
|
62298
62897
|
async listSkillsByTag(principal, tag) {
|
|
62299
62898
|
await this.purgeExpiredTombstones(principal);
|
|
62300
|
-
return Array.from(this.skills.values()).filter((skill) => skill.orgId === principal.orgId && !skill.tombstonedAt && skill.tags.includes(tag)).sort((a4, b4) => a4.slug.localeCompare(b4.slug));
|
|
62899
|
+
return Array.from(this.skills.values()).filter((skill) => skill.orgId === principal.orgId && !skill.tombstonedAt && skill.lifecycle === "active" && skill.tags.includes(tag)).sort((a4, b4) => a4.slug.localeCompare(b4.slug));
|
|
62301
62900
|
}
|
|
62302
62901
|
async listPinsByTag(principal, tag) {
|
|
62303
62902
|
await this.purgeExpiredTombstones(principal);
|
|
62304
62903
|
const taggedSlugs = new Set;
|
|
62305
62904
|
for (const skill of this.skills.values()) {
|
|
62306
|
-
if (skill.orgId === principal.orgId && !skill.tombstonedAt && skill.tags.includes(tag))
|
|
62905
|
+
if (skill.orgId === principal.orgId && !skill.tombstonedAt && skill.lifecycle === "active" && skill.tags.includes(tag))
|
|
62307
62906
|
taggedSlugs.add(skill.slug);
|
|
62308
62907
|
}
|
|
62309
62908
|
return Array.from(this.pins.values()).filter((pin) => pin.orgId === principal.orgId && pin.principal === principal.apiKeyId && taggedSlugs.has(pin.slug)).sort((a4, b4) => a4.slug.localeCompare(b4.slug));
|
|
@@ -62316,12 +62915,12 @@ class MemorySkillsStore {
|
|
|
62316
62915
|
if (!referenced)
|
|
62317
62916
|
this.bundles.delete(skillKey(orgId, sha2562));
|
|
62318
62917
|
}
|
|
62319
|
-
patchRun(
|
|
62320
|
-
const run = this.runs.get(
|
|
62918
|
+
patchRun(runId3, patch) {
|
|
62919
|
+
const run = this.runs.get(runId3);
|
|
62321
62920
|
if (!run)
|
|
62322
62921
|
return null;
|
|
62323
62922
|
const next2 = { ...run, ...patch };
|
|
62324
|
-
this.runs.set(
|
|
62923
|
+
this.runs.set(runId3, next2);
|
|
62325
62924
|
return next2;
|
|
62326
62925
|
}
|
|
62327
62926
|
}
|
|
@@ -62394,7 +62993,7 @@ class PostgresSkillsStore {
|
|
|
62394
62993
|
ON CONFLICT (key_hash) DO NOTHING
|
|
62395
62994
|
`;
|
|
62396
62995
|
}
|
|
62397
|
-
async authenticateApiKeyHash(
|
|
62996
|
+
async authenticateApiKeyHash(hash3) {
|
|
62398
62997
|
const rows = await this.sql`
|
|
62399
62998
|
SELECT k.id AS api_key_id, k.scopes_json, o.id AS org_id, o.slug AS org_slug, o.name AS org_name,
|
|
62400
62999
|
u.id AS user_id, u.email, m.role
|
|
@@ -62402,7 +63001,7 @@ class PostgresSkillsStore {
|
|
|
62402
63001
|
JOIN organizations o ON o.id = k.org_id
|
|
62403
63002
|
JOIN users u ON u.id = k.user_id
|
|
62404
63003
|
LEFT JOIN organization_members m ON m.org_id = k.org_id AND m.user_id = k.user_id
|
|
62405
|
-
WHERE k.key_hash = ${
|
|
63004
|
+
WHERE k.key_hash = ${hash3} AND k.revoked_at IS NULL
|
|
62406
63005
|
LIMIT 1
|
|
62407
63006
|
`;
|
|
62408
63007
|
const row = rows[0];
|
|
@@ -62420,6 +63019,38 @@ class PostgresSkillsStore {
|
|
|
62420
63019
|
scopes: parseJsonArray(row.scopes_json)
|
|
62421
63020
|
};
|
|
62422
63021
|
}
|
|
63022
|
+
async updateApiKeyScopes(actor, keyId, expectedScopes, addScopes) {
|
|
63023
|
+
return this.sql.begin(async (tx) => {
|
|
63024
|
+
const rows = await tx`
|
|
63025
|
+
SELECT scopes_json FROM api_keys
|
|
63026
|
+
WHERE id = ${keyId} AND org_id = ${actor.orgId} AND revoked_at IS NULL
|
|
63027
|
+
LIMIT 1
|
|
63028
|
+
`;
|
|
63029
|
+
const row = rows[0];
|
|
63030
|
+
if (!row)
|
|
63031
|
+
return { kind: "not_found" };
|
|
63032
|
+
const current = parseJsonArray(row.scopes_json);
|
|
63033
|
+
if (current.length !== expectedScopes.length || current.some((scope2, index) => scope2 !== expectedScopes[index])) {
|
|
63034
|
+
return { kind: "stale", scopes: current };
|
|
63035
|
+
}
|
|
63036
|
+
const scopes = [...current, ...addScopes.filter((scope2) => !current.includes(scope2))];
|
|
63037
|
+
const updated = await tx`
|
|
63038
|
+
UPDATE api_keys SET scopes_json = ${JSON.stringify(scopes)}::jsonb
|
|
63039
|
+
WHERE id = ${keyId} AND org_id = ${actor.orgId} AND revoked_at IS NULL
|
|
63040
|
+
AND scopes_json = ${JSON.stringify(expectedScopes)}::jsonb
|
|
63041
|
+
RETURNING scopes_json
|
|
63042
|
+
`;
|
|
63043
|
+
if (!updated[0]) {
|
|
63044
|
+
const latest = await tx`SELECT scopes_json FROM api_keys WHERE id = ${keyId} AND org_id = ${actor.orgId} AND revoked_at IS NULL LIMIT 1`;
|
|
63045
|
+
return { kind: "stale", scopes: latest[0] ? parseJsonArray(latest[0].scopes_json) : current };
|
|
63046
|
+
}
|
|
63047
|
+
await tx`
|
|
63048
|
+
INSERT INTO skills_audit_events (org_id, user_id, api_key_id, action, target_type, target_id, metadata_json)
|
|
63049
|
+
VALUES (${actor.orgId}, ${actor.userId}, ${actor.apiKeyId}, ${"api_key_scopes_added"}, ${"api_key"}, ${keyId}, ${JSON.stringify({ added: addScopes, scopes })}::jsonb)
|
|
63050
|
+
`;
|
|
63051
|
+
return { kind: "updated", scopes };
|
|
63052
|
+
});
|
|
63053
|
+
}
|
|
62423
63054
|
async withContext(orgId, worker, fn) {
|
|
62424
63055
|
return this.sql.begin(async (tx) => {
|
|
62425
63056
|
await tx`SELECT set_config('app.skills_org_id', ${orgId ?? ""}, true)`;
|
|
@@ -62438,7 +63069,7 @@ class PostgresSkillsStore {
|
|
|
62438
63069
|
if (existing[0])
|
|
62439
63070
|
return rowToRun(existing[0]);
|
|
62440
63071
|
}
|
|
62441
|
-
const id2 =
|
|
63072
|
+
const id2 = runId2();
|
|
62442
63073
|
const rows = await tx`
|
|
62443
63074
|
INSERT INTO skills_runs (id, org_id, user_id, skill_slug, requested_slug, status, input_json, args_json, idempotency_key, correlation_id)
|
|
62444
63075
|
VALUES (${id2}, ${input.principal.orgId}, ${input.principal.userId}, ${input.slug}, ${input.slug}, ${"queued"}, ${JSON.stringify(input.input)}::jsonb, ${JSON.stringify(input.args)}::jsonb, ${input.idempotencyKey ?? null}, ${randomUUID9()})
|
|
@@ -62456,10 +63087,10 @@ class PostgresSkillsStore {
|
|
|
62456
63087
|
return rows.map(rowToRun);
|
|
62457
63088
|
});
|
|
62458
63089
|
}
|
|
62459
|
-
async getRun(principal,
|
|
63090
|
+
async getRun(principal, runId3) {
|
|
62460
63091
|
return this.withContext(principal.orgId, false, async (tx) => {
|
|
62461
63092
|
const rows = await tx`
|
|
62462
|
-
SELECT * FROM skills_runs WHERE id = ${
|
|
63093
|
+
SELECT * FROM skills_runs WHERE id = ${runId3} AND org_id = ${principal.orgId} LIMIT 1
|
|
62463
63094
|
`;
|
|
62464
63095
|
return rows[0] ? rowToRun(rows[0]) : null;
|
|
62465
63096
|
});
|
|
@@ -62482,9 +63113,9 @@ class PostgresSkillsStore {
|
|
|
62482
63113
|
return rows[0] ? rowToRun(rows[0]) : null;
|
|
62483
63114
|
});
|
|
62484
63115
|
}
|
|
62485
|
-
async updateRun(
|
|
63116
|
+
async updateRun(runId3, patch) {
|
|
62486
63117
|
return this.withContext(null, true, async (tx) => {
|
|
62487
|
-
const current = await tx`SELECT * FROM skills_runs WHERE id = ${
|
|
63118
|
+
const current = await tx`SELECT * FROM skills_runs WHERE id = ${runId3} LIMIT 1`;
|
|
62488
63119
|
if (!current[0])
|
|
62489
63120
|
return null;
|
|
62490
63121
|
const run = { ...rowToRun(current[0]), ...patch };
|
|
@@ -62497,20 +63128,20 @@ class PostgresSkillsStore {
|
|
|
62497
63128
|
error_message = ${run.errorMessage ?? null},
|
|
62498
63129
|
started_at = ${run.startedAt ?? null},
|
|
62499
63130
|
completed_at = ${run.completedAt ?? null}
|
|
62500
|
-
WHERE id = ${
|
|
63131
|
+
WHERE id = ${runId3}
|
|
62501
63132
|
RETURNING *
|
|
62502
63133
|
`;
|
|
62503
63134
|
return rows[0] ? rowToRun(rows[0]) : null;
|
|
62504
63135
|
});
|
|
62505
63136
|
}
|
|
62506
|
-
async transitionRun(
|
|
63137
|
+
async transitionRun(runId3, patch, expectedGeneration) {
|
|
62507
63138
|
return this.withContext(null, true, async (tx) => {
|
|
62508
|
-
const current = await tx`SELECT * FROM skills_runs WHERE id = ${
|
|
63139
|
+
const current = await tx`SELECT * FROM skills_runs WHERE id = ${runId3} LIMIT 1`;
|
|
62509
63140
|
if (!current[0])
|
|
62510
63141
|
return null;
|
|
62511
63142
|
const stored = rowToRun(current[0]);
|
|
62512
63143
|
if (stored.leaseGeneration !== expectedGeneration) {
|
|
62513
|
-
throw new StaleLeaseGenerationError(
|
|
63144
|
+
throw new StaleLeaseGenerationError(runId3, expectedGeneration, stored.leaseGeneration, stored.status);
|
|
62514
63145
|
}
|
|
62515
63146
|
const run = { ...stored, ...patch };
|
|
62516
63147
|
const rows = await tx`
|
|
@@ -62523,13 +63154,13 @@ class PostgresSkillsStore {
|
|
|
62523
63154
|
started_at = ${run.startedAt ?? null},
|
|
62524
63155
|
completed_at = ${run.completedAt ?? null},
|
|
62525
63156
|
lease_generation = ${run.leaseGeneration}
|
|
62526
|
-
WHERE id = ${
|
|
63157
|
+
WHERE id = ${runId3} AND lease_generation = ${expectedGeneration}
|
|
62527
63158
|
RETURNING *
|
|
62528
63159
|
`;
|
|
62529
63160
|
return rows[0] ? rowToRun(rows[0]) : null;
|
|
62530
63161
|
});
|
|
62531
63162
|
}
|
|
62532
|
-
async appendLog(
|
|
63163
|
+
async appendLog(runId3, orgId, level, message) {
|
|
62533
63164
|
let lastError;
|
|
62534
63165
|
for (let attempt = 0;attempt < LOG_SEQUENCE_ATTEMPTS; attempt += 1) {
|
|
62535
63166
|
try {
|
|
@@ -62537,9 +63168,9 @@ class PostgresSkillsStore {
|
|
|
62537
63168
|
return await tx`
|
|
62538
63169
|
INSERT INTO skills_run_logs (run_id, org_id, sequence, level, message)
|
|
62539
63170
|
VALUES (
|
|
62540
|
-
${
|
|
63171
|
+
${runId3},
|
|
62541
63172
|
${orgId},
|
|
62542
|
-
(SELECT COALESCE(MAX(sequence), 0) + 1 FROM skills_run_logs WHERE run_id = ${
|
|
63173
|
+
(SELECT COALESCE(MAX(sequence), 0) + 1 FROM skills_run_logs WHERE run_id = ${runId3}),
|
|
62543
63174
|
${level},
|
|
62544
63175
|
${message}
|
|
62545
63176
|
)
|
|
@@ -62555,12 +63186,12 @@ class PostgresSkillsStore {
|
|
|
62555
63186
|
}
|
|
62556
63187
|
throw lastError;
|
|
62557
63188
|
}
|
|
62558
|
-
async listLogs(principal,
|
|
63189
|
+
async listLogs(principal, runId3) {
|
|
62559
63190
|
return this.withContext(principal.orgId, false, async (tx) => {
|
|
62560
63191
|
const rows = await tx`
|
|
62561
63192
|
SELECT l.* FROM skills_run_logs l
|
|
62562
63193
|
JOIN skills_runs r ON r.id = l.run_id AND r.org_id = ${principal.orgId}
|
|
62563
|
-
WHERE l.run_id = ${
|
|
63194
|
+
WHERE l.run_id = ${runId3}
|
|
62564
63195
|
ORDER BY l.sequence ASC
|
|
62565
63196
|
`;
|
|
62566
63197
|
return rows.map(rowToLog);
|
|
@@ -62576,23 +63207,23 @@ class PostgresSkillsStore {
|
|
|
62576
63207
|
return rowToArtifact(rows[0]);
|
|
62577
63208
|
});
|
|
62578
63209
|
}
|
|
62579
|
-
async listArtifacts(principal,
|
|
63210
|
+
async listArtifacts(principal, runId3) {
|
|
62580
63211
|
return this.withContext(principal.orgId, false, async (tx) => {
|
|
62581
63212
|
const rows = await tx`
|
|
62582
63213
|
SELECT a.* FROM skills_artifacts a
|
|
62583
63214
|
JOIN skills_runs r ON r.id = a.run_id AND r.org_id = ${principal.orgId}
|
|
62584
|
-
WHERE a.run_id = ${
|
|
63215
|
+
WHERE a.run_id = ${runId3}
|
|
62585
63216
|
ORDER BY a.created_at ASC
|
|
62586
63217
|
`;
|
|
62587
63218
|
return rows.map(rowToArtifact);
|
|
62588
63219
|
});
|
|
62589
63220
|
}
|
|
62590
|
-
async getArtifact(principal,
|
|
63221
|
+
async getArtifact(principal, runId3, artifactId2) {
|
|
62591
63222
|
return this.withContext(principal.orgId, false, async (tx) => {
|
|
62592
63223
|
const rows = await tx`
|
|
62593
63224
|
SELECT a.* FROM skills_artifacts a
|
|
62594
63225
|
JOIN skills_runs r ON r.id = a.run_id AND r.org_id = ${principal.orgId}
|
|
62595
|
-
WHERE a.run_id = ${
|
|
63226
|
+
WHERE a.run_id = ${runId3} AND a.id = ${artifactId2}
|
|
62596
63227
|
LIMIT 1
|
|
62597
63228
|
`;
|
|
62598
63229
|
return rows[0] ? rowToArtifact(rows[0]) : null;
|
|
@@ -62740,6 +63371,42 @@ class PostgresSkillsStore {
|
|
|
62740
63371
|
const rows = await this.sql`SELECT * FROM skills_registry WHERE org_id = ${principal.orgId} AND slug = ${slug} LIMIT 1`;
|
|
62741
63372
|
return rows[0] ? rowToSkill(rows[0]) : null;
|
|
62742
63373
|
}
|
|
63374
|
+
async setSkillLifecycle(principal, slug, patch, expectedRevisionId) {
|
|
63375
|
+
return this.sql.begin(async (tx) => {
|
|
63376
|
+
const currentRows = await tx`SELECT * FROM skills_registry WHERE org_id=${principal.orgId} AND slug=${slug} LIMIT 1 FOR UPDATE`;
|
|
63377
|
+
const current = currentRows[0] ? rowToSkill(currentRows[0]) : null;
|
|
63378
|
+
if (!current || current.tombstonedAt)
|
|
63379
|
+
return null;
|
|
63380
|
+
if (expectedRevisionId !== current.revisionId)
|
|
63381
|
+
throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
|
|
63382
|
+
if (patch.lifecycle === "archived" && current.lifecycle !== "archived") {
|
|
63383
|
+
const profiles = await tx`SELECT profile_id,selections_json FROM skills_profiles WHERE org_id=${principal.orgId}`;
|
|
63384
|
+
const referencing = profiles.filter((profile2) => {
|
|
63385
|
+
const selections2 = typeof profile2.selections_json === "string" ? JSON.parse(profile2.selections_json) : profile2.selections_json;
|
|
63386
|
+
return Array.isArray(selections2) && selections2.some((selection2) => selection2?.slug === slug);
|
|
63387
|
+
}).map((profile2) => String(profile2.profile_id)).sort();
|
|
63388
|
+
if (referencing.length)
|
|
63389
|
+
throw new SkillLifecycleConflictError(slug, referencing);
|
|
63390
|
+
}
|
|
63391
|
+
const next2 = {
|
|
63392
|
+
...current,
|
|
63393
|
+
lifecycle: patch.lifecycle,
|
|
63394
|
+
updatedAt: nowIso(),
|
|
63395
|
+
revisionId: revisionIdOfRecord({ ...current, lifecycle: patch.lifecycle, archiveReason: patch.reason, replacementSlug: patch.replacementSlug }),
|
|
63396
|
+
revisionNumber: current.revisionNumber + 1,
|
|
63397
|
+
...patch.lifecycle === "archived" ? { archivedAt: current.archivedAt ?? nowIso(), ...patch.reason ? { archiveReason: patch.reason } : {}, ...patch.replacementSlug ? { replacementSlug: patch.replacementSlug } : {} } : {}
|
|
63398
|
+
};
|
|
63399
|
+
if (patch.lifecycle === "active") {
|
|
63400
|
+
delete next2.archivedAt;
|
|
63401
|
+
delete next2.archiveReason;
|
|
63402
|
+
delete next2.replacementSlug;
|
|
63403
|
+
}
|
|
63404
|
+
const rows = await tx`UPDATE skills_registry SET lifecycle=${next2.lifecycle}, archived_at=${next2.archivedAt ?? null}, archive_reason=${next2.archiveReason ?? null}, replacement_slug=${next2.replacementSlug ?? null}, revision_id=${next2.revisionId}, revision_number=revision_number+1, updated_at=${next2.updatedAt} WHERE org_id=${principal.orgId} AND slug=${slug} AND tombstoned_at IS NULL AND revision_id=${current.revisionId} RETURNING *`;
|
|
63405
|
+
if (!rows[0])
|
|
63406
|
+
throw new SkillRevisionConflictError(slug, expectedRevisionId, (await this.getSkill(principal, slug))?.revisionId ?? null);
|
|
63407
|
+
return rowToSkill(rows[0]);
|
|
63408
|
+
});
|
|
63409
|
+
}
|
|
62743
63410
|
async updateSkill(principal, slug, patch, expectedRevisionId) {
|
|
62744
63411
|
const current = await this.getSkill(principal, slug);
|
|
62745
63412
|
if (!current || current.tombstonedAt)
|
|
@@ -62875,7 +63542,7 @@ class PostgresSkillsStore {
|
|
|
62875
63542
|
async listTags(principal) {
|
|
62876
63543
|
await this.purgeExpiredTombstones(principal);
|
|
62877
63544
|
const rows = await this.sql`
|
|
62878
|
-
SELECT DISTINCT tag FROM skills_tags WHERE org_id = ${principal.orgId} ORDER BY tag ASC
|
|
63545
|
+
SELECT DISTINCT t.tag FROM skills_tags t JOIN skills_registry s ON s.org_id = t.org_id AND s.slug = t.slug WHERE t.org_id = ${principal.orgId} AND s.lifecycle = 'active' ORDER BY t.tag ASC
|
|
62879
63546
|
`;
|
|
62880
63547
|
return rows.map((row) => String(row.tag));
|
|
62881
63548
|
}
|
|
@@ -62884,7 +63551,7 @@ class PostgresSkillsStore {
|
|
|
62884
63551
|
const rows = await this.sql`
|
|
62885
63552
|
SELECT s.* FROM skills_registry s
|
|
62886
63553
|
JOIN skills_tags t ON t.org_id = s.org_id AND t.slug = s.slug
|
|
62887
|
-
WHERE t.org_id = ${principal.orgId} AND t.tag = ${tag} AND s.tombstoned_at IS NULL
|
|
63554
|
+
WHERE t.org_id = ${principal.orgId} AND t.tag = ${tag} AND s.tombstoned_at IS NULL AND s.lifecycle = 'active'
|
|
62888
63555
|
ORDER BY s.slug ASC
|
|
62889
63556
|
`;
|
|
62890
63557
|
return rows.map(rowToSkill);
|
|
@@ -62896,7 +63563,7 @@ class PostgresSkillsStore {
|
|
|
62896
63563
|
JOIN skills_tags t ON t.org_id = p.org_id AND t.slug = p.slug
|
|
62897
63564
|
JOIN skills_registry s ON s.org_id = p.org_id AND s.slug = p.slug
|
|
62898
63565
|
WHERE p.org_id = ${principal.orgId} AND p.principal = ${principal.apiKeyId}
|
|
62899
|
-
AND t.tag = ${tag} AND s.tombstoned_at IS NULL
|
|
63566
|
+
AND t.tag = ${tag} AND s.tombstoned_at IS NULL AND s.lifecycle = 'active'
|
|
62900
63567
|
ORDER BY p.slug ASC
|
|
62901
63568
|
`;
|
|
62902
63569
|
return rows.map(rowToPin);
|
|
@@ -62993,7 +63660,7 @@ async function createSkillsFetchHandler(options = {}) {
|
|
|
62993
63660
|
if (request2.method === "GET" && url.pathname === "/api/v1/capabilities") {
|
|
62994
63661
|
const response = await handleProfileApi(store, principal, request2, ["capabilities"], config);
|
|
62995
63662
|
const payload = await response.json();
|
|
62996
|
-
return json4({ ...payload, cloudExecution: Boolean(runtime), capabilities: [...payload.capabilities, ...runtime ? ["skills.cloud-execution"] : []] });
|
|
63663
|
+
return json4({ ...payload, cloudExecution: Boolean(runtime), cloudExecutionContracts: runtime ? ["pdf.v1", "regex-test.v1"] : [], capabilities: [...payload.capabilities, ...runtime ? ["skills.cloud-execution"] : []] });
|
|
62997
63664
|
}
|
|
62998
63665
|
if (request2.method === "GET" && url.pathname === "/api/auth/whoami") {
|
|
62999
63666
|
return json4(identityPayload(principal));
|
|
@@ -63017,6 +63684,9 @@ async function createSkillsFetchHandler(options = {}) {
|
|
|
63017
63684
|
attemptedBundleSha256: error.attemptedSha256
|
|
63018
63685
|
}, { status: 409 });
|
|
63019
63686
|
}
|
|
63687
|
+
if (error instanceof SkillLifecycleConflictError) {
|
|
63688
|
+
return json4({ error: error.message, code: "SKILL_ARCHIVE_PROFILE_CONFLICT", profiles: error.profiles }, { status: 409 });
|
|
63689
|
+
}
|
|
63020
63690
|
if (error instanceof SkillRevisionConflictError) {
|
|
63021
63691
|
return json4({
|
|
63022
63692
|
error: error.message,
|
|
@@ -63055,6 +63725,32 @@ async function handleApiV1(store, governanceStore, principal, request2, parts, c
|
|
|
63055
63725
|
if (parts.some(segmentEscapesPath)) {
|
|
63056
63726
|
return json4({ error: "invalid path segment", code: "INVALID_PATH" }, { status: 400 });
|
|
63057
63727
|
}
|
|
63728
|
+
if (parts.length === 4 && resource === "admin" && id2 === "keys" && subresource && childId === "scopes" && request2.method === "PATCH") {
|
|
63729
|
+
const adminScope = principal.scopes.includes("*") || principal.scopes.includes("skills:keys.admin");
|
|
63730
|
+
if (!adminScope || !["owner", "admin"].includes(principal.role)) {
|
|
63731
|
+
return json4({ error: "Key scope administration requires an owner/admin key with skills:keys.admin", code: "KEY_ADMIN_REQUIRED" }, { status: 403 });
|
|
63732
|
+
}
|
|
63733
|
+
if (!/^[A-Za-z0-9_-]+$/.test(subresource)) {
|
|
63734
|
+
return json4({ error: "Key id must be url-safe (letters, digits, '_' or '-')", code: "INVALID_KEY_ID" }, { status: 400 });
|
|
63735
|
+
}
|
|
63736
|
+
const body4 = await readJson(request2, config.requestBodyLimitBytes);
|
|
63737
|
+
const expected = body4.expected_scopes;
|
|
63738
|
+
const additions = body4.add_scopes;
|
|
63739
|
+
if (!Array.isArray(expected) || !expected.every((scope2) => typeof scope2 === "string") || !Array.isArray(additions) || !additions.every((scope2) => typeof scope2 === "string")) {
|
|
63740
|
+
return json4({ error: "expected_scopes and add_scopes must be arrays of scope strings", code: "INVALID_KEY_SCOPE_UPDATE" }, { status: 400 });
|
|
63741
|
+
}
|
|
63742
|
+
if (additions.length !== 1 || additions[0] !== "skills:publish") {
|
|
63743
|
+
return json4({ error: "Only adding skills:publish to an existing Skills key is supported", code: "INVALID_KEY_SCOPE_UPDATE" }, { status: 400 });
|
|
63744
|
+
}
|
|
63745
|
+
const update = await store.updateApiKeyScopes?.(principal, subresource, expected, additions);
|
|
63746
|
+
if (!update)
|
|
63747
|
+
return json4({ error: "Key scope administration is unavailable", code: "KEY_SCOPE_UPDATE_UNAVAILABLE" }, { status: 501 });
|
|
63748
|
+
if (update.kind === "not_found")
|
|
63749
|
+
return json4({ error: "API key not found", code: "API_KEY_NOT_FOUND" }, { status: 404 });
|
|
63750
|
+
if (update.kind === "stale")
|
|
63751
|
+
return json4({ error: "API key scopes changed; re-read and retry with expected_scopes", code: "API_KEY_SCOPE_CONFLICT", keyId: subresource, scopes: update.scopes }, { status: 409 });
|
|
63752
|
+
return json4({ keyId: subresource, orgId: principal.orgId, scopes: update.scopes, updated: true });
|
|
63753
|
+
}
|
|
63058
63754
|
if (!permitsSkillsRoute(principal, request2.method, resource ?? "")) {
|
|
63059
63755
|
return json4({ error: "The API key does not allow this operation", code: "INSUFFICIENT_SCOPE" }, { status: 403 });
|
|
63060
63756
|
}
|
|
@@ -63088,6 +63784,13 @@ async function handleApiV1(store, governanceStore, principal, request2, parts, c
|
|
|
63088
63784
|
if (request2.method === "GET" && id2 && subresource === "versions" && !childId) {
|
|
63089
63785
|
return json4(await listSkillVersionsPayload(store, principal, id2));
|
|
63090
63786
|
}
|
|
63787
|
+
if (request2.method === "PATCH" && id2 && subresource === "lifecycle" && !childId) {
|
|
63788
|
+
if (!["owner", "admin"].includes(principal.role))
|
|
63789
|
+
return json4({ error: "Only organization owners or admins may change skill lifecycle", code: "LIFECYCLE_ROLE_REQUIRED" }, { status: 403 });
|
|
63790
|
+
const expectedRevisionId = parseIfMatch(request2.headers.get("if-match"));
|
|
63791
|
+
const updated = await store.setSkillLifecycle(principal, id2, skillLifecyclePatch(await readJson(request2, config.requestBodyLimitBytes)), expectedRevisionId);
|
|
63792
|
+
return updated ? json4(publishedPayload(updated), { headers: { ETag: revisionEtag(updated.revisionId) } }) : json4({ error: "published skill not found", code: "SKILL_NOT_FOUND" }, { status: 404 });
|
|
63793
|
+
}
|
|
63091
63794
|
if (request2.method === "GET" && id2 && subresource === "versions" && childId && !parts[4]) {
|
|
63092
63795
|
const version2 = await readSkillVersion(store, principal, id2, childId);
|
|
63093
63796
|
const current = (await store.getSkill(principal, id2))?.bundleSha256;
|
|
@@ -63246,8 +63949,11 @@ async function handleApiV1(store, governanceStore, principal, request2, parts, c
|
|
|
63246
63949
|
return json4({ error: "not found", code: "NOT_FOUND" }, { status: 404 });
|
|
63247
63950
|
}
|
|
63248
63951
|
function identityPayload(principal) {
|
|
63952
|
+
const safeKeyId = /^[A-Za-z0-9_-]{1,256}$/.test(principal.apiKeyId) ? principal.apiKeyId : undefined;
|
|
63953
|
+
const safeScopes = principal.scopes.length <= 32 && principal.scopes.every((scope2) => scope2.length <= 128 && /^(?:\*|[a-z][a-z0-9_-]*:(?:\*|[a-z][a-z0-9_-]*))$/.test(scope2)) ? [...new Set(principal.scopes)] : undefined;
|
|
63249
63954
|
return {
|
|
63250
63955
|
user: { id: principal.userId, email: principal.email, role: principal.role },
|
|
63956
|
+
...safeKeyId && safeScopes ? { apiKey: { id: safeKeyId, scopes: safeScopes } } : {},
|
|
63251
63957
|
organization: { id: principal.orgId, slug: principal.orgSlug, name: principal.orgName }
|
|
63252
63958
|
};
|
|
63253
63959
|
}
|
|
@@ -63503,14 +64209,14 @@ var util;
|
|
|
63503
64209
|
return obj[e5];
|
|
63504
64210
|
});
|
|
63505
64211
|
};
|
|
63506
|
-
util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (
|
|
63507
|
-
const
|
|
63508
|
-
for (const key in
|
|
63509
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
63510
|
-
|
|
64212
|
+
util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object6) => {
|
|
64213
|
+
const keys3 = [];
|
|
64214
|
+
for (const key in object6) {
|
|
64215
|
+
if (Object.prototype.hasOwnProperty.call(object6, key)) {
|
|
64216
|
+
keys3.push(key);
|
|
63511
64217
|
}
|
|
63512
64218
|
}
|
|
63513
|
-
return
|
|
64219
|
+
return keys3;
|
|
63514
64220
|
};
|
|
63515
64221
|
util2.find = (arr, checker) => {
|
|
63516
64222
|
for (const item of arr) {
|
|
@@ -65760,8 +66466,8 @@ class ZodObject extends ZodType {
|
|
|
65760
66466
|
if (this._cached !== null)
|
|
65761
66467
|
return this._cached;
|
|
65762
66468
|
const shape = this._def.shape();
|
|
65763
|
-
const
|
|
65764
|
-
this._cached = { shape, keys:
|
|
66469
|
+
const keys3 = util.objectKeys(shape);
|
|
66470
|
+
this._cached = { shape, keys: keys3 };
|
|
65765
66471
|
return this._cached;
|
|
65766
66472
|
}
|
|
65767
66473
|
_parse(input) {
|
|
@@ -67435,8 +68141,8 @@ function createRunService({ store, governance }) {
|
|
|
67435
68141
|
return run;
|
|
67436
68142
|
},
|
|
67437
68143
|
leaseNext: (workerId) => store.claimNextRun({ workerId }),
|
|
67438
|
-
transition: (
|
|
67439
|
-
get: (principal,
|
|
68144
|
+
transition: (runId3, patch) => store.updateRun(runId3, patch),
|
|
68145
|
+
get: (principal, runId3) => store.getRun(principal, runId3)
|
|
67440
68146
|
};
|
|
67441
68147
|
}
|
|
67442
68148
|
async function settleRun(store, options, run, actualCents) {
|
|
@@ -67481,7 +68187,7 @@ var localRunExecutor = {
|
|
|
67481
68187
|
// src/sdk/storage.ts
|
|
67482
68188
|
var artifactStorageSeam = ArtifactStorage;
|
|
67483
68189
|
// src/sdk/outputs.ts
|
|
67484
|
-
import { createHash as
|
|
68190
|
+
import { createHash as createHash27 } from "crypto";
|
|
67485
68191
|
class DatabaseOnlyObjectStore {
|
|
67486
68192
|
usesS3 = false;
|
|
67487
68193
|
async materialize(run, artifact, body4) {
|
|
@@ -67491,8 +68197,8 @@ class DatabaseOnlyObjectStore {
|
|
|
67491
68197
|
return null;
|
|
67492
68198
|
}
|
|
67493
68199
|
async deleteBundle() {}
|
|
67494
|
-
quarantineKeyFor(tenantId,
|
|
67495
|
-
return `${tenantId}/${
|
|
68200
|
+
quarantineKeyFor(tenantId, runId3, artifactId2) {
|
|
68201
|
+
return `${tenantId}/${runId3}/quarantine/${artifactId2}`;
|
|
67496
68202
|
}
|
|
67497
68203
|
}
|
|
67498
68204
|
function redactRunOutput(value, patterns = DEFAULT_OUTPUT_GOVERNANCE.redactPatterns) {
|
|
@@ -67528,7 +68234,7 @@ function createGovernedArtifactWriter(options) {
|
|
|
67528
68234
|
const stamped = {
|
|
67529
68235
|
...meta,
|
|
67530
68236
|
byteSize: outputBytes,
|
|
67531
|
-
sha256:
|
|
68237
|
+
sha256: createHash27("sha256").update(persistedBytes).digest("hex"),
|
|
67532
68238
|
visibility: options.visibility ?? config.defaultVisibility,
|
|
67533
68239
|
...config.artifactTtlSeconds !== undefined ? { expiresAt: expiresAtFor(createdAt, config.artifactTtlSeconds) } : {}
|
|
67534
68240
|
};
|
|
@@ -67674,13 +68380,13 @@ function createSpendService(options) {
|
|
|
67674
68380
|
exhaust("monthly", `org spend for ${monthPrefix} is ${monthly} cents and this run estimates ${estimated}; monthly ceiling is ${ceilings.monthlyTotalCents} cents`);
|
|
67675
68381
|
}
|
|
67676
68382
|
},
|
|
67677
|
-
async reserve(tenantId,
|
|
68383
|
+
async reserve(tenantId, runId3, estimatedCents) {
|
|
67678
68384
|
assertReservationCents(estimatedCents, "estimatedCents");
|
|
67679
|
-
return store.createReservation({ orgId: tenantId, runId:
|
|
68385
|
+
return store.createReservation({ orgId: tenantId, runId: runId3, estimatedCents });
|
|
67680
68386
|
},
|
|
67681
|
-
async reconcile(tenantId,
|
|
68387
|
+
async reconcile(tenantId, runId3, actualCents) {
|
|
67682
68388
|
assertReservationCents(actualCents, "actualCents");
|
|
67683
|
-
const reservations = await store.reservationsForRun(tenantId,
|
|
68389
|
+
const reservations = await store.reservationsForRun(tenantId, runId3);
|
|
67684
68390
|
const open = reservations.find((reservation) => reservation.status === "reserved");
|
|
67685
68391
|
if (!open)
|
|
67686
68392
|
return null;
|
|
@@ -67773,7 +68479,7 @@ __export(exports_external2, {
|
|
|
67773
68479
|
overwrite: () => _overwrite,
|
|
67774
68480
|
output: () => output,
|
|
67775
68481
|
optional: () => optional,
|
|
67776
|
-
object: () =>
|
|
68482
|
+
object: () => object6,
|
|
67777
68483
|
number: () => number2,
|
|
67778
68484
|
nullish: () => nullish2,
|
|
67779
68485
|
nullable: () => nullable,
|
|
@@ -67824,7 +68530,7 @@ __export(exports_external2, {
|
|
|
67824
68530
|
httpUrl: () => httpUrl,
|
|
67825
68531
|
hostname: () => hostname2,
|
|
67826
68532
|
hex: () => hex2,
|
|
67827
|
-
hash: () =>
|
|
68533
|
+
hash: () => hash3,
|
|
67828
68534
|
guid: () => guid2,
|
|
67829
68535
|
gte: () => _gte,
|
|
67830
68536
|
gt: () => _gt,
|
|
@@ -68403,9 +69109,9 @@ function floatSafeRemainder2(val, step) {
|
|
|
68403
69109
|
return ratio - roundedRatio;
|
|
68404
69110
|
}
|
|
68405
69111
|
var EVALUATING = /* @__PURE__ */ Symbol("evaluating");
|
|
68406
|
-
function defineLazy(
|
|
69112
|
+
function defineLazy(object6, key, getter) {
|
|
68407
69113
|
let value = undefined;
|
|
68408
|
-
Object.defineProperty(
|
|
69114
|
+
Object.defineProperty(object6, key, {
|
|
68409
69115
|
get() {
|
|
68410
69116
|
if (value === EVALUATING) {
|
|
68411
69117
|
return;
|
|
@@ -68417,7 +69123,7 @@ function defineLazy(object5, key, getter) {
|
|
|
68417
69123
|
return value;
|
|
68418
69124
|
},
|
|
68419
69125
|
set(v2) {
|
|
68420
|
-
Object.defineProperty(
|
|
69126
|
+
Object.defineProperty(object6, key, {
|
|
68421
69127
|
value: v2
|
|
68422
69128
|
});
|
|
68423
69129
|
},
|
|
@@ -68452,12 +69158,12 @@ function getElementAtPath(obj, path2) {
|
|
|
68452
69158
|
return path2.reduce((acc, key) => acc?.[key], obj);
|
|
68453
69159
|
}
|
|
68454
69160
|
function promiseAllObject(promisesObj) {
|
|
68455
|
-
const
|
|
68456
|
-
const promises =
|
|
69161
|
+
const keys3 = Object.keys(promisesObj);
|
|
69162
|
+
const promises = keys3.map((key) => promisesObj[key]);
|
|
68457
69163
|
return Promise.all(promises).then((results) => {
|
|
68458
69164
|
const resolvedObj = {};
|
|
68459
|
-
for (let i4 = 0;i4 <
|
|
68460
|
-
resolvedObj[
|
|
69165
|
+
for (let i4 = 0;i4 < keys3.length; i4++) {
|
|
69166
|
+
resolvedObj[keys3[i4]] = results[i4];
|
|
68461
69167
|
}
|
|
68462
69168
|
return resolvedObj;
|
|
68463
69169
|
});
|
|
@@ -71087,10 +71793,10 @@ function handlePropertyResult(result, final, key, input, optin, optout) {
|
|
|
71087
71793
|
}
|
|
71088
71794
|
var NO_SYMBOL_KEYS = [];
|
|
71089
71795
|
function normalizeDef(def) {
|
|
71090
|
-
const
|
|
71796
|
+
const keys3 = Object.keys(def.shape);
|
|
71091
71797
|
const ownSymbols = Object.getOwnPropertySymbols(def.shape);
|
|
71092
71798
|
const symbolKeys = ownSymbols.length ? ownSymbols : NO_SYMBOL_KEYS;
|
|
71093
|
-
const allKeys = symbolKeys.length ? [...
|
|
71799
|
+
const allKeys = symbolKeys.length ? [...keys3, ...symbolKeys] : keys3;
|
|
71094
71800
|
for (const k4 of allKeys) {
|
|
71095
71801
|
if (!def.shape?.[k4]?._zod?.traits?.has("$ZodType")) {
|
|
71096
71802
|
throw new Error(`Invalid element at key "${String(k4)}": expected a Zod schema`);
|
|
@@ -71101,8 +71807,8 @@ function normalizeDef(def) {
|
|
|
71101
71807
|
...def,
|
|
71102
71808
|
allKeys,
|
|
71103
71809
|
symbolKeys,
|
|
71104
|
-
keySet: new Set(
|
|
71105
|
-
numKeys:
|
|
71810
|
+
keySet: new Set(keys3),
|
|
71811
|
+
numKeys: keys3.length,
|
|
71106
71812
|
optionalKeys: new Set(okeys)
|
|
71107
71813
|
};
|
|
71108
71814
|
}
|
|
@@ -71619,19 +72325,19 @@ function handleIntersectionResults(result, left, right) {
|
|
|
71619
72325
|
let unrecIssue;
|
|
71620
72326
|
const keyIssues = new Map;
|
|
71621
72327
|
const collect = (iss, side) => {
|
|
71622
|
-
let
|
|
72328
|
+
let keys3;
|
|
71623
72329
|
if (iss.code === "unrecognized_keys" && !iss.path?.length) {
|
|
71624
72330
|
unrecIssue ?? (unrecIssue = iss);
|
|
71625
|
-
|
|
72331
|
+
keys3 = iss.keys;
|
|
71626
72332
|
} else if (iss.code === "invalid_key" && iss.origin === "record" && iss.path?.length === 1) {
|
|
71627
72333
|
const k4 = String(iss.path[0]);
|
|
71628
72334
|
if (!keyIssues.has(k4))
|
|
71629
72335
|
keyIssues.set(k4, iss);
|
|
71630
|
-
|
|
72336
|
+
keys3 = [k4];
|
|
71631
72337
|
} else {
|
|
71632
72338
|
return false;
|
|
71633
72339
|
}
|
|
71634
|
-
for (const k4 of
|
|
72340
|
+
for (const k4 of keys3) {
|
|
71635
72341
|
if (!unrecKeys.has(k4))
|
|
71636
72342
|
unrecKeys.set(k4, {});
|
|
71637
72343
|
unrecKeys.get(k4)[side] = true;
|
|
@@ -80835,13 +81541,13 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
|
80835
81541
|
const def = schema._zod.def;
|
|
80836
81542
|
doc.write(`if (typeof ${accessor} !== "object" || ${accessor} === null || Array.isArray(${accessor})) return INVALID;`);
|
|
80837
81543
|
const shape = def.shape;
|
|
80838
|
-
const
|
|
81544
|
+
const keys3 = Object.keys(shape);
|
|
80839
81545
|
const symbolKeys = Object.getOwnPropertySymbols(shape);
|
|
80840
|
-
const allKeys = symbolKeys.length ? [...
|
|
81546
|
+
const allKeys = symbolKeys.length ? [...keys3, ...symbolKeys] : keys3;
|
|
80841
81547
|
const keyExpr = (k4) => typeof k4 === "symbol" ? addConstant(ctx, k4) : esc(k4);
|
|
80842
81548
|
const propKey = (k4) => typeof k4 === "symbol" ? `[${keyExpr(k4)}]` : esc(k4);
|
|
80843
81549
|
const propShape = shape;
|
|
80844
|
-
if (
|
|
81550
|
+
if (keys3.includes("__proto__")) {
|
|
80845
81551
|
throw new ZodCompileUnsupportedError('object shape key "__proto__"');
|
|
80846
81552
|
}
|
|
80847
81553
|
const propOutputs = new Map;
|
|
@@ -80883,7 +81589,7 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
|
80883
81589
|
if (catchall) {
|
|
80884
81590
|
const catchallType = catchall._zod.def.type;
|
|
80885
81591
|
if (catchallType === "never") {
|
|
80886
|
-
const condition =
|
|
81592
|
+
const condition = keys3.map((k4) => `k !== ${esc(k4)}`).join(" && ") || "true";
|
|
80887
81593
|
doc.write(`for (const k in ${accessor}) {`);
|
|
80888
81594
|
doc.indented((d4) => {
|
|
80889
81595
|
d4.write(`if (${condition}) return INVALID;`);
|
|
@@ -80899,7 +81605,7 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
|
80899
81605
|
const hasConditionalKeys = allKeys.some((k4) => mayOutputUndefined(propShape[k4]) || dropsWhenAbsent(propShape[k4]));
|
|
80900
81606
|
if (!buildsValue) {
|
|
80901
81607
|
if (unknownKeysMode === "schema") {
|
|
80902
|
-
const knownSet =
|
|
81608
|
+
const knownSet = keys3.length > 0 ? addConstant(ctx, new Set(keys3)) : null;
|
|
80903
81609
|
doc.write(`for (const k in ${accessor}) {`);
|
|
80904
81610
|
doc.indented((d4) => {
|
|
80905
81611
|
d4.write(`if (k === "__proto__") continue;`);
|
|
@@ -80931,7 +81637,7 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
|
80931
81637
|
}
|
|
80932
81638
|
}
|
|
80933
81639
|
if (unknownKeysMode !== "none") {
|
|
80934
|
-
const knownSet =
|
|
81640
|
+
const knownSet = keys3.length > 0 ? addConstant(ctx, new Set(keys3)) : null;
|
|
80935
81641
|
doc.write(`for (const k in ${accessor}) {`);
|
|
80936
81642
|
doc.indented((d4) => {
|
|
80937
81643
|
d4.write(`if (k === "__proto__") continue;`);
|
|
@@ -82794,8 +83500,8 @@ function compactTypeUnion(schema) {
|
|
|
82794
83500
|
if (!option || typeof option !== "object")
|
|
82795
83501
|
return;
|
|
82796
83502
|
compactTypeUnion(option);
|
|
82797
|
-
const
|
|
82798
|
-
if (
|
|
83503
|
+
const keys3 = Object.keys(option);
|
|
83504
|
+
if (keys3.length !== 1 || keys3[0] !== "type")
|
|
82799
83505
|
return;
|
|
82800
83506
|
const type = option.type;
|
|
82801
83507
|
for (const member of Array.isArray(type) ? type : [type]) {
|
|
@@ -82829,8 +83535,8 @@ function foldObjects(members2) {
|
|
|
82829
83535
|
}
|
|
82830
83536
|
const properties = {};
|
|
82831
83537
|
const required2 = new Set;
|
|
82832
|
-
for (const
|
|
82833
|
-
for (const key in
|
|
83538
|
+
for (const object6 of objects) {
|
|
83539
|
+
for (const key in object6.properties) {
|
|
82834
83540
|
if (Object.prototype.hasOwnProperty.call(properties, key))
|
|
82835
83541
|
continue;
|
|
82836
83542
|
const parts = [];
|
|
@@ -82844,18 +83550,18 @@ function foldObjects(members2) {
|
|
|
82844
83550
|
const merged = parts.length === 1 ? parts[0] : foldObjects(parts) ?? { allOf: parts };
|
|
82845
83551
|
assignProp(properties, key, merged);
|
|
82846
83552
|
}
|
|
82847
|
-
for (const key of
|
|
83553
|
+
for (const key of object6.required ?? [])
|
|
82848
83554
|
required2.add(key);
|
|
82849
83555
|
}
|
|
82850
83556
|
const folded = { type: "object", properties };
|
|
82851
83557
|
if (required2.size)
|
|
82852
83558
|
folded.required = [...required2];
|
|
82853
|
-
if (objects.every((
|
|
83559
|
+
if (objects.every((object6) => object6.additionalProperties === false)) {
|
|
82854
83560
|
folded.additionalProperties = false;
|
|
82855
83561
|
} else {
|
|
82856
83562
|
const constraints = [];
|
|
82857
|
-
for (const
|
|
82858
|
-
const constraint = undeclaredConstraint(
|
|
83563
|
+
for (const object6 of objects) {
|
|
83564
|
+
const constraint = undeclaredConstraint(object6);
|
|
82859
83565
|
if (constraint && !constraints.some((seen) => JSON.stringify(seen) === JSON.stringify(constraint)))
|
|
82860
83566
|
constraints.push(constraint);
|
|
82861
83567
|
}
|
|
@@ -83867,7 +84573,7 @@ __export(exports_schemas2, {
|
|
|
83867
84573
|
pipe: () => pipe,
|
|
83868
84574
|
partialRecord: () => partialRecord,
|
|
83869
84575
|
optional: () => optional,
|
|
83870
|
-
object: () =>
|
|
84576
|
+
object: () => object6,
|
|
83871
84577
|
number: () => number2,
|
|
83872
84578
|
nullish: () => nullish2,
|
|
83873
84579
|
nullable: () => nullable,
|
|
@@ -83899,7 +84605,7 @@ __export(exports_schemas2, {
|
|
|
83899
84605
|
httpUrl: () => httpUrl,
|
|
83900
84606
|
hostname: () => hostname2,
|
|
83901
84607
|
hex: () => hex2,
|
|
83902
|
-
hash: () =>
|
|
84608
|
+
hash: () => hash3,
|
|
83903
84609
|
guid: () => guid2,
|
|
83904
84610
|
function: () => _function,
|
|
83905
84611
|
float64: () => float64,
|
|
@@ -84613,7 +85319,7 @@ function hostname2(_params) {
|
|
|
84613
85319
|
function hex2(_params) {
|
|
84614
85320
|
return _stringFormat(ZodCustomStringFormat, "hex", exports_regexes.hex, _params);
|
|
84615
85321
|
}
|
|
84616
|
-
function
|
|
85322
|
+
function hash3(alg, params) {
|
|
84617
85323
|
const enc = params?.enc ?? "hex";
|
|
84618
85324
|
const format = `${alg}_${enc}`;
|
|
84619
85325
|
const regex = exports_regexes[format];
|
|
@@ -84913,7 +85619,7 @@ var ZodObject2 = /* @__PURE__ */ $constructor("ZodObject", (inst, def) => {
|
|
|
84913
85619
|
return exports_util.required(ZodNonOptional, this, args[0]);
|
|
84914
85620
|
}
|
|
84915
85621
|
});
|
|
84916
|
-
function
|
|
85622
|
+
function object6(shape, params) {
|
|
84917
85623
|
const def = {
|
|
84918
85624
|
type: "object",
|
|
84919
85625
|
shape: shape ?? {},
|
|
@@ -85106,11 +85812,11 @@ var ZodEnum2 = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
|
|
|
85106
85812
|
inst._zod.processJSONSchema = (ctx, json5, params) => enumProcessor(inst, ctx, json5, params);
|
|
85107
85813
|
inst.enum = def.entries;
|
|
85108
85814
|
inst.options = Object.values(def.entries);
|
|
85109
|
-
const
|
|
85815
|
+
const keys3 = new Set(Object.keys(def.entries));
|
|
85110
85816
|
inst.extract = (values, params) => {
|
|
85111
85817
|
const newEntries = {};
|
|
85112
85818
|
for (const value of values) {
|
|
85113
|
-
if (
|
|
85819
|
+
if (keys3.has(value)) {
|
|
85114
85820
|
newEntries[value] = def.entries[value];
|
|
85115
85821
|
} else
|
|
85116
85822
|
throw new Error(`Key ${value} not found in enum`);
|
|
@@ -85125,7 +85831,7 @@ var ZodEnum2 = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
|
|
|
85125
85831
|
inst.exclude = (values, params) => {
|
|
85126
85832
|
const newEntries = { ...def.entries };
|
|
85127
85833
|
for (const value of values) {
|
|
85128
|
-
if (
|
|
85834
|
+
if (keys3.has(value)) {
|
|
85129
85835
|
delete newEntries[value];
|
|
85130
85836
|
} else
|
|
85131
85837
|
throw new Error(`Key ${value} not found in enum`);
|
|
@@ -86112,10 +86818,10 @@ function visit2(schema, fnOrHandlers) {
|
|
|
86112
86818
|
switch (kind) {
|
|
86113
86819
|
case "object": {
|
|
86114
86820
|
const oldShape = def.shape;
|
|
86115
|
-
const
|
|
86821
|
+
const keys3 = Object.keys(oldShape);
|
|
86116
86822
|
let changed = false;
|
|
86117
86823
|
const newShape = {};
|
|
86118
|
-
for (const k4 of
|
|
86824
|
+
for (const k4 of keys3) {
|
|
86119
86825
|
const mapped = run(oldShape[k4]);
|
|
86120
86826
|
if (mapped !== oldShape[k4])
|
|
86121
86827
|
changed = true;
|
|
@@ -86295,7 +87001,7 @@ function date4(params) {
|
|
|
86295
87001
|
return _coercedDate(ZodDate2, params);
|
|
86296
87002
|
}
|
|
86297
87003
|
// src/lib/canonical-json.ts
|
|
86298
|
-
import { createHash as
|
|
87004
|
+
import { createHash as createHash28 } from "crypto";
|
|
86299
87005
|
function canonicalJson3(value, maximumBytes = 2 * 1024 * 1024) {
|
|
86300
87006
|
return canonicalJsonAtDepth(value, maximumBytes, 64);
|
|
86301
87007
|
}
|
|
@@ -86339,7 +87045,7 @@ function canonicalJsonAtDepth(value, maximumBytes, maximumDepth) {
|
|
|
86339
87045
|
return visit3(value, 0);
|
|
86340
87046
|
}
|
|
86341
87047
|
function canonicalJsonSha256(value, maximumBytes) {
|
|
86342
|
-
return
|
|
87048
|
+
return createHash28("sha256").update(canonicalJson3(value, maximumBytes)).digest("hex");
|
|
86343
87049
|
}
|
|
86344
87050
|
|
|
86345
87051
|
// src/lib/remote-recurring-contract.ts
|
|
@@ -86597,9 +87303,9 @@ function requireResult(value) {
|
|
|
86597
87303
|
if (!value)
|
|
86598
87304
|
throw new RemoteRecurringReadError;
|
|
86599
87305
|
}
|
|
86600
|
-
function termsIntegrity(terms,
|
|
87306
|
+
function termsIntegrity(terms, hash4, identity3, own2) {
|
|
86601
87307
|
requireResult(terms.organizationId === identity3.organization.id && (!own2 || terms.approvedByUserId === identity3.user.id && terms.approvedMembershipId === identity3.user.membershipId));
|
|
86602
|
-
requireResult(canonicalJsonSha256(terms) ===
|
|
87308
|
+
requireResult(canonicalJsonSha256(terms) === hash4);
|
|
86603
87309
|
requireResult(canonicalJsonSha256({ skill: terms.skill, input: terms.input, args: terms.args, runtime: terms.runtime }) === terms.payloadSha256);
|
|
86604
87310
|
requireResult(Date.parse(terms.expiresAt) > Date.parse(terms.startsAt));
|
|
86605
87311
|
}
|
|
@@ -86734,7 +87440,7 @@ var inputFailure = () => {
|
|
|
86734
87440
|
function invitationInput(action, input2) {
|
|
86735
87441
|
if (!record4(input2))
|
|
86736
87442
|
return inputFailure();
|
|
86737
|
-
const
|
|
87443
|
+
const keys3 = {
|
|
86738
87444
|
list: ["after"],
|
|
86739
87445
|
get: ["invitationId"],
|
|
86740
87446
|
issue: ["email", "role", "idempotencyKey", "confirm"],
|
|
@@ -86742,7 +87448,7 @@ function invitationInput(action, input2) {
|
|
|
86742
87448
|
revoke: ["invitationId", "expectedGeneration", "confirm"],
|
|
86743
87449
|
accept: ["invitationId", "token", "confirm"]
|
|
86744
87450
|
};
|
|
86745
|
-
if (Object.keys(input2).some((key) => !
|
|
87451
|
+
if (Object.keys(input2).some((key) => !keys3[action].includes(key)) || action !== "list" && keys3[action].some((key) => !Object.hasOwn(input2, key)))
|
|
86746
87452
|
return inputFailure();
|
|
86747
87453
|
const value = { ...input2 };
|
|
86748
87454
|
if (action === "list") {
|
|
@@ -87119,15 +87825,15 @@ function runQuoteReceipt(value) {
|
|
|
87119
87825
|
return value;
|
|
87120
87826
|
}
|
|
87121
87827
|
function parseRemoteRunQuote(value) {
|
|
87122
|
-
const quote2 =
|
|
87828
|
+
const quote2 = object7(value);
|
|
87123
87829
|
runQuoteReceipt(quote2.quoteReceipt);
|
|
87124
|
-
const pricing =
|
|
87830
|
+
const pricing = object7(quote2.pricing);
|
|
87125
87831
|
if (typeof quote2.skill !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(quote2.skill))
|
|
87126
87832
|
throw new Error("Invalid quoted skill");
|
|
87127
87833
|
const costCents = creditCount(pricing.costCredits ?? pricing.costCents);
|
|
87128
87834
|
if (pricing.costCredits !== undefined && pricing.costCents !== undefined && pricing.costCents !== costCents)
|
|
87129
87835
|
throw new Error("Inconsistent quoted credit count");
|
|
87130
|
-
if (quote2.availability &&
|
|
87836
|
+
if (quote2.availability && object7(quote2.availability).status !== "available")
|
|
87131
87837
|
throw new Error("This skill is unavailable for remote execution");
|
|
87132
87838
|
return { ...quote2, skill: quote2.skill, pricing: { ...pricing, costCredits: costCents, costCents, formattedCost: `${costCents} credits` } };
|
|
87133
87839
|
}
|
|
@@ -87136,7 +87842,7 @@ function parseRemoteCreditPacks(value) {
|
|
|
87136
87842
|
throw new Error("Invalid credit pack response");
|
|
87137
87843
|
const ids = new Set;
|
|
87138
87844
|
return value.map((value2) => {
|
|
87139
|
-
const row =
|
|
87845
|
+
const row = object7(value2);
|
|
87140
87846
|
const counts = [row.credits, row.creditsCents, row.amountCents].filter((value3) => value3 !== undefined).map(creditCount);
|
|
87141
87847
|
if (!counts.length || counts[0] === 0 || counts.some((count) => count !== counts[0]))
|
|
87142
87848
|
throw new Error("Inconsistent credit pack counts");
|
|
@@ -87151,7 +87857,7 @@ function parseRemoteCreditPacks(value) {
|
|
|
87151
87857
|
});
|
|
87152
87858
|
}
|
|
87153
87859
|
function parseRemoteBillingStatus(value) {
|
|
87154
|
-
const row =
|
|
87860
|
+
const row = object7(value);
|
|
87155
87861
|
const counts = [row.creditBalance, row.balanceCents].filter((value2) => value2 !== undefined).map(creditCount);
|
|
87156
87862
|
if (!counts.length || counts.some((count) => count !== counts[0]))
|
|
87157
87863
|
throw new Error("Inconsistent credit balance");
|
|
@@ -87163,7 +87869,7 @@ function parseRemoteBillingStatus(value) {
|
|
|
87163
87869
|
};
|
|
87164
87870
|
}
|
|
87165
87871
|
function parseRemoteCheckout(value) {
|
|
87166
|
-
const row =
|
|
87872
|
+
const row = object7(value);
|
|
87167
87873
|
if (typeof row.url !== "string")
|
|
87168
87874
|
throw new Error("Invalid checkout URL");
|
|
87169
87875
|
const url2 = new URL(row.url);
|
|
@@ -87171,7 +87877,7 @@ function parseRemoteCheckout(value) {
|
|
|
87171
87877
|
throw new Error("Invalid checkout URL");
|
|
87172
87878
|
return { url: row.url };
|
|
87173
87879
|
}
|
|
87174
|
-
function
|
|
87880
|
+
function object7(value) {
|
|
87175
87881
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
87176
87882
|
throw new Error("Invalid Skills server response");
|
|
87177
87883
|
return value;
|
|
@@ -87375,6 +88081,18 @@ class RemoteRequestError extends Error {
|
|
|
87375
88081
|
this.name = "RemoteRequestError";
|
|
87376
88082
|
}
|
|
87377
88083
|
}
|
|
88084
|
+
var KNOWN_SKILL_LIFECYCLE_CODES = new Set(["SKILL_ARCHIVE_PROFILE_CONFLICT", "LIFECYCLE_ROLE_REQUIRED"]);
|
|
88085
|
+
|
|
88086
|
+
class RemoteSkillLifecycleError extends RemoteRequestError {
|
|
88087
|
+
code;
|
|
88088
|
+
constructor(path2, status, code) {
|
|
88089
|
+
super(path2, status);
|
|
88090
|
+
this.name = "RemoteSkillLifecycleError";
|
|
88091
|
+
const safeCode = code !== undefined && KNOWN_SKILL_LIFECYCLE_CODES.has(code) ? code : undefined;
|
|
88092
|
+
this.code = safeCode;
|
|
88093
|
+
this.message = `Skill lifecycle update was refused (HTTP ${status}${safeCode ? `, code ${safeCode}` : ""})`;
|
|
88094
|
+
}
|
|
88095
|
+
}
|
|
87378
88096
|
|
|
87379
88097
|
class RemoteCreditCheckoutError extends RemoteRequestError {
|
|
87380
88098
|
code;
|
|
@@ -87555,6 +88273,39 @@ class RemoteSkillsClient {
|
|
|
87555
88273
|
})();
|
|
87556
88274
|
return this.capabilities;
|
|
87557
88275
|
}
|
|
88276
|
+
async addSkillPublishScope(keyId, expectedScopes, expectedOrgId) {
|
|
88277
|
+
if (!/^[A-Za-z0-9_-]+$/.test(keyId))
|
|
88278
|
+
throw new Error("Invalid API key id");
|
|
88279
|
+
if (!/^[A-Za-z0-9_-]{1,256}$/.test(expectedOrgId))
|
|
88280
|
+
throw new Error("Invalid expected organization id");
|
|
88281
|
+
if (!Array.isArray(expectedScopes) || expectedScopes.length > 32 || expectedScopes.some((scope2) => typeof scope2 !== "string" || scope2.length > 128 || !/^(?:\*|[a-z][a-z0-9_-]*:(?:\*|[a-z][a-z0-9_-]*))$/.test(scope2)) || new Set(expectedScopes).size !== expectedScopes.length)
|
|
88282
|
+
throw new Error("Invalid expected API key scopes");
|
|
88283
|
+
const path2 = `/api/v1/admin/keys/${encodeURIComponent(keyId)}/scopes`;
|
|
88284
|
+
const response = await this.request(path2, { method: "PATCH", body: JSON.stringify({ expected_scopes: expectedScopes, add_scopes: ["skills:publish"] }) });
|
|
88285
|
+
if (!response.ok)
|
|
88286
|
+
throw new RemoteRequestError(path2, response.status);
|
|
88287
|
+
let value;
|
|
88288
|
+
try {
|
|
88289
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readBoundedResponse(response, 64 * 1024)));
|
|
88290
|
+
} catch {
|
|
88291
|
+
throw new Error("Invalid API key scope update response");
|
|
88292
|
+
}
|
|
88293
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
88294
|
+
throw new Error("Invalid API key scope update response");
|
|
88295
|
+
const body4 = value;
|
|
88296
|
+
const returnedId = body4.keyId;
|
|
88297
|
+
const orgId = body4.orgId;
|
|
88298
|
+
const scopes = body4.scopes;
|
|
88299
|
+
if (returnedId !== keyId || typeof orgId !== "string" || !orgId || expectedOrgId !== undefined && orgId !== expectedOrgId || !Array.isArray(scopes) || scopes.some((scope2) => typeof scope2 !== "string") || body4.updated !== true) {
|
|
88300
|
+
throw new Error("Invalid API key scope update response");
|
|
88301
|
+
}
|
|
88302
|
+
const returnedScopes = scopes;
|
|
88303
|
+
const expectedResult = expectedScopes.includes("skills:publish") ? expectedScopes : [...expectedScopes, "skills:publish"];
|
|
88304
|
+
if (returnedScopes.length !== expectedResult.length || expectedResult.some((scope2, index) => returnedScopes[index] !== scope2)) {
|
|
88305
|
+
throw new Error("API key scope update response did not preserve the expected scopes");
|
|
88306
|
+
}
|
|
88307
|
+
return { keyId, orgId, scopes: [...returnedScopes], updated: true };
|
|
88308
|
+
}
|
|
87558
88309
|
async submitQuotedRun(slug2, input2 = {}, args = [], approval = {}) {
|
|
87559
88310
|
runQuoteReceipt(approval.quoteReceipt);
|
|
87560
88311
|
({ input: input2, args, approval } = JSON.parse(JSON.stringify({ input: input2, args, approval })));
|
|
@@ -87928,14 +88679,14 @@ class RemoteSkillsClient {
|
|
|
87928
88679
|
async createBillingPortal() {
|
|
87929
88680
|
return this.checkoutResponse("/api/v1/billing/portal");
|
|
87930
88681
|
}
|
|
87931
|
-
async cancelRun(
|
|
87932
|
-
return this.controlRun(
|
|
88682
|
+
async cancelRun(runId3) {
|
|
88683
|
+
return this.controlRun(runId3, "cancel");
|
|
87933
88684
|
}
|
|
87934
|
-
async resumeRun(
|
|
87935
|
-
return this.controlRun(
|
|
88685
|
+
async resumeRun(runId3) {
|
|
88686
|
+
return this.controlRun(runId3, "resume");
|
|
87936
88687
|
}
|
|
87937
|
-
async controlRun(
|
|
87938
|
-
const response = await this.requestNewRoute(`/api/v1/runs/${encodeURIComponent(
|
|
88688
|
+
async controlRun(runId3, action) {
|
|
88689
|
+
const response = await this.requestNewRoute(`/api/v1/runs/${encodeURIComponent(runId3)}/${action}`, { method: "POST", body: "{}" });
|
|
87939
88690
|
return normalizeRemoteSkillRunContract(await response.json());
|
|
87940
88691
|
}
|
|
87941
88692
|
async checkoutResponse(path2) {
|
|
@@ -87947,8 +88698,8 @@ class RemoteSkillsClient {
|
|
|
87947
88698
|
throw new Error("Invalid Skills server list response");
|
|
87948
88699
|
return rows;
|
|
87949
88700
|
}
|
|
87950
|
-
async getRun(
|
|
87951
|
-
const path2 = `/api/v1/runs/${encodeURIComponent(
|
|
88701
|
+
async getRun(runId3) {
|
|
88702
|
+
const path2 = `/api/v1/runs/${encodeURIComponent(runId3)}`;
|
|
87952
88703
|
const res = await this.request(path2);
|
|
87953
88704
|
if (res.status === 404)
|
|
87954
88705
|
return null;
|
|
@@ -87956,30 +88707,30 @@ class RemoteSkillsClient {
|
|
|
87956
88707
|
throw new RemoteRequestError(path2, res.status, res.statusText);
|
|
87957
88708
|
return normalizeRemoteSkillRunContract(await res.json());
|
|
87958
88709
|
}
|
|
87959
|
-
async getRunLogs(
|
|
87960
|
-
return this.arrayResponse(`/api/v1/runs/${encodeURIComponent(
|
|
88710
|
+
async getRunLogs(runId3) {
|
|
88711
|
+
return this.arrayResponse(`/api/v1/runs/${encodeURIComponent(runId3)}/logs`);
|
|
87961
88712
|
}
|
|
87962
88713
|
async listRuns(limit = 20) {
|
|
87963
88714
|
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
87964
88715
|
throw new Error("Run limit must be an integer from 1 to 100");
|
|
87965
88716
|
return this.arrayResponse(`/api/v1/runs?limit=${limit}`);
|
|
87966
88717
|
}
|
|
87967
|
-
async getRunArtifacts(
|
|
87968
|
-
return this.arrayResponse(`/api/v1/runs/${encodeURIComponent(
|
|
88718
|
+
async getRunArtifacts(runId3) {
|
|
88719
|
+
return this.arrayResponse(`/api/v1/runs/${encodeURIComponent(runId3)}/artifacts`);
|
|
87969
88720
|
}
|
|
87970
|
-
async downloadRunArtifact(
|
|
87971
|
-
return this.request(`/api/v1/runs/${encodeURIComponent(
|
|
88721
|
+
async downloadRunArtifact(runId3, artifactId2) {
|
|
88722
|
+
return this.request(`/api/v1/runs/${encodeURIComponent(runId3)}/artifacts/${encodeURIComponent(artifactId2)}/download`, {
|
|
87972
88723
|
method: "GET"
|
|
87973
88724
|
});
|
|
87974
88725
|
}
|
|
87975
|
-
async getVerifiedRunArtifact(
|
|
87976
|
-
const artifacts = await this.getRunArtifacts(
|
|
88726
|
+
async getVerifiedRunArtifact(runId3, artifactId2, maximumBytes = MAX_REMOTE_FILE_BYTES) {
|
|
88727
|
+
const artifacts = await this.getRunArtifacts(runId3);
|
|
87977
88728
|
const artifact = artifacts.find((row) => row.id === artifactId2);
|
|
87978
88729
|
if (!artifact)
|
|
87979
88730
|
throw new Error("Run artifact not found");
|
|
87980
88731
|
if (!Number.isSafeInteger(artifact.byteSize) || artifact.byteSize < 0 || artifact.byteSize > maximumBytes || typeof artifact.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(artifact.sha256))
|
|
87981
88732
|
throw new Error("The server does not provide valid artifact integrity metadata");
|
|
87982
|
-
const response = await this.downloadRunArtifact(
|
|
88733
|
+
const response = await this.downloadRunArtifact(runId3, artifactId2);
|
|
87983
88734
|
if (!response.ok)
|
|
87984
88735
|
throw new RemoteRequestError("artifact download", response.status, response.statusText);
|
|
87985
88736
|
const bytes = await readBoundedResponse(response, artifact.byteSize);
|
|
@@ -88027,9 +88778,9 @@ class RemoteSkillsClient {
|
|
|
88027
88778
|
}
|
|
88028
88779
|
return run;
|
|
88029
88780
|
}
|
|
88030
|
-
async uploadRunFiles(
|
|
88781
|
+
async uploadRunFiles(runId3, files) {
|
|
88031
88782
|
const descriptors = describeRemoteFiles(files);
|
|
88032
|
-
const response = await this.requestNewRoute(`/api/v1/runs/${encodeURIComponent(
|
|
88783
|
+
const response = await this.requestNewRoute(`/api/v1/runs/${encodeURIComponent(runId3)}/uploads`, { method: "POST", body: JSON.stringify({ files: descriptors }) });
|
|
88033
88784
|
const payload = await response.json();
|
|
88034
88785
|
if (!Array.isArray(payload.files) || payload.files.length !== files.length || new Set(payload.files.map((file2) => file2.name)).size !== files.length)
|
|
88035
88786
|
throw new Error("Invalid input upload response");
|
|
@@ -88072,6 +88823,19 @@ class RemoteSkillsClient {
|
|
|
88072
88823
|
async deleteSkill(slug2) {
|
|
88073
88824
|
return this.request(`/api/v1/skills/${encodeURIComponent(slug2)}`, { method: "DELETE" });
|
|
88074
88825
|
}
|
|
88826
|
+
async setSkillLifecycle(slug2, lifecycle, options) {
|
|
88827
|
+
const path2 = `/api/v1/skills/${encodeURIComponent(slug2)}/lifecycle`;
|
|
88828
|
+
const response = await this.request(path2, {
|
|
88829
|
+
method: "PATCH",
|
|
88830
|
+
headers: { "If-Match": options.expectedRevisionId },
|
|
88831
|
+
body: JSON.stringify({ lifecycle, ...options.reason ? { reason: options.reason } : {}, ...options.replacementSlug ? { replacementSlug: options.replacementSlug } : {} })
|
|
88832
|
+
});
|
|
88833
|
+
if (!response.ok) {
|
|
88834
|
+
const code = await readResponseCode(response);
|
|
88835
|
+
throw new RemoteSkillLifecycleError(path2, response.status, code);
|
|
88836
|
+
}
|
|
88837
|
+
return response.json();
|
|
88838
|
+
}
|
|
88075
88839
|
async downloadSkillBundle(slug2) {
|
|
88076
88840
|
return this.request(`/api/v1/skills/${encodeURIComponent(slug2)}/bundle`, { method: "GET" });
|
|
88077
88841
|
}
|
|
@@ -88224,9 +88988,13 @@ function normalizeSkillSummaryList(payload) {
|
|
|
88224
88988
|
return payload.map(normalizeSkillSummary);
|
|
88225
88989
|
}
|
|
88226
88990
|
async function responseBodyCarriesCode(response, codes) {
|
|
88991
|
+
const code = await readResponseCode(response);
|
|
88992
|
+
return code !== undefined && codes.includes(code);
|
|
88993
|
+
}
|
|
88994
|
+
async function readResponseCode(response) {
|
|
88227
88995
|
const reader = response.body?.getReader();
|
|
88228
88996
|
if (!reader)
|
|
88229
|
-
return
|
|
88997
|
+
return;
|
|
88230
88998
|
const maximum = 8 * 1024;
|
|
88231
88999
|
let deadline;
|
|
88232
89000
|
const expired = new Promise((_, reject) => {
|
|
@@ -88241,7 +89009,7 @@ async function responseBodyCarriesCode(response, codes) {
|
|
|
88241
89009
|
break;
|
|
88242
89010
|
size += next2.value.byteLength;
|
|
88243
89011
|
if (size > maximum)
|
|
88244
|
-
return
|
|
89012
|
+
return;
|
|
88245
89013
|
chunks.push(next2.value);
|
|
88246
89014
|
}
|
|
88247
89015
|
const bytes = new Uint8Array(size);
|
|
@@ -88252,11 +89020,11 @@ async function responseBodyCarriesCode(response, codes) {
|
|
|
88252
89020
|
}
|
|
88253
89021
|
const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
88254
89022
|
if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Object.hasOwn(payload, "code"))
|
|
88255
|
-
return
|
|
89023
|
+
return;
|
|
88256
89024
|
const code = payload.code;
|
|
88257
|
-
return typeof code === "string" &&
|
|
89025
|
+
return typeof code === "string" && /^[A-Z][A-Z0-9_:-]{0,127}$/.test(code) ? code : undefined;
|
|
88258
89026
|
} catch {
|
|
88259
|
-
return
|
|
89027
|
+
return;
|
|
88260
89028
|
} finally {
|
|
88261
89029
|
clearTimeout(deadline);
|
|
88262
89030
|
reader.cancel().catch(() => {});
|
|
@@ -88327,8 +89095,8 @@ function invitationEmailIds(invitationId, challengeId) {
|
|
|
88327
89095
|
return { invitationId, challengeId };
|
|
88328
89096
|
}
|
|
88329
89097
|
function invitationEmailInput(action, input2) {
|
|
88330
|
-
const
|
|
88331
|
-
if (!record8(input2) || Object.keys(input2).length !==
|
|
89098
|
+
const keys3 = ["invitationId", "token", "challengeId", "confirm", ...action === "accept" ? ["code"] : []];
|
|
89099
|
+
if (!record8(input2) || Object.keys(input2).length !== keys3.length || keys3.some((key) => !Object.hasOwn(input2, key)))
|
|
88332
89100
|
throw new InvitationEmailInputError;
|
|
88333
89101
|
const value = { ...input2 };
|
|
88334
89102
|
const ids = invitationEmailIds(value.invitationId, value.challengeId);
|
|
@@ -88378,7 +89146,7 @@ async function requestInvitationEmail(origin, action, input2) {
|
|
|
88378
89146
|
}
|
|
88379
89147
|
|
|
88380
89148
|
// src/lib/remote-private-publications.ts
|
|
88381
|
-
import { createHash as
|
|
89149
|
+
import { createHash as createHash29 } from "crypto";
|
|
88382
89150
|
var PRIVATE_PUBLICATION_MAX_BYTES = 16 * 1024 * 1024;
|
|
88383
89151
|
var failures2 = {
|
|
88384
89152
|
INVALID_REQUEST: [400, "The publication request is invalid."],
|
|
@@ -88421,17 +89189,17 @@ var invalid7 = () => {
|
|
|
88421
89189
|
throw new PrivatePublicationError("INVALID_PUBLICATION_RESPONSE", "The server returned an invalid publication result.");
|
|
88422
89190
|
};
|
|
88423
89191
|
var publicationUuid = (v2) => typeof v2 === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v2);
|
|
88424
|
-
var
|
|
89192
|
+
var hash4 = (v2) => typeof v2 === "string" && /^[a-f0-9]{64}$/.test(v2);
|
|
88425
89193
|
var record9 = (v2) => !!v2 && typeof v2 === "object" && !Array.isArray(v2);
|
|
88426
|
-
var exact = (v2,
|
|
89194
|
+
var exact = (v2, keys3) => record9(v2) && Object.keys(v2).sort().join(",") === keys3.sort().join(",");
|
|
88427
89195
|
var date5 = (v2) => typeof v2 === "string" && /^\d{4}-\d\d-\d\dT[0-9:.]+(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(v2) && Number.isFinite(Date.parse(v2));
|
|
88428
|
-
var publicationSha256 = (bytes) =>
|
|
89196
|
+
var publicationSha256 = (bytes) => createHash29("sha256").update(bytes).digest("hex");
|
|
88429
89197
|
function checkedPublicationDeclaration(value) {
|
|
88430
|
-
if (!exact(value, ["idempotencyKey", "version", "expectedCurrentVersionId", "manifestText", "archiveSha256", "archiveByteSize"]) || !publicationUuid(value.idempotencyKey) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || typeof value.manifestText !== "string" || Buffer.byteLength(value.manifestText) > 16384 || !
|
|
89198
|
+
if (!exact(value, ["idempotencyKey", "version", "expectedCurrentVersionId", "manifestText", "archiveSha256", "archiveByteSize"]) || !publicationUuid(value.idempotencyKey) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || typeof value.manifestText !== "string" || Buffer.byteLength(value.manifestText) > 16384 || !hash4(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES)
|
|
88431
89199
|
return bad();
|
|
88432
89200
|
try {
|
|
88433
89201
|
const manifest = JSON.parse(value.manifestText);
|
|
88434
|
-
if (!record9(manifest) || manifest.version !== value.version || validatePortableManifestContract(manifest, { strict: true }).length || !
|
|
89202
|
+
if (!record9(manifest) || manifest.version !== value.version || validatePortableManifestContract(manifest, { strict: true }).length || !hash4(manifest.provenance?.content_hash))
|
|
88435
89203
|
return bad();
|
|
88436
89204
|
} catch {
|
|
88437
89205
|
return bad();
|
|
@@ -88439,7 +89207,7 @@ function checkedPublicationDeclaration(value) {
|
|
|
88439
89207
|
return Object.freeze({ ...value });
|
|
88440
89208
|
}
|
|
88441
89209
|
function checkedPublicationView(value, skillId, intentId, declaration) {
|
|
88442
|
-
if (!exact(value, ["id", "skillId", "version", "expectedCurrentVersionId", "archiveSha256", "archiveByteSize", "state", "expiresAt", "createdAt", "versionId"]) || !publicationUuid(value.id) || value.skillId !== skillId || intentId !== undefined && value.id !== intentId || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || !
|
|
89210
|
+
if (!exact(value, ["id", "skillId", "version", "expectedCurrentVersionId", "archiveSha256", "archiveByteSize", "state", "expiresAt", "createdAt", "versionId"]) || !publicationUuid(value.id) || value.skillId !== skillId || intentId !== undefined && value.id !== intentId || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || !hash4(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES || typeof value.state !== "string" || !["awaiting_upload", "queued", "verifying", "needs_attention", "committed", "rejected", "cancelled", "expired"].includes(value.state) || !date5(value.expiresAt) || !date5(value.createdAt) || !(value.versionId === null || publicationUuid(value.versionId)) || value.state === "committed" && value.versionId === null)
|
|
88443
89211
|
return invalid7();
|
|
88444
89212
|
if (declaration && ["version", "expectedCurrentVersionId", "archiveSha256", "archiveByteSize"].some((k4) => value[k4] !== declaration[k4]))
|
|
88445
89213
|
return invalid7();
|
|
@@ -89165,8 +89933,8 @@ function record10(value) {
|
|
|
89165
89933
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
89166
89934
|
}
|
|
89167
89935
|
function exact2(value, names) {
|
|
89168
|
-
const
|
|
89169
|
-
return
|
|
89936
|
+
const keys3 = Object.keys(value).sort(), expected = [...names].sort();
|
|
89937
|
+
return keys3.length === expected.length && keys3.every((key, index) => key === expected[index]);
|
|
89170
89938
|
}
|
|
89171
89939
|
function snapshot(value, maximum) {
|
|
89172
89940
|
let nodes4 = 0, bytes = 0;
|
|
@@ -89203,13 +89971,13 @@ function snapshot(value, maximum) {
|
|
|
89203
89971
|
const array3 = Array.isArray(input2), proto = Object.getPrototypeOf(input2);
|
|
89204
89972
|
if (array3 ? proto !== Array.prototype : proto !== Object.prototype && proto !== null)
|
|
89205
89973
|
throw Error();
|
|
89206
|
-
const
|
|
89207
|
-
if (
|
|
89974
|
+
const keys3 = Reflect.ownKeys(input2);
|
|
89975
|
+
if (keys3.length > SKILL_OPERATION_LIMITS.nodes + 1 || keys3.some((key) => typeof key !== "string"))
|
|
89208
89976
|
throw Error();
|
|
89209
89977
|
ancestors2.add(input2);
|
|
89210
89978
|
try {
|
|
89211
89979
|
if (array3) {
|
|
89212
|
-
if (input2.length > SKILL_OPERATION_LIMITS.nodes ||
|
|
89980
|
+
if (input2.length > SKILL_OPERATION_LIMITS.nodes || keys3.length !== input2.length + 1)
|
|
89213
89981
|
throw Error();
|
|
89214
89982
|
const result2 = [];
|
|
89215
89983
|
append("[");
|
|
@@ -89226,7 +89994,7 @@ function snapshot(value, maximum) {
|
|
|
89226
89994
|
}
|
|
89227
89995
|
const result = Object.create(null);
|
|
89228
89996
|
append("{");
|
|
89229
|
-
for (const [i4, key] of
|
|
89997
|
+
for (const [i4, key] of keys3.sort().entries()) {
|
|
89230
89998
|
const descriptor = Object.getOwnPropertyDescriptor(input2, key);
|
|
89231
89999
|
if (!("value" in descriptor) || !descriptor.enumerable || /[\ud800-\udfff]/u.test(key) || key.length > maximum - bytes)
|
|
89232
90000
|
throw Error();
|
|
@@ -89513,6 +90281,7 @@ export {
|
|
|
89513
90281
|
DEFAULT_OUTPUT_GOVERNANCE,
|
|
89514
90282
|
DEFAULT_IMAGE_PROFILES,
|
|
89515
90283
|
ContentHashInputError,
|
|
90284
|
+
CloudExecutionClient,
|
|
89516
90285
|
CONTENT_HASH_LIMITS,
|
|
89517
90286
|
CLIENT_TOKEN_BYTES,
|
|
89518
90287
|
ArtifactStorage
|