@hasna/skills 0.8.12 → 0.9.1

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