@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/index.js +1211 -926
- package/bin/maintenance.js +7 -2
- package/bin/mcp.js +577 -360
- package/bin/migrate.js +7 -5
- package/bin/server.js +29 -10
- package/bin/worker.js +12 -7
- package/dist/index.js +826 -613
- package/dist/lib/agent-discovery.d.ts +1 -1
- package/dist/lib/claude-plugin-manifest-witness.d.ts +9 -0
- package/dist/lib/profile-client.d.ts +6 -2
- package/dist/lib/remote-files.d.ts +1 -1
- package/dist/lib/selection-cache.d.ts +5 -0
- package/dist/lib/selection-resolver.d.ts +1 -0
- package/dist/lib/session-reconciliation.d.ts +6 -3
- package/dist/sdk/index.js +796 -564
- package/dist/server/skills-api.d.ts +1 -0
- package/dist/server/types.d.ts +3 -1
- package/dist/types/skill-selection.d.ts +2 -0
- package/migrations/postgres/0011_skill_authorization_epoch.sql +22 -0
- package/migrations/sqlite/0011_skill_authorization_epoch.sql +17 -0
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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'
|