@hasna/skills 0.10.46 → 0.10.47

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/bin/migrate.js CHANGED
@@ -7,7 +7,7 @@ import { join as join5 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@hasna/skills",
10
- version: "0.10.46",
10
+ version: "0.10.47",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
@@ -976,6 +976,7 @@ function rowToSkill(row) {
976
976
  revisionNumber: Number(row.revision_number ?? 0),
977
977
  ...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {},
978
978
  ...row.tombstone_purge_after ? { tombstonePurgeAfter: dateString(row.tombstone_purge_after) } : {},
979
+ ...typeof row.authorization_epoch === "string" ? { authorizationEpoch: row.authorization_epoch } : {},
979
980
  lifecycle: row.lifecycle === "archived" ? "archived" : "active",
980
981
  ...row.archived_at ? { archivedAt: dateString(row.archived_at) } : {},
981
982
  ...row.archive_reason ? { archiveReason: String(row.archive_reason) } : {},
@@ -987,6 +988,7 @@ function rowToPublishedSelectionState(row) {
987
988
  slug: String(row.slug),
988
989
  version: String(row.version),
989
990
  current: row.skill_exists ? {
991
+ ...typeof row.authorization_epoch === "string" ? { authorizationEpoch: row.authorization_epoch } : {},
990
992
  lifecycle: row.lifecycle === "archived" ? "archived" : "active",
991
993
  ...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {}
992
994
  } : null,
@@ -1636,7 +1638,7 @@ class SqliteSkillsStore {
1636
1638
  continue;
1637
1639
  insertTag.run(orgId, input.slug, tag);
1638
1640
  }
1639
- return rowToSkill(row);
1641
+ return this.getSkillSync(input.principal, input.slug);
1640
1642
  })();
1641
1643
  }
1642
1644
  async listSkills(principal) {
@@ -1673,7 +1675,7 @@ class SqliteSkillsStore {
1673
1675
  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]);
1674
1676
  if (!row)
1675
1677
  throw new SkillRevisionConflictError(slug, expectedRevisionId, this.getSkillSync(principal, slug)?.revisionId ?? null);
1676
- return rowToSkill(row);
1678
+ return this.getSkillSync(principal, slug);
1677
1679
  }).immediate();
1678
1680
  }
1679
1681
  getSkillSync(principal, slug) {
@@ -1744,7 +1746,7 @@ class SqliteSkillsStore {
1744
1746
  SET tombstoned_at = ?, tombstone_purge_after = ?, updated_at = ?
1745
1747
  WHERE org_id = ? AND slug = ?
1746
1748
  RETURNING *`, [tombstonedAt, purgeAfter, tombstonedAt, principal.orgId, slug]);
1747
- return rowToSkill(row);
1749
+ return this.getSkillSync(principal, slug);
1748
1750
  })();
1749
1751
  }
1750
1752
  async purgeExpiredTombstones(principal) {
@@ -1781,7 +1783,7 @@ class SqliteSkillsStore {
1781
1783
  return [];
1782
1784
  const requested = JSON.stringify(selections2.map(({ slug, version }) => ({ slug, version })));
1783
1785
  return this.all(`SELECT json_extract(q.value, '$.slug') AS slug, json_extract(q.value, '$.version') AS version,
1784
- r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
1786
+ r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at, r.authorization_epoch,
1785
1787
  v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
1786
1788
  FROM json_each(?) q
1787
1789
  LEFT JOIN skills_registry r ON r.org_id = ? AND r.slug = json_extract(q.value, '$.slug')
package/bin/server.js CHANGED
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
24122
24122
  // package.json
24123
24123
  var package_default = {
24124
24124
  name: "@hasna/skills",
24125
- version: "0.10.46",
24125
+ version: "0.10.47",
24126
24126
  description: "Skills library for AI coding agents",
24127
24127
  type: "module",
24128
24128
  bin: {
@@ -35189,6 +35189,7 @@ function rowToSkill(row) {
35189
35189
  revisionNumber: Number(row.revision_number ?? 0),
35190
35190
  ...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {},
35191
35191
  ...row.tombstone_purge_after ? { tombstonePurgeAfter: dateString(row.tombstone_purge_after) } : {},
35192
+ ...typeof row.authorization_epoch === "string" ? { authorizationEpoch: row.authorization_epoch } : {},
35192
35193
  lifecycle: row.lifecycle === "archived" ? "archived" : "active",
35193
35194
  ...row.archived_at ? { archivedAt: dateString(row.archived_at) } : {},
35194
35195
  ...row.archive_reason ? { archiveReason: String(row.archive_reason) } : {},
@@ -35200,6 +35201,7 @@ function rowToPublishedSelectionState(row) {
35200
35201
  slug: String(row.slug),
35201
35202
  version: String(row.version),
35202
35203
  current: row.skill_exists ? {
35204
+ ...typeof row.authorization_epoch === "string" ? { authorizationEpoch: row.authorization_epoch } : {},
35203
35205
  lifecycle: row.lifecycle === "archived" ? "archived" : "active",
35204
35206
  ...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {}
35205
35207
  } : null,
@@ -35862,7 +35864,7 @@ class SqliteSkillsStore {
35862
35864
  continue;
35863
35865
  insertTag.run(orgId, input.slug, tag);
35864
35866
  }
35865
- return rowToSkill(row);
35867
+ return this.getSkillSync(input.principal, input.slug);
35866
35868
  })();
35867
35869
  }
35868
35870
  async listSkills(principal) {
@@ -35899,7 +35901,7 @@ class SqliteSkillsStore {
35899
35901
  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]);
35900
35902
  if (!row)
35901
35903
  throw new SkillRevisionConflictError(slug, expectedRevisionId, this.getSkillSync(principal, slug)?.revisionId ?? null);
35902
- return rowToSkill(row);
35904
+ return this.getSkillSync(principal, slug);
35903
35905
  }).immediate();
35904
35906
  }
