@hasna/skills 0.1.72 → 0.2.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.
@@ -38,23 +38,7 @@ export declare const runAdmissionSchema: z.ZodObject<{
38
38
  skill: z.ZodString;
39
39
  status: z.ZodLiteral<"admitted">;
40
40
  createdAt: z.ZodString;
41
- }, "strip", z.ZodTypeAny, {
42
- status: "admitted";
43
- createdAt: string;
44
- skill: string;
45
- contractVersion: 1;
46
- runId: string;
47
- leaseGeneration: number;
48
- attemptId: string;
49
- }, {
50
- status: "admitted";
51
- createdAt: string;
52
- skill: string;
53
- contractVersion: 1;
54
- runId: string;
55
- leaseGeneration: number;
56
- attemptId: string;
57
- }>;
41
+ }, z.core.$strip>;
58
42
  export type RunAdmission = z.infer<typeof runAdmissionSchema>;
59
43
  /** Lease: a worker has claimed the run. */
60
44
  export declare const runLeaseSchema: z.ZodObject<{
@@ -64,21 +48,7 @@ export declare const runLeaseSchema: z.ZodObject<{
64
48
  leaseGeneration: z.ZodNumber;
65
49
  workerId: z.ZodString;
66
50
  status: z.ZodLiteral<"leased">;
67
- }, "strip", z.ZodTypeAny, {
68
- status: "leased";
69
- contractVersion: 1;
70
- runId: string;
71
- leaseGeneration: number;
72
- attemptId: string;
73
- workerId: string;
74
- }, {
75
- status: "leased";
76
- contractVersion: 1;
77
- runId: string;
78
- leaseGeneration: number;
79
- attemptId: string;
80
- workerId: string;
81
- }>;
51
+ }, z.core.$strip>;
82
52
  export type RunLease = z.infer<typeof runLeaseSchema>;
83
53
  /** Terminal: the run reached a final state. */
84
54
  export declare const runTerminalSchema: z.ZodObject<{
@@ -87,28 +57,17 @@ export declare const runTerminalSchema: z.ZodObject<{
87
57
  attemptId: z.ZodString;
88
58
  leaseGeneration: z.ZodNumber;
89
59
  skill: z.ZodString;
90
- status: z.ZodEnum<["succeeded", "failed", "cancelled", "expired"]>;
60
+ status: z.ZodEnum<{
61
+ cancelled: "cancelled";
62
+ failed: "failed";
63
+ succeeded: "succeeded";
64
+ expired: "expired";
65
+ }>;
91
66
  completedAt: z.ZodString;
92
- }, "strip", z.ZodTypeAny, {
93
- status: "cancelled" | "failed" | "succeeded" | "expired";
94
- skill: string;
95
- completedAt: string;
96
- contractVersion: 1;
97
- runId: string;
98
- leaseGeneration: number;
99
- attemptId: string;
100
- }, {
101
- status: "cancelled" | "failed" | "succeeded" | "expired";
102
- skill: string;
103
- completedAt: string;
104
- contractVersion: 1;
105
- runId: string;
106
- leaseGeneration: number;
107
- attemptId: string;
108
- }>;
67
+ }, z.core.$strip>;
109
68
  export type RunTerminal = z.infer<typeof runTerminalSchema>;
110
69
  /** Any single protocol message, discriminated by `status`. */
