@hasna/skills 0.8.12 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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.2",
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/execution-grant-client.ts
32375
- import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
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/remote-files.ts
33595
- import { createHash } from "crypto";
33596
- var MAX_REMOTE_FILE_BYTES = 64 * 1024 * 1024;
33597
- function describeRemoteFiles(files) {
33598
- if (files.length > 10)
33599
- throw new Error("At most 10 input files are supported");
33600
- const names = new Set;
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
- const reader = response.body?.getReader();
33624
- if (!reader)
33625
- return new Uint8Array;
33626
- const chunks = [];
33627
- let size = 0;
33628
- try {
33629
- while (true) {
33630
- const next = await reader.read();
33631
- if (next.done)
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 += next.value.byteLength;
33634
- if (size > maximum)
33635
- throw new Error("Artifact exceeds its declared size limit");
33636
- chunks.push(next.value);
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
- } catch (error) {
33639
- await reader.cancel().catch(() => {});
33640
- throw error;
33641
- } finally {
33642
- reader.releaseLock();
33871
+ return Buffer.concat(chunks);
33643
33872
  }
33644
- const bytes = new Uint8Array(size);
33645
- let offset = 0;
33646
- for (const chunk of chunks) {
33647
- bytes.set(chunk, offset);
33648
- offset += chunk.byteLength;
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));
@@ -40518,6 +40661,35 @@ function treeHash(root2) {
40518
40661
  visit2(root2);
40519
40662
  return hash2.digest("hex");
40520
40663
  }
40664
+ function selectNativeMigrationTargets(inventory, manifest) {
40665
+ if (manifest.schema !== "hasna.skills-native-migration-targets.v1" || !/^[0-9a-f]{64}$/.test(manifest.digest) || !Array.isArray(manifest.targets) || manifest.targets.length === 0)
40666
+ throw new Error("Invalid native migration target manifest");
40667
+ const identities = new Set;
40668
+ const selected = [];
40669
+ for (const target of manifest.targets) {
40670
+ const projectRoot = resolve14(target.projectRoot), normalized = relative3(projectRoot, resolve14(projectRoot, target.path));
40671
+ if (!isAbsolute15(target.projectRoot) || projectRoot !== target.projectRoot || !existsSync6(projectRoot) || !lstatSync17(projectRoot).isDirectory())
40672
+ throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
40673
+ assertSafePath(projectRoot);
40674
+ if (isAbsolute15(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep4}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
40675
+ throw new Error(`Invalid native migration target: ${target.path}`);
40676
+ const identity3 = `${target.agent}\x00${projectRoot}\x00${normalized}`;
40677
+ if (identities.has(identity3))
40678
+ throw new Error(`Duplicate native migration target: ${target.path}`);
40679
+ identities.add(identity3);
40680
+ const expected = resolve14(target.projectRoot, target.path);
40681
+ const matches = inventory.filter((entry2) => entry2.agent === target.agent && resolve14(entry2.path) === expected);
40682
+ if (matches.length !== 1)
40683
+ throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
40684
+ const entry = matches[0];
40685
+ if (entry.bridge || entry.system || entry.vendor)
40686
+ throw new Error(`Native migration target is protected or vendor-owned: ${entry.path}`);
40687
+ if (entry.hash !== target.treeSha256 || treeHash(entry.path) !== target.treeSha256)
40688
+ throw new Error(`Native migration target changed after review: ${entry.path}`);
40689
+ selected.push(entry);
40690
+ }
40691
+ return selected;
40692
+ }
40521
40693
  function projectAncestorDirectories(projects) {
40522
40694
  const directories = new Set;
40523
40695
  for (const project of projects) {
@@ -41006,12 +41178,13 @@ function archiveNativeSkills(inventory, options) {
41006
41178
  if (!expected || treeHash(entry.path) !== entry.hash || !isOwnedCliBridge(entry.path, [expected]))
41007
41179
  throw new Error(`Skills bridge changed after planning: ${entry.path}`);
41008
41180
  }
41009
- const selected = inventory.filter((entry) => !entry.bridge && !entry.system && (entry.vendor ? options.includeVendor : entry.managed || options.includeUnmanaged));
41181
+ const selected = options.targetManifest ? selectNativeMigrationTargets(inventory, options.targetManifest) : inventory.filter((entry) => !entry.bridge && !entry.system && (entry.vendor ? options.includeVendor : entry.managed || options.includeUnmanaged));
41010
41182
  for (const entry of selected)
41011
41183
  if (treeHash(entry.path) !== entry.hash)
41012
41184
  throw new Error(`Native skill changed after planning: ${entry.path}`);
41185
+ const targetManifest = options.targetManifest ? { schema: options.targetManifest.schema, digest: options.targetManifest.digest, targetCount: options.targetManifest.targets.length } : undefined;
41013
41186
  if (!selected.length)
41014
- return { entries: [], ...aliases.length ? { rootAliases: aliases } : {} };
41187
+ return { entries: [], ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
41015
41188
  const operationId = randomUUID3(), archiveRoot = join21(options.dataDir ?? getDataDir(), "migration", operationId, "native"), receiptPath = join21(archiveRoot, "receipt.json");
41016
41189
  const entries = selected.map((entry, index) => ({
41017
41190
  source: entry.vendor ? join21(entry.path, "SKILL.md") : entry.path,
@@ -41020,7 +41193,7 @@ function archiveNativeSkills(inventory, options) {
41020
41193
  ...entry.vendor ? { discoveryOnly: true } : {},
41021
41194
  status: "planned"
41022
41195
  }));
41023
- const journal = { version: 2, operationId, status: "planned", entries, ...aliases.length ? { rootAliases: aliases } : {} };
41196
+ const journal = { version: 2, operationId, status: "planned", entries, ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
41024
41197
  const moved = [];
41025
41198
  const verifyArchive = (entry) => {
41026
41199
  assertSafePath(entry.archive);
@@ -41114,7 +41287,7 @@ function archiveNativeSkills(inventory, options) {
41114
41287
  } catch {}
41115
41288
  throw new Error(`Native archive failed; inspect the recovery journal at ${receiptPath} before retrying.`, { cause: error });
41116
41289
  }
41117
- return { entries: entries.map(({ status, conflict, ...entry }) => entry), receiptPath, ...aliases.length ? { rootAliases: aliases } : {} };
41290
+ return { entries: entries.map(({ status, conflict, ...entry }) => entry), receiptPath, ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
41118
41291
  }
41119
41292
  function assertNativeExportAllowed(dataDir2 = getDataDirReadOnly()) {
41120
41293
  if (requiresCliSkillLoading(dataDir2))
@@ -41726,6 +41899,7 @@ function readManifest(skillPath, fallbackName, normalizeName) {
41726
41899
  const version = readDeclaredSkillVersion(skillPath) ?? PORTABLE_SKILL_DEFAULT_VERSION;
41727
41900
  const kind = parseSkillKind(stringField(jsonManifest, "kind") ?? frontmatter?.kind);
41728
41901
  const commands = parseManifestCommands(jsonManifest) ?? (kind === "instruction" ? [] : inferPackageCommands(pkg, name)) ?? [];
41902
+ const runtime = parseManifestRuntime(jsonManifest) ?? inferRuntimeFromCommands(commands);
41729
41903
  return {
41730
41904
  $schema: stringField(jsonManifest, "$schema") ?? PORTABLE_SKILL_SCHEMA,
41731
41905
  standard: stringField(jsonManifest, "standard") ?? PORTABLE_SKILL_STANDARD,
@@ -41738,7 +41912,7 @@ function readManifest(skillPath, fallbackName, normalizeName) {
41738
41912
  ...kind ? { kind } : {},
41739
41913
  inputs: kind === "instruction" ? parseManifestInputs(jsonManifest) ?? [] : parseManifestInputs(jsonManifest) ?? DEFAULT_INPUTS,
41740
41914
  commands,
41741
- ...parseManifestRuntime(jsonManifest) ? { runtime: parseManifestRuntime(jsonManifest) } : {},
41915
+ ...runtime ? { runtime } : {},
41742
41916
  ...parseManifestProvenance(jsonManifest) ? { provenance: parseManifestProvenance(jsonManifest) } : {}
41743
41917
  };
41744
41918
  }
@@ -42257,6 +42431,12 @@ function inferPackageCommands(pkg, fallbackName) {
42257
42431
  }
42258
42432
  return;
42259
42433
  }
42434
+ function inferRuntimeFromCommands(commands) {
42435
+ const entrypoint = commands.find((command) => command.entry)?.entry;
42436
+ if (!entrypoint || !entrypoint.toLowerCase().endsWith(".py"))
42437
+ return;
42438
+ return { ...defaultRuntimeContract(entrypoint), runtime: "python3" };
42439
+ }
42260
42440
  function readJsonObject(path) {
42261
42441
  const parsed = JSON.parse(readFileSync15(path, "utf-8"));
42262
42442
  if (!isRecord2(parsed))
@@ -54107,12 +54287,12 @@ class ArtifactStorage {
54107
54287
  get usesS3() {
54108
54288
  return Boolean(this.bucket);
54109
54289
  }
54110
- objectKeyFor(tenantId, runId, relativePath) {
54290
+ objectKeyFor(tenantId, runId2, relativePath) {
54111
54291
  const safeRelativePath = relativePath.replace(/^\/+/, "").replace(/\.\.(?:\/|$)/g, "");
54112
- return `${this.runPrefix}/${tenantId}/${runId}/${safeRelativePath}`;
54292
+ return `${this.runPrefix}/${tenantId}/${runId2}/${safeRelativePath}`;
54113
54293
  }
54114
- quarantineKeyFor(tenantId, runId, artifactId) {
54115
- return `${this.runPrefix}/quarantine/${tenantId}/${runId}/${artifactId}`;
54294
+ quarantineKeyFor(tenantId, runId2, artifactId) {
54295
+ return `${this.runPrefix}/quarantine/${tenantId}/${runId2}/${artifactId}`;
54116
54296
  }
54117
54297
  async materialize(run, artifact, body) {
54118
54298
  if (!this.bucket) {
@@ -54363,13 +54543,13 @@ function createCancelService(options) {
54363
54543
  const governanceStore = options.governanceStore;
54364
54544
  const storage = options.storage ?? new ArtifactStorage;
54365
54545
  return {
54366
- async cancel(principal, runId, requestedBy) {
54546
+ async cancel(principal, runId2, requestedBy) {
54367
54547
  if (!store.transitionRun) {
54368
- throw new GovernanceError(GOVERNANCE_ERROR_CODES.FENCING_UNSUPPORTED, `cannot cancel run ${runId}: the store has no transitionRun generation fencing`, { gate: "transitionRun" });
54548
+ throw new GovernanceError(GOVERNANCE_ERROR_CODES.FENCING_UNSUPPORTED, `cannot cancel run ${runId2}: the store has no transitionRun generation fencing`, { gate: "transitionRun" });
54369
54549
  }
54370
- const run = await store.getRun(principal, runId);
54550
+ const run = await store.getRun(principal, runId2);
54371
54551
  if (!run)
54372
- throw new GovernanceError(GOVERNANCE_ERROR_CODES.STALE_LEASE_GENERATION, `run ${runId} not found`, { gate: "run" });
54552
+ throw new GovernanceError(GOVERNANCE_ERROR_CODES.STALE_LEASE_GENERATION, `run ${runId2} not found`, { gate: "run" });
54373
54553
  const terminalStatuses = new Set(["succeeded", "failed", "cancelled", "expired", "refunded"]);
54374
54554
  if (terminalStatuses.has(run.status)) {
54375
54555
  const receipt2 = await governanceStore.appendReceipt({
@@ -54382,11 +54562,11 @@ function createCancelService(options) {
54382
54562
  return { run, fencedGeneration: run.leaseGeneration, quarantined: [], receipt: receipt2, alreadyTerminal: true };
54383
54563
  }
54384
54564
  const fencedGeneration = run.leaseGeneration + 1;
54385
- const cancelling = await store.transitionRun(runId, { status: "cancel_requested", leaseGeneration: fencedGeneration }, run.leaseGeneration);
54565
+ const cancelling = await store.transitionRun(runId2, { status: "cancel_requested", leaseGeneration: fencedGeneration }, run.leaseGeneration);
54386
54566
  if (!cancelling) {
54387
- throw new GovernanceError(GOVERNANCE_ERROR_CODES.STALE_LEASE_GENERATION, `run ${runId} changed while cancelling`, { gate: "transition" });
54567
+ throw new GovernanceError(GOVERNANCE_ERROR_CODES.STALE_LEASE_GENERATION, `run ${runId2} changed while cancelling`, { gate: "transition" });
54388
54568
  }
54389
- const artifacts = await store.listArtifacts(principal, runId);
54569
+ const artifacts = await store.listArtifacts(principal, runId2);
54390
54570
  const quarantined = [];
54391
54571
  for (const artifact of artifacts) {
54392
54572
  const quarantineKey = await storage.moveToQuarantine?.(artifact);
@@ -54403,9 +54583,9 @@ function createCancelService(options) {
54403
54583
  });
54404
54584
  quarantined.push({ ...artifact, ...quarantineKey ? { storageKey: quarantineKey } : {} });
54405
54585
  }
54406
- const cancelled = await store.transitionRun(runId, { status: "cancelled", completedAt: new Date().toISOString() }, fencedGeneration);
54586
+ const cancelled = await store.transitionRun(runId2, { status: "cancelled", completedAt: new Date().toISOString() }, fencedGeneration);
54407
54587
  if (!cancelled) {
54408
- throw new GovernanceError(GOVERNANCE_ERROR_CODES.STALE_LEASE_GENERATION, `run ${runId} moved on while finalising cancellation`, { gate: "transition" });
54588
+ throw new GovernanceError(GOVERNANCE_ERROR_CODES.STALE_LEASE_GENERATION, `run ${runId2} moved on while finalising cancellation`, { gate: "transition" });
54409
54589
  }
54410
54590
  const receipt = await governanceStore.appendReceipt({
54411
54591
  kind: "cancel",
@@ -54623,6 +54803,9 @@ class MemorySkillSelectionStore {
54623
54803
  async getProfile(p2, id2) {
54624
54804
  return structuredClone(this.profiles.get(this.key(p2.orgId, id2)) ?? null);
54625
54805
  }
54806
+ async profilesReferencingSkill(p2, slug) {
54807
+ return [...this.profiles.values()].filter((profile2) => profile2.workspaceId === p2.orgId && profile2.selections.some((selection2) => selection2.slug === slug)).map((profile2) => profile2.id).sort();
54808
+ }
54626
54809
  async saveProfile(p2, id2, selected, expected) {
54627
54810
  const key = this.key(p2.orgId, id2), previous = this.profiles.get(key);
54628
54811
  if ((previous?.revision ?? null) !== expected)
@@ -54660,14 +54843,25 @@ class SqliteSkillSelectionStore {
54660
54843
  constructor(db) {
54661
54844
  this.db = db;
54662
54845
  }
54846
+ async profilesReferencingSkill(p2, slug) {
54847
+ const rows = this.db.query("SELECT profile_id,selections_json FROM skills_profiles WHERE org_id=?").all(p2.orgId);
54848
+ return rows.filter((row) => selections(row.selections_json).some((selection2) => selection2.slug === slug)).map((row) => String(row.profile_id)).sort();
54849
+ }
54663
54850
  async getProfile(p2, id2) {
54664
54851
  const row = this.db.query("SELECT * FROM skills_profiles WHERE org_id=? AND profile_id=?").get(p2.orgId, id2);
54665
54852
  return row ? profile(row) : null;
54666
54853
  }
54667
54854
  async saveProfile(p2, id2, selected, expected) {
54668
- const revision = randomUUID5(), timestamp = new Date().toISOString();
54669
- 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);
54670
- return row ? profile(row) : null;
54855
+ return this.db.transaction(() => {
54856
+ for (const selection2 of [...selected].sort((a3, b3) => a3.slug.localeCompare(b3.slug))) {
54857
+ const skill = this.db.query("SELECT lifecycle FROM skills_registry WHERE org_id=? AND slug=?").get(p2.orgId, selection2.slug);
54858
+ if (skill?.lifecycle === "archived")
54859
+ return null;
54860
+ }
54861
+ const revision = randomUUID5(), timestamp = new Date().toISOString();
54862
+ 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);
54863
+ return row ? profile(row) : null;
54864
+ }).immediate();
54671
54865
  }
54672
54866
  async getStationState(p2, id2) {
54673
54867
  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 +54880,25 @@ class PostgresSkillSelectionStore {
54686
54880
  constructor(sql) {
54687
54881
  this.sql = sql;
54688
54882
  }
54883
+ async profilesReferencingSkill(p2, slug) {
54884
+ const rows = await this.sql`SELECT profile_id,selections_json FROM skills_profiles WHERE org_id=${p2.orgId}`;
54885
+ return rows.filter((row) => selections(row.selections_json).some((selection2) => selection2.slug === slug)).map((row) => String(row.profile_id)).sort();
54886
+ }
54689
54887
  async getProfile(p2, id2) {
54690
54888
  const rows = await this.sql`SELECT * FROM skills_profiles WHERE org_id=${p2.orgId} AND profile_id=${id2}`;
54691
54889
  return rows[0] ? profile(rows[0]) : null;
54692
54890
  }
54693
54891
  async saveProfile(p2, id2, selected, expected) {
54694
- const revision = randomUUID5(), timestamp = new Date().toISOString();
54695
- const rows = expected === null ? await this.sql`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 this.sql`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 *`;
54696
- return rows[0] ? profile(rows[0]) : null;
54892
+ return this.sql.begin(async (tx) => {
54893
+ for (const selection2 of [...selected].sort((a3, b3) => a3.slug.localeCompare(b3.slug))) {
54894
+ const rows2 = await tx`SELECT lifecycle FROM skills_registry WHERE org_id=${p2.orgId} AND slug=${selection2.slug} FOR UPDATE`;
54895
+ if (rows2[0]?.lifecycle === "archived")
54896
+ return null;
54897
+ }
54898
+ const revision = randomUUID5(), timestamp = new Date().toISOString();
54899
+ 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 *`;
54900
+ return rows[0] ? profile(rows[0]) : null;
54901
+ });
54697
54902
  }
54698
54903
  async getStationState(p2, id2) {
54699
54904
  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 +55019,7 @@ function nowIso() {
54814
55019
  function normalizeLimit(limit) {
54815
55020
  return Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : 0;
54816
55021
  }
54817
- function runId() {
55022
+ function runId2() {
54818
55023
  return `run_${Date.now().toString(36)}_${randomUUID6().slice(0, 8)}`;
54819
55024
  }
54820
55025
  function rowToRun(row) {
@@ -54887,7 +55092,11 @@ function rowToSkill(row) {
54887
55092
  revisionId: String(row.revision_id ?? ""),
54888
55093
  revisionNumber: Number(row.revision_number ?? 0),
54889
55094
  ...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {},
54890
- ...row.tombstone_purge_after ? { tombstonePurgeAfter: dateString(row.tombstone_purge_after) } : {}
55095
+ ...row.tombstone_purge_after ? { tombstonePurgeAfter: dateString(row.tombstone_purge_after) } : {},
55096
+ lifecycle: row.lifecycle === "archived" ? "archived" : "active",
55097
+ ...row.archived_at ? { archivedAt: dateString(row.archived_at) } : {},
55098
+ ...row.archive_reason ? { archiveReason: String(row.archive_reason) } : {},
55099
+ ...row.replacement_slug ? { replacementSlug: String(row.replacement_slug) } : {}
54891
55100
  };
54892
55101
  }
54893
55102
  function rowToPin(row) {
@@ -54968,10 +55177,10 @@ class StaleLeaseGenerationError extends Error {
54968
55177
  expectedGeneration;
54969
55178
  currentGeneration;
54970
55179
  status;
54971
- constructor(runId2, expectedGeneration, currentGeneration, status) {
54972
- super(`late transition rejected for run ${runId2}: expected lease_generation ${expectedGeneration}, ` + `current ${currentGeneration} (status ${status})`);
55180
+ constructor(runId3, expectedGeneration, currentGeneration, status) {
55181
+ super(`late transition rejected for run ${runId3}: expected lease_generation ${expectedGeneration}, ` + `current ${currentGeneration} (status ${status})`);
54973
55182
  this.name = "StaleLeaseGenerationError";
54974
- this.runId = runId2;
55183
+ this.runId = runId3;
54975
55184
  this.expectedGeneration = expectedGeneration;
54976
55185
  this.currentGeneration = currentGeneration;
54977
55186
  this.status = status;
@@ -55006,11 +55215,22 @@ class SkillVersionExistsError extends Error {
55006
55215
  }
55007
55216
  }
55008
55217
 
55218
+ class SkillLifecycleConflictError extends Error {
55219
+ slug;
55220
+ profiles;
55221
+ constructor(slug, profiles) {
55222
+ super(`cannot archive '${slug}' while active profiles reference it: ${profiles.join(", ")}`);
55223
+ this.slug = slug;
55224
+ this.profiles = profiles;
55225
+ this.name = "SkillLifecycleConflictError";
55226
+ }
55227
+ }
55228
+
55009
55229
  // src/lib/revision.ts
55010
55230
  import { createHash as createHash20 } from "crypto";
55011
55231
  var REVISION_ID_PATTERN = /^[0-9a-f]{64}$/;
55012
55232
  function revisionIdOf(content) {
55013
- const canonical4 = JSON.stringify({
55233
+ const canonicalContent = {
55014
55234
  slug: content.slug,
55015
55235
  displayName: content.displayName,
55016
55236
  description: content.description,
@@ -55022,7 +55242,13 @@ function revisionIdOf(content) {
55022
55242
  skillMd: content.skillMd ?? null,
55023
55243
  bundleSha256: content.bundleSha256 ?? null,
55024
55244
  bundleByteSize: content.bundleByteSize ?? null
55025
- });
55245
+ };
55246
+ if (content.lifecycle === "archived" || content.archiveReason !== undefined || content.replacementSlug !== undefined) {
55247
+ canonicalContent.lifecycle = content.lifecycle ?? "active";
55248
+ canonicalContent.archiveReason = content.archiveReason ?? null;
55249
+ canonicalContent.replacementSlug = content.replacementSlug ?? null;
55250
+ }
55251
+ const canonical4 = JSON.stringify(canonicalContent);
55026
55252
  return createHash20("sha256").update(canonical4).digest("hex");
55027
55253
  }
55028
55254
  function revisionIdOfRecord(record3) {
@@ -55125,6 +55351,28 @@ class SqliteSkillsStore {
55125
55351
  scopes: parseScopes(row.scopes_json)
55126
55352
  };
55127
55353
  }
55354
+ async updateApiKeyScopes(actor, keyId, expectedScopes, addScopes) {
55355
+ 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]);
55356
+ if (!row)
55357
+ return { kind: "not_found" };
55358
+ const current = parseScopes(row.scopes_json);
55359
+ if (current.length !== expectedScopes.length || current.some((scope, index) => scope !== expectedScopes[index])) {
55360
+ return { kind: "stale", scopes: current };
55361
+ }
55362
+ const scopes = [...current, ...addScopes.filter((scope) => !current.includes(scope))];
55363
+ const tx = this.db.transaction(() => {
55364
+ 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)]);
55365
+ if (changed.changes !== 1)
55366
+ return false;
55367
+ 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 })]);
55368
+ return true;
55369
+ });
55370
+ if (!tx()) {
55371
+ 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]);
55372
+ return { kind: "stale", scopes: latest ? parseScopes(latest.scopes_json) : current };
55373
+ }
55374
+ return { kind: "updated", scopes };
55375
+ }
55128
55376
  async createRun(input) {
55129
55377
  if (input.idempotencyKey) {
55130
55378
  const existing = this.get("SELECT * FROM skills_runs WHERE org_id = ? AND idempotency_key = ? LIMIT 1", [input.principal.orgId, input.idempotencyKey]);
@@ -55134,7 +55382,7 @@ class SqliteSkillsStore {
55134
55382
  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
55383
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
55136
55384
  RETURNING *`, [
55137
- runId(),
55385
+ runId2(),
55138
55386
  input.principal.orgId,
55139
55387
  input.principal.userId,
55140
55388
  input.slug,
@@ -55446,6 +55694,39 @@ class SqliteSkillsStore {
55446
55694
  const row = this.get("SELECT * FROM skills_registry WHERE org_id = ? AND slug = ? LIMIT 1", [principal.orgId, slug]);
55447
55695
  return row ? rowToSkill(row) : null;
55448
55696
  }
55697
+ async setSkillLifecycle(principal, slug, patch, expectedRevisionId) {
55698
+ return this.db.transaction(() => {
55699
+ const rowCurrent = this.get("SELECT * FROM skills_registry WHERE org_id=? AND slug=? LIMIT 1", [principal.orgId, slug]);
55700
+ const current = rowCurrent ? rowToSkill(rowCurrent) : null;
55701
+ if (!current || current.tombstonedAt)
55702
+ return null;
55703
+ if (expectedRevisionId !== current.revisionId)
55704
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
55705
+ if (patch.lifecycle === "archived" && current.lifecycle !== "archived") {
55706
+ const profiles = this.db.query("SELECT profile_id,selections_json FROM skills_profiles WHERE org_id=?").all(principal.orgId);
55707
+ const referencing = profiles.filter((profile2) => {
55708
+ const selections2 = typeof profile2.selections_json === "string" ? JSON.parse(profile2.selections_json) : profile2.selections_json;
55709
+ return Array.isArray(selections2) && selections2.some((selection2) => selection2?.slug === slug);
55710
+ }).map((profile2) => String(profile2.profile_id)).sort();
55711
+ if (referencing.length)
55712
+ throw new SkillLifecycleConflictError(slug, referencing);
55713
+ }
55714
+ 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 } : {} } : {} };
55715
+ if (patch.lifecycle === "active") {
55716
+ delete next2.archivedAt;
55717
+ delete next2.archiveReason;
55718
+ delete next2.replacementSlug;
55719
+ }
55720
+ 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]);
55721
+ if (!row)
55722
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, this.getSkillSync(principal, slug)?.revisionId ?? null);
55723
+ return rowToSkill(row);
55724
+ }).immediate();
55725
+ }
55726
+ getSkillSync(principal, slug) {
55727
+ const row = this.get("SELECT * FROM skills_registry WHERE org_id=? AND slug=? LIMIT 1", [principal.orgId, slug]);
55728
+ return row ? rowToSkill(row) : null;
55729
+ }
55449
55730
  async updateSkill(principal, slug, patch, expectedRevisionId) {
55450
55731
  const current = await this.getSkill(principal, slug);
55451
55732
  if (!current || current.tombstonedAt)
@@ -55558,13 +55839,13 @@ class SqliteSkillsStore {
55558
55839
  }
55559
55840
  async listTags(principal) {
55560
55841
  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));
55842
+ 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
55843
  }
55563
55844
  async listSkillsByTag(principal, tag) {
55564
55845
  await this.purgeExpiredTombstones(principal);
55565
55846
  return this.all(`SELECT s.* FROM skills_registry s
55566
55847
  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
55848
+ WHERE t.org_id = ? AND t.tag = ? AND s.tombstoned_at IS NULL AND s.lifecycle = 'active'
55568
55849
  ORDER BY s.slug ASC`, [principal.orgId, tag]).map(rowToSkill);
55569
55850
  }
55570
55851
  async listPinsByTag(principal, tag) {
@@ -55572,7 +55853,7 @@ class SqliteSkillsStore {
55572
55853
  return this.all(`SELECT p.* FROM skills_pins p
55573
55854
  JOIN skills_tags t ON t.org_id = p.org_id AND t.slug = p.slug
55574
55855
  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
55856
+ WHERE p.org_id = ? AND p.principal = ? AND t.tag = ? AND s.tombstoned_at IS NULL AND s.lifecycle = 'active'
55576
55857
  ORDER BY p.slug ASC`, [principal.orgId, principal.apiKeyId, tag]).map(rowToPin);
55577
55858
  }
55578
55859
  async listPublishedSlugs(principal) {
@@ -55694,8 +55975,8 @@ class MemoryGovernanceStore {
55694
55975
  this.receipts.push(next2);
55695
55976
  return next2;
55696
55977
  }
55697
- async listReceipts(orgId, runId2) {
55698
- return this.receipts.filter((receipt) => receipt.orgId === orgId && receipt.runId === runId2);
55978
+ async listReceipts(orgId, runId3) {
55979
+ return this.receipts.filter((receipt) => receipt.orgId === orgId && receipt.runId === runId3);
55699
55980
  }
55700
55981
  async createReservation(input) {
55701
55982
  const estimatedCents = input.estimatedCents;
@@ -55704,8 +55985,8 @@ class MemoryGovernanceStore {
55704
55985
  this.reservations.push(reservation);
55705
55986
  return reservation;
55706
55987
  }
55707
- async reservationsForRun(orgId, runId2) {
55708
- return this.reservations.filter((reservation) => reservation.orgId === orgId && reservation.runId === runId2);
55988
+ async reservationsForRun(orgId, runId3) {
55989
+ return this.reservations.filter((reservation) => reservation.orgId === orgId && reservation.runId === runId3);
55709
55990
  }
55710
55991
  async reconcileReservation(reservationId2, actualCents, status) {
55711
55992
  assertReservationCents(actualCents, "actualCents");
@@ -55777,8 +56058,8 @@ class SqliteGovernanceStore {
55777
56058
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [next2.id, next2.kind, next2.orgId, next2.runId, next2.artifactId ?? null, next2.requestedBy, JSON.stringify(next2.metadata), next2.createdAt]);
55778
56059
  return next2;
55779
56060
  }
55780
- async listReceipts(orgId, runId2) {
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, runId2);
56061
+ async listReceipts(orgId, runId3) {
56062
+ 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
56063
  return rows.map((row) => ({
55783
56064
  id: String(row.id),
55784
56065
  kind: String(row.kind),
@@ -55798,8 +56079,8 @@ class SqliteGovernanceStore {
55798
56079
  VALUES (?, ?, ?, ?, ?, ?)`, [reservation.id, reservation.orgId, reservation.runId, reservation.estimatedCents, reservation.status, reservation.createdAt]);
55799
56080
  return reservation;
55800
56081
  }
55801
- async reservationsForRun(orgId, runId2) {
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, runId2);
56082
+ async reservationsForRun(orgId, runId3) {
56083
+ 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
56084
  return rows.map((row) => this.reservationFrom(row));
55804
56085
  }
55805
56086
  async reconcileReservation(reservationId2, actualCents, status) {
@@ -55873,9 +56154,9 @@ class PostgresGovernanceStore {
55873
56154
  `;
55874
56155
  return next2;
55875
56156
  }
55876
- async listReceipts(orgId, runId2) {
56157
+ async listReceipts(orgId, runId3) {
55877
56158
  const rows = await this.sql`
55878
- SELECT * FROM skills_lifecycle_receipts WHERE org_id = ${orgId} AND run_id = ${runId2} ORDER BY created_at ASC
56159
+ SELECT * FROM skills_lifecycle_receipts WHERE org_id = ${orgId} AND run_id = ${runId3} ORDER BY created_at ASC
55879
56160
  `;
55880
56161
  return rows.map((row) => ({
55881
56162
  id: String(row.id),
@@ -55898,9 +56179,9 @@ class PostgresGovernanceStore {
55898
56179
  `;
55899
56180
  return reservation;
55900
56181
  }
55901
- async reservationsForRun(orgId, runId2) {
56182
+ async reservationsForRun(orgId, runId3) {
55902
56183
  const rows = await this.sql`
55903
- SELECT * FROM skills_credit_reservations WHERE org_id = ${orgId} AND run_id = ${runId2} ORDER BY created_at ASC
56184
+ SELECT * FROM skills_credit_reservations WHERE org_id = ${orgId} AND run_id = ${runId3} ORDER BY created_at ASC
55904
56185
  `;
55905
56186
  return rows.map((row) => this.reservationFrom(row));
55906
56187
  }
@@ -56016,6 +56297,15 @@ class SkillRequestError extends Error {
56016
56297
  this.code = code;
56017
56298
  }
56018
56299
  }
56300
+ function skillLifecyclePatch(body) {
56301
+ if (body.lifecycle !== "active" && body.lifecycle !== "archived")
56302
+ throw new SkillRequestError(400, "INVALID_LIFECYCLE", "lifecycle must be active or archived");
56303
+ if (body.reason !== undefined && (typeof body.reason !== "string" || body.reason.length > 512))
56304
+ throw new SkillRequestError(400, "INVALID_LIFECYCLE", "reason must be at most 512 characters");
56305
+ if (body.replacementSlug !== undefined && (typeof body.replacementSlug !== "string" || !SLUG_PATTERN.test(body.replacementSlug)))
56306
+ throw new SkillRequestError(400, "INVALID_LIFECYCLE", "replacementSlug must be a valid skill slug");
56307
+ return { lifecycle: body.lifecycle, ...body.reason ? { reason: body.reason } : {}, ...body.replacementSlug ? { replacementSlug: body.replacementSlug } : {} };
56308
+ }
56019
56309
  function publishedSkillMeta(record3) {
56020
56310
  return {
56021
56311
  name: record3.slug,
@@ -56040,7 +56330,11 @@ function publishedPayload(record3, opts = {}) {
56040
56330
  publishedAt: record3.createdAt,
56041
56331
  updatedAt: record3.updatedAt,
56042
56332
  revisionId: record3.revisionId,
56043
- revisionNumber: record3.revisionNumber
56333
+ revisionNumber: record3.revisionNumber,
56334
+ lifecycle: record3.lifecycle,
56335
+ ...record3.archivedAt ? { archivedAt: record3.archivedAt } : {},
56336
+ ...record3.archiveReason ? { archiveReason: record3.archiveReason } : {},
56337
+ ...record3.replacementSlug ? { replacementSlug: record3.replacementSlug } : {}
56044
56338
  };
56045
56339
  }
56046
56340
  function revisionEtag(revisionId) {
@@ -56080,7 +56374,7 @@ async function tombstoneStatus(store, artifactStorage, principal, record3) {
56080
56374
  };
56081
56375
  }
56082
56376
  async function listMergedSkills(store, principal) {
56083
- return (await store.listSkills(principal)).sort((a3, b3) => a3.slug.localeCompare(b3.slug)).map((record3) => publishedPayload(record3));
56377
+ return (await store.listSkills(principal)).filter((record3) => record3.lifecycle === "active").sort((a3, b3) => a3.slug.localeCompare(b3.slug)).map((record3) => publishedPayload(record3));
56084
56378
  }
56085
56379
  async function resolvePublishedSkill(store, artifactStorage, principal, slug) {
56086
56380
  const record3 = await store.getSkill(principal, slug);
@@ -56097,7 +56391,7 @@ async function listOrgTags(store, principal) {
56097
56391
  return [...new Set((await store.listTags(principal)).filter((tag) => tag.trim()))].sort();
56098
56392
  }
56099
56393
  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));
56394
+ return (await store.listSkillsByTag(principal, tag)).filter((record3) => record3.lifecycle === "active").sort((a3, b3) => a3.slug.localeCompare(b3.slug)).map((record3) => publishedPayload(record3));
56101
56395
  }
56102
56396
  function skillSummary(skill) {
56103
56397
  return {
@@ -56659,6 +56953,8 @@ async function validatePublished(store, principal, selections2) {
56659
56953
  const current = await store.getSkill(principal, selection2.slug);
56660
56954
  if (!current || current.tombstonedAt)
56661
56955
  throw new SkillRequestError(current?.tombstonedAt ? 410 : 404, "SELECTION_UNAVAILABLE", `Selected skill '${selection2.slug}' is unavailable`);
56956
+ if (current.lifecycle === "archived")
56957
+ throw new SkillRequestError(409, "SELECTION_ARCHIVED", `Selected skill '${selection2.slug}' is archived`);
56662
56958
  const version2 = await store.getSkillVersion(principal, selection2.slug, selection2.version);
56663
56959
  if (!version2 || `sha256:${version2.bundleSha256}` !== selection2.bundleDigest)
56664
56960
  throw new SkillRequestError(409, "SELECTION_VERSION_MISMATCH", `Selected version of '${selection2.slug}' does not match its published digest`);
@@ -56895,8 +57191,8 @@ class RuntimeExecutionStore {
56895
57191
  this.sqlite?.close();
56896
57192
  await this.postgres?.close();
56897
57193
  }
56898
- async job(runId2) {
56899
- const row = this.sqlite ? this.sqlite.query("SELECT payload FROM skills_runtime_jobs WHERE run_id=?").get(runId2) : (await this.postgres`SELECT payload FROM skills_runtime_jobs WHERE run_id=${runId2}`)[0];
57194
+ async job(runId3) {
57195
+ 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
57196
  return row ? JSON.parse(String(row.payload)) : null;
56901
57197
  }
56902
57198
  async mutate(id2, fn) {
@@ -57229,19 +57525,19 @@ class MemoryRunExecutionStore {
57229
57525
  }), admission.runId);
57230
57526
  return row;
57231
57527
  }
57232
- async getRun(runId2) {
57233
- return this.runs.get(runId2) ?? null;
57528
+ async getRun(runId3) {
57529
+ return this.runs.get(runId3) ?? null;
57234
57530
  }
57235
57531
  async getRunByKey(tenantId, idempotencyKey) {
57236
- const runId2 = this.byKey.get(tenantId + "\x00" + idempotencyKey);
57237
- return runId2 ? this.runs.get(runId2) ?? null : null;
57532
+ const runId3 = this.byKey.get(tenantId + "\x00" + idempotencyKey);
57533
+ return runId3 ? this.runs.get(runId3) ?? null : null;
57238
57534
  }
57239
57535
  async getRunByDigests(input) {
57240
- const runId2 = this.byDigests.get(digestKey(input));
57241
- return runId2 ? this.runs.get(runId2) ?? null : null;
57536
+ const runId3 = this.byDigests.get(digestKey(input));
57537
+ return runId3 ? this.runs.get(runId3) ?? null : null;
57242
57538
  }
57243
- async listAttempts(runId2) {
57244
- return Array.from(this.attempts.values()).filter((attempt) => attempt.runId === runId2).sort((a3, b3) => a3.attemptNumber - b3.attemptNumber);
57539
+ async listAttempts(runId3) {
57540
+ return Array.from(this.attempts.values()).filter((attempt) => attempt.runId === runId3).sort((a3, b3) => a3.attemptNumber - b3.attemptNumber);
57245
57541
  }
57246
57542
  async createAttempt(input) {
57247
57543
  const attemptId = `${input.runId}/attempt/${input.attemptNumber}`;
@@ -57321,7 +57617,7 @@ class MemoryRunExecutionStore {
57321
57617
  async recordTransition(transition) {
57322
57618
  this.transitions.push(transition);
57323
57619
  }
57324
- async markAttemptTerminal(runId2, attemptId) {
57620
+ async markAttemptTerminal(runId3, attemptId) {
57325
57621
  const attempt = this.attempts.get(attemptId);
57326
57622
  if (!attempt)
57327
57623
  return null;
@@ -57333,15 +57629,15 @@ class MemoryRunExecutionStore {
57333
57629
  this.receipts.set(receipt.runId + "\x00" + receipt.attemptId, receipt);
57334
57630
  return receipt;
57335
57631
  }
57336
- async getReceipt(runId2, attemptId) {
57337
- return this.receipts.get(runId2 + "\x00" + attemptId) ?? null;
57632
+ async getReceipt(runId3, attemptId) {
57633
+ return this.receipts.get(runId3 + "\x00" + attemptId) ?? null;
57338
57634
  }
57339
- async finalizeRun(runId2, status, receiptId2) {
57340
- const run = this.runs.get(runId2);
57635
+ async finalizeRun(runId3, status, receiptId2) {
57636
+ const run = this.runs.get(runId3);
57341
57637
  if (!run)
57342
57638
  return null;
57343
57639
  for (const [attemptId, attempt] of this.attempts) {
57344
- if (attempt.runId === runId2 && attempt.status !== "terminal") {
57640
+ if (attempt.runId === runId3 && attempt.status !== "terminal") {
57345
57641
  this.attempts.set(attemptId, { ...attempt, status: "terminal" });
57346
57642
  }
57347
57643
  }
@@ -57351,15 +57647,15 @@ class MemoryRunExecutionStore {
57351
57647
  terminalReceiptId: receiptId2,
57352
57648
  updatedAt: new Date().toISOString()
57353
57649
  };
57354
- this.runs.set(runId2, next2);
57650
+ this.runs.set(runId3, next2);
57355
57651
  return next2;
57356
57652
  }
57357
- async setRunStatus(runId2, status) {
57358
- const run = this.runs.get(runId2);
57653
+ async setRunStatus(runId3, status) {
57654
+ const run = this.runs.get(runId3);
57359
57655
  if (!run)
57360
57656
  return null;
57361
57657
  const next2 = { ...run, status, updatedAt: new Date().toISOString() };
57362
- this.runs.set(runId2, next2);
57658
+ this.runs.set(runId3, next2);
57363
57659
  return next2;
57364
57660
  }
57365
57661
  }
@@ -57377,21 +57673,28 @@ class SqliteRunExecutionStore {
57377
57673
  this.db = new Database4(path);
57378
57674
  this.db.run("PRAGMA journal_mode = WAL");
57379
57675
  this.db.run("PRAGMA foreign_keys = ON");
57380
- this.db.run(SCHEMA_SQL);
57676
+ this.db.transaction(() => {
57677
+ this.db.run(SCHEMA_SQL);
57678
+ for (const table of ["execution_runs", "execution_receipts"]) {
57679
+ const columns = this.db.query(`PRAGMA table_info(${table})`).all();
57680
+ if (!columns.some((column) => column.name === "execution_contract_json"))
57681
+ this.db.run(`ALTER TABLE ${table} ADD COLUMN execution_contract_json TEXT`);
57682
+ }
57683
+ }).immediate();
57381
57684
  }
57382
57685
  async admit(admission) {
57383
57686
  this.db.query(`INSERT OR IGNORE INTO execution_runs (
57384
57687
  run_id, contract_version, tenant_id, skill_id, skill_version, bundle_digest,
57385
57688
  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);
57689
+ policy_json, limits_json, idempotency_key, status, created_at, updated_at, execution_contract_json
57690
+ ) 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
57691
  const row = this.readRun(admission.runId);
57389
57692
  if (!row)
57390
57693
  throw new Error(`admission did not persist run ${admission.runId}`);
57391
57694
  return row;
57392
57695
  }
57393
- async getRun(runId2) {
57394
- return this.readRun(runId2);
57696
+ async getRun(runId3) {
57697
+ return this.readRun(runId3);
57395
57698
  }
57396
57699
  async getRunByKey(tenantId, idempotencyKey) {
57397
57700
  const row = this.db.query(`SELECT * FROM execution_runs WHERE tenant_id = ? AND idempotency_key = ? LIMIT 1`).get(tenantId, idempotencyKey);
@@ -57403,8 +57706,8 @@ class SqliteRunExecutionStore {
57403
57706
  LIMIT 1`).get(input.tenantId, input.skillId, input.skillVersion, input.bundleDigest, input.inputDigest);
57404
57707
  return row ? rowToRun2(row) : null;
57405
57708
  }
57406
- async listAttempts(runId2) {
57407
- const rows = this.db.query(`SELECT * FROM execution_attempts WHERE run_id = ? ORDER BY attempt_number ASC`).all(runId2);
57709
+ async listAttempts(runId3) {
57710
+ const rows = this.db.query(`SELECT * FROM execution_attempts WHERE run_id = ? ORDER BY attempt_number ASC`).all(runId3);
57408
57711
  return rows.map(rowToAttempt);
57409
57712
  }
57410
57713
  async createAttempt(input) {
@@ -57474,8 +57777,8 @@ class SqliteRunExecutionStore {
57474
57777
  this.db.query(`INSERT INTO execution_transitions (run_id, attempt_id, from_status, to_status, at)
57475
57778
  VALUES (?, ?, ?, ?, ?)`).run(transition.runId, transition.attemptId, transition.from, transition.to, transition.at);
57476
57779
  }
57477
- async markAttemptTerminal(runId2, attemptId) {
57478
- this.db.query(`UPDATE execution_attempts SET status = 'terminal' WHERE attempt_id = ? AND run_id = ? AND status != 'terminal'`).run(attemptId, runId2);
57780
+ async markAttemptTerminal(runId3, attemptId) {
57781
+ this.db.query(`UPDATE execution_attempts SET status = 'terminal' WHERE attempt_id = ? AND run_id = ? AND status != 'terminal'`).run(attemptId, runId3);
57479
57782
  return this.readAttempt(attemptId);
57480
57783
  }
57481
57784
  async writeReceipt(receipt) {
@@ -57483,28 +57786,28 @@ class SqliteRunExecutionStore {
57483
57786
  run_id, attempt_id, lease_generation, client_token, request_digest, started_by,
57484
57787
  task_id, launched_at, completed_at, runtime_image_digest, bundle_digest,
57485
57788
  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);
57789
+ artifact_pointers_json, log_pointers_json, cost_cents, execution_contract_json
57790
+ ) 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
57791
  return receipt;
57489
57792
  }
57490
- async getReceipt(runId2, attemptId) {
57491
- const row = this.db.query(`SELECT * FROM execution_receipts WHERE run_id = ? AND attempt_id = ?`).get(runId2, attemptId);
57793
+ async getReceipt(runId3, attemptId) {
57794
+ const row = this.db.query(`SELECT * FROM execution_receipts WHERE run_id = ? AND attempt_id = ?`).get(runId3, attemptId);
57492
57795
  return row ? rowToReceipt(row) : null;
57493
57796
  }
57494
- async finalizeRun(runId2, status, receiptId2) {
57495
- this.db.query(`UPDATE execution_runs SET status = ?, terminal_receipt_id = ?, updated_at = ? WHERE run_id = ?`).run(status, receiptId2, new Date().toISOString(), runId2);
57496
- this.db.query(`UPDATE execution_attempts SET status = 'terminal' WHERE run_id = ? AND status != 'terminal'`).run(runId2);
57497
- return this.readRun(runId2);
57797
+ async finalizeRun(runId3, status, receiptId2) {
57798
+ this.db.query(`UPDATE execution_runs SET status = ?, terminal_receipt_id = ?, updated_at = ? WHERE run_id = ?`).run(status, receiptId2, new Date().toISOString(), runId3);
57799
+ this.db.query(`UPDATE execution_attempts SET status = 'terminal' WHERE run_id = ? AND status != 'terminal'`).run(runId3);
57800
+ return this.readRun(runId3);
57498
57801
  }
57499
- async setRunStatus(runId2, status) {
57500
- this.db.query(`UPDATE execution_runs SET status = ?, updated_at = ? WHERE run_id = ?`).run(status, new Date().toISOString(), runId2);
57501
- return this.readRun(runId2);
57802
+ async setRunStatus(runId3, status) {
57803
+ this.db.query(`UPDATE execution_runs SET status = ?, updated_at = ? WHERE run_id = ?`).run(status, new Date().toISOString(), runId3);
57804
+ return this.readRun(runId3);
57502
57805
  }
57503
57806
  async close() {
57504
57807
  this.db.close();
57505
57808
  }
57506
- readRun(runId2) {
57507
- const row = this.db.query(`SELECT * FROM execution_runs WHERE run_id = ?`).get(runId2);
57809
+ readRun(runId3) {
57810
+ const row = this.db.query(`SELECT * FROM execution_runs WHERE run_id = ?`).get(runId3);
57508
57811
  return row ? rowToRun2(row) : null;
57509
57812
  }
57510
57813
  readAttempt(attemptId) {
@@ -57523,6 +57826,7 @@ function rowToRun2(row) {
57523
57826
  bundleDigest: row.bundle_digest,
57524
57827
  runtimeImageDigest: row.runtime_image_digest,
57525
57828
  dependencyLayerTag: row.dependency_layer_tag,
57829
+ ...row.execution_contract_json ? { executionContract: JSON.parse(row.execution_contract_json) } : {},
57526
57830
  inputDigest: row.input_digest,
57527
57831
  runtime: row.runtime,
57528
57832
  policy: JSON.parse(row.policy_json),
@@ -57566,6 +57870,7 @@ function rowToReceipt(row) {
57566
57870
  runtimeImageDigest: row.runtime_image_digest,
57567
57871
  bundleDigest: row.bundle_digest,
57568
57872
  dependencyLayerTag: row.dependency_layer_tag,
57873
+ ...row.execution_contract_json ? { executionContract: JSON.parse(row.execution_contract_json) } : {},
57569
57874
  policy: JSON.parse(row.policy_json),
57570
57875
  limits: JSON.parse(row.limits_json),
57571
57876
  exitCode: row.exit_code,
@@ -57585,6 +57890,7 @@ CREATE TABLE IF NOT EXISTS execution_runs (
57585
57890
  bundle_digest TEXT NOT NULL,
57586
57891
  runtime_image_digest TEXT NOT NULL,
57587
57892
  dependency_layer_tag TEXT,
57893
+ execution_contract_json TEXT,
57588
57894
  input_digest TEXT NOT NULL,
57589
57895
  runtime TEXT NOT NULL,
57590
57896
  policy_json TEXT NOT NULL,
@@ -57638,6 +57944,7 @@ CREATE TABLE IF NOT EXISTS execution_receipts (
57638
57944
  runtime_image_digest TEXT NOT NULL,
57639
57945
  bundle_digest TEXT NOT NULL,
57640
57946
  dependency_layer_tag TEXT,
57947
+ execution_contract_json TEXT,
57641
57948
  policy_json TEXT NOT NULL,
57642
57949
  limits_json TEXT NOT NULL,
57643
57950
  exit_code INTEGER,
@@ -57672,6 +57979,7 @@ function createSubmitRunService(options) {
57672
57979
  const policy = { ...DEFAULT_RUN_POLICY, ...input.policy };
57673
57980
  input = {
57674
57981
  ...input,
57982
+ ...input.executionContract ? { executionContract: { ...input.executionContract } } : {},
57675
57983
  systemDeps: [...input.systemDeps ?? []],
57676
57984
  policy: { ...policy, egressAllowlist: [...policy.egressAllowlist] },
57677
57985
  limits: { ...DEFAULT_RUN_LIMITS, ...input.limits }
@@ -57680,7 +57988,7 @@ function createSubmitRunService(options) {
57680
57988
  const byKey = await store.getRunByKey(input.tenantId, input.idempotencyKey);
57681
57989
  if (byKey)
57682
57990
  return { run: ownedAdmission(byKey.admission), created: false };
57683
- const byDigests = await store.getRunByDigests({
57991
+ const byDigests = input.executionContract ? null : await store.getRunByDigests({
57684
57992
  tenantId: input.tenantId,
57685
57993
  skillId: input.skillId,
57686
57994
  skillVersion: input.skillVersion,
@@ -57702,6 +58010,7 @@ function createSubmitRunService(options) {
57702
58010
  bundleDigest: input.bundleDigest,
57703
58011
  runtimeImageDigest: image.runtimeImageDigest,
57704
58012
  dependencyLayerTag: image.dependencyLayerTag,
58013
+ ...input.executionContract ? { executionContract: { ...input.executionContract } } : {},
57705
58014
  inputDigest,
57706
58015
  runtime: image.runtime,
57707
58016
  policy: { ...DEFAULT_RUN_POLICY, ...input.policy },
@@ -57717,6 +58026,7 @@ function createSubmitRunService(options) {
57717
58026
  function ownedAdmission(admission) {
57718
58027
  return {
57719
58028
  ...admission,
58029
+ ...admission.executionContract ? { executionContract: { ...admission.executionContract } } : {},
57720
58030
  policy: { ...admission.policy, egressAllowlist: [...admission.policy.egressAllowlist] },
57721
58031
  limits: { ...admission.limits }
57722
58032
  };
@@ -57738,12 +58048,12 @@ var LEGAL_TRANSITIONS = {
57738
58048
  cancelled: []
57739
58049
  };
57740
58050
  function createRunStateMachine(store) {
57741
- async function transition(runId2, from, to, attemptId = null) {
57742
- const run = await store.getRun(runId2);
58051
+ async function transition(runId3, from, to, attemptId = null) {
58052
+ const run = await store.getRun(runId3);
57743
58053
  if (!run)
57744
58054
  return { ok: false, reason: "NO_SUCH_RUN" };
57745
58055
  if (attemptId) {
57746
- const attempt = await findAttempt(store, runId2, attemptId);
58056
+ const attempt = await findAttempt(store, runId3, attemptId);
57747
58057
  if (!attempt)
57748
58058
  return { ok: false, reason: "NO_SUCH_ATTEMPT" };
57749
58059
  }
@@ -57755,11 +58065,11 @@ function createRunStateMachine(store) {
57755
58065
  if (!LEGAL_TRANSITIONS[from].includes(to)) {
57756
58066
  return { ok: false, reason: "INVALID_TRANSITION" };
57757
58067
  }
57758
- const next2 = await store.setRunStatus(runId2, to);
58068
+ const next2 = await store.setRunStatus(runId3, to);
57759
58069
  if (!next2)
57760
58070
  return { ok: false, reason: "NO_SUCH_RUN" };
57761
58071
  const record3 = {
57762
- runId: runId2,
58072
+ runId: runId3,
57763
58073
  attemptId,
57764
58074
  from,
57765
58075
  to,
@@ -57796,8 +58106,8 @@ function createRunStateMachine(store) {
57796
58106
  });
57797
58107
  return claimed;
57798
58108
  },
57799
- async start(runId2, attemptId) {
57800
- return transition(runId2, "leased", "running", attemptId);
58109
+ async start(runId3, attemptId) {
58110
+ return transition(runId3, "leased", "running", attemptId);
57801
58111
  },
57802
58112
  async terminate(input) {
57803
58113
  const run = await store.getRun(input.runId);
@@ -57817,26 +58127,26 @@ function createRunStateMachine(store) {
57817
58127
  await store.markAttemptTerminal(input.runId, input.attemptId);
57818
58128
  return { ok: true };
57819
58129
  },
57820
- async cancel(runId2) {
57821
- const run = await store.getRun(runId2);
58130
+ async cancel(runId3) {
58131
+ const run = await store.getRun(runId3);
57822
58132
  if (!run)
57823
58133
  return { ok: false, reason: "NO_SUCH_RUN" };
57824
58134
  if (isTerminalStatus(run.status))
57825
58135
  return { ok: false, reason: "RUN_TERMINAL" };
57826
- const result = await transition(runId2, run.status, "cancelled", run.currentAttemptId);
58136
+ const result = await transition(runId3, run.status, "cancelled", run.currentAttemptId);
57827
58137
  if (!result.ok)
57828
58138
  return result;
57829
58139
  return { ok: true };
57830
58140
  },
57831
58141
  transition,
57832
- async getStatus(runId2) {
57833
- const run = await store.getRun(runId2);
58142
+ async getStatus(runId3) {
58143
+ const run = await store.getRun(runId3);
57834
58144
  return run ? run.status : null;
57835
58145
  }
57836
58146
  };
57837
58147
  }
57838
- async function findAttempt(store, runId2, attemptId) {
57839
- const attempts = await store.listAttempts(runId2);
58148
+ async function findAttempt(store, runId3, attemptId) {
58149
+ const attempts = await store.listAttempts(runId3);
57840
58150
  return attempts.find((attempt) => attempt.attemptId === attemptId) ?? null;
57841
58151
  }
57842
58152
 
@@ -57857,6 +58167,7 @@ function createReceiptService(store) {
57857
58167
  runtimeImageDigest: input.admission.runtimeImageDigest,
57858
58168
  bundleDigest: input.admission.bundleDigest,
57859
58169
  dependencyLayerTag: input.admission.dependencyLayerTag,
58170
+ ...input.admission.executionContract ? { executionContract: { ...input.admission.executionContract } } : {},
57860
58171
  policy: input.admission.policy,
57861
58172
  limits: input.admission.limits,
57862
58173
  exitCode: null,
@@ -57885,7 +58196,7 @@ function createReceiptService(store) {
57885
58196
  await store.finalizeRun(input.runId, input.status, input.attemptId);
57886
58197
  return { receipt: written, runId: input.runId, status: input.status };
57887
58198
  },
57888
- get: (runId2, attemptId) => store.getReceipt(runId2, attemptId)
58199
+ get: (runId3, attemptId) => store.getReceipt(runId3, attemptId)
57889
58200
  };
57890
58201
  }
57891
58202
 
@@ -61124,11 +61435,11 @@ function observedTask(states, taskArn) {
61124
61435
  const state2 = states[0];
61125
61436
  return state2.lastStatus === "STOPPED" || LIVE_TASK_STATUSES.has(state2.lastStatus) ? state2 : undefined;
61126
61437
  }
61127
- function clientTokenFor(runId2, attemptId) {
61128
- return createHash24("sha256").update(`${runId2}\x00${attemptId}`).digest("hex").slice(0, CLIENT_TOKEN_BYTES * 2);
61438
+ function clientTokenFor(runId3, attemptId) {
61439
+ return createHash24("sha256").update(`${runId3}\x00${attemptId}`).digest("hex").slice(0, CLIENT_TOKEN_BYTES * 2);
61129
61440
  }
61130
- function startedByFor(runId2, attemptNumber) {
61131
- return `skills-exec/${runId2}/a${attemptNumber}`;
61441
+ function startedByFor(runId3, attemptNumber) {
61442
+ return `skills-exec/${runId3}/a${attemptNumber}`;
61132
61443
  }
61133
61444
  function requestDigestFor(admission, attemptId) {
61134
61445
  return createHash24("sha256").update(canonicalJson2({ admission, attemptId })).digest("hex");
@@ -61173,29 +61484,29 @@ class EcsDispatcher {
61173
61484
  return { accepted: false, detail: `run already ${outcome.status}` };
61174
61485
  }
61175
61486
  }
61176
- async cancel(runId2) {
61177
- const run = await this.store.getRun(runId2);
61487
+ async cancel(runId3) {
61488
+ const run = await this.store.getRun(runId3);
61178
61489
  if (!run)
61179
61490
  return { accepted: false, detail: "no such run" };
61180
61491
  if (run.status === "succeeded" || run.status === "failed") {
61181
61492
  return { accepted: false, detail: `run already ${run.status}` };
61182
61493
  }
61183
- const attempts = await this.store.listAttempts(runId2);
61494
+ const attempts = await this.store.listAttempts(runId3);
61184
61495
  const current = attempts[attempts.length - 1];
61185
61496
  if (!current) {
61186
61497
  if (run.status === "cancelled")
61187
61498
  return { accepted: true, detail: "already cancelled (no attempt launched)" };
61188
- const cancelled = await this.stateMachine.cancel(runId2);
61499
+ const cancelled = await this.stateMachine.cancel(runId3);
61189
61500
  return cancelled.ok ? { accepted: true, detail: "cancelled (no attempt launched)" } : { accepted: false, detail: cancelled.reason };
61190
61501
  }
61191
- if (!this.cancellationReceipt(await this.receipts.get(runId2, current.attemptId), run.admission, current)) {
61502
+ if (!this.cancellationReceipt(await this.receipts.get(runId3, current.attemptId), run.admission, current)) {
61192
61503
  return { accepted: false, detail: "matching cancellation receipt is unavailable or contradictory" };
61193
61504
  }
61194
61505
  const observation = await this.reconcile(run.admission, current);
61195
61506
  if (observation.kind !== "already-launched" && observation.kind !== "previous-terminal") {
61196
61507
  return { accepted: false, detail: "task state unknown; cancellation is not yet confirmed" };
61197
61508
  }
61198
- const taskArn = observation.kind === "already-launched" ? observation.taskId : (await this.store.listAttempts(runId2)).find((row) => row.attemptId === current.attemptId)?.taskId ?? null;
61509
+ const taskArn = observation.kind === "already-launched" ? observation.taskId : (await this.store.listAttempts(runId3)).find((row) => row.attemptId === current.attemptId)?.taskId ?? null;
61199
61510
  if (!taskArn)
61200
61511
  return { accepted: false, detail: "stopped task identity is not confirmed" };
61201
61512
  if (observation.kind === "already-launched") {
@@ -61208,7 +61519,7 @@ class EcsDispatcher {
61208
61519
  }
61209
61520
  }
61210
61521
  if (run.status !== "cancelled") {
61211
- const cancelled = await this.stateMachine.cancel(runId2);
61522
+ const cancelled = await this.stateMachine.cancel(runId3);
61212
61523
  if (!cancelled.ok)
61213
61524
  return { accepted: false, detail: cancelled.reason };
61214
61525
  }
@@ -61219,22 +61530,22 @@ class EcsDispatcher {
61219
61530
  }
61220
61531
  return { accepted: true, target: taskArn, detail: "cancelled after confirmed task stop and receipt" };
61221
61532
  }
61222
- async launchAttempt(runId2) {
61223
- const run = await this.store.getRun(runId2);
61533
+ async launchAttempt(runId3) {
61534
+ const run = await this.store.getRun(runId3);
61224
61535
  if (!run)
61225
61536
  return { kind: "no-admission" };
61226
61537
  if (run.status === "succeeded" || run.status === "failed" || run.status === "cancelled") {
61227
61538
  return { kind: "run-terminal", status: run.status };
61228
61539
  }
61229
- const attempts = await this.store.listAttempts(runId2);
61540
+ const attempts = await this.store.listAttempts(runId3);
61230
61541
  const previous = attempts[attempts.length - 1];
61231
61542
  if (previous) {
61232
61543
  return this.reconcile(run.admission, previous);
61233
61544
  }
61234
61545
  const attemptNumber = 1;
61235
- const attempt = await this.store.createAttempt({ runId: runId2, attemptNumber });
61546
+ const attempt = await this.store.createAttempt({ runId: runId3, attemptNumber });
61236
61547
  const claimed = await this.stateMachine.claim({
61237
- runId: runId2,
61548
+ runId: runId3,
61238
61549
  attemptId: attempt.attemptId,
61239
61550
  workerId: this.workerId,
61240
61551
  expectedLeaseGeneration: 0
@@ -61242,11 +61553,11 @@ class EcsDispatcher {
61242
61553
  if (!claimed.ok) {
61243
61554
  return { kind: "claim-refused", attemptId: attempt.attemptId, reason: claimed.reason };
61244
61555
  }
61245
- const clientToken = clientTokenFor(runId2, attempt.attemptId);
61246
- const startedBy = startedByFor(runId2, attemptNumber);
61556
+ const clientToken = clientTokenFor(runId3, attempt.attemptId);
61557
+ const startedBy = startedByFor(runId3, attemptNumber);
61247
61558
  const requestDigest = requestDigestFor(run.admission, attempt.attemptId);
61248
61559
  const intent = await this.store.recordLaunchIntent({
61249
- runId: runId2,
61560
+ runId: runId3,
61250
61561
  attemptId: attempt.attemptId,
61251
61562
  clientToken,
61252
61563
  requestDigest,
@@ -61264,11 +61575,11 @@ class EcsDispatcher {
61264
61575
  try {
61265
61576
  result = await this.client.runTask(this.runTaskInput(run.admission, intent.attempt, clientToken, startedBy, requestDigest));
61266
61577
  } catch {
61267
- await this.store.recordLaunchState({ runId: runId2, attemptId: attempt.attemptId, launchState: "ambiguous" });
61578
+ await this.store.recordLaunchState({ runId: runId3, attemptId: attempt.attemptId, launchState: "ambiguous" });
61268
61579
  const reconciled = await this.reconcile(run.admission, intent.attempt);
61269
61580
  return reconciled;
61270
61581
  }
61271
- await this.store.recordLaunchState({ runId: runId2, attemptId: attempt.attemptId, launchState: "launched", taskId: result.taskArn });
61582
+ await this.store.recordLaunchState({ runId: runId3, attemptId: attempt.attemptId, launchState: "launched", taskId: result.taskArn });
61272
61583
  return { kind: "launched", attemptId: attempt.attemptId, taskId: result.taskArn };
61273
61584
  }
61274
61585
  async reconcile(admission, attempt) {
@@ -61330,7 +61641,7 @@ class EcsDispatcher {
61330
61641
  }
61331
61642
  cancellationReceipt(receipt, admission, attempt) {
61332
61643
  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)));
61644
+ 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
61645
  } catch {
61335
61646
  return false;
61336
61647
  }
@@ -61427,7 +61738,174 @@ function validText(value) {
61427
61738
  }
61428
61739
 
61429
61740
  // src/server/runtime-policy.ts
61430
- import { createHash as createHash25, createHmac as createHmac3, timingSafeEqual as timingSafeEqual2 } from "crypto";
61741
+ import { createHash as createHash26, createHmac as createHmac3, timingSafeEqual as timingSafeEqual2 } from "crypto";
61742
+
61743
+ // src/server/runtime-pure-contract.ts
61744
+ import { createHash as createHash25 } from "crypto";
61745
+ var PURE_DESCRIPTOR = Object.freeze({
61746
+ id: "regex-test.v1",
61747
+ runtime: "bun",
61748
+ adapter: "skills-input-json.v1",
61749
+ input: Object.freeze({ patternBytes: 512, textBytes: 4096, totalBytes: 8192, flags: "dgimsuvy" }),
61750
+ output: "regex-matches.v1",
61751
+ secrets: "none",
61752
+ egress: "deny",
61753
+ maxDurationMs: 5000,
61754
+ maxMemoryMb: 512,
61755
+ maxCpuUnits: 256,
61756
+ stdoutBytes: 16384,
61757
+ stderrBytes: 8192,
61758
+ artifactsBytes: 0
61759
+ });
61760
+ var PURE_DESCRIPTOR_DIGEST = digestInput(PURE_DESCRIPTOR);
61761
+ var PURE_LIMITS = Object.freeze({
61762
+ maxDurationMs: 5000,
61763
+ maxMemoryMb: 512,
61764
+ maxCpuUnits: 256,
61765
+ maxArtifactsBytes: 0,
61766
+ maxConcurrency: 1
61767
+ });
61768
+ var hash2 = (bytes) => createHash25("sha256").update(bytes).digest("hex");
61769
+ var object5 = (v2) => {
61770
+ if (!v2 || typeof v2 !== "object" || Array.isArray(v2))
61771
+ throw Error("Pure contract requires an object");
61772
+ return v2;
61773
+ };
61774
+ function keys2(v2, allowed) {
61775
+ if (Object.keys(v2).some((k4) => !allowed.includes(k4)))
61776
+ throw Error("Unsupported pure contract field");
61777
+ }
61778
+ function uniqueJson(text2) {
61779
+ const result = JSON.parse(text2);
61780
+ let i4 = 0;
61781
+ const ws = () => {
61782
+ while (/\s/.test(text2[i4] ?? "") && i4 < text2.length)
61783
+ i4++;
61784
+ };
61785
+ const string = () => {
61786
+ const start = i4++;
61787
+ while (i4 < text2.length) {
61788
+ const c4 = text2[i4++];
61789
+ if (c4 === "\\")
61790
+ i4++;
61791
+ else if (c4 === '"')
61792
+ break;
61793
+ }
61794
+ return JSON.parse(text2.slice(start, i4));
61795
+ };
61796
+ const value = (depth) => {
61797
+ if (depth > 32)
61798
+ throw Error("JSON nesting limit exceeded");
61799
+ ws();
61800
+ if (text2[i4] === '"') {
61801
+ string();
61802
+ return;
61803
+ }
61804
+ if (text2[i4] === "{") {
61805
+ i4++;
61806
+ ws();
61807
+ const seen = new Set;
61808
+ if (text2[i4] === "}") {
61809
+ i4++;
61810
+ return;
61811
+ }
61812
+ for (;; ) {
61813
+ ws();
61814
+ const key = string();
61815
+ if (seen.has(key))
61816
+ throw Error("Duplicate JSON key");
61817
+ seen.add(key);
61818
+ ws();
61819
+ i4++;
61820
+ value(depth + 1);
61821
+ ws();
61822
+ if (text2[i4++] === "}")
61823
+ return;
61824
+ }
61825
+ }
61826
+ if (text2[i4] === "[") {
61827
+ i4++;
61828
+ ws();
61829
+ if (text2[i4] === "]") {
61830
+ i4++;
61831
+ return;
61832
+ }
61833
+ for (;; ) {
61834
+ value(depth + 1);
61835
+ ws();
61836
+ if (text2[i4++] === "]")
61837
+ return;
61838
+ }
61839
+ }
61840
+ while (i4 < text2.length && !/[\s,\]}]/.test(text2[i4]))
61841
+ i4++;
61842
+ };
61843
+ value(0);
61844
+ return result;
61845
+ }
61846
+ function pureInput(value) {
61847
+ const v2 = object5(value);
61848
+ keys2(v2, ["pattern", "text", "flags"]);
61849
+ 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)
61850
+ throw Error("Invalid pure regex input");
61851
+ return { pattern: v2.pattern, text: v2.text, flags: v2.flags };
61852
+ }
61853
+ function assertPureContract(value) {
61854
+ const v2 = object5(value);
61855
+ keys2(v2, ["id", "descriptorDigest", "entrypoint", "entrypointDigest"]);
61856
+ 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))
61857
+ throw Error("Unsupported pure execution contract");
61858
+ }
61859
+ function assertPureAdmission(a4) {
61860
+ assertPureContract(a4.executionContract);
61861
+ 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))
61862
+ throw Error("Pure admission isolation policy mismatch");
61863
+ return a4.executionContract;
61864
+ }
61865
+ async function inspectPureBundle(bytes, contract) {
61866
+ assertPureContract(contract);
61867
+ const { entries } = await inspectSkillBundle(bytes, { limits: {
61868
+ compressedBytes: 1e6,
61869
+ decompressedBytes: 4000000,
61870
+ fileBytes: 1e6,
61871
+ entries: 100,
61872
+ timeoutMs: 5000
61873
+ } });
61874
+ const entry = entries.find((e5) => e5.path === contract.entrypoint);
61875
+ if (!entry || hash2(entry.bytes) !== contract.entrypointDigest)
61876
+ throw Error("Pure entrypoint integrity failed");
61877
+ if (entries.some((e5) => e5.path.split("/").includes("node_modules")))
61878
+ throw Error("Pure bundle cannot supply dependencies");
61879
+ const read2 = (name) => {
61880
+ const entry2 = entries.find((e5) => e5.path === name);
61881
+ if (!entry2)
61882
+ throw Error("Pure bundle manifest missing");
61883
+ return object5(uniqueJson(Buffer.from(entry2.bytes).toString("utf8")));
61884
+ };
61885
+ const manifest = read2("skill.json"), runtime = object5(manifest.runtime), pkg = read2("package.json");
61886
+ 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)
61887
+ throw Error("Pure bundle requires a credential-free self-contained Bun entrypoint");
61888
+ for (const key of ["dependencies", "optionalDependencies", "peerDependencies", "devDependencies", "scripts"])
61889
+ if (pkg[key] !== undefined && Object.keys(object5(pkg[key])).length)
61890
+ throw Error("Pure bundle cannot install dependencies or lifecycle scripts");
61891
+ return entries;
61892
+ }
61893
+ function validatePureOutput(stdout, input) {
61894
+ if (Buffer.byteLength(stdout) > PURE_DESCRIPTOR.stdoutBytes)
61895
+ throw Error("Pure output limit exceeded");
61896
+ const v2 = object5(uniqueJson(stdout));
61897
+ keys2(v2, ["pattern", "flags", "matches"]);
61898
+ if (v2.pattern !== input.pattern || v2.flags !== input.flags || !Array.isArray(v2.matches))
61899
+ throw Error("Pure output does not match admitted input");
61900
+ for (const value of v2.matches) {
61901
+ const m2 = object5(value);
61902
+ keys2(m2, ["match", "groups", "namedGroups", "index"]);
61903
+ 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"))
61904
+ throw Error("Invalid pure match output");
61905
+ }
61906
+ }
61907
+
61908
+ // src/server/runtime-policy.ts
61431
61909
  var RUNTIME_MAX_INPUT_BYTES = 1e5;
61432
61910
  var RUNTIME_MAX_BUNDLE_BYTES = 1e6;
61433
61911
  var RUNTIME_MAX_ARTIFACT_BYTES = 2000000;
@@ -61462,8 +61940,25 @@ function readRuntimeConfig(env = process.env) {
61462
61940
  for (const key of ["subnets", "securityGroups"])
61463
61941
  if (!Array.isArray(c4[key]) || !c4[key].length || c4[key].some((v2) => typeof v2 !== "string" || !v2))
61464
61942
  throw Error(`Runtime configuration missing ${key}`);
61465
- if (!Array.isArray(c4.reviewedBundles) || !c4.reviewedBundles.length || c4.reviewedBundles.some((b4) => b4.slug !== "pdf-generate" || typeof b4.version !== "string" || !/^\d+\.\d+\.\d+$/.test(b4.version) || !/^[a-f0-9]{64}$/.test(b4.sha256)))
61466
- throw Error("Runtime requires exact reviewed pdf-generate versions and bundle digests");
61943
+ if (!Array.isArray(c4.reviewedBundles) || !c4.reviewedBundles.length)
61944
+ throw Error("Runtime requires exact reviewed versions and bundle digests");
61945
+ const seen = new Set;
61946
+ for (const b4 of c4.reviewedBundles) {
61947
+ 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))
61948
+ throw Error("Runtime requires exact reviewed versions and bundle digests");
61949
+ if (b4.executionContract) {
61950
+ if (b4.slug === "pdf-generate")
61951
+ throw Error("pdf-generate is reserved for the legacy PDF contract");
61952
+ assertPureContract(b4.executionContract);
61953
+ if (typeof b4.tenantId !== "string" || !b4.tenantId.trim() || b4.imageDigest !== c4.imageDigest)
61954
+ throw Error("Pure runtime review must bind tenant and configured image");
61955
+ } else if (b4.slug !== "pdf-generate")
61956
+ throw Error("Missing reviewed execution contract");
61957
+ const key = JSON.stringify([b4.executionContract ? b4.tenantId : null, b4.slug, b4.version, b4.sha256]);
61958
+ if (seen.has(key))
61959
+ throw Error("Ambiguous runtime review");
61960
+ seen.add(key);
61961
+ }
61467
61962
  return c4;
61468
61963
  }
61469
61964
  function runtimeInput(value) {
@@ -61499,7 +61994,7 @@ function verifyRuntimeToken(token, key, admission, attempt, now = Date.now()) {
61499
61994
  const actual = Buffer.from(token);
61500
61995
  return actual.length === expected.length && timingSafeEqual2(actual, expected);
61501
61996
  }
61502
- var hashBytes = (bytes) => createHash25("sha256").update(bytes).digest("hex");
61997
+ var hashBytes = (bytes) => createHash26("sha256").update(bytes).digest("hex");
61503
61998
 
61504
61999
  // src/server/runtime-api.ts
61505
62000
  async function createRuntimeService(options) {
@@ -61575,7 +62070,7 @@ async function body3(request2, max) {
61575
62070
  }
61576
62071
  chunks.push(v2.value);
61577
62072
  }
61578
- return JSON.parse(Buffer.concat(chunks).toString("utf8"));
62073
+ return uniqueJson(Buffer.concat(chunks).toString("utf8"));
61579
62074
  }
61580
62075
  var scope = (p2, action) => p2.scopes.includes("*") || p2.scopes.includes("runs:*") || p2.scopes.includes(action) || action === "runs:read" && p2.scopes.includes("skills:read");
61581
62076
  function publicJob(job) {
@@ -61589,6 +62084,7 @@ function publicJob(job) {
61589
62084
  bundleDigest: a4.bundleDigest,
61590
62085
  inputDigest: a4.inputDigest,
61591
62086
  runtimeImageDigest: a4.runtimeImageDigest,
62087
+ executionContract: a4.executionContract ?? { id: "pdf.v1" },
61592
62088
  status: job.execution.status,
61593
62089
  createdAt: a4.createdAt,
61594
62090
  updatedAt: job.execution.updatedAt,
@@ -61599,6 +62095,33 @@ function publicJob(job) {
61599
62095
  artifacts: (job.result?.artifacts ?? []).map(({ base64, ...meta }) => meta)
61600
62096
  };
61601
62097
  }
62098
+ function findReview(service, tenant, slug, version2, sha2562) {
62099
+ 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));
62100
+ }
62101
+ function sameContract(admission, contract, image) {
62102
+ return digestInput(admission.executionContract ?? null) === digestInput(contract ?? null) && (!contract || admission.runtimeImageDigest === image);
62103
+ }
62104
+
62105
+ class RuntimeBundleError extends Error {
62106
+ code;
62107
+ status;
62108
+ constructor(code, status) {
62109
+ super(code);
62110
+ this.code = code;
62111
+ this.status = status;
62112
+ }
62113
+ }
62114
+ async function reviewedBytes(service, principal, digest4) {
62115
+ const bundle2 = await service.productStore.getSkillBundle(principal, digest4);
62116
+ if (!bundle2)
62117
+ throw new RuntimeBundleError("BUNDLE_UNAVAILABLE", 503);
62118
+ if (bundle2.byteSize > RUNTIME_MAX_BUNDLE_BYTES)
62119
+ throw new RuntimeBundleError("BUNDLE_LIMIT", 413);
62120
+ const bytes = await service.artifacts.readBundle(bundle2);
62121
+ if (!bytes || bytes.length > RUNTIME_MAX_BUNDLE_BYTES || hashBytes(bytes) !== digest4)
62122
+ throw new RuntimeBundleError("BUNDLE_INTEGRITY_FAILED", 503);
62123
+ return bytes;
62124
+ }
61602
62125
  async function handleRuntimeApiRequest(request2, principal, service) {
61603
62126
  const segments = path(request2);
61604
62127
  if (segments[0] !== "api" || segments[1] !== "v1" || segments[2] !== "executions")
@@ -61611,9 +62134,50 @@ async function handleRuntimeApiRequest(request2, principal, service) {
61611
62134
  if (!id2 || segments.length > 6 || !/^[a-z0-9_-]+$/.test(id2))
61612
62135
  return refusal2("INVALID_EXECUTION_PATH", 400);
61613
62136
  try {
62137
+ if (request2.method === "GET" && sub === "eligibility" && segments.length === 5) {
62138
+ const query = new URL(request2.url).searchParams;
62139
+ const versionName = query.get("version");
62140
+ if (!versionName || !/^\d+\.\d+\.\d+$/.test(versionName))
62141
+ return refusal2("REVIEWED_VERSION_REQUIRED", 400);
62142
+ const skill = await service.productStore.getSkill(principal, id2);
62143
+ if (skill?.lifecycle === "archived")
62144
+ return refusal2("SKILL_ARCHIVED", 410);
62145
+ const version2 = skill && !skill.tombstonedAt && await service.productStore.getSkillVersion(principal, id2, versionName);
62146
+ if (!version2)
62147
+ return refusal2("SKILL_VERSION_NOT_FOUND", 404);
62148
+ const pinned = query.get("bundleDigest");
62149
+ if (pinned && pinned.replace(/^sha256:/, "") !== version2.bundleSha256)
62150
+ return refusal2("SELECTION_DIGEST_MISMATCH", 409);
62151
+ const review = findReview(service, principal.orgId, id2, versionName, version2.bundleSha256);
62152
+ if (!review)
62153
+ return json3({
62154
+ contractVersion: 1,
62155
+ eligible: false,
62156
+ reason: "BUNDLE_NOT_REVIEWED_FOR_CLOUD",
62157
+ skill: id2,
62158
+ version: versionName,
62159
+ bundleDigest: version2.bundleSha256
62160
+ });
62161
+ if (review.executionContract) {
62162
+ const bytes = await reviewedBytes(service, principal, version2.bundleSha256);
62163
+ await inspectPureBundle(bytes, review.executionContract);
62164
+ }
62165
+ return json3({
62166
+ contractVersion: 1,
62167
+ eligible: true,
62168
+ skill: id2,
62169
+ version: versionName,
62170
+ bundleDigest: version2.bundleSha256,
62171
+ runtimeImageDigest: service.config.imageDigest,
62172
+ executionContract: review.executionContract ?? { id: "pdf.v1" },
62173
+ secrets: "none",
62174
+ egress: "deny",
62175
+ ...review.executionContract ? { descriptor: PURE_DESCRIPTOR, limits: PURE_LIMITS } : {}
62176
+ });
62177
+ }
61614
62178
  if (request2.method === "POST" && !sub) {
61615
62179
  const raw = await body3(request2, 150000);
61616
- if (id2 !== "pdf-generate" || typeof raw.version !== "string" || !/^\d+\.\d+\.\d+$/.test(raw.version))
62180
+ if (typeof raw.version !== "string" || !/^\d+\.\d+\.\d+$/.test(raw.version))
61617
62181
  return refusal2("REVIEWED_VERSION_REQUIRED", 400);
61618
62182
  if (Object.keys(raw).some((k4) => ![
61619
62183
  "version",
@@ -61625,31 +62189,32 @@ async function handleRuntimeApiRequest(request2, principal, service) {
61625
62189
  return refusal2("UNSUPPORTED_EXECUTION_FIELD", 400);
61626
62190
  if (raw.workspaceId !== undefined && raw.workspaceId !== principal.orgId)
61627
62191
  return refusal2("SELECTION_WORKSPACE_MISMATCH", 409);
61628
- const input = runtimeInput(raw.input);
61629
62192
  const key = request2.headers.get("idempotency-key") ?? raw.idempotencyKey;
61630
62193
  if (typeof key !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(key))
61631
62194
  return refusal2("IDEMPOTENCY_KEY_REQUIRED", 400);
61632
62195
  const skill = await service.productStore.getSkill(principal, id2);
61633
62196
  if (!skill || skill.tombstonedAt)
61634
62197
  return refusal2("SKILL_NOT_FOUND", 404);
62198
+ if (skill.lifecycle === "archived")
62199
+ return refusal2("SKILL_ARCHIVED", 410);
61635
62200
  const version2 = await service.productStore.getSkillVersion(principal, id2, raw.version);
61636
62201
  if (!version2)
61637
62202
  return refusal2("SKILL_VERSION_NOT_FOUND", 404);
61638
62203
  if (raw.bundleDigest !== undefined && (typeof raw.bundleDigest !== "string" || raw.bundleDigest.replace(/^sha256:/, "") !== version2.bundleSha256))
61639
62204
  return refusal2("SELECTION_DIGEST_MISMATCH", 409);
61640
- if (!service.config.reviewedBundles.some((b4) => b4.slug === id2 && b4.version === raw.version && b4.sha256 === version2.bundleSha256))
62205
+ const review = findReview(service, principal.orgId, id2, raw.version, version2.bundleSha256);
62206
+ if (!review)
61641
62207
  return refusal2("BUNDLE_NOT_REVIEWED_FOR_CLOUD", 403);
62208
+ const contract = review.executionContract;
62209
+ if (contract && (raw.workspaceId !== principal.orgId || typeof raw.bundleDigest !== "string"))
62210
+ return refusal2("PURE_SELECTION_REQUIRED", 400);
62211
+ const input = contract ? pureInput(raw.input) : runtimeInput(raw.input);
61642
62212
  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)))
62213
+ 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
62214
  return refusal2("IDEMPOTENCY_CONFLICT", 409);
61645
- const bundle2 = await service.productStore.getSkillBundle(principal, version2.bundleSha256);
61646
- if (!bundle2)
61647
- return refusal2("BUNDLE_UNAVAILABLE", 503);
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);
62215
+ const bytes = await reviewedBytes(service, principal, version2.bundleSha256);
62216
+ if (contract)
62217
+ await inspectPureBundle(bytes, contract);
61653
62218
  const admission = createSubmitRunService({
61654
62219
  store: service.store,
61655
62220
  imageProfiles: createImageProfileRegistry({
@@ -61671,12 +62236,13 @@ async function handleRuntimeApiRequest(request2, principal, service) {
61671
62236
  input,
61672
62237
  idempotencyKey: key,
61673
62238
  runtime: "bun",
61674
- limits: {
62239
+ ...contract ? { executionContract: contract } : {},
62240
+ limits: contract ? PURE_LIMITS : {
61675
62241
  maxDurationMs: RUNTIME_TIMEOUT_MS,
61676
62242
  maxArtifactsBytes: RUNTIME_MAX_ARTIFACT_BYTES
61677
62243
  }
61678
62244
  });
61679
- if (run.tenantId !== principal.orgId || run.skillId !== id2 || run.skillVersion !== raw.version || run.bundleDigest !== version2.bundleSha256 || run.inputDigest !== digestInput(input))
62245
+ 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
62246
  return refusal2("IDEMPOTENCY_CONFLICT", 409);
61681
62247
  await service.store.mutate(run.runId, (j4) => {
61682
62248
  if (!j4.input) {
@@ -61768,6 +62334,8 @@ async function handleRuntimeApiRequest(request2, principal, service) {
61768
62334
  }
61769
62335
  return refusal2("EXECUTION_ROUTE_NOT_FOUND", 404);
61770
62336
  } catch (error) {
62337
+ if (error instanceof RuntimeBundleError)
62338
+ return refusal2(error.code, error.status);
61771
62339
  if (error.message === "Runtime concurrency limit reached")
61772
62340
  return refusal2("RUNTIME_CONCURRENCY_LIMIT", 429);
61773
62341
  return refusal2("EXECUTION_REQUEST_FAILED", 400, error.message.startsWith("PDF ") ? error.message : "Execution request could not be completed");
@@ -61798,7 +62366,7 @@ async function handleRuntimeWorkerRequest(request2, service) {
61798
62366
  }
61799
62367
  if (request2.method === "POST" && s2[4] === "complete") {
61800
62368
  try {
61801
- const result = validatedResult(await body3(request2, 3000000));
62369
+ const result = validatedResult(await body3(request2, 3000000), job);
61802
62370
  await service.store.mutate(s2[3], (j4) => {
61803
62371
  if (j4.execution.status === "cancelled")
61804
62372
  throw Error("Execution cancelled");
@@ -61835,12 +62403,28 @@ async function handleRuntimeWorkerRequest(request2, service) {
61835
62403
  }
61836
62404
  return refusal2("METHOD_NOT_ALLOWED", 405);
61837
62405
  }
61838
- function validatedResult(value) {
62406
+ function validatedResult(value, job) {
61839
62407
  if (!value || typeof value !== "object")
61840
62408
  throw Error("Invalid result");
61841
62409
  const v2 = value;
61842
62410
  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
62411
  throw Error("Invalid result");
62412
+ if (job.execution.admission.executionContract) {
62413
+ assertPureAdmission(job.execution.admission);
62414
+ if (v2.artifacts.length || Buffer.byteLength(v2.stdout) > PURE_DESCRIPTOR.stdoutBytes || Buffer.byteLength(v2.stderr) > PURE_DESCRIPTOR.stderrBytes)
62415
+ throw Error("Invalid pure result limits");
62416
+ if (v2.exitCode === 0)
62417
+ validatePureOutput(v2.stdout, pureInput(job.input));
62418
+ return {
62419
+ exitCode: v2.exitCode,
62420
+ stdout: v2.stdout,
62421
+ stderr: v2.stderr,
62422
+ artifacts: [],
62423
+ ...typeof v2.error === "string" ? { error: v2.error.slice(0, 500) } : {}
62424
+ };
62425
+ }
62426
+ if (job.execution.admission.skillId !== "pdf-generate")
62427
+ throw Error("Missing execution contract");
61844
62428
  let total = 0;
61845
62429
  const seen = new Set;
61846
62430
  for (const a4 of v2.artifacts) {
@@ -62048,8 +62632,22 @@ class MemorySkillsStore {
62048
62632
  async ensureBootstrapApiKey(token, principal) {
62049
62633
  this.addApiKey(token, principal);
62050
62634
  }
62051
- async authenticateApiKeyHash(hash2) {
62052
- return this.apiKeys.get(hash2) ?? null;
62635
+ async authenticateApiKeyHash(hash3) {
62636
+ return this.apiKeys.get(hash3) ?? null;
62637
+ }
62638
+ async updateApiKeyScopes(actor, keyId, expectedScopes, addScopes) {
62639
+ for (const [hash3, target] of this.apiKeys) {
62640
+ if (target.apiKeyId !== keyId || target.orgId !== actor.orgId)
62641
+ continue;
62642
+ if (target.scopes.length !== expectedScopes.length || target.scopes.some((scope2, index) => scope2 !== expectedScopes[index])) {
62643
+ return { kind: "stale", scopes: [...target.scopes] };
62644
+ }
62645
+ const scopes = [...target.scopes, ...addScopes.filter((scope2) => !target.scopes.includes(scope2))];
62646
+ const updated = { ...target, scopes };
62647
+ this.apiKeys.set(hash3, updated);
62648
+ return { kind: "updated", scopes };
62649
+ }
62650
+ return { kind: "not_found" };
62053
62651
  }
62054
62652
  async createRun(input) {
62055
62653
  const idemKey = input.idempotencyKey?.trim();
@@ -62061,7 +62659,7 @@ class MemorySkillsStore {
62061
62659
  }
62062
62660
  const now = nowIso();
62063
62661
  const run = {
62064
- id: runId(),
62662
+ id: runId2(),
62065
62663
  orgId: input.principal.orgId,
62066
62664
  userId: input.principal.userId,
62067
62665
  skill: input.slug,
@@ -62085,8 +62683,8 @@ class MemorySkillsStore {
62085
62683
  async listRuns(principal, limit) {
62086
62684
  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
62685
  }
62088
- async getRun(principal, runId2) {
62089
- const run = this.runs.get(runId2);
62686
+ async getRun(principal, runId3) {
62687
+ const run = this.runs.get(runId3);
62090
62688
  return run && run.orgId === principal.orgId ? run : null;
62091
62689
  }
62092
62690
  async claimNextRun(_input) {
@@ -62095,28 +62693,28 @@ class MemorySkillsStore {
62095
62693
  return null;
62096
62694
  return this.patchRun(run.id, { status: "running", startedAt: run.startedAt ?? nowIso(), leaseGeneration: run.leaseGeneration + 1 });
62097
62695
  }
62098
- async updateRun(runId2, patch) {
62099
- return this.patchRun(runId2, patch);
62696
+ async updateRun(runId3, patch) {
62697
+ return this.patchRun(runId3, patch);
62100
62698
  }
62101
- async transitionRun(runId2, patch, expectedGeneration) {
62102
- const run = this.runs.get(runId2);
62699
+ async transitionRun(runId3, patch, expectedGeneration) {
62700
+ const run = this.runs.get(runId3);
62103
62701
  if (!run)
62104
62702
  return null;
62105
62703
  if (run.leaseGeneration !== expectedGeneration) {
62106
- throw new StaleLeaseGenerationError(runId2, expectedGeneration, run.leaseGeneration, run.status);
62704
+ throw new StaleLeaseGenerationError(runId3, expectedGeneration, run.leaseGeneration, run.status);
62107
62705
  }
62108
- return this.patchRun(runId2, patch);
62706
+ return this.patchRun(runId3, patch);
62109
62707
  }
62110
- async appendLog(runId2, orgId, level, message) {
62111
- const entries = this.logs.get(runId2) ?? [];
62112
- const log = { runId: runId2, sequence: entries.length + 1, level, message, createdAt: nowIso() };
62708
+ async appendLog(runId3, orgId, level, message) {
62709
+ const entries = this.logs.get(runId3) ?? [];
62710
+ const log = { runId: runId3, sequence: entries.length + 1, level, message, createdAt: nowIso() };
62113
62711
  entries.push(log);
62114
- this.logs.set(runId2, entries);
62712
+ this.logs.set(runId3, entries);
62115
62713
  return log;
62116
62714
  }
62117
- async listLogs(principal, runId2) {
62118
- const run = await this.getRun(principal, runId2);
62119
- return run ? [...this.logs.get(runId2) ?? []] : [];
62715
+ async listLogs(principal, runId3) {
62716
+ const run = await this.getRun(principal, runId3);
62717
+ return run ? [...this.logs.get(runId3) ?? []] : [];
62120
62718
  }
62121
62719
  async addArtifact(artifact) {
62122
62720
  const next2 = { ...artifact, createdAt: nowIso() };
@@ -62125,12 +62723,12 @@ class MemorySkillsStore {
62125
62723
  this.artifacts.set(artifact.runId, artifacts);
62126
62724
  return next2;
62127
62725
  }
62128
- async listArtifacts(principal, runId2) {
62129
- const run = await this.getRun(principal, runId2);
62130
- return run ? [...this.artifacts.get(runId2) ?? []] : [];
62726
+ async listArtifacts(principal, runId3) {
62727
+ const run = await this.getRun(principal, runId3);
62728
+ return run ? [...this.artifacts.get(runId3) ?? []] : [];
62131
62729
  }
62132
- async getArtifact(principal, runId2, id2) {
62133
- const artifacts = await this.listArtifacts(principal, runId2);
62730
+ async getArtifact(principal, runId3, id2) {
62731
+ const artifacts = await this.listArtifacts(principal, runId3);
62134
62732
  return artifacts.find((artifact) => artifact.id === id2) ?? null;
62135
62733
  }
62136
62734
  async publishSkill(input) {
@@ -62180,7 +62778,11 @@ class MemorySkillsStore {
62180
62778
  createdAt: previous?.createdAt ?? now,
62181
62779
  updatedAt: now,
62182
62780
  revisionId: revisionIdOfRecord(recordFieldsOf(input, carriedBundle, carriedSkillMd)),
62183
- revisionNumber: (previous?.revisionNumber ?? 0) + 1
62781
+ revisionNumber: (previous?.revisionNumber ?? 0) + 1,
62782
+ lifecycle: previous?.lifecycle ?? "active",
62783
+ ...previous?.archivedAt ? { archivedAt: previous.archivedAt } : {},
62784
+ ...previous?.archiveReason ? { archiveReason: previous.archiveReason } : {},
62785
+ ...previous?.replacementSlug ? { replacementSlug: previous.replacementSlug } : {}
62184
62786
  };
62185
62787
  this.skills.set(key, record3);
62186
62788
  if (input.version && versionSha && !this.versions.has(versionKey(input.principal.orgId, input.slug, input.version))) {
@@ -62216,6 +62818,33 @@ class MemorySkillsStore {
62216
62818
  const skill = this.skills.get(skillKey(principal.orgId, slug));
62217
62819
  return skill && skill.orgId === principal.orgId ? skill : null;
62218
62820
  }
62821
+ async setSkillLifecycle(principal, slug, patch, expectedRevisionId) {
62822
+ const current = await this.getSkill(principal, slug);
62823
+ if (!current || current.tombstonedAt)
62824
+ return null;
62825
+ if (expectedRevisionId !== current.revisionId)
62826
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
62827
+ if (patch.lifecycle === "archived" && current.lifecycle !== "archived") {
62828
+ const profiles = await this.selectionStore.profilesReferencingSkill(principal, slug);
62829
+ if (profiles.length)
62830
+ throw new SkillLifecycleConflictError(slug, profiles);
62831
+ }
62832
+ const next2 = {
62833
+ ...current,
62834
+ lifecycle: patch.lifecycle,
62835
+ updatedAt: nowIso(),
62836
+ revisionId: revisionIdOfRecord({ ...current, lifecycle: patch.lifecycle, archiveReason: patch.reason, replacementSlug: patch.replacementSlug }),
62837
+ revisionNumber: current.revisionNumber + 1,
62838
+ ...patch.lifecycle === "archived" ? { archivedAt: current.archivedAt ?? nowIso(), ...patch.reason ? { archiveReason: patch.reason } : {}, ...patch.replacementSlug ? { replacementSlug: patch.replacementSlug } : {} } : {}
62839
+ };
62840
+ if (patch.lifecycle === "active") {
62841
+ delete next2.archivedAt;
62842
+ delete next2.archiveReason;
62843
+ delete next2.replacementSlug;
62844
+ }
62845
+ this.skills.set(skillKey(principal.orgId, slug), next2);
62846
+ return next2;
62847
+ }
62219
62848
  async updateSkill(principal, slug, patch, expectedRevisionId) {
62220
62849
  const current = await this.getSkill(principal, slug);
62221
62850
  if (!current || current.tombstonedAt)
@@ -62286,7 +62915,7 @@ class MemorySkillsStore {
62286
62915
  await this.purgeExpiredTombstones(principal);
62287
62916
  const tags = new Set;
62288
62917
  for (const skill of this.skills.values()) {
62289
- if (skill.orgId !== principal.orgId)
62918
+ if (skill.orgId !== principal.orgId || skill.lifecycle !== "active")
62290
62919
  continue;
62291
62920
  for (const tag of skill.tags) {
62292
62921
  if (tag.trim())
@@ -62297,13 +62926,13 @@ class MemorySkillsStore {
62297
62926
  }
62298
62927
  async listSkillsByTag(principal, tag) {
62299
62928
  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));
62929
+ 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
62930
  }
62302
62931
  async listPinsByTag(principal, tag) {
62303
62932
  await this.purgeExpiredTombstones(principal);
62304
62933
  const taggedSlugs = new Set;
62305
62934
  for (const skill of this.skills.values()) {
62306
- if (skill.orgId === principal.orgId && !skill.tombstonedAt && skill.tags.includes(tag))
62935
+ if (skill.orgId === principal.orgId && !skill.tombstonedAt && skill.lifecycle === "active" && skill.tags.includes(tag))
62307
62936
  taggedSlugs.add(skill.slug);
62308
62937
  }
62309
62938
  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 +62945,12 @@ class MemorySkillsStore {
62316
62945
  if (!referenced)
62317
62946
  this.bundles.delete(skillKey(orgId, sha2562));
62318
62947
  }
62319
- patchRun(runId2, patch) {
62320
- const run = this.runs.get(runId2);
62948
+ patchRun(runId3, patch) {
62949
+ const run = this.runs.get(runId3);
62321
62950
  if (!run)
62322
62951
  return null;
62323
62952
  const next2 = { ...run, ...patch };
62324
- this.runs.set(runId2, next2);
62953
+ this.runs.set(runId3, next2);
62325
62954
  return next2;
62326
62955
  }
62327
62956
  }
@@ -62394,7 +63023,7 @@ class PostgresSkillsStore {
62394
63023
  ON CONFLICT (key_hash) DO NOTHING
62395
63024
  `;
62396
63025
  }
62397
- async authenticateApiKeyHash(hash2) {
63026
+ async authenticateApiKeyHash(hash3) {
62398
63027
  const rows = await this.sql`
62399
63028
  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
63029
  u.id AS user_id, u.email, m.role
@@ -62402,7 +63031,7 @@ class PostgresSkillsStore {
62402
63031
  JOIN organizations o ON o.id = k.org_id
62403
63032
  JOIN users u ON u.id = k.user_id
62404
63033
  LEFT JOIN organization_members m ON m.org_id = k.org_id AND m.user_id = k.user_id
62405
- WHERE k.key_hash = ${hash2} AND k.revoked_at IS NULL
63034
+ WHERE k.key_hash = ${hash3} AND k.revoked_at IS NULL
62406
63035
  LIMIT 1
62407
63036
  `;
62408
63037
  const row = rows[0];
@@ -62420,6 +63049,38 @@ class PostgresSkillsStore {
62420
63049
  scopes: parseJsonArray(row.scopes_json)
62421
63050
  };
62422
63051
  }
63052
+ async updateApiKeyScopes(actor, keyId, expectedScopes, addScopes) {
63053
+ return this.sql.begin(async (tx) => {
63054
+ const rows = await tx`
63055
+ SELECT scopes_json FROM api_keys
63056
+ WHERE id = ${keyId} AND org_id = ${actor.orgId} AND revoked_at IS NULL
63057
+ LIMIT 1
63058
+ `;
63059
+ const row = rows[0];
63060
+ if (!row)
63061
+ return { kind: "not_found" };
63062
+ const current = parseJsonArray(row.scopes_json);
63063
+ if (current.length !== expectedScopes.length || current.some((scope2, index) => scope2 !== expectedScopes[index])) {
63064
+ return { kind: "stale", scopes: current };
63065
+ }
63066
+ const scopes = [...current, ...addScopes.filter((scope2) => !current.includes(scope2))];
63067
+ const updated = await tx`
63068
+ UPDATE api_keys SET scopes_json = ${JSON.stringify(scopes)}::jsonb
63069
+ WHERE id = ${keyId} AND org_id = ${actor.orgId} AND revoked_at IS NULL
63070
+ AND scopes_json = ${JSON.stringify(expectedScopes)}::jsonb
63071
+ RETURNING scopes_json
63072
+ `;
63073
+ if (!updated[0]) {
63074
+ 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`;
63075
+ return { kind: "stale", scopes: latest[0] ? parseJsonArray(latest[0].scopes_json) : current };
63076
+ }
63077
+ await tx`
63078
+ INSERT INTO skills_audit_events (org_id, user_id, api_key_id, action, target_type, target_id, metadata_json)
63079
+ VALUES (${actor.orgId}, ${actor.userId}, ${actor.apiKeyId}, ${"api_key_scopes_added"}, ${"api_key"}, ${keyId}, ${JSON.stringify({ added: addScopes, scopes })}::jsonb)
63080
+ `;
63081
+ return { kind: "updated", scopes };
63082
+ });
63083
+ }
62423
63084
  async withContext(orgId, worker, fn) {
62424
63085
  return this.sql.begin(async (tx) => {
62425
63086
  await tx`SELECT set_config('app.skills_org_id', ${orgId ?? ""}, true)`;
@@ -62438,7 +63099,7 @@ class PostgresSkillsStore {
62438
63099
  if (existing[0])
62439
63100
  return rowToRun(existing[0]);
62440
63101
  }
62441
- const id2 = runId();
63102
+ const id2 = runId2();
62442
63103
  const rows = await tx`
62443
63104
  INSERT INTO skills_runs (id, org_id, user_id, skill_slug, requested_slug, status, input_json, args_json, idempotency_key, correlation_id)
62444
63105
  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 +63117,10 @@ class PostgresSkillsStore {
62456
63117
  return rows.map(rowToRun);
62457
63118
  });
62458
63119
  }
62459
- async getRun(principal, runId2) {
63120
+ async getRun(principal, runId3) {
62460
63121
  return this.withContext(principal.orgId, false, async (tx) => {
62461
63122
  const rows = await tx`
62462
- SELECT * FROM skills_runs WHERE id = ${runId2} AND org_id = ${principal.orgId} LIMIT 1
63123
+ SELECT * FROM skills_runs WHERE id = ${runId3} AND org_id = ${principal.orgId} LIMIT 1
62463
63124
  `;
62464
63125
  return rows[0] ? rowToRun(rows[0]) : null;
62465
63126
  });
@@ -62482,9 +63143,9 @@ class PostgresSkillsStore {
62482
63143
  return rows[0] ? rowToRun(rows[0]) : null;
62483
63144
  });
62484
63145
  }
62485
- async updateRun(runId2, patch) {
63146
+ async updateRun(runId3, patch) {
62486
63147
  return this.withContext(null, true, async (tx) => {
62487
- const current = await tx`SELECT * FROM skills_runs WHERE id = ${runId2} LIMIT 1`;
63148
+ const current = await tx`SELECT * FROM skills_runs WHERE id = ${runId3} LIMIT 1`;
62488
63149
  if (!current[0])
62489
63150
  return null;
62490
63151
  const run = { ...rowToRun(current[0]), ...patch };
@@ -62497,20 +63158,20 @@ class PostgresSkillsStore {
62497
63158
  error_message = ${run.errorMessage ?? null},
62498
63159
  started_at = ${run.startedAt ?? null},
62499
63160
  completed_at = ${run.completedAt ?? null}
62500
- WHERE id = ${runId2}
63161
+ WHERE id = ${runId3}
62501
63162
  RETURNING *
62502
63163
  `;
62503
63164
  return rows[0] ? rowToRun(rows[0]) : null;
62504
63165
  });
62505
63166
  }
62506
- async transitionRun(runId2, patch, expectedGeneration) {
63167
+ async transitionRun(runId3, patch, expectedGeneration) {
62507
63168
  return this.withContext(null, true, async (tx) => {
62508
- const current = await tx`SELECT * FROM skills_runs WHERE id = ${runId2} LIMIT 1`;
63169
+ const current = await tx`SELECT * FROM skills_runs WHERE id = ${runId3} LIMIT 1`;
62509
63170
  if (!current[0])
62510
63171
  return null;
62511
63172
  const stored = rowToRun(current[0]);
62512
63173
  if (stored.leaseGeneration !== expectedGeneration) {
62513
- throw new StaleLeaseGenerationError(runId2, expectedGeneration, stored.leaseGeneration, stored.status);
63174
+ throw new StaleLeaseGenerationError(runId3, expectedGeneration, stored.leaseGeneration, stored.status);
62514
63175
  }
62515
63176
  const run = { ...stored, ...patch };
62516
63177
  const rows = await tx`
@@ -62523,13 +63184,13 @@ class PostgresSkillsStore {
62523
63184
  started_at = ${run.startedAt ?? null},
62524
63185
  completed_at = ${run.completedAt ?? null},
62525
63186
  lease_generation = ${run.leaseGeneration}
62526
- WHERE id = ${runId2} AND lease_generation = ${expectedGeneration}
63187
+ WHERE id = ${runId3} AND lease_generation = ${expectedGeneration}
62527
63188
  RETURNING *
62528
63189
  `;
62529
63190
  return rows[0] ? rowToRun(rows[0]) : null;
62530
63191
  });
62531
63192
  }
62532
- async appendLog(runId2, orgId, level, message) {
63193
+ async appendLog(runId3, orgId, level, message) {
62533
63194
  let lastError;
62534
63195
  for (let attempt = 0;attempt < LOG_SEQUENCE_ATTEMPTS; attempt += 1) {
62535
63196
  try {
@@ -62537,9 +63198,9 @@ class PostgresSkillsStore {
62537
63198
  return await tx`
62538
63199
  INSERT INTO skills_run_logs (run_id, org_id, sequence, level, message)
62539
63200
  VALUES (
62540
- ${runId2},
63201
+ ${runId3},
62541
63202
  ${orgId},
62542
- (SELECT COALESCE(MAX(sequence), 0) + 1 FROM skills_run_logs WHERE run_id = ${runId2}),
63203
+ (SELECT COALESCE(MAX(sequence), 0) + 1 FROM skills_run_logs WHERE run_id = ${runId3}),
62543
63204
  ${level},
62544
63205
  ${message}
62545
63206
  )
@@ -62555,12 +63216,12 @@ class PostgresSkillsStore {
62555
63216
  }
62556
63217
  throw lastError;
62557
63218
  }
62558
- async listLogs(principal, runId2) {
63219
+ async listLogs(principal, runId3) {
62559
63220
  return this.withContext(principal.orgId, false, async (tx) => {
62560
63221
  const rows = await tx`
62561
63222
  SELECT l.* FROM skills_run_logs l
62562
63223
  JOIN skills_runs r ON r.id = l.run_id AND r.org_id = ${principal.orgId}
62563
- WHERE l.run_id = ${runId2}
63224
+ WHERE l.run_id = ${runId3}
62564
63225
  ORDER BY l.sequence ASC
62565
63226
  `;
62566
63227
  return rows.map(rowToLog);
@@ -62576,23 +63237,23 @@ class PostgresSkillsStore {
62576
63237
  return rowToArtifact(rows[0]);
62577
63238
  });
62578
63239
  }
62579
- async listArtifacts(principal, runId2) {
63240
+ async listArtifacts(principal, runId3) {
62580
63241
  return this.withContext(principal.orgId, false, async (tx) => {
62581
63242
  const rows = await tx`
62582
63243
  SELECT a.* FROM skills_artifacts a
62583
63244
  JOIN skills_runs r ON r.id = a.run_id AND r.org_id = ${principal.orgId}
62584
- WHERE a.run_id = ${runId2}
63245
+ WHERE a.run_id = ${runId3}
62585
63246
  ORDER BY a.created_at ASC
62586
63247
  `;
62587
63248
  return rows.map(rowToArtifact);
62588
63249
  });
62589
63250
  }
62590
- async getArtifact(principal, runId2, artifactId2) {
63251
+ async getArtifact(principal, runId3, artifactId2) {
62591
63252
  return this.withContext(principal.orgId, false, async (tx) => {
62592
63253
  const rows = await tx`
62593
63254
  SELECT a.* FROM skills_artifacts a
62594
63255
  JOIN skills_runs r ON r.id = a.run_id AND r.org_id = ${principal.orgId}
62595
- WHERE a.run_id = ${runId2} AND a.id = ${artifactId2}
63256
+ WHERE a.run_id = ${runId3} AND a.id = ${artifactId2}
62596
63257
  LIMIT 1
62597
63258
  `;
62598
63259
  return rows[0] ? rowToArtifact(rows[0]) : null;
@@ -62740,6 +63401,42 @@ class PostgresSkillsStore {
62740
63401
  const rows = await this.sql`SELECT * FROM skills_registry WHERE org_id = ${principal.orgId} AND slug = ${slug} LIMIT 1`;
62741
63402
  return rows[0] ? rowToSkill(rows[0]) : null;
62742
63403
  }
63404
+ async setSkillLifecycle(principal, slug, patch, expectedRevisionId) {
63405
+ return this.sql.begin(async (tx) => {
63406
+ const currentRows = await tx`SELECT * FROM skills_registry WHERE org_id=${principal.orgId} AND slug=${slug} LIMIT 1 FOR UPDATE`;
63407
+ const current = currentRows[0] ? rowToSkill(currentRows[0]) : null;
63408
+ if (!current || current.tombstonedAt)
63409
+ return null;
63410
+ if (expectedRevisionId !== current.revisionId)
63411
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
63412
+ if (patch.lifecycle === "archived" && current.lifecycle !== "archived") {
63413
+ const profiles = await tx`SELECT profile_id,selections_json FROM skills_profiles WHERE org_id=${principal.orgId}`;
63414
+ const referencing = profiles.filter((profile2) => {
63415
+ const selections2 = typeof profile2.selections_json === "string" ? JSON.parse(profile2.selections_json) : profile2.selections_json;
63416
+ return Array.isArray(selections2) && selections2.some((selection2) => selection2?.slug === slug);
63417
+ }).map((profile2) => String(profile2.profile_id)).sort();
63418
+ if (referencing.length)
63419
+ throw new SkillLifecycleConflictError(slug, referencing);
63420
+ }
63421
+ const next2 = {
63422
+ ...current,
63423
+ lifecycle: patch.lifecycle,
63424
+ updatedAt: nowIso(),
63425
+ revisionId: revisionIdOfRecord({ ...current, lifecycle: patch.lifecycle, archiveReason: patch.reason, replacementSlug: patch.replacementSlug }),
63426
+ revisionNumber: current.revisionNumber + 1,
63427
+ ...patch.lifecycle === "archived" ? { archivedAt: current.archivedAt ?? nowIso(), ...patch.reason ? { archiveReason: patch.reason } : {}, ...patch.replacementSlug ? { replacementSlug: patch.replacementSlug } : {} } : {}
63428
+ };
63429
+ if (patch.lifecycle === "active") {
63430
+ delete next2.archivedAt;
63431
+ delete next2.archiveReason;
63432
+ delete next2.replacementSlug;
63433
+ }
63434
+ 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 *`;
63435
+ if (!rows[0])
63436
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, (await this.getSkill(principal, slug))?.revisionId ?? null);
63437
+ return rowToSkill(rows[0]);
63438
+ });
63439
+ }
62743
63440
  async updateSkill(principal, slug, patch, expectedRevisionId) {
62744
63441
  const current = await this.getSkill(principal, slug);
62745
63442
  if (!current || current.tombstonedAt)
@@ -62875,7 +63572,7 @@ class PostgresSkillsStore {
62875
63572
  async listTags(principal) {
62876
63573
  await this.purgeExpiredTombstones(principal);
62877
63574
  const rows = await this.sql`
62878
- SELECT DISTINCT tag FROM skills_tags WHERE org_id = ${principal.orgId} ORDER BY tag ASC
63575
+ 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
63576
  `;
62880
63577
  return rows.map((row) => String(row.tag));
62881
63578
  }
@@ -62884,7 +63581,7 @@ class PostgresSkillsStore {
62884
63581
  const rows = await this.sql`
62885
63582
  SELECT s.* FROM skills_registry s
62886
63583
  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
63584
+ WHERE t.org_id = ${principal.orgId} AND t.tag = ${tag} AND s.tombstoned_at IS NULL AND s.lifecycle = 'active'
62888
63585
  ORDER BY s.slug ASC
62889
63586
  `;
62890
63587
  return rows.map(rowToSkill);
@@ -62896,7 +63593,7 @@ class PostgresSkillsStore {
62896
63593
  JOIN skills_tags t ON t.org_id = p.org_id AND t.slug = p.slug
62897
63594
  JOIN skills_registry s ON s.org_id = p.org_id AND s.slug = p.slug
62898
63595
  WHERE p.org_id = ${principal.orgId} AND p.principal = ${principal.apiKeyId}
62899
- AND t.tag = ${tag} AND s.tombstoned_at IS NULL
63596
+ AND t.tag = ${tag} AND s.tombstoned_at IS NULL AND s.lifecycle = 'active'
62900
63597
  ORDER BY p.slug ASC
62901
63598
  `;
62902
63599
  return rows.map(rowToPin);
@@ -62993,7 +63690,7 @@ async function createSkillsFetchHandler(options = {}) {
62993
63690
  if (request2.method === "GET" && url.pathname === "/api/v1/capabilities") {
62994
63691
  const response = await handleProfileApi(store, principal, request2, ["capabilities"], config);
62995
63692
  const payload = await response.json();
62996
- return json4({ ...payload, cloudExecution: Boolean(runtime), capabilities: [...payload.capabilities, ...runtime ? ["skills.cloud-execution"] : []] });
63693
+ return json4({ ...payload, cloudExecution: Boolean(runtime), cloudExecutionContracts: runtime ? ["pdf.v1", "regex-test.v1"] : [], capabilities: [...payload.capabilities, ...runtime ? ["skills.cloud-execution"] : []] });
62997
63694
  }
62998
63695
  if (request2.method === "GET" && url.pathname === "/api/auth/whoami") {
62999
63696
  return json4(identityPayload(principal));
@@ -63017,6 +63714,9 @@ async function createSkillsFetchHandler(options = {}) {
63017
63714
  attemptedBundleSha256: error.attemptedSha256
63018
63715
  }, { status: 409 });
63019
63716
  }
63717
+ if (error instanceof SkillLifecycleConflictError) {
63718
+ return json4({ error: error.message, code: "SKILL_ARCHIVE_PROFILE_CONFLICT", profiles: error.profiles }, { status: 409 });
63719
+ }
63020
63720
  if (error instanceof SkillRevisionConflictError) {
63021
63721
  return json4({
63022
63722
  error: error.message,
@@ -63055,6 +63755,32 @@ async function handleApiV1(store, governanceStore, principal, request2, parts, c
63055
63755
  if (parts.some(segmentEscapesPath)) {
63056
63756
  return json4({ error: "invalid path segment", code: "INVALID_PATH" }, { status: 400 });
63057
63757
  }
63758
+ if (parts.length === 4 && resource === "admin" && id2 === "keys" && subresource && childId === "scopes" && request2.method === "PATCH") {
63759
+ const adminScope = principal.scopes.includes("*") || principal.scopes.includes("skills:keys.admin");
63760
+ if (!adminScope || !["owner", "admin"].includes(principal.role)) {
63761
+ return json4({ error: "Key scope administration requires an owner/admin key with skills:keys.admin", code: "KEY_ADMIN_REQUIRED" }, { status: 403 });
63762
+ }
63763
+ if (!/^[A-Za-z0-9_-]+$/.test(subresource)) {
63764
+ return json4({ error: "Key id must be url-safe (letters, digits, '_' or '-')", code: "INVALID_KEY_ID" }, { status: 400 });
63765
+ }
63766
+ const body4 = await readJson(request2, config.requestBodyLimitBytes);
63767
+ const expected = body4.expected_scopes;
63768
+ const additions = body4.add_scopes;
63769
+ if (!Array.isArray(expected) || !expected.every((scope2) => typeof scope2 === "string") || !Array.isArray(additions) || !additions.every((scope2) => typeof scope2 === "string")) {
63770
+ return json4({ error: "expected_scopes and add_scopes must be arrays of scope strings", code: "INVALID_KEY_SCOPE_UPDATE" }, { status: 400 });
63771
+ }
63772
+ if (additions.length !== 1 || additions[0] !== "skills:publish") {
63773
+ return json4({ error: "Only adding skills:publish to an existing Skills key is supported", code: "INVALID_KEY_SCOPE_UPDATE" }, { status: 400 });
63774
+ }
63775
+ const update = await store.updateApiKeyScopes?.(principal, subresource, expected, additions);
63776
+ if (!update)
63777
+ return json4({ error: "Key scope administration is unavailable", code: "KEY_SCOPE_UPDATE_UNAVAILABLE" }, { status: 501 });
63778
+ if (update.kind === "not_found")
63779
+ return json4({ error: "API key not found", code: "API_KEY_NOT_FOUND" }, { status: 404 });
63780
+ if (update.kind === "stale")
63781
+ 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 });
63782
+ return json4({ keyId: subresource, orgId: principal.orgId, scopes: update.scopes, updated: true });
63783
+ }
63058
63784
  if (!permitsSkillsRoute(principal, request2.method, resource ?? "")) {
63059
63785
  return json4({ error: "The API key does not allow this operation", code: "INSUFFICIENT_SCOPE" }, { status: 403 });
63060
63786
  }
@@ -63088,6 +63814,13 @@ async function handleApiV1(store, governanceStore, principal, request2, parts, c
63088
63814
  if (request2.method === "GET" && id2 && subresource === "versions" && !childId) {
63089
63815
  return json4(await listSkillVersionsPayload(store, principal, id2));
63090
63816
  }
63817
+ if (request2.method === "PATCH" && id2 && subresource === "lifecycle" && !childId) {
63818
+ if (!["owner", "admin"].includes(principal.role))
63819
+ return json4({ error: "Only organization owners or admins may change skill lifecycle", code: "LIFECYCLE_ROLE_REQUIRED" }, { status: 403 });
63820
+ const expectedRevisionId = parseIfMatch(request2.headers.get("if-match"));
63821
+ const updated = await store.setSkillLifecycle(principal, id2, skillLifecyclePatch(await readJson(request2, config.requestBodyLimitBytes)), expectedRevisionId);
63822
+ return updated ? json4(publishedPayload(updated), { headers: { ETag: revisionEtag(updated.revisionId) } }) : json4({ error: "published skill not found", code: "SKILL_NOT_FOUND" }, { status: 404 });
63823
+ }
63091
63824
  if (request2.method === "GET" && id2 && subresource === "versions" && childId && !parts[4]) {
63092
63825
  const version2 = await readSkillVersion(store, principal, id2, childId);
63093
63826
  const current = (await store.getSkill(principal, id2))?.bundleSha256;
@@ -63246,8 +63979,11 @@ async function handleApiV1(store, governanceStore, principal, request2, parts, c
63246
63979
  return json4({ error: "not found", code: "NOT_FOUND" }, { status: 404 });
63247
63980
  }
63248
63981
  function identityPayload(principal) {
63982
+ const safeKeyId = /^[A-Za-z0-9_-]{1,256}$/.test(principal.apiKeyId) ? principal.apiKeyId : undefined;
63983
+ 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
63984
  return {
63250
63985
  user: { id: principal.userId, email: principal.email, role: principal.role },
63986
+ ...safeKeyId && safeScopes ? { apiKey: { id: safeKeyId, scopes: safeScopes } } : {},
63251
63987
  organization: { id: principal.orgId, slug: principal.orgSlug, name: principal.orgName }
63252
63988
  };
63253
63989
  }
@@ -63503,14 +64239,14 @@ var util;
63503
64239
  return obj[e5];
63504
64240
  });
63505
64241
  };
63506
- util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object5) => {
63507
- const keys2 = [];
63508
- for (const key in object5) {
63509
- if (Object.prototype.hasOwnProperty.call(object5, key)) {
63510
- keys2.push(key);
64242
+ util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object6) => {
64243
+ const keys3 = [];
64244
+ for (const key in object6) {
64245
+ if (Object.prototype.hasOwnProperty.call(object6, key)) {
64246
+ keys3.push(key);
63511
64247
  }
63512
64248
  }
63513
- return keys2;
64249
+ return keys3;
63514
64250
  };
63515
64251
  util2.find = (arr, checker) => {
63516
64252
  for (const item of arr) {
@@ -65760,8 +66496,8 @@ class ZodObject extends ZodType {
65760
66496
  if (this._cached !== null)
65761
66497
  return this._cached;
65762
66498
  const shape = this._def.shape();
65763
- const keys2 = util.objectKeys(shape);
65764
- this._cached = { shape, keys: keys2 };
66499
+ const keys3 = util.objectKeys(shape);
66500
+ this._cached = { shape, keys: keys3 };
65765
66501
  return this._cached;
65766
66502
  }
65767
66503
  _parse(input) {
@@ -67435,8 +68171,8 @@ function createRunService({ store, governance }) {
67435
68171
  return run;
67436
68172
  },
67437
68173
  leaseNext: (workerId) => store.claimNextRun({ workerId }),
67438
- transition: (runId2, patch) => store.updateRun(runId2, patch),
67439
- get: (principal, runId2) => store.getRun(principal, runId2)
68174
+ transition: (runId3, patch) => store.updateRun(runId3, patch),
68175
+ get: (principal, runId3) => store.getRun(principal, runId3)
67440
68176
  };
67441
68177
  }
67442
68178
  async function settleRun(store, options, run, actualCents) {
@@ -67481,7 +68217,7 @@ var localRunExecutor = {
67481
68217
  // src/sdk/storage.ts
67482
68218
  var artifactStorageSeam = ArtifactStorage;
67483
68219
  // src/sdk/outputs.ts
67484
- import { createHash as createHash26 } from "crypto";
68220
+ import { createHash as createHash27 } from "crypto";
67485
68221
  class DatabaseOnlyObjectStore {
67486
68222
  usesS3 = false;
67487
68223
  async materialize(run, artifact, body4) {
@@ -67491,8 +68227,8 @@ class DatabaseOnlyObjectStore {
67491
68227
  return null;
67492
68228
  }
67493
68229
  async deleteBundle() {}
67494
- quarantineKeyFor(tenantId, runId2, artifactId2) {
67495
- return `${tenantId}/${runId2}/quarantine/${artifactId2}`;
68230
+ quarantineKeyFor(tenantId, runId3, artifactId2) {
68231
+ return `${tenantId}/${runId3}/quarantine/${artifactId2}`;
67496
68232
  }
67497
68233
  }
67498
68234
  function redactRunOutput(value, patterns = DEFAULT_OUTPUT_GOVERNANCE.redactPatterns) {
@@ -67528,7 +68264,7 @@ function createGovernedArtifactWriter(options) {
67528
68264
  const stamped = {
67529
68265
  ...meta,
67530
68266
  byteSize: outputBytes,
67531
- sha256: createHash26("sha256").update(persistedBytes).digest("hex"),
68267
+ sha256: createHash27("sha256").update(persistedBytes).digest("hex"),
67532
68268
  visibility: options.visibility ?? config.defaultVisibility,
67533
68269
  ...config.artifactTtlSeconds !== undefined ? { expiresAt: expiresAtFor(createdAt, config.artifactTtlSeconds) } : {}
67534
68270
  };
@@ -67674,13 +68410,13 @@ function createSpendService(options) {
67674
68410
  exhaust("monthly", `org spend for ${monthPrefix} is ${monthly} cents and this run estimates ${estimated}; monthly ceiling is ${ceilings.monthlyTotalCents} cents`);
67675
68411
  }
67676
68412
  },
67677
- async reserve(tenantId, runId2, estimatedCents) {
68413
+ async reserve(tenantId, runId3, estimatedCents) {
67678
68414
  assertReservationCents(estimatedCents, "estimatedCents");
67679
- return store.createReservation({ orgId: tenantId, runId: runId2, estimatedCents });
68415
+ return store.createReservation({ orgId: tenantId, runId: runId3, estimatedCents });
67680
68416
  },
67681
- async reconcile(tenantId, runId2, actualCents) {
68417
+ async reconcile(tenantId, runId3, actualCents) {
67682
68418
  assertReservationCents(actualCents, "actualCents");
67683
- const reservations = await store.reservationsForRun(tenantId, runId2);
68419
+ const reservations = await store.reservationsForRun(tenantId, runId3);
67684
68420
  const open = reservations.find((reservation) => reservation.status === "reserved");
67685
68421
  if (!open)
67686
68422
  return null;
@@ -67773,7 +68509,7 @@ __export(exports_external2, {
67773
68509
  overwrite: () => _overwrite,
67774
68510
  output: () => output,
67775
68511
  optional: () => optional,
67776
- object: () => object5,
68512
+ object: () => object6,
67777
68513
  number: () => number2,
67778
68514
  nullish: () => nullish2,
67779
68515
  nullable: () => nullable,
@@ -67824,7 +68560,7 @@ __export(exports_external2, {
67824
68560
  httpUrl: () => httpUrl,
67825
68561
  hostname: () => hostname2,
67826
68562
  hex: () => hex2,
67827
- hash: () => hash2,
68563
+ hash: () => hash3,
67828
68564
  guid: () => guid2,
67829
68565
  gte: () => _gte,
67830
68566
  gt: () => _gt,
@@ -68403,9 +69139,9 @@ function floatSafeRemainder2(val, step) {
68403
69139
  return ratio - roundedRatio;
68404
69140
  }
68405
69141
  var EVALUATING = /* @__PURE__ */ Symbol("evaluating");
68406
- function defineLazy(object5, key, getter) {
69142
+ function defineLazy(object6, key, getter) {
68407
69143
  let value = undefined;
68408
- Object.defineProperty(object5, key, {
69144
+ Object.defineProperty(object6, key, {
68409
69145
  get() {
68410
69146
  if (value === EVALUATING) {
68411
69147
  return;
@@ -68417,7 +69153,7 @@ function defineLazy(object5, key, getter) {
68417
69153
  return value;
68418
69154
  },
68419
69155
  set(v2) {
68420
- Object.defineProperty(object5, key, {
69156
+ Object.defineProperty(object6, key, {
68421
69157
  value: v2
68422
69158
  });
68423
69159
  },
@@ -68452,12 +69188,12 @@ function getElementAtPath(obj, path2) {
68452
69188
  return path2.reduce((acc, key) => acc?.[key], obj);
68453
69189
  }
68454
69190
  function promiseAllObject(promisesObj) {
68455
- const keys2 = Object.keys(promisesObj);
68456
- const promises = keys2.map((key) => promisesObj[key]);
69191
+ const keys3 = Object.keys(promisesObj);
69192
+ const promises = keys3.map((key) => promisesObj[key]);
68457
69193
  return Promise.all(promises).then((results) => {
68458
69194
  const resolvedObj = {};
68459
- for (let i4 = 0;i4 < keys2.length; i4++) {
68460
- resolvedObj[keys2[i4]] = results[i4];
69195
+ for (let i4 = 0;i4 < keys3.length; i4++) {
69196
+ resolvedObj[keys3[i4]] = results[i4];
68461
69197
  }
68462
69198
  return resolvedObj;
68463
69199
  });
@@ -71087,10 +71823,10 @@ function handlePropertyResult(result, final, key, input, optin, optout) {
71087
71823
  }
71088
71824
  var NO_SYMBOL_KEYS = [];
71089
71825
  function normalizeDef(def) {
71090
- const keys2 = Object.keys(def.shape);
71826
+ const keys3 = Object.keys(def.shape);
71091
71827
  const ownSymbols = Object.getOwnPropertySymbols(def.shape);
71092
71828
  const symbolKeys = ownSymbols.length ? ownSymbols : NO_SYMBOL_KEYS;
71093
- const allKeys = symbolKeys.length ? [...keys2, ...symbolKeys] : keys2;
71829
+ const allKeys = symbolKeys.length ? [...keys3, ...symbolKeys] : keys3;
71094
71830
  for (const k4 of allKeys) {
71095
71831
  if (!def.shape?.[k4]?._zod?.traits?.has("$ZodType")) {
71096
71832
  throw new Error(`Invalid element at key "${String(k4)}": expected a Zod schema`);
@@ -71101,8 +71837,8 @@ function normalizeDef(def) {
71101
71837
  ...def,
71102
71838
  allKeys,
71103
71839
  symbolKeys,
71104
- keySet: new Set(keys2),
71105
- numKeys: keys2.length,
71840
+ keySet: new Set(keys3),
71841
+ numKeys: keys3.length,
71106
71842
  optionalKeys: new Set(okeys)
71107
71843
  };
71108
71844
  }
@@ -71619,19 +72355,19 @@ function handleIntersectionResults(result, left, right) {
71619
72355
  let unrecIssue;
71620
72356
  const keyIssues = new Map;
71621
72357
  const collect = (iss, side) => {
71622
- let keys2;
72358
+ let keys3;
71623
72359
  if (iss.code === "unrecognized_keys" && !iss.path?.length) {
71624
72360
  unrecIssue ?? (unrecIssue = iss);
71625
- keys2 = iss.keys;
72361
+ keys3 = iss.keys;
71626
72362
  } else if (iss.code === "invalid_key" && iss.origin === "record" && iss.path?.length === 1) {
71627
72363
  const k4 = String(iss.path[0]);
71628
72364
  if (!keyIssues.has(k4))
71629
72365
  keyIssues.set(k4, iss);
71630
- keys2 = [k4];
72366
+ keys3 = [k4];
71631
72367
  } else {
71632
72368
  return false;
71633
72369
  }
71634
- for (const k4 of keys2) {
72370
+ for (const k4 of keys3) {
71635
72371
  if (!unrecKeys.has(k4))
71636
72372
  unrecKeys.set(k4, {});
71637
72373
  unrecKeys.get(k4)[side] = true;
@@ -80835,13 +81571,13 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
80835
81571
  const def = schema._zod.def;
80836
81572
  doc.write(`if (typeof ${accessor} !== "object" || ${accessor} === null || Array.isArray(${accessor})) return INVALID;`);
80837
81573
  const shape = def.shape;
80838
- const keys2 = Object.keys(shape);
81574
+ const keys3 = Object.keys(shape);
80839
81575
  const symbolKeys = Object.getOwnPropertySymbols(shape);
80840
- const allKeys = symbolKeys.length ? [...keys2, ...symbolKeys] : keys2;
81576
+ const allKeys = symbolKeys.length ? [...keys3, ...symbolKeys] : keys3;
80841
81577
  const keyExpr = (k4) => typeof k4 === "symbol" ? addConstant(ctx, k4) : esc(k4);
80842
81578
  const propKey = (k4) => typeof k4 === "symbol" ? `[${keyExpr(k4)}]` : esc(k4);
80843
81579
  const propShape = shape;
80844
- if (keys2.includes("__proto__")) {
81580
+ if (keys3.includes("__proto__")) {
80845
81581
  throw new ZodCompileUnsupportedError('object shape key "__proto__"');
80846
81582
  }
80847
81583
  const propOutputs = new Map;
@@ -80883,7 +81619,7 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
80883
81619
  if (catchall) {
80884
81620
  const catchallType = catchall._zod.def.type;
80885
81621
  if (catchallType === "never") {
80886
- const condition = keys2.map((k4) => `k !== ${esc(k4)}`).join(" && ") || "true";
81622
+ const condition = keys3.map((k4) => `k !== ${esc(k4)}`).join(" && ") || "true";
80887
81623
  doc.write(`for (const k in ${accessor}) {`);
80888
81624
  doc.indented((d4) => {
80889
81625
  d4.write(`if (${condition}) return INVALID;`);
@@ -80899,7 +81635,7 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
80899
81635
  const hasConditionalKeys = allKeys.some((k4) => mayOutputUndefined(propShape[k4]) || dropsWhenAbsent(propShape[k4]));
80900
81636
  if (!buildsValue) {
80901
81637
  if (unknownKeysMode === "schema") {
80902
- const knownSet = keys2.length > 0 ? addConstant(ctx, new Set(keys2)) : null;
81638
+ const knownSet = keys3.length > 0 ? addConstant(ctx, new Set(keys3)) : null;
80903
81639
  doc.write(`for (const k in ${accessor}) {`);
80904
81640
  doc.indented((d4) => {
80905
81641
  d4.write(`if (k === "__proto__") continue;`);
@@ -80931,7 +81667,7 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
80931
81667
  }
80932
81668
  }
80933
81669
  if (unknownKeysMode !== "none") {
80934
- const knownSet = keys2.length > 0 ? addConstant(ctx, new Set(keys2)) : null;
81670
+ const knownSet = keys3.length > 0 ? addConstant(ctx, new Set(keys3)) : null;
80935
81671
  doc.write(`for (const k in ${accessor}) {`);
80936
81672
  doc.indented((d4) => {
80937
81673
  d4.write(`if (k === "__proto__") continue;`);
@@ -82794,8 +83530,8 @@ function compactTypeUnion(schema) {
82794
83530
  if (!option || typeof option !== "object")
82795
83531
  return;
82796
83532
  compactTypeUnion(option);
82797
- const keys2 = Object.keys(option);
82798
- if (keys2.length !== 1 || keys2[0] !== "type")
83533
+ const keys3 = Object.keys(option);
83534
+ if (keys3.length !== 1 || keys3[0] !== "type")
82799
83535
  return;
82800
83536
  const type = option.type;
82801
83537
  for (const member of Array.isArray(type) ? type : [type]) {
@@ -82829,8 +83565,8 @@ function foldObjects(members2) {
82829
83565
  }
82830
83566
  const properties = {};
82831
83567
  const required2 = new Set;
82832
- for (const object5 of objects) {
82833
- for (const key in object5.properties) {
83568
+ for (const object6 of objects) {
83569
+ for (const key in object6.properties) {
82834
83570
  if (Object.prototype.hasOwnProperty.call(properties, key))
82835
83571
  continue;
82836
83572
  const parts = [];
@@ -82844,18 +83580,18 @@ function foldObjects(members2) {
82844
83580
  const merged = parts.length === 1 ? parts[0] : foldObjects(parts) ?? { allOf: parts };
82845
83581
  assignProp(properties, key, merged);
82846
83582
  }
82847
- for (const key of object5.required ?? [])
83583
+ for (const key of object6.required ?? [])
82848
83584
  required2.add(key);
82849
83585
  }
82850
83586
  const folded = { type: "object", properties };
82851
83587
  if (required2.size)
82852
83588
  folded.required = [...required2];
82853
- if (objects.every((object5) => object5.additionalProperties === false)) {
83589
+ if (objects.every((object6) => object6.additionalProperties === false)) {
82854
83590
  folded.additionalProperties = false;
82855
83591
  } else {
82856
83592
  const constraints = [];
82857
- for (const object5 of objects) {
82858
- const constraint = undeclaredConstraint(object5);
83593
+ for (const object6 of objects) {
83594
+ const constraint = undeclaredConstraint(object6);
82859
83595
  if (constraint && !constraints.some((seen) => JSON.stringify(seen) === JSON.stringify(constraint)))
82860
83596
  constraints.push(constraint);
82861
83597
  }
@@ -83867,7 +84603,7 @@ __export(exports_schemas2, {
83867
84603
  pipe: () => pipe,
83868
84604
  partialRecord: () => partialRecord,
83869
84605
  optional: () => optional,
83870
- object: () => object5,
84606
+ object: () => object6,
83871
84607
  number: () => number2,
83872
84608
  nullish: () => nullish2,
83873
84609
  nullable: () => nullable,
@@ -83899,7 +84635,7 @@ __export(exports_schemas2, {
83899
84635
  httpUrl: () => httpUrl,
83900
84636
  hostname: () => hostname2,
83901
84637
  hex: () => hex2,
83902
- hash: () => hash2,
84638
+ hash: () => hash3,
83903
84639
  guid: () => guid2,
83904
84640
  function: () => _function,
83905
84641
  float64: () => float64,
@@ -84613,7 +85349,7 @@ function hostname2(_params) {
84613
85349
  function hex2(_params) {
84614
85350
  return _stringFormat(ZodCustomStringFormat, "hex", exports_regexes.hex, _params);
84615
85351
  }
84616
- function hash2(alg, params) {
85352
+ function hash3(alg, params) {
84617
85353
  const enc = params?.enc ?? "hex";
84618
85354
  const format = `${alg}_${enc}`;
84619
85355
  const regex = exports_regexes[format];
@@ -84913,7 +85649,7 @@ var ZodObject2 = /* @__PURE__ */ $constructor("ZodObject", (inst, def) => {
84913
85649
  return exports_util.required(ZodNonOptional, this, args[0]);
84914
85650
  }
84915
85651
  });
84916
- function object5(shape, params) {
85652
+ function object6(shape, params) {
84917
85653
  const def = {
84918
85654
  type: "object",
84919
85655
  shape: shape ?? {},
@@ -85106,11 +85842,11 @@ var ZodEnum2 = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
85106
85842
  inst._zod.processJSONSchema = (ctx, json5, params) => enumProcessor(inst, ctx, json5, params);
85107
85843
  inst.enum = def.entries;
85108
85844
  inst.options = Object.values(def.entries);
85109
- const keys2 = new Set(Object.keys(def.entries));
85845
+ const keys3 = new Set(Object.keys(def.entries));
85110
85846
  inst.extract = (values, params) => {
85111
85847
  const newEntries = {};
85112
85848
  for (const value of values) {
85113
- if (keys2.has(value)) {
85849
+ if (keys3.has(value)) {
85114
85850
  newEntries[value] = def.entries[value];
85115
85851
  } else
85116
85852
  throw new Error(`Key ${value} not found in enum`);
@@ -85125,7 +85861,7 @@ var ZodEnum2 = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
85125
85861
  inst.exclude = (values, params) => {
85126
85862
  const newEntries = { ...def.entries };
85127
85863
  for (const value of values) {
85128
- if (keys2.has(value)) {
85864
+ if (keys3.has(value)) {
85129
85865
  delete newEntries[value];
85130
85866
  } else
85131
85867
  throw new Error(`Key ${value} not found in enum`);
@@ -86112,10 +86848,10 @@ function visit2(schema, fnOrHandlers) {
86112
86848
  switch (kind) {
86113
86849
  case "object": {
86114
86850
  const oldShape = def.shape;
86115
- const keys2 = Object.keys(oldShape);
86851
+ const keys3 = Object.keys(oldShape);
86116
86852
  let changed = false;
86117
86853
  const newShape = {};
86118
- for (const k4 of keys2) {
86854
+ for (const k4 of keys3) {
86119
86855
  const mapped = run(oldShape[k4]);
86120
86856
  if (mapped !== oldShape[k4])
86121
86857
  changed = true;
@@ -86295,7 +87031,7 @@ function date4(params) {
86295
87031
  return _coercedDate(ZodDate2, params);
86296
87032
  }
86297
87033
  // src/lib/canonical-json.ts
86298
- import { createHash as createHash27 } from "crypto";
87034
+ import { createHash as createHash28 } from "crypto";
86299
87035
  function canonicalJson3(value, maximumBytes = 2 * 1024 * 1024) {
86300
87036
  return canonicalJsonAtDepth(value, maximumBytes, 64);
86301
87037
  }
@@ -86339,7 +87075,7 @@ function canonicalJsonAtDepth(value, maximumBytes, maximumDepth) {
86339
87075
  return visit3(value, 0);
86340
87076
  }
86341
87077
  function canonicalJsonSha256(value, maximumBytes) {
86342
- return createHash27("sha256").update(canonicalJson3(value, maximumBytes)).digest("hex");
87078
+ return createHash28("sha256").update(canonicalJson3(value, maximumBytes)).digest("hex");
86343
87079
  }
86344
87080
 
86345
87081
  // src/lib/remote-recurring-contract.ts
@@ -86597,9 +87333,9 @@ function requireResult(value) {
86597
87333
  if (!value)
86598
87334
  throw new RemoteRecurringReadError;
86599
87335
  }
86600
- function termsIntegrity(terms, hash3, identity3, own2) {
87336
+ function termsIntegrity(terms, hash4, identity3, own2) {
86601
87337
  requireResult(terms.organizationId === identity3.organization.id && (!own2 || terms.approvedByUserId === identity3.user.id && terms.approvedMembershipId === identity3.user.membershipId));
86602
- requireResult(canonicalJsonSha256(terms) === hash3);
87338
+ requireResult(canonicalJsonSha256(terms) === hash4);
86603
87339
  requireResult(canonicalJsonSha256({ skill: terms.skill, input: terms.input, args: terms.args, runtime: terms.runtime }) === terms.payloadSha256);
86604
87340
  requireResult(Date.parse(terms.expiresAt) > Date.parse(terms.startsAt));
86605
87341
  }
@@ -86734,7 +87470,7 @@ var inputFailure = () => {
86734
87470
  function invitationInput(action, input2) {
86735
87471
  if (!record4(input2))
86736
87472
  return inputFailure();
86737
- const keys2 = {
87473
+ const keys3 = {
86738
87474
  list: ["after"],
86739
87475
  get: ["invitationId"],
86740
87476
  issue: ["email", "role", "idempotencyKey", "confirm"],
@@ -86742,7 +87478,7 @@ function invitationInput(action, input2) {
86742
87478
  revoke: ["invitationId", "expectedGeneration", "confirm"],
86743
87479
  accept: ["invitationId", "token", "confirm"]
86744
87480
  };
86745
- if (Object.keys(input2).some((key) => !keys2[action].includes(key)) || action !== "list" && keys2[action].some((key) => !Object.hasOwn(input2, key)))
87481
+ if (Object.keys(input2).some((key) => !keys3[action].includes(key)) || action !== "list" && keys3[action].some((key) => !Object.hasOwn(input2, key)))
86746
87482
  return inputFailure();
86747
87483
  const value = { ...input2 };
86748
87484
  if (action === "list") {
@@ -87119,15 +87855,15 @@ function runQuoteReceipt(value) {
87119
87855
  return value;
87120
87856
  }
87121
87857
  function parseRemoteRunQuote(value) {
87122
- const quote2 = object6(value);
87858
+ const quote2 = object7(value);
87123
87859
  runQuoteReceipt(quote2.quoteReceipt);
87124
- const pricing = object6(quote2.pricing);
87860
+ const pricing = object7(quote2.pricing);
87125
87861
  if (typeof quote2.skill !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(quote2.skill))
87126
87862
  throw new Error("Invalid quoted skill");
87127
87863
  const costCents = creditCount(pricing.costCredits ?? pricing.costCents);
87128
87864
  if (pricing.costCredits !== undefined && pricing.costCents !== undefined && pricing.costCents !== costCents)
87129
87865
  throw new Error("Inconsistent quoted credit count");
87130
- if (quote2.availability && object6(quote2.availability).status !== "available")
87866
+ if (quote2.availability && object7(quote2.availability).status !== "available")
87131
87867
  throw new Error("This skill is unavailable for remote execution");
87132
87868
  return { ...quote2, skill: quote2.skill, pricing: { ...pricing, costCredits: costCents, costCents, formattedCost: `${costCents} credits` } };
87133
87869
  }
@@ -87136,7 +87872,7 @@ function parseRemoteCreditPacks(value) {
87136
87872
  throw new Error("Invalid credit pack response");
87137
87873
  const ids = new Set;
87138
87874
  return value.map((value2) => {
87139
- const row = object6(value2);
87875
+ const row = object7(value2);
87140
87876
  const counts = [row.credits, row.creditsCents, row.amountCents].filter((value3) => value3 !== undefined).map(creditCount);
87141
87877
  if (!counts.length || counts[0] === 0 || counts.some((count) => count !== counts[0]))
87142
87878
  throw new Error("Inconsistent credit pack counts");
@@ -87151,7 +87887,7 @@ function parseRemoteCreditPacks(value) {
87151
87887
  });
87152
87888
  }
87153
87889
  function parseRemoteBillingStatus(value) {
87154
- const row = object6(value);
87890
+ const row = object7(value);
87155
87891
  const counts = [row.creditBalance, row.balanceCents].filter((value2) => value2 !== undefined).map(creditCount);
87156
87892
  if (!counts.length || counts.some((count) => count !== counts[0]))
87157
87893
  throw new Error("Inconsistent credit balance");
@@ -87163,7 +87899,7 @@ function parseRemoteBillingStatus(value) {
87163
87899
  };
87164
87900
  }
87165
87901
  function parseRemoteCheckout(value) {
87166
- const row = object6(value);
87902
+ const row = object7(value);
87167
87903
  if (typeof row.url !== "string")
87168
87904
  throw new Error("Invalid checkout URL");
87169
87905
  const url2 = new URL(row.url);
@@ -87171,7 +87907,7 @@ function parseRemoteCheckout(value) {
87171
87907
  throw new Error("Invalid checkout URL");
87172
87908
  return { url: row.url };
87173
87909
  }
87174
- function object6(value) {
87910
+ function object7(value) {
87175
87911
  if (!value || typeof value !== "object" || Array.isArray(value))
87176
87912
  throw new Error("Invalid Skills server response");
87177
87913
  return value;
@@ -87375,6 +88111,18 @@ class RemoteRequestError extends Error {
87375
88111
  this.name = "RemoteRequestError";
87376
88112
  }
87377
88113
  }
88114
+ var KNOWN_SKILL_LIFECYCLE_CODES = new Set(["SKILL_ARCHIVE_PROFILE_CONFLICT", "LIFECYCLE_ROLE_REQUIRED"]);
88115
+
88116
+ class RemoteSkillLifecycleError extends RemoteRequestError {
88117
+ code;
88118
+ constructor(path2, status, code) {
88119
+ super(path2, status);
88120
+ this.name = "RemoteSkillLifecycleError";
88121
+ const safeCode = code !== undefined && KNOWN_SKILL_LIFECYCLE_CODES.has(code) ? code : undefined;
88122
+ this.code = safeCode;
88123
+ this.message = `Skill lifecycle update was refused (HTTP ${status}${safeCode ? `, code ${safeCode}` : ""})`;
88124
+ }
88125
+ }
87378
88126
 
87379
88127
  class RemoteCreditCheckoutError extends RemoteRequestError {
87380
88128
  code;
@@ -87555,6 +88303,39 @@ class RemoteSkillsClient {
87555
88303
  })();
87556
88304
  return this.capabilities;
87557
88305
  }
88306
+ async addSkillPublishScope(keyId, expectedScopes, expectedOrgId) {
88307
+ if (!/^[A-Za-z0-9_-]+$/.test(keyId))
88308
+ throw new Error("Invalid API key id");
88309
+ if (!/^[A-Za-z0-9_-]{1,256}$/.test(expectedOrgId))
88310
+ throw new Error("Invalid expected organization id");
88311
+ 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)
88312
+ throw new Error("Invalid expected API key scopes");
88313
+ const path2 = `/api/v1/admin/keys/${encodeURIComponent(keyId)}/scopes`;
88314
+ const response = await this.request(path2, { method: "PATCH", body: JSON.stringify({ expected_scopes: expectedScopes, add_scopes: ["skills:publish"] }) });
88315
+ if (!response.ok)
88316
+ throw new RemoteRequestError(path2, response.status);
88317
+ let value;
88318
+ try {
88319
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readBoundedResponse(response, 64 * 1024)));
88320
+ } catch {
88321
+ throw new Error("Invalid API key scope update response");
88322
+ }
88323
+ if (!value || typeof value !== "object" || Array.isArray(value))
88324
+ throw new Error("Invalid API key scope update response");
88325
+ const body4 = value;
88326
+ const returnedId = body4.keyId;
88327
+ const orgId = body4.orgId;
88328
+ const scopes = body4.scopes;
88329
+ if (returnedId !== keyId || typeof orgId !== "string" || !orgId || expectedOrgId !== undefined && orgId !== expectedOrgId || !Array.isArray(scopes) || scopes.some((scope2) => typeof scope2 !== "string") || body4.updated !== true) {
88330
+ throw new Error("Invalid API key scope update response");
88331
+ }
88332
+ const returnedScopes = scopes;
88333
+ const expectedResult = expectedScopes.includes("skills:publish") ? expectedScopes : [...expectedScopes, "skills:publish"];
88334
+ if (returnedScopes.length !== expectedResult.length || expectedResult.some((scope2, index) => returnedScopes[index] !== scope2)) {
88335
+ throw new Error("API key scope update response did not preserve the expected scopes");
88336
+ }
88337
+ return { keyId, orgId, scopes: [...returnedScopes], updated: true };
88338
+ }
87558
88339
  async submitQuotedRun(slug2, input2 = {}, args = [], approval = {}) {
87559
88340
  runQuoteReceipt(approval.quoteReceipt);
87560
88341
  ({ input: input2, args, approval } = JSON.parse(JSON.stringify({ input: input2, args, approval })));
@@ -87928,14 +88709,14 @@ class RemoteSkillsClient {
87928
88709
  async createBillingPortal() {
87929
88710
  return this.checkoutResponse("/api/v1/billing/portal");
87930
88711
  }
87931
- async cancelRun(runId2) {
87932
- return this.controlRun(runId2, "cancel");
88712
+ async cancelRun(runId3) {
88713
+ return this.controlRun(runId3, "cancel");
87933
88714
  }
87934
- async resumeRun(runId2) {
87935
- return this.controlRun(runId2, "resume");
88715
+ async resumeRun(runId3) {
88716
+ return this.controlRun(runId3, "resume");
87936
88717
  }
87937
- async controlRun(runId2, action) {
87938
- const response = await this.requestNewRoute(`/api/v1/runs/${encodeURIComponent(runId2)}/${action}`, { method: "POST", body: "{}" });
88718
+ async controlRun(runId3, action) {
88719
+ const response = await this.requestNewRoute(`/api/v1/runs/${encodeURIComponent(runId3)}/${action}`, { method: "POST", body: "{}" });
87939
88720
  return normalizeRemoteSkillRunContract(await response.json());
87940
88721
  }
87941
88722
  async checkoutResponse(path2) {
@@ -87947,8 +88728,8 @@ class RemoteSkillsClient {
87947
88728
  throw new Error("Invalid Skills server list response");
87948
88729
  return rows;
87949
88730
  }
87950
- async getRun(runId2) {
87951
- const path2 = `/api/v1/runs/${encodeURIComponent(runId2)}`;
88731
+ async getRun(runId3) {
88732
+ const path2 = `/api/v1/runs/${encodeURIComponent(runId3)}`;
87952
88733
  const res = await this.request(path2);
87953
88734
  if (res.status === 404)
87954
88735
  return null;
@@ -87956,30 +88737,30 @@ class RemoteSkillsClient {
87956
88737
  throw new RemoteRequestError(path2, res.status, res.statusText);
87957
88738
  return normalizeRemoteSkillRunContract(await res.json());
87958
88739
  }
87959
- async getRunLogs(runId2) {
87960
- return this.arrayResponse(`/api/v1/runs/${encodeURIComponent(runId2)}/logs`);
88740
+ async getRunLogs(runId3) {
88741
+ return this.arrayResponse(`/api/v1/runs/${encodeURIComponent(runId3)}/logs`);
87961
88742
  }
87962
88743
  async listRuns(limit = 20) {
87963
88744
  if (!Number.isInteger(limit) || limit < 1 || limit > 100)
87964
88745
  throw new Error("Run limit must be an integer from 1 to 100");
87965
88746
  return this.arrayResponse(`/api/v1/runs?limit=${limit}`);
87966
88747
  }
87967
- async getRunArtifacts(runId2) {
87968
- return this.arrayResponse(`/api/v1/runs/${encodeURIComponent(runId2)}/artifacts`);
88748
+ async getRunArtifacts(runId3) {
88749
+ return this.arrayResponse(`/api/v1/runs/${encodeURIComponent(runId3)}/artifacts`);
87969
88750
  }
87970
- async downloadRunArtifact(runId2, artifactId2) {
87971
- return this.request(`/api/v1/runs/${encodeURIComponent(runId2)}/artifacts/${encodeURIComponent(artifactId2)}/download`, {
88751
+ async downloadRunArtifact(runId3, artifactId2) {
88752
+ return this.request(`/api/v1/runs/${encodeURIComponent(runId3)}/artifacts/${encodeURIComponent(artifactId2)}/download`, {
87972
88753
  method: "GET"
87973
88754
  });
87974
88755
  }
87975
- async getVerifiedRunArtifact(runId2, artifactId2, maximumBytes = MAX_REMOTE_FILE_BYTES) {
87976
- const artifacts = await this.getRunArtifacts(runId2);
88756
+ async getVerifiedRunArtifact(runId3, artifactId2, maximumBytes = MAX_REMOTE_FILE_BYTES) {
88757
+ const artifacts = await this.getRunArtifacts(runId3);
87977
88758
  const artifact = artifacts.find((row) => row.id === artifactId2);
87978
88759
  if (!artifact)
87979
88760
  throw new Error("Run artifact not found");
87980
88761
  if (!Number.isSafeInteger(artifact.byteSize) || artifact.byteSize < 0 || artifact.byteSize > maximumBytes || typeof artifact.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(artifact.sha256))
87981
88762
  throw new Error("The server does not provide valid artifact integrity metadata");
87982
- const response = await this.downloadRunArtifact(runId2, artifactId2);
88763
+ const response = await this.downloadRunArtifact(runId3, artifactId2);
87983
88764
  if (!response.ok)
87984
88765
  throw new RemoteRequestError("artifact download", response.status, response.statusText);
87985
88766
  const bytes = await readBoundedResponse(response, artifact.byteSize);
@@ -88027,9 +88808,9 @@ class RemoteSkillsClient {
88027
88808
  }
88028
88809
  return run;
88029
88810
  }
88030
- async uploadRunFiles(runId2, files) {
88811
+ async uploadRunFiles(runId3, files) {
88031
88812
  const descriptors = describeRemoteFiles(files);
88032
- const response = await this.requestNewRoute(`/api/v1/runs/${encodeURIComponent(runId2)}/uploads`, { method: "POST", body: JSON.stringify({ files: descriptors }) });
88813
+ const response = await this.requestNewRoute(`/api/v1/runs/${encodeURIComponent(runId3)}/uploads`, { method: "POST", body: JSON.stringify({ files: descriptors }) });
88033
88814
  const payload = await response.json();
88034
88815
  if (!Array.isArray(payload.files) || payload.files.length !== files.length || new Set(payload.files.map((file2) => file2.name)).size !== files.length)
88035
88816
  throw new Error("Invalid input upload response");
@@ -88072,6 +88853,19 @@ class RemoteSkillsClient {
88072
88853
  async deleteSkill(slug2) {
88073
88854
  return this.request(`/api/v1/skills/${encodeURIComponent(slug2)}`, { method: "DELETE" });
88074
88855
  }
88856
+ async setSkillLifecycle(slug2, lifecycle, options) {
88857
+ const path2 = `/api/v1/skills/${encodeURIComponent(slug2)}/lifecycle`;
88858
+ const response = await this.request(path2, {
88859
+ method: "PATCH",
88860
+ headers: { "If-Match": options.expectedRevisionId },
88861
+ body: JSON.stringify({ lifecycle, ...options.reason ? { reason: options.reason } : {}, ...options.replacementSlug ? { replacementSlug: options.replacementSlug } : {} })
88862
+ });
88863
+ if (!response.ok) {
88864
+ const code = await readResponseCode(response);
88865
+ throw new RemoteSkillLifecycleError(path2, response.status, code);
88866
+ }
88867
+ return response.json();
88868
+ }
88075
88869
  async downloadSkillBundle(slug2) {
88076
88870
  return this.request(`/api/v1/skills/${encodeURIComponent(slug2)}/bundle`, { method: "GET" });
88077
88871
  }
@@ -88224,9 +89018,13 @@ function normalizeSkillSummaryList(payload) {
88224
89018
  return payload.map(normalizeSkillSummary);
88225
89019
  }
88226
89020
  async function responseBodyCarriesCode(response, codes) {
89021
+ const code = await readResponseCode(response);
89022
+ return code !== undefined && codes.includes(code);
89023
+ }
89024
+ async function readResponseCode(response) {
88227
89025
  const reader = response.body?.getReader();
88228
89026
  if (!reader)
88229
- return false;
89027
+ return;
88230
89028
  const maximum = 8 * 1024;
88231
89029
  let deadline;
88232
89030
  const expired = new Promise((_, reject) => {
@@ -88241,7 +89039,7 @@ async function responseBodyCarriesCode(response, codes) {
88241
89039
  break;
88242
89040
  size += next2.value.byteLength;
88243
89041
  if (size > maximum)
88244
- return false;
89042
+ return;
88245
89043
  chunks.push(next2.value);
88246
89044
  }
88247
89045
  const bytes = new Uint8Array(size);
@@ -88252,11 +89050,11 @@ async function responseBodyCarriesCode(response, codes) {
88252
89050
  }
88253
89051
  const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
88254
89052
  if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Object.hasOwn(payload, "code"))
88255
- return false;
89053
+ return;
88256
89054
  const code = payload.code;
88257
- return typeof code === "string" && codes.includes(code);
89055
+ return typeof code === "string" && /^[A-Z][A-Z0-9_:-]{0,127}$/.test(code) ? code : undefined;
88258
89056
  } catch {
88259
- return false;
89057
+ return;
88260
89058
  } finally {
88261
89059
  clearTimeout(deadline);
88262
89060
  reader.cancel().catch(() => {});
@@ -88327,8 +89125,8 @@ function invitationEmailIds(invitationId, challengeId) {
88327
89125
  return { invitationId, challengeId };
88328
89126
  }
88329
89127
  function invitationEmailInput(action, input2) {
88330
- const keys2 = ["invitationId", "token", "challengeId", "confirm", ...action === "accept" ? ["code"] : []];
88331
- if (!record8(input2) || Object.keys(input2).length !== keys2.length || keys2.some((key) => !Object.hasOwn(input2, key)))
89128
+ const keys3 = ["invitationId", "token", "challengeId", "confirm", ...action === "accept" ? ["code"] : []];
89129
+ if (!record8(input2) || Object.keys(input2).length !== keys3.length || keys3.some((key) => !Object.hasOwn(input2, key)))
88332
89130
  throw new InvitationEmailInputError;
88333
89131
  const value = { ...input2 };
88334
89132
  const ids = invitationEmailIds(value.invitationId, value.challengeId);
@@ -88378,7 +89176,7 @@ async function requestInvitationEmail(origin, action, input2) {
88378
89176
  }
88379
89177
 
88380
89178
  // src/lib/remote-private-publications.ts
88381
- import { createHash as createHash28 } from "crypto";
89179
+ import { createHash as createHash29 } from "crypto";
88382
89180
  var PRIVATE_PUBLICATION_MAX_BYTES = 16 * 1024 * 1024;
88383
89181
  var failures2 = {
88384
89182
  INVALID_REQUEST: [400, "The publication request is invalid."],
@@ -88421,17 +89219,17 @@ var invalid7 = () => {
88421
89219
  throw new PrivatePublicationError("INVALID_PUBLICATION_RESPONSE", "The server returned an invalid publication result.");
88422
89220
  };
88423
89221
  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 hash3 = (v2) => typeof v2 === "string" && /^[a-f0-9]{64}$/.test(v2);
89222
+ var hash4 = (v2) => typeof v2 === "string" && /^[a-f0-9]{64}$/.test(v2);
88425
89223
  var record9 = (v2) => !!v2 && typeof v2 === "object" && !Array.isArray(v2);
88426
- var exact = (v2, keys2) => record9(v2) && Object.keys(v2).sort().join(",") === keys2.sort().join(",");
89224
+ var exact = (v2, keys3) => record9(v2) && Object.keys(v2).sort().join(",") === keys3.sort().join(",");
88427
89225
  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) => createHash28("sha256").update(bytes).digest("hex");
89226
+ var publicationSha256 = (bytes) => createHash29("sha256").update(bytes).digest("hex");
88429
89227
  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 || !hash3(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES)
89228
+ 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
89229
  return bad();
88432
89230
  try {
88433
89231
  const manifest = JSON.parse(value.manifestText);
88434
- if (!record9(manifest) || manifest.version !== value.version || validatePortableManifestContract(manifest, { strict: true }).length || !hash3(manifest.provenance?.content_hash))
89232
+ if (!record9(manifest) || manifest.version !== value.version || validatePortableManifestContract(manifest, { strict: true }).length || !hash4(manifest.provenance?.content_hash))
88435
89233
  return bad();
88436
89234
  } catch {
88437
89235
  return bad();
@@ -88439,7 +89237,7 @@ function checkedPublicationDeclaration(value) {
88439
89237
  return Object.freeze({ ...value });
88440
89238
  }
88441
89239
  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)) || !hash3(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)
89240
+ 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
89241
  return invalid7();
88444
89242
  if (declaration && ["version", "expectedCurrentVersionId", "archiveSha256", "archiveByteSize"].some((k4) => value[k4] !== declaration[k4]))
88445
89243
  return invalid7();
@@ -89165,8 +89963,8 @@ function record10(value) {
89165
89963
  return !!value && typeof value === "object" && !Array.isArray(value);
89166
89964
  }
89167
89965
  function exact2(value, names) {
89168
- const keys2 = Object.keys(value).sort(), expected = [...names].sort();
89169
- return keys2.length === expected.length && keys2.every((key, index) => key === expected[index]);
89966
+ const keys3 = Object.keys(value).sort(), expected = [...names].sort();
89967
+ return keys3.length === expected.length && keys3.every((key, index) => key === expected[index]);
89170
89968
  }
89171
89969
  function snapshot(value, maximum) {
89172
89970
  let nodes4 = 0, bytes = 0;
@@ -89203,13 +90001,13 @@ function snapshot(value, maximum) {
89203
90001
  const array3 = Array.isArray(input2), proto = Object.getPrototypeOf(input2);
89204
90002
  if (array3 ? proto !== Array.prototype : proto !== Object.prototype && proto !== null)
89205
90003
  throw Error();
89206
- const keys2 = Reflect.ownKeys(input2);
89207
- if (keys2.length > SKILL_OPERATION_LIMITS.nodes + 1 || keys2.some((key) => typeof key !== "string"))
90004
+ const keys3 = Reflect.ownKeys(input2);
90005
+ if (keys3.length > SKILL_OPERATION_LIMITS.nodes + 1 || keys3.some((key) => typeof key !== "string"))
89208
90006
  throw Error();
89209
90007
  ancestors2.add(input2);
89210
90008
  try {
89211
90009
  if (array3) {
89212
- if (input2.length > SKILL_OPERATION_LIMITS.nodes || keys2.length !== input2.length + 1)
90010
+ if (input2.length > SKILL_OPERATION_LIMITS.nodes || keys3.length !== input2.length + 1)
89213
90011
  throw Error();
89214
90012
  const result2 = [];
89215
90013
  append("[");
@@ -89226,7 +90024,7 @@ function snapshot(value, maximum) {
89226
90024
  }
89227
90025
  const result = Object.create(null);
89228
90026
  append("{");
89229
- for (const [i4, key] of keys2.sort().entries()) {
90027
+ for (const [i4, key] of keys3.sort().entries()) {
89230
90028
  const descriptor = Object.getOwnPropertyDescriptor(input2, key);
89231
90029
  if (!("value" in descriptor) || !descriptor.enumerable || /[\ud800-\udfff]/u.test(key) || key.length > maximum - bytes)
89232
90030
  throw Error();
@@ -89513,6 +90311,7 @@ export {
89513
90311
  DEFAULT_OUTPUT_GOVERNANCE,
89514
90312
  DEFAULT_IMAGE_PROFILES,
89515
90313
  ContentHashInputError,
90314
+ CloudExecutionClient,
89516
90315
  CONTENT_HASH_LIMITS,
89517
90316
  CLIENT_TOKEN_BYTES,
89518
90317
  ArtifactStorage