35905
35907
  getSkillSync(principal, slug) {
@@ -35970,7 +35972,7 @@ class SqliteSkillsStore {
35970
35972
  SET tombstoned_at = ?, tombstone_purge_after = ?, updated_at = ?
35971
35973
  WHERE org_id = ? AND slug = ?
35972
35974
  RETURNING *`, [tombstonedAt, purgeAfter, tombstonedAt, principal.orgId, slug]);
35973
- return rowToSkill(row);
35975
+ return this.getSkillSync(principal, slug);
35974
35976
  })();
35975
35977
  }
35976
35978
  async purgeExpiredTombstones(principal) {
@@ -36007,7 +36009,7 @@ class SqliteSkillsStore {
36007
36009
  return [];
36008
36010
  const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
36009
36011
  return this.all(`SELECT json_extract(q.value, '$.slug') AS slug, json_extract(q.value, '$.version') AS version,
36010
- r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
36012
+ r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at, r.authorization_epoch,
36011
36013
  v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
36012
36014
  FROM json_each(?) q
36013
36015
  LEFT JOIN skills_registry r ON r.org_id = ? AND r.slug = json_extract(q.value, '$.slug')
@@ -37409,7 +37411,7 @@ async function readSkillVersionBundle(store, artifactStorage, principal, slug, v
37409
37411
  if (actual !== found.bundleSha256) {
37410
37412
  throw new SkillRequestError(500, "BUNDLE_DIGEST_DRIFT", `stored bundle for '${slug}@${version2}' hashes to ${actual} but was published as ${found.bundleSha256}`);
37411
37413
  }
37412
- return { version: found, bytes };
37414
+ return { version: found, bytes, authorizationEpoch: resolved.record.authorizationEpoch };
37413
37415
  }
37414
37416
  function publishVersionOf(manifest) {
37415
37417
  const version2 = optionalString(manifest.version);
@@ -37649,6 +37651,7 @@ async function validatePublished(store, principal, selections2) {
37649
37651
  if (!state2.bundleAvailable)
37650
37652
  throw new SkillRequestError(503, "SELECTION_BUNDLE_UNAVAILABLE", "Selected bundle is unavailable");
37651
37653
  }
37654
+ return states;
37652
37655
  }
37653
37656
  async function body2(request, limit) {
37654
37657
  if (!request.body)
@@ -37709,6 +37712,7 @@ async function handleProfileApi(store, principal, request, parts, config) {
37709
37712
  capabilities: [
37710
37713
  "skills.registry",
37711
37714
  "skills.versions",
37715
+ "skills.session-pin-renewal",
37712
37716
  ...store.selectionStore && store.executionGrantStore ? ["skills.execution-grants"] : [],
37713
37717
  ...store.selectionStore ? ["skills.profiles", "skills.station-state"] : []
37714
37718
  ],
@@ -37754,6 +37758,7 @@ async function handleProfileApi(store, principal, request, parts, config) {
37754
37758
  const authority2 = skillsApiRequestUrl(normalizeSkillsApiOrigin(config.publicBaseUrl), "/api/v1/").replace(/\/+$/, "");
37755
37759
  const revision = "x".repeat(128);
37756
37760
  const projected = resolvedProfileSnapshot({ id, workspaceId: principal.orgId, revision, selections: selections2 }, authority2);
37761
+ projected.selections = projected.selections.map((selection2) => ({ ...selection2, authorizationEpoch: "0".repeat(32) }));
37757
37762
  if (profileDocumentBytes(projected) > MAX_RESOLVED_PROFILE_BYTES || profileDocumentBytes({ profileId: id, profileRevision: revision, selections: selections2 }) > Math.min(config.requestBodyLimitBytes, MAX_PROFILE_DOCUMENT_BYTES)) {
37758
37763
  throw new SkillRequestError(413, "PROFILE_TOO_LARGE", "The complete resolved profile and station/session receipts must fit the advertised profile limits");
37759
37764
  }
@@ -37775,7 +37780,17 @@ async function handleProfileApi(store, principal, request, parts, config) {
37775
37780
  const result = resolvedProfileSnapshot(profile2, authority2);
37776
37781
  if (result.selections.length > MAX_PROFILE_SELECTIONS || profileDocumentBytes(result) > MAX_RESOLVED_PROFILE_BYTES)
37777
37782
  throw new SkillRequestError(413, "PROFILE_TOO_LARGE", "Resolved profile exceeds the advertised profile limits");
37778
- await validatePublished(store, principal, profile2.selections);
37783
+ const states = await validatePublished(store, principal, profile2.selections);
37784
+ if (new URL(request.url).searchParams.get("pinAuthorization") === "epoch-v1") {
37785
+ result.selections = result.selections.map((selection2, index) => {
37786
+ const epoch = states[index].current?.authorizationEpoch;
37787
+ if (!epoch || !/^[a-f0-9]{32}$/.test(epoch))
37788
+ throw new SkillRequestError(503, "PIN_AUTHORIZATION_UNAVAILABLE", "The selection lifecycle fence is unavailable");
37789
+ return { ...selection2, authorizationEpoch: epoch };
37790
+ });
37791
+ }
37792
+ if (profileDocumentBytes(result) > MAX_RESOLVED_PROFILE_BYTES)
37793
+ throw new SkillRequestError(413, "PROFILE_TOO_LARGE", "Resolved profile exceeds the advertised profile limits");
37779
37794
  return json2(result, 200, profile2.revision);
37780
37795
  }
37781
37796
  if (resource === "stations" && parts.length === 3 && child === "state") {
@@ -43171,6 +43186,7 @@ class MemorySkillsStore {
43171
43186
  updatedAt: now,
43172
43187
  revisionId: revisionIdOfRecord(recordFieldsOf(input, carriedBundle, carriedSkillMd)),
43173
43188
  revisionNumber: (previous?.revisionNumber ?? 0) + 1,
43189
+ authorizationEpoch: previous?.authorizationEpoch ?? randomUUID6().replaceAll("-", ""),
43174
43190
  lifecycle: previous?.lifecycle ?? "active",
43175
43191
  ...previous?.archivedAt ? { archivedAt: previous.archivedAt } : {},
43176
43192
  ...previous?.archiveReason ? { archiveReason: previous.archiveReason } : {},
@@ -43214,6 +43230,7 @@ class MemorySkillsStore {
43214
43230
  slug,
43215
43231
  version: version2,
43216
43232
  current: current?.orgId === principal.orgId ? {
43233
+ authorizationEpoch: current.authorizationEpoch,
43217
43234
  lifecycle: current.lifecycle,
43218
43235
  ...current.tombstonedAt ? { tombstonedAt: current.tombstonedAt } : {}
43219
43236
  } : null,
@@ -43242,6 +43259,7 @@ class MemorySkillsStore {
43242
43259
  }
43243
43260
  const next2 = {
43244
43261
  ...current,
43262
+ authorizationEpoch: current.lifecycle === patch.lifecycle ? current.authorizationEpoch : randomUUID6().replaceAll("-", ""),
43245
43263
  lifecycle: patch.lifecycle,
43246
43264
  updatedAt: nowIso(),
43247
43265
  revisionId: revisionIdOfRecord({ ...current, lifecycle: patch.lifecycle, archiveReason: patch.reason, replacementSlug: patch.replacementSlug }),
@@ -43286,7 +43304,7 @@ class MemorySkillsStore {
43286
43304
  if (!current.tombstonedAt) {
43287
43305
  const tombstoned = nowIso();
43288
43306
  const purgeAfter = new Date(Date.now() + tombstoneWindowMs).toISOString();
43289
- const next2 = { ...current, tombstonedAt: tombstoned, tombstonePurgeAfter: purgeAfter, updatedAt: tombstoned };
43307
+ const next2 = { ...current, authorizationEpoch: randomUUID6().replaceAll("-", ""), tombstonedAt: tombstoned, tombstonePurgeAfter: purgeAfter, updatedAt: tombstoned };
43290
43308
  this.skills.set(skillKey(principal.orgId, slug), next2);
43291
43309
  return next2;
43292
43310
  }
@@ -44040,7 +44058,7 @@ class PostgresSkillsStore {
44040
44058
  const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
44041
44059
  const rows = await this.sql`
44042
44060
  SELECT q.value->>'slug' AS slug, q.value->>'version' AS version,
44043
- r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
44061
+ r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at, r.authorization_epoch,
44044
44062
  v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
44045
44063
  FROM jsonb_array_elements(${requested}::text::jsonb) WITH ORDINALITY AS q(value, ordinal)
44046
44064
  LEFT JOIN skills_registry r ON r.org_id = ${principal.orgId} AND r.slug = q.value->>'slug'
@@ -44336,13 +44354,14 @@ async function handleApiV1(store, governanceStore, principal, request, parts, co
44336
44354
  return json4(skillVersionPayload(version2, current));
44337
44355
  }
44338
44356
  if (request.method === "GET" && id && subresource === "versions" && childId && parts[4] === "bundle" && !parts[5]) {
44339
- const { version: version2, bytes } = await readSkillVersionBundle(store, artifactStorage, principal, id, childId);
44357
+ const { version: version2, bytes, authorizationEpoch } = await readSkillVersionBundle(store, artifactStorage, principal, id, childId);
44340
44358
  const headers = {
44341
44359
  "Content-Type": "application/gzip",
44342
44360
  "Content-Length": String(bytes.byteLength),
44343
44361
  "Content-Disposition": `attachment; filename="${version2.slug}-${version2.version}.tar.gz"`,
44344
44362
  "X-Skill-Bundle-Sha256": version2.bundleSha256,
44345
44363
  "X-Skill-Version": version2.version,
44364
+ ...authorizationEpoch ? { "X-Skill-Authorization-Epoch": authorizationEpoch } : {},
44346
44365
  ETag: `"${version2.bundleSha256}"`,
44347
44366
  "Cache-Control": "no-store"
44348
44367
  };
package/bin/worker.js CHANGED
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
24125
24125
  // package.json
24126
24126
  var package_default = {
24127
24127
  name: "@hasna/skills",
24128
- version: "0.10.46",
24128
+ version: "0.10.47",
24129
24129
  description: "Skills library for AI coding agents",
24130
24130
  type: "module",
24131
24131
  bin: {
@@ -34937,6 +34937,7 @@ function rowToSkill(row) {
34937
34937
  revisionNumber: Number(row.revision_number ?? 0),
34938
34938
  ...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {},
34939
34939
  ...row.tombstone_purge_after ? { tombstonePurgeAfter: dateString(row.tombstone_purge_after) } : {},
34940
+ ...typeof row.authorization_epoch === "string" ? { authorizationEpoch: row.authorization_epoch } : {},
34940
34941
  lifecycle: row.lifecycle === "archived" ? "archived" : "active",
34941
34942
  ...row.archived_at ? { archivedAt: dateString(row.archived_at) } : {},
34942
34943
  ...row.archive_reason ? { archiveReason: String(row.archive_reason) } : {},
@@ -34948,6 +34949,7 @@ function rowToPublishedSelectionState(row) {
34948
34949
  slug: String(row.slug),
34949
34950
  version: String(row.version),
34950
34951
  current: row.skill_exists ? {
34952
+ ...typeof row.authorization_epoch === "string" ? { authorizationEpoch: row.authorization_epoch } : {},
34951
34953
  lifecycle: row.lifecycle === "archived" ? "archived" : "active",
34952
34954
  ...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {}
34953
34955
  } : null,
@@ -35597,7 +35599,7 @@ class SqliteSkillsStore {
35597
35599
  continue;
35598
35600
  insertTag.run(orgId, input.slug, tag);
35599
35601
  }
35600
- return rowToSkill(row);
35602
+ return this.getSkillSync(input.principal, input.slug);
35601
35603
  })();
35602
35604
  }
35603
35605
  async listSkills(principal) {
@@ -35634,7 +35636,7 @@ class SqliteSkillsStore {
35634
35636
  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]);
35635
35637
  if (!row)
35636
35638
  throw new SkillRevisionConflictError(slug, expectedRevisionId, this.getSkillSync(principal, slug)?.revisionId ?? null);
35637
- return rowToSkill(row);
35639
+ return this.getSkillSync(principal, slug);
35638
35640
  }).immediate();
35639
35641
  }
35640
35642
  getSkillSync(principal, slug) {
@@ -35705,7 +35707,7 @@ class SqliteSkillsStore {
35705
35707
  SET tombstoned_at = ?, tombstone_purge_after = ?, updated_at = ?
35706
35708
  WHERE org_id = ? AND slug = ?
35707
35709
  RETURNING *`, [tombstonedAt, purgeAfter, tombstonedAt, principal.orgId, slug]);
35708
- return rowToSkill(row);
35710
+ return this.getSkillSync(principal, slug);
35709
35711
  })();
35710
35712
  }
35711
35713
  async purgeExpiredTombstones(principal) {
@@ -35742,7 +35744,7 @@ class SqliteSkillsStore {
35742
35744
  return [];
35743
35745
  const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
35744
35746
  return this.all(`SELECT json_extract(q.value, '$.slug') AS slug, json_extract(q.value, '$.version') AS version,
35745
- r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
35747
+ r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at, r.authorization_epoch,
35746
35748
  v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
35747
35749
  FROM json_each(?) q
35748
35750
  LEFT JOIN skills_registry r ON r.org_id = ? AND r.slug = json_extract(q.value, '$.slug')
@@ -36700,6 +36702,7 @@ class MemorySkillsStore {
36700
36702
  updatedAt: now,
36701
36703
  revisionId: revisionIdOfRecord(recordFieldsOf(input, carriedBundle, carriedSkillMd)),
36702
36704
  revisionNumber: (previous?.revisionNumber ?? 0) + 1,
36705
+ authorizationEpoch: previous?.authorizationEpoch ?? randomUUID5().replaceAll("-", ""),
36703
36706
  lifecycle: previous?.lifecycle ?? "active",
36704
36707
  ...previous?.archivedAt ? { archivedAt: previous.archivedAt } : {},
36705
36708
  ...previous?.archiveReason ? { archiveReason: previous.archiveReason } : {},
@@ -36743,6 +36746,7 @@ class MemorySkillsStore {
36743
36746
  slug,
36744
36747
  version: version2,
36745
36748
  current: current?.orgId === principal.orgId ? {
36749
+ authorizationEpoch: current.authorizationEpoch,
36746
36750
  lifecycle: current.lifecycle,
36747
36751
  ...current.tombstonedAt ? { tombstonedAt: current.tombstonedAt } : {}
36748
36752
  } : null,
@@ -36771,6 +36775,7 @@ class MemorySkillsStore {
36771
36775
  }
36772
36776
  const next2 = {
36773
36777
  ...current,
36778
+ authorizationEpoch: current.lifecycle === patch.lifecycle ? current.authorizationEpoch : randomUUID5().replaceAll("-", ""),
36774
36779
  lifecycle: patch.lifecycle,
36775
36780
  updatedAt: nowIso(),
36776
36781
  revisionId: revisionIdOfRecord({ ...current, lifecycle: patch.lifecycle, archiveReason: patch.reason, replacementSlug: patch.replacementSlug }),
@@ -36815,7 +36820,7 @@ class MemorySkillsStore {
36815
36820
  if (!current.tombstonedAt) {
36816
36821
  const tombstoned = nowIso();
36817
36822
  const purgeAfter = new Date(Date.now() + tombstoneWindowMs).toISOString();
36818
- const next2 = { ...current, tombstonedAt: tombstoned, tombstonePurgeAfter: purgeAfter, updatedAt: tombstoned };
36823
+ const next2 = { ...current, authorizationEpoch: randomUUID5().replaceAll("-", ""), tombstonedAt: tombstoned, tombstonePurgeAfter: purgeAfter, updatedAt: tombstoned };
36819
36824
  this.skills.set(skillKey(principal.orgId, slug), next2);
36820
36825
  return next2;
36821
36826
  }
@@ -37569,7 +37574,7 @@ class PostgresSkillsStore {
37569
37574
  const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
37570
37575
  const rows = await this.sql`
37571
37576
  SELECT q.value->>'slug' AS slug, q.value->>'version' AS version,
37572
- r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
37577
+ r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at, r.authorization_epoch,
37573
37578
  v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
37574
37579
  FROM jsonb_array_elements(${requested}::text::jsonb) WITH ORDINALITY AS q(value, ordinal)
37575
37580
  LEFT JOIN skills_registry r ON r.org_id = ${principal.orgId} AND r.slug = q.value->>'slug'