111
- export declare const runProtocolSchema: z.ZodDiscriminatedUnion<"status", [z.ZodObject<{
70
+ export declare const runProtocolSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
112
71
  contractVersion: z.ZodLiteral<1>;
113
72
  runId: z.ZodString;
114
73
  attemptId: z.ZodString;
@@ -116,68 +75,27 @@ export declare const runProtocolSchema: z.ZodDiscriminatedUnion<"status", [z.Zod
116
75
  skill: z.ZodString;
117
76
  status: z.ZodLiteral<"admitted">;
118
77
  createdAt: z.ZodString;
119
- }, "strip", z.ZodTypeAny, {
120
- status: "admitted";
121
- createdAt: string;
122
- skill: string;
123
- contractVersion: 1;
124
- runId: string;
125
- leaseGeneration: number;
126
- attemptId: string;
127
- }, {
128
- status: "admitted";
129
- createdAt: string;
130
- skill: string;
131
- contractVersion: 1;
132
- runId: string;
133
- leaseGeneration: number;
134
- attemptId: string;
135
- }>, z.ZodObject<{
78
+ }, z.core.$strip>, z.ZodObject<{
136
79
  contractVersion: z.ZodLiteral<1>;
137
80
  runId: z.ZodString;
138
81
  attemptId: z.ZodString;
139
82
  leaseGeneration: z.ZodNumber;
140
83
  workerId: z.ZodString;
141
84
  status: z.ZodLiteral<"leased">;
142
- }, "strip", z.ZodTypeAny, {
143
- status: "leased";
144
- contractVersion: 1;
145
- runId: string;
146
- leaseGeneration: number;
147
- attemptId: string;
148
- workerId: string;
149
- }, {
150
- status: "leased";
151
- contractVersion: 1;
152
- runId: string;
153
- leaseGeneration: number;
154
- attemptId: string;
155
- workerId: string;
156
- }>, z.ZodObject<{
85
+ }, z.core.$strip>, z.ZodObject<{
157
86
  contractVersion: z.ZodLiteral<1>;
158
87
  runId: z.ZodString;
159
88
  attemptId: z.ZodString;
160
89
  leaseGeneration: z.ZodNumber;
161
90
  skill: z.ZodString;
162
- status: z.ZodEnum<["succeeded", "failed", "cancelled", "expired"]>;
91
+ status: z.ZodEnum<{
92
+ cancelled: "cancelled";
93
+ failed: "failed";
94
+ succeeded: "succeeded";
95
+ expired: "expired";
96
+ }>;
163
97
  completedAt: z.ZodString;
164
- }, "strip", z.ZodTypeAny, {
165
- status: "cancelled" | "failed" | "succeeded" | "expired";
166
- skill: string;
167
- completedAt: string;
168
- contractVersion: 1;
169
- runId: string;
170
- leaseGeneration: number;
171
- attemptId: string;
172
- }, {
173
- status: "cancelled" | "failed" | "succeeded" | "expired";
174
- skill: string;
175
- completedAt: string;
176
- contractVersion: 1;
177
- runId: string;
178
- leaseGeneration: number;
179
- attemptId: string;
180
- }>]>;
98
+ }, z.core.$strip>], "status">;
181
99
  export type RunProtocolMessage = z.infer<typeof runProtocolSchema>;
182
100
  /** Map the store's status vocabulary onto the protocol lifecycle. */
183
101
  export declare function protocolStateOf(status: ServerRunStatus): RunProtocolState;
@@ -1,8 +1,11 @@
1
1
  import { type GovernanceStore } from "../sdk/governance-store.js";
2
+ import { ArtifactStorage } from "./artifact-storage.js";
2
3
  import { type SkillsServerConfig } from "./config.js";
3
4
  import { type MemorySkillsStore } from "./store.js";
4
5
  import { type SkillsProductStore } from "./types.js";
5
6
  export interface SkillsServerOptions {
7
+ /** Overrides the artifact storage (tests inject an in-memory S3 stand-in). */
8
+ artifactStorage?: ArtifactStorage;
6
9
  config?: Partial<SkillsServerConfig>;
7
10
  store?: SkillsProductStore;
8
11
  /** Lifecycle ledger and ceiling reads for governance surfaces (cancellation). Defaults to the store's database. */
@@ -1,3 +1,4 @@
1
+ import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
1
2
  import { type OwnedBytes } from "../lib/skill-bundle.js";
2
3
  import type { BlobStorageKind, ServerArtifact, ServerRunRecord, ServerSkillBundle } from "./types.js";
3
4
  export interface ArtifactBody {
@@ -5,10 +6,18 @@ export interface ArtifactBody {
5
6
  bodyText: string;
6
7
  contentType: string;
7
8
  }
9
+ /** The slice of the AWS client the storage uses; injectable so tests can stand in an in-memory bucket. */
10
+ export interface S3ClientLike {
11
+ send(command: PutObjectCommand | GetObjectCommand | DeleteObjectCommand): Promise<{
12
+ Body?: unknown;
13
+ }>;
14
+ }
8
15
  export interface ArtifactStorageOptions {
9
16
  bucket?: string;
10
17
  prefix?: string;
11
18
  region?: string;
19
+ /** Overrides the real S3 client (tests). Ignored when no bucket is configured. */
20
+ client?: S3ClientLike;
12
21
  }
13
22
  export declare class ArtifactStorage {
14
23
  private bucket?;
@@ -98,5 +107,34 @@ export declare class ArtifactStorage {
98
107
  */
99
108
  moveToQuarantine(artifact: ServerArtifact): Promise<string | null>;
100
109
  private keyFor;
110
+ /**
111
+ * Version-addressed copy of a published bundle plus its manifest (hasna/apps#1630):
112
+ * <prefix>/skills/<org>/<slug>/<version>/bundle.tar.gz
113
+ * <prefix>/skills/<org>/<slug>/<version>/manifest.json
114
+ * The content-addressed object under bundles/ stays the read path (dedupe); these keys
115
+ * are the durable, browsable history and are never deleted by orphan collection.
116
+ *
117
+ * RETENTION (hasna/apps#1671): these objects accumulate without bound by design — every
118
+ * version keeps a full copy — so the bucket lifecycle is the retention story, not the
119
+ * store. The intended rule (operated in the bucket's lifecycle config, outside this
120
+ * repository): expire `<prefix>/skills/` objects older than N days per the slot policy,
121
+ * while `<prefix>/bundles/` (content-addressed, deduped across versions and slugs) is
122
+ * NEVER expired by lifecycle — only the reference-guarded delete path removes those,
123
+ * because one object can serve many versions. A busy org that pushes thousands of
124
+ * versions should pair the lifecycle rule with a documented cap on retained versions
125
+ * per slug (e.g. the newest K, pruned by a maintenance job that deletes the version row
126
+ * and its two objects together); row deletion alone would strand the objects.
127
+ * Returns the placement recorded on the version row; in db mode nothing is written.
128
+ */
129
+ putVersionObjects(orgId: string, slug: string, version: string, bytes: OwnedBytes, manifest: Record<string, unknown>, contentType?: string): Promise<{
130
+ storageKind: BlobStorageKind;
131
+ storageKey?: string;
132
+ }>;
133
+ /** Where putVersionObjects WOULD place a version, without writing: recorded on the row before the objects exist. */
134
+ versionPlacement(orgId: string, slug: string, version: string): {
135
+ storageKind: BlobStorageKind;
136
+ storageKey?: string;
137
+ };
138
+ versionKeyFor(orgId: string, slug: string, version: string, file: string): string;
101
139
  private bundleKeyFor;
102
140
  }
@@ -3,6 +3,8 @@ export interface SkillsServerConfig {
3
3
  port: number;
4
4
  databaseUrl?: string;
5
5
  bootstrapApiKey?: string;
6
+ /** Publish the bundled corpus as versioned skills on boot (default on; set HASNA_SKILLS_SEED_BUNDLED_CORPUS=0 to skip). */
7
+ seedBundledCorpus: boolean;
6
8
  artifactBucket?: string;
7
9
  artifactPrefix: string;
8
10
  inlineWorker: boolean;
@@ -1,4 +1,4 @@
1
- import type { ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord } from "./types.js";
1
+ import type { ServerSkillVersion, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord } from "./types.js";
2
2
  export declare function nowIso(): string;
3
3
  /**
4
4
  * Coerce a caller-supplied row limit to a non-negative integer.
@@ -22,3 +22,4 @@ export declare function rowToSkillBundle(row: Record<string, unknown>): ServerSk
22
22
  export declare function parseJsonObject(value: unknown): Record<string, unknown>;
23
23
  export declare function parseJsonArray(value: unknown): string[];
24
24
  export declare function dateString(value: unknown): string;
25
+ export declare function rowToSkillVersion(row: Record<string, unknown>): ServerSkillVersion;
@@ -0,0 +1,20 @@
1
+ import type { ArtifactStorage } from "./artifact-storage.js";
2
+ import type { ApiPrincipal, SkillsProductStore } from "./types.js";
3
+ export interface SeedBundledCorpusOptions {
4
+ store: SkillsProductStore;
5
+ artifactStorage: ArtifactStorage;
6
+ principal: ApiPrincipal;
7
+ /** Version recorded for every seeded skill. Defaults to the package version. */
8
+ version?: string;
9
+ log?: (line: string) => void;
10
+ }
11
+ export interface SeedBundledCorpusResult {
12
+ version: string;
13
+ seeded: string[];
14
+ skipped: string[];
15
+ failed: Array<{
16
+ slug: string;
17
+ error: string;
18
+ }>;
19
+ }
20
+ export declare function seedBundledCorpus(options: SeedBundledCorpusOptions): Promise<SeedBundledCorpusResult>;
@@ -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 SkillsProductStore } from "./types.js";
5
+ import { type ApiPrincipal, type PublishSkillInput, type ServerPin, type ServerSkillRecord, type ServerSkillVersion, 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
  /**
@@ -22,7 +22,15 @@ export declare class SkillRequestError extends Error {
22
22
  }
23
23
  /** SkillMeta shape for a published row, so a client can treat both kinds alike. */
24
24
  export declare function publishedSkillMeta(record: ServerSkillRecord): SkillMeta;
25
- export declare function publishedPayload(record: ServerSkillRecord): Record<string, unknown>;
25
+ /**
26
+ * SkillMeta shape for a published row, plus a payload-level "alreadyPublished" flag:
27
+ * true when the accepted publish found the version already recorded with the same
28
+ * digest (an idempotent re-push), so the CLI can distinguish "published as X" from
29
+ * "already published as X" (hasna/apps#1671).
30
+ */
31
+ export declare function publishedPayload(record: ServerSkillRecord, opts?: {
32
+ alreadyPublished?: boolean;
33
+ }): Record<string, unknown>;
26
34
  /**
27
35
  * The HTTP ETag for a published row: the revision id, quoted exactly as RFC 9110 wants.
28
36
  * The quotes are load-bearing — a client that echoes the whole header value back as
@@ -143,7 +151,10 @@ interface ParsedPublish {
143
151
  * measured.
144
152
  */
145
153
  export declare function parsePublishRequest(request: Request, config: SkillsServerConfig): Promise<ParsedPublish>;
146
- export declare function storePublishedSkill(store: SkillsProductStore, artifactStorage: ArtifactStorage, principal: ApiPrincipal, parsed: ParsedPublish, expectedRevisionId?: string): Promise<ServerSkillRecord>;
154
+ export declare function storePublishedSkill(store: SkillsProductStore, artifactStorage: ArtifactStorage, principal: ApiPrincipal, parsed: ParsedPublish, expectedRevisionId?: string): Promise<{
155
+ record: ServerSkillRecord;
156
+ alreadyPublished: boolean;
157
+ }>;
147
158
  /**
148
159
  * Tombstone a published skill (todos d061fcda): the row survives with a tombstone
149
160
  * marker for `tombstoneWindowMs`, so reads answer 410 and a pulling client can
@@ -167,4 +178,21 @@ export declare function readPublishedBundle(store: SkillsProductStore, artifactS
167
178
  }>;
168
179
  export declare function assertPublishableSlug(slug: string): void;
169
180
  export declare function assertSha256(value: string): void;
181
+ /**
182
+ * Versions surface (hasna/apps#1630).
183
+ *
184
+ * GET /api/v1/skills/:slug/versions -> { slug, current, versions: [...] }
185
+ * GET /api/v1/skills/:slug/versions/:version -> one version's manifest
186
+ * GET /api/v1/skills/:slug/versions/:version/bundle -> the exact bytes of that version
187
+ *
188
+ * The registry row is the "current" pointer; a version's bytes come from the
189
+ * content-addressed bundle store so db-mode instances serve history exactly like S3 ones.
190
+ */
191
+ export declare function skillVersionPayload(version: ServerSkillVersion, currentSha?: string): Record<string, unknown>;
192
+ export declare function listSkillVersionsPayload(store: SkillsProductStore, principal: ApiPrincipal, slug: string): Promise<Record<string, unknown>>;
193
+ export declare function readSkillVersion(store: SkillsProductStore, principal: ApiPrincipal, slug: string, version: string): Promise<ServerSkillVersion>;
194
+ export declare function readSkillVersionBundle(store: SkillsProductStore, artifactStorage: ArtifactStorage, principal: ApiPrincipal, slug: string, version: string): Promise<{
195
+ version: ServerSkillVersion;
196
+ bytes: OwnedBytes;
197
+ }>;
170
198
  export {};
@@ -13,7 +13,7 @@
13
13
  * requirement; changing Postgres behaviour is not this module's job.
14
14
  */
15
15
  import { Database } from "bun:sqlite";
16
- import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
16
+ import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillVersion, ServerSkillRecord, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
17
17
  export interface SqliteStoreOptions {
18
18
  /** Apply pending migrations on open. Default true - it is what makes zero-config work. */
19
19
  migrate?: boolean;
@@ -102,6 +102,8 @@ export declare class SqliteSkillsStore implements SkillsProductStore {
102
102
  deleteSkill(principal: ApiPrincipal, slug: string, tombstoneWindowMs: number): Promise<ServerSkillRecord | null>;
103
103
  purgeExpiredTombstones(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
104
104
  getSkillBundle(principal: ApiPrincipal, sha256: string): Promise<ServerSkillBundle | null>;
105
+ listSkillVersions(principal: ApiPrincipal, slug: string): Promise<ServerSkillVersion[]>;
106
+ getSkillVersion(principal: ApiPrincipal, slug: string, version: string): Promise<ServerSkillVersion | null>;
105
107
  pinSkill(principal: ApiPrincipal, slug: string, metadata?: Record<string, unknown>): Promise<ServerPin>;
106
108
  unpinSkill(principal: ApiPrincipal, slug: string): Promise<boolean>;
107
109
  listPins(principal: ApiPrincipal): Promise<ServerPin[]>;
@@ -1,4 +1,4 @@
1
- import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
1
+ import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord, ServerSkillVersion, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
2
2
  import { type SqliteStoreOptions } from "./sqlite-store.js";
3
3
  export declare function createArtifactId(): string;
4
4
  export interface StoreOptions {
@@ -31,6 +31,7 @@ export declare class MemorySkillsStore implements SkillsProductStore {
31
31
  private idempotency;
32
32
  private skills;
33
33
  private bundles;
34
+ private versions;
34
35
  private pins;
35
36
  constructor(apiKeys?: Array<{
36
37
  token: string;
@@ -67,6 +68,8 @@ export declare class MemorySkillsStore implements SkillsProductStore {
67
68
  listArtifacts(principal: ApiPrincipal, runId: string): Promise<ServerArtifact[]>;
68
69
  getArtifact(principal: ApiPrincipal, runId: string, id: string): Promise<ServerArtifact | null>;
69
70
  publishSkill(input: PublishSkillInput): Promise<ServerSkillRecord>;
71
+ listSkillVersions(principal: ApiPrincipal, slug: string): Promise<ServerSkillVersion[]>;
72
+ getSkillVersion(principal: ApiPrincipal, slug: string, version: string): Promise<ServerSkillVersion | null>;
70
73
  listSkills(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
71
74
  getSkill(principal: ApiPrincipal, slug: string): Promise<ServerSkillRecord | null>;
72
75
  updateSkill(principal: ApiPrincipal, slug: string, patch: UpdateSkillPatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
@@ -158,6 +161,8 @@ export declare class PostgresSkillsStore implements SkillsProductStore {
158
161
  deleteSkill(principal: ApiPrincipal, slug: string, tombstoneWindowMs: number): Promise<ServerSkillRecord | null>;
159
162
  purgeExpiredTombstones(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
160
163
  getSkillBundle(principal: ApiPrincipal, sha256: string): Promise<ServerSkillBundle | null>;
164
+ listSkillVersions(principal: ApiPrincipal, slug: string): Promise<ServerSkillVersion[]>;
165
+ getSkillVersion(principal: ApiPrincipal, slug: string, version: string): Promise<ServerSkillVersion | null>;
161
166
  pinSkill(principal: ApiPrincipal, slug: string, metadata?: Record<string, unknown>): Promise<ServerPin>;
162
167
  unpinSkill(principal: ApiPrincipal, slug: string): Promise<boolean>;
163
168
  listPins(principal: ApiPrincipal): Promise<ServerPin[]>;
@@ -31,6 +31,18 @@ export declare class SkillRevisionConflictError extends Error {
31
31
  readonly currentRevisionId: string | null;
32
32
  constructor(slug: string, expectedRevisionId: string | null | undefined, currentRevisionId: string | null);
33
33
  }
34
+ /**
35
+ * A publish was refused because (org, slug, version) already exists with a DIFFERENT bundle
36
+ * digest. Versions are immutable (hasna/apps#1630): the same digest is idempotent, a new
37
+ * digest needs a new version. Carries the stored digest so the client can say which.
38
+ */
39
+ export declare class SkillVersionExistsError extends Error {
40
+ readonly slug: string;
41
+ readonly version: string;
42
+ readonly existingSha256: string;
43
+ readonly attemptedSha256: string;
44
+ constructor(slug: string, version: string, existingSha256: string, attemptedSha256: string);
45
+ }
34
46
  export interface ApiPrincipal {
35
47
  apiKeyId: string;
36
48
  orgId: string;
@@ -182,6 +194,24 @@ export interface ServerSkillBundle {
182
194
  bytes?: OwnedBytes;
183
195
  createdAt: string;
184
196
  }
197
+ /**
198
+ * One immutable published version of a skill (hasna/apps#1630). The registry row is the
199
+ * mutable "current" pointer; these rows are the history: each (org, slug, version) exactly once,
200
+ * pointing at a content-addressed bundle that orphan collection must keep alive.
201
+ */
202
+ export interface ServerSkillVersion {
203
+ orgId: string;
204
+ slug: string;
205
+ version: string;
206
+ bundleSha256: string;
207
+ bundleByteSize: number;
208
+ storageKind: BlobStorageKind;
209
+ storageKey?: string;
210
+ /** Files (path -> sha256), byte counts and provenance the publisher sent; never secret. */
211
+ manifest: Record<string, unknown>;
212
+ publishedByUserId?: string;
213
+ createdAt: string;
214
+ }
185
215
  export interface PublishSkillInput {
186
216
  principal: ApiPrincipal;
187
217
  slug: string;
@@ -195,6 +225,16 @@ export interface PublishSkillInput {
195
225
  skillMd?: string;
196
226
  /** Omitted for a metadata-only publish or update. */
197
227
  bundle?: Omit<ServerSkillBundle, "orgId" | "createdAt">;
228
+ /**
229
+ * Publisher-supplied version manifest (file digests, provenance). Stored verbatim on the
230
+ * version row when `version` is set; ignored otherwise.
231
+ */
232
+ versionManifest?: Record<string, unknown>;
233
+ /** Version-addressed object key the API wrote (S3 mode); recorded on the version row. */
234
+ versionStorage?: {
235
+ storageKind: BlobStorageKind;
236
+ storageKey?: string;
237
+ };
198
238
  /**
199
239
  * Optimistic-concurrency guard (todos d061fcda): the revision_id (ETag) the writer
200
240
  * read. A publish against an existing, LIVE row requires the guard to name that row's
@@ -320,6 +360,9 @@ export interface SkillsProductStore {
320
360
  */
321
361
  purgeExpiredTombstones(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
322
362
  getSkillBundle(principal: ApiPrincipal, sha256: string): Promise<ServerSkillBundle | null>;
363
+ /** Every published version of a slug, newest first (hasna/apps#1630). */
364
+ listSkillVersions(principal: ApiPrincipal, slug: string): Promise<ServerSkillVersion[]>;
365
+ getSkillVersion(principal: ApiPrincipal, slug: string, version: string): Promise<ServerSkillVersion | null>;
323
366
  pinSkill(principal: ApiPrincipal, slug: string, metadata?: Record<string, unknown>): Promise<ServerPin>;
324
367
  /** False when this principal has no pin by that slug. */
325
368
  unpinSkill(principal: ApiPrincipal, slug: string): Promise<boolean>;