@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.
@@ -1,5 +1,6 @@
1
1
  import type { EcsDispatcherConfig } from "../sdk/execution/dispatchers/ecs.js";
2
2
  import type { FrozenAdmission, AttemptRecord } from "../sdk/execution/types.js";
3
+ import { type PureReviewedBundle } from "./runtime-pure-contract.js";
3
4
  export declare const RUNTIME_MAX_INPUT_BYTES = 100000;
4
5
  export declare const RUNTIME_MAX_BUNDLE_BYTES = 1000000;
5
6
  export declare const RUNTIME_MAX_ARTIFACT_BYTES = 2000000;
@@ -9,11 +10,12 @@ export interface RuntimeConfig extends EcsDispatcherConfig {
9
10
  imageDigest: string;
10
11
  apiOrigin: string;
11
12
  /** Only independently reviewed exact bundles enter this first credential-free lane. */
12
- reviewedBundles: {
13
+ reviewedBundles: ({
13
14
  slug: "pdf-generate";
14
15
  version: string;
15
16
  sha256: string;
16
- }[];
17
+ executionContract?: never;
18
+ } | PureReviewedBundle)[];
17
19
  }
18
20
  export declare function readRuntimeConfig(env?: Record<string, string | undefined>): RuntimeConfig | null;
19
21
  export declare function runtimeInput(value: unknown): {
@@ -0,0 +1,51 @@
1
+ /** Trusted pure interface; publishing a skill never grants admission to it. */
2
+ import { type SkillBundleEntry } from "../lib/skill-bundle.js";
3
+ import type { FrozenAdmission, PureExecutionContract } from "../sdk/execution/types.js";
4
+ export declare const PURE_DESCRIPTOR: Readonly<{
5
+ id: "regex-test.v1";
6
+ runtime: "bun";
7
+ adapter: "skills-input-json.v1";
8
+ input: Readonly<{
9
+ patternBytes: 512;
10
+ textBytes: 4096;
11
+ totalBytes: 8192;
12
+ flags: "dgimsuvy";
13
+ }>;
14
+ output: "regex-matches.v1";
15
+ secrets: "none";
16
+ egress: "deny";
17
+ maxDurationMs: 5000;
18
+ maxMemoryMb: 512;
19
+ maxCpuUnits: 256;
20
+ stdoutBytes: 16384;
21
+ stderrBytes: 8192;
22
+ artifactsBytes: 0;
23
+ }>;
24
+ export declare const PURE_DESCRIPTOR_DIGEST: string;
25
+ export declare const PURE_LIMITS: Readonly<{
26
+ maxDurationMs: 5000;
27
+ maxMemoryMb: 512;
28
+ maxCpuUnits: 256;
29
+ maxArtifactsBytes: 0;
30
+ maxConcurrency: 1;
31
+ }>;
32
+ export interface PureInput {
33
+ pattern: string;
34
+ text: string;
35
+ flags: string;
36
+ }
37
+ export interface PureReviewedBundle {
38
+ slug: string;
39
+ version: string;
40
+ sha256: string;
41
+ tenantId: string;
42
+ imageDigest: string;
43
+ executionContract: PureExecutionContract;
44
+ }
45
+ /** JSON.parse alone silently discards duplicate keys, including escaped spellings. */
46
+ export declare function uniqueJson(text: string): unknown;
47
+ export declare function pureInput(value: unknown): PureInput;
48
+ export declare function assertPureContract(value: unknown): asserts value is PureExecutionContract;
49
+ export declare function assertPureAdmission(a: FrozenAdmission): PureExecutionContract;
50
+ export declare function inspectPureBundle(bytes: Uint8Array, contract: PureExecutionContract): Promise<SkillBundleEntry[]>;
51
+ export declare function validatePureOutput(stdout: string, input: PureInput): void;
@@ -22,6 +22,10 @@ export interface RuntimeJob {
22
22
  input?: {
23
23
  content: string;
24
24
  title?: string;
25
+ } | {
26
+ pattern: string;
27
+ text: string;
28
+ flags: string;
25
29
  };
26
30
  bundleBase64?: string;
27
31
  result?: RuntimeResult;
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import type { FrozenAdmission } from "../sdk/execution/types.js";
3
+ import { type PureInput } from "./runtime-pure-contract.js";
3
4
  import type { RuntimeResult } from "./runtime-store.js";
4
5
  export interface RuntimeWork {
5
6
  admission: FrozenAdmission;
@@ -7,7 +8,7 @@ export interface RuntimeWork {
7
8
  input: {
8
9
  content: string;
9
10
  title?: string;
10
- };
11
+ } | PureInput;
11
12
  }
12
13
  export interface SupervisorOptions {
13
14
  executable?: string;
@@ -2,6 +2,7 @@ import type { Database } from "bun:sqlite";
2
2
  import type { ApiPrincipal } from "./types.js";
3
3
  import type { SkillProfile, SkillSelection, StationSkillState, StationSkillStateInput } from "../types/skill-selection.js";
4
4
  export interface SkillSelectionStore {
5
+ profilesReferencingSkill(principal: ApiPrincipal, slug: string): Promise<string[]>;
5
6
  getProfile(principal: ApiPrincipal, id: string): Promise<SkillProfile | null>;
6
7
  /** null expected revision means create-only; null result means CAS conflict. */
7
8
  saveProfile(principal: ApiPrincipal, id: string, selections: SkillSelection[], expected: string | null): Promise<SkillProfile | null>;
@@ -15,6 +16,7 @@ export declare class MemorySkillSelectionStore implements SkillSelectionStore {
15
16
  private states;
16
17
  private key;
17
18
  getProfile(p: ApiPrincipal, id: string): Promise<SkillProfile | null>;
19
+ profilesReferencingSkill(p: ApiPrincipal, slug: string): Promise<string[]>;
18
20
  saveProfile(p: ApiPrincipal, id: string, selected: SkillSelection[], expected: string | null): Promise<SkillProfile | null>;
19
21
  getStationState(p: ApiPrincipal, id: string): Promise<StationSkillState | null>;
20
22
  saveStationState(p: ApiPrincipal, id: string, input: StationSkillStateInput): Promise<StationSkillState | null>;
@@ -22,15 +24,21 @@ export declare class MemorySkillSelectionStore implements SkillSelectionStore {
22
24
  export declare class SqliteSkillSelectionStore implements SkillSelectionStore {
23
25
  private db;
24
26
  constructor(db: Database);
27
+ profilesReferencingSkill(p: ApiPrincipal, slug: string): Promise<string[]>;
25
28
  getProfile(p: ApiPrincipal, id: string): Promise<SkillProfile | null>;
26
29
  saveProfile(p: ApiPrincipal, id: string, selected: SkillSelection[], expected: string | null): Promise<SkillProfile | null>;
27
30
  getStationState(p: ApiPrincipal, id: string): Promise<StationSkillState | null>;
28
31
  saveStationState(p: ApiPrincipal, id: string, input: StationSkillStateInput): Promise<StationSkillState | null>;
29
32
  }
30
33
  type Sql = (strings: TemplateStringsArray, ...values: unknown[]) => Promise<Row[]>;
34
+ type SqlTransaction = Sql;
35
+ interface SqlWithTransaction extends Sql {
36
+ begin<T>(fn: (tx: SqlTransaction) => Promise<T>): Promise<T>;
37
+ }
31
38
  export declare class PostgresSkillSelectionStore implements SkillSelectionStore {
32
39
  private sql;
33
- constructor(sql: Sql);
40
+ constructor(sql: SqlWithTransaction);
41
+ profilesReferencingSkill(p: ApiPrincipal, slug: string): Promise<string[]>;
34
42
  getProfile(p: ApiPrincipal, id: string): Promise<SkillProfile | null>;
35
43
  saveProfile(p: ApiPrincipal, id: string, selected: SkillSelection[], expected: string | null): Promise<SkillProfile | null>;
36
44
  getStationState(p: ApiPrincipal, id: string): Promise<StationSkillState | null>;
@@ -2,7 +2,7 @@ import { type OwnedBytes } from "../lib/skill-bundle.js";
2
2
  import type { SkillMeta } from "../lib/registry-types.js";
3
3
  import type { ArtifactStorage } from "./artifact-storage.js";
4
4
  import type { SkillsServerConfig } from "./config.js";
5
- import { type ApiPrincipal, type PublishSkillInput, type ServerPin, type ServerSkillRecord, type ServerSkillVersion, type SkillsProductStore } from "./types.js";
5
+ import { type ApiPrincipal, type PublishSkillInput, type ServerPin, type ServerSkillRecord, type ServerSkillVersion, type SkillLifecyclePatch, type SkillsProductStore } from "./types.js";
6
6
  /** Wire shape of a pin: the client-facing facts, without the storage columns. */
7
7
  export declare function pinPayload(pin: ServerPin): Record<string, unknown>;
8
8
  /**
@@ -20,6 +20,7 @@ export declare class SkillRequestError extends Error {
20
20
  readonly code: string;
21
21
  constructor(status: number, code: string, message: string);
22
22
  }
23
+ export declare function skillLifecyclePatch(body: Record<string, unknown>): SkillLifecyclePatch;
23
24
  /** SkillMeta shape for a published row, so a client can treat both kinds alike. */
24
25
  export declare function publishedSkillMeta(record: ServerSkillRecord): SkillMeta;
25
26
  /**
@@ -15,7 +15,7 @@ import { SqliteExecutionGrantStore } from "./execution-grant-store.js";
15
15
  */
16
16
  import { Database } from "bun:sqlite";
17
17
  import { SqliteSkillSelectionStore } from "./selection-store.js";
18
- import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillVersion, ServerSkillRecord, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
18
+ import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillVersion, ApiKeyScopeUpdateResult, ServerSkillRecord, SkillLifecyclePatch, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
19
19
  export interface SqliteStoreOptions {
20
20
  /** Apply pending migrations on open. Default true - it is what makes zero-config work. */
21
21
  migrate?: boolean;
@@ -52,6 +52,7 @@ export declare class SqliteSkillsStore implements SkillsProductStore {
52
52
  close(): Promise<void>;
53
53
  ensureBootstrapApiKey(token: string, principal?: Partial<ApiPrincipal>): Promise<void>;
54
54
  authenticateApiKeyHash(hash: string): Promise<ApiPrincipal | null>;
55
+ updateApiKeyScopes(actor: ApiPrincipal, keyId: string, expectedScopes: string[], addScopes: string[]): Promise<ApiKeyScopeUpdateResult>;
55
56
  createRun(input: CreateRunInput): Promise<ServerRunRecord>;
56
57
  listRuns(principal: ApiPrincipal, limit: number): Promise<ServerRunRecord[]>;
57
58
  getRun(principal: ApiPrincipal, id: string): Promise<ServerRunRecord | null>;
@@ -102,6 +103,8 @@ export declare class SqliteSkillsStore implements SkillsProductStore {
102
103
  publishSkill(input: PublishSkillInput): Promise<ServerSkillRecord>;
103
104
  listSkills(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
104
105
  getSkill(principal: ApiPrincipal, slug: string): Promise<ServerSkillRecord | null>;
106
+ setSkillLifecycle(principal: ApiPrincipal, slug: string, patch: SkillLifecyclePatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
107
+ private getSkillSync;
105
108
  updateSkill(principal: ApiPrincipal, slug: string, patch: UpdateSkillPatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
106
109
  deleteSkill(principal: ApiPrincipal, slug: string, tombstoneWindowMs: number): Promise<ServerSkillRecord | null>;
107
110
  purgeExpiredTombstones(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
@@ -1,6 +1,7 @@
1
1
  import { MemoryExecutionGrantStore, PostgresExecutionGrantStore } from "./execution-grant-store.js";
2
2
  import { MemorySkillSelectionStore, PostgresSkillSelectionStore } from "./selection-store.js";
3
- import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord, ServerSkillVersion, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
3
+ import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord, ServerSkillVersion, ApiKeyScopeUpdateResult, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
4
+ import { type SkillLifecyclePatch } from "./types.js";
4
5
  import { type SqliteStoreOptions } from "./sqlite-store.js";
5
6
  export declare function createArtifactId(): string;
6
7
  export interface StoreOptions {
@@ -44,6 +45,7 @@ export declare class MemorySkillsStore implements SkillsProductStore {
44
45
  addApiKey(token: string, principal?: Partial<ApiPrincipal>): ApiPrincipal;
45
46
  ensureBootstrapApiKey(token: string, principal?: Partial<ApiPrincipal>): Promise<void>;
46
47
  authenticateApiKeyHash(hash: string): Promise<ApiPrincipal | null>;
48
+ updateApiKeyScopes(actor: ApiPrincipal, keyId: string, expectedScopes: string[], addScopes: string[]): Promise<ApiKeyScopeUpdateResult>;
47
49
  createRun(input: CreateRunInput): Promise<ServerRunRecord>;
48
50
  listRuns(principal: ApiPrincipal, limit: number): Promise<ServerRunRecord[]>;
49
51
  getRun(principal: ApiPrincipal, runId: string): Promise<ServerRunRecord | null>;
@@ -76,6 +78,7 @@ export declare class MemorySkillsStore implements SkillsProductStore {
76
78
  getSkillVersion(principal: ApiPrincipal, slug: string, version: string): Promise<ServerSkillVersion | null>;
77
79
  listSkills(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
78
80
  getSkill(principal: ApiPrincipal, slug: string): Promise<ServerSkillRecord | null>;
81
+ setSkillLifecycle(principal: ApiPrincipal, slug: string, patch: SkillLifecyclePatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
79
82
  updateSkill(principal: ApiPrincipal, slug: string, patch: UpdateSkillPatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
80
83
  deleteSkill(principal: ApiPrincipal, slug: string, tombstoneWindowMs: number): Promise<ServerSkillRecord | null>;
81
84
  purgeExpiredTombstones(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
@@ -118,6 +121,7 @@ export declare class PostgresSkillsStore implements SkillsProductStore {
118
121
  close(): Promise<void>;
119
122
  ensureBootstrapApiKey(token: string, principal?: Partial<ApiPrincipal>): Promise<void>;
120
123
  authenticateApiKeyHash(hash: string): Promise<ApiPrincipal | null>;
124
+ updateApiKeyScopes(actor: ApiPrincipal, keyId: string, expectedScopes: string[], addScopes: string[]): Promise<ApiKeyScopeUpdateResult>;
121
125
  /**
122
126
  * Run a callback under an RLS tenant or worker context, on one pooled
123
127
  * connection, for the duration of one transaction only.
@@ -163,6 +167,7 @@ export declare class PostgresSkillsStore implements SkillsProductStore {
163
167
  publishSkill(input: PublishSkillInput): Promise<ServerSkillRecord>;
164
168
  listSkills(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
165
169
  getSkill(principal: ApiPrincipal, slug: string): Promise<ServerSkillRecord | null>;
170
+ setSkillLifecycle(principal: ApiPrincipal, slug: string, patch: SkillLifecyclePatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
166
171
  updateSkill(principal: ApiPrincipal, slug: string, patch: UpdateSkillPatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
167
172
  deleteSkill(principal: ApiPrincipal, slug: string, tombstoneWindowMs: number): Promise<ServerSkillRecord | null>;
168
173
  purgeExpiredTombstones(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
@@ -45,6 +45,11 @@ export declare class SkillVersionExistsError extends Error {
45
45
  readonly attemptedSha256: string;
46
46
  constructor(slug: string, version: string, existingSha256: string, attemptedSha256: string);
47
47
  }
48
+ export declare class SkillLifecycleConflictError extends Error {
49
+ readonly slug: string;
50
+ readonly profiles: string[];
51
+ constructor(slug: string, profiles: string[]);
52
+ }
48
53
  export interface ApiPrincipal {
49
54
  apiKeyId: string;
50
55
  orgId: string;
@@ -55,6 +60,15 @@ export interface ApiPrincipal {
55
60
  role: string;
56
61
  scopes: string[];
57
62
  }
63
+ export type ApiKeyScopeUpdateResult = {
64
+ kind: "updated";
65
+ scopes: string[];
66
+ } | {
67
+ kind: "not_found";
68
+ } | {
69
+ kind: "stale";
70
+ scopes: string[];
71
+ };
58
72
  export interface ServerRunRecord {
59
73
  id: string;
60
74
  orgId: string;
@@ -162,6 +176,16 @@ export interface ServerSkillRecord {
162
176
  */
163
177
  tombstonedAt?: string;
164
178
  tombstonePurgeAfter?: string;
179
+ /** Catalog lifecycle state. Archived rows retain versions and bundles for explicit reads. */
180
+ lifecycle: "active" | "archived";
181
+ archivedAt?: string;
182
+ archiveReason?: string;
183
+ replacementSlug?: string;
184
+ }
185
+ export interface SkillLifecyclePatch {
186
+ lifecycle: "active" | "archived";
187
+ reason?: string;
188
+ replacementSlug?: string;
165
189
  }
166
190
  /**
167
191
  * Bundle bytes, addressed by their own digest.
@@ -313,6 +337,8 @@ export interface SkillsProductStore {
313
337
  */
314
338
  verifyConnectivity?(): Promise<void>;
315
339
  authenticateApiKeyHash(hash: string): Promise<ApiPrincipal | null>;
340
+ /** Guarded metadata-only scope admission for an existing key. */
341
+ updateApiKeyScopes?(actor: ApiPrincipal, keyId: string, expectedScopes: string[], addScopes: string[]): Promise<ApiKeyScopeUpdateResult>;
316
342
  ensureBootstrapApiKey?(token: string, principal?: Partial<ApiPrincipal>): Promise<void>;
317
343
  createRun(input: CreateRunInput): Promise<ServerRunRecord>;
318
344
  listRuns(principal: ApiPrincipal, limit: number): Promise<ServerRunRecord[]>;
@@ -350,6 +376,7 @@ export interface SkillsProductStore {
350
376
  * when the org has no skill by that slug, or when the row is tombstoned.
351
377
  */
352
378
  updateSkill(principal: ApiPrincipal, slug: string, patch: UpdateSkillPatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
379
+ setSkillLifecycle(principal: ApiPrincipal, slug: string, patch: SkillLifecyclePatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
353
380
  /**
354
381
  * Tombstone a skill instead of hard-deleting it (todos d061fcda): the row is stamped
355
382
  * tombstoned_at + tombstone_purge_after (now + tombstoneWindowMs) and kept, so reads
@@ -0,0 +1,5 @@
1
+ ALTER TABLE skills_registry ADD COLUMN lifecycle text NOT NULL DEFAULT 'active' CHECK (lifecycle IN ('active','archived'));
2
+ ALTER TABLE skills_registry ADD COLUMN archived_at timestamptz;
3
+ ALTER TABLE skills_registry ADD COLUMN archive_reason text;
4
+ ALTER TABLE skills_registry ADD COLUMN replacement_slug text;
5
+ CREATE INDEX IF NOT EXISTS skills_registry_org_lifecycle_idx ON skills_registry (org_id, lifecycle, slug);
@@ -0,0 +1,5 @@
1
+ ALTER TABLE skills_registry ADD COLUMN lifecycle text NOT NULL DEFAULT 'active' CHECK (lifecycle IN ('active','archived'));
2
+ ALTER TABLE skills_registry ADD COLUMN archived_at text;
3
+ ALTER TABLE skills_registry ADD COLUMN archive_reason text;
4
+ ALTER TABLE skills_registry ADD COLUMN replacement_slug text;
5
+ CREATE INDEX IF NOT EXISTS skills_registry_org_lifecycle_idx ON skills_registry (org_id, lifecycle, slug);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.8.12",
3
+ "version": "0.9.1",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {