@skillerr/core 0.6.0

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/mint.js ADDED
@@ -0,0 +1,467 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { detectAgentRuntimeMarkers, hasAgentRuntimeEvidence, isValidAgentHost, } from "@skillerr/protocol";
3
+ import { canonicalize, PUBLIC_DEV_MINT_KEY, PUBLIC_DEV_MINT_KEY_ID, sealedManifestDigest, sha256Digest, } from "./hash.js";
4
+ import { packSkill, unpackSkill } from "./pack.js";
5
+ import { validatePackageBytes } from "./validate.js";
6
+ function loadCoreIdentity() {
7
+ const metadata = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
8
+ if (typeof metadata.name !== "string" || typeof metadata.version !== "string") {
9
+ throw new Error("Invalid @skillerr/core package metadata");
10
+ }
11
+ return { name: metadata.name, version: metadata.version };
12
+ }
13
+ const CORE_IDENTITY = loadCoreIdentity();
14
+ function resolveIssuerClass(secret, keyId) {
15
+ if (secret === PUBLIC_DEV_MINT_KEY || keyId === PUBLIC_DEV_MINT_KEY_ID) {
16
+ return "public_dev_hmac";
17
+ }
18
+ return "configured_hmac";
19
+ }
20
+ function resolveHostClaimBinding(opts, issuerClass, markers) {
21
+ if (opts.host_claim_binding === "verified_issuer") {
22
+ if (issuerClass === "public_dev_hmac") {
23
+ throw new Error("Cannot mark host_claim_binding=verified_issuer with the public development HMAC key");
24
+ }
25
+ if (!hasAgentRuntimeEvidence(opts.agent_runtime_evidence, opts.env) && markers.length === 0) {
26
+ throw new Error("verified_issuer host binding requires agent runtime evidence (markers or session_id)");
27
+ }
28
+ return "verified_issuer";
29
+ }
30
+ return "self_reported";
31
+ }
32
+ /**
33
+ * Seal a draft package as minted.
34
+ * Content under signatures/ may change; package_digest (content) stays fixed after finalize.
35
+ *
36
+ * Trust rules:
37
+ * - Forbidden hosts (human/cli/shell/manual/…) refuse mint entirely.
38
+ * - Env-only SKILL_HOST without agent runtime markers still mints, but as self_reported
39
+ * + public_dev_hmac — never production trust.
40
+ * - Seal binds sealed_manifest_digest (identity + policy + content claims).
41
+ */
42
+ export function mintSkillPackage(pkg, opts) {
43
+ if (!isValidAgentHost(opts.host)) {
44
+ throw new Error(`Mint host "${opts.host}" is not a valid AI agent host (denylisted human/cli/shell/manual). ` +
45
+ `Use an agent host id such as cursor, ollama, lmstudio, llama-cpp, custom-agent.`);
46
+ }
47
+ if (pkg.manifest.needs_human_review) {
48
+ throw new Error("Cannot mint while needs_human_review is true — approve inputs/permissions first");
49
+ }
50
+ if (pkg.manifest.compile_profile !== "release") {
51
+ throw new Error("Cannot mint: compile_profile must be release. Complete the journey and release compile first.");
52
+ }
53
+ if (!pkg.manifest.completeness?.complete) {
54
+ throw new Error(`Cannot mint incomplete skill. Missing: ${pkg.manifest.completeness?.missing.join(", ") || "completeness report"}`);
55
+ }
56
+ const report = pkg.provenance?.compilation_report;
57
+ if (!report ||
58
+ report.profile !== "release" ||
59
+ report.semantic_contract !== "native_0.5" ||
60
+ !report.completeness.complete ||
61
+ !report.approved ||
62
+ report.pending_approvals.length > 0) {
63
+ throw new Error("Cannot mint: approved release compilation report required");
64
+ }
65
+ const pending = pkg.manifest.inputs.filter((i) => i.required && i.approved !== true);
66
+ if (pending.length) {
67
+ throw new Error(`Cannot mint with unapproved inputs: ${pending.map((p) => p.name).join(", ")}`);
68
+ }
69
+ const draftBytes = packSkill({
70
+ ...pkg,
71
+ signatures: undefined,
72
+ attestation: undefined,
73
+ anchors: pkg.anchors ?? pkg.manifest.anchors,
74
+ });
75
+ const unpacked = unpackSkill(draftBytes);
76
+ const package_digest = unpacked.manifest.package_digest;
77
+ const agentRuntime = opts.agent_runtime ?? CORE_IDENTITY.name;
78
+ const agentVersion = opts.agent_version ?? (agentRuntime === CORE_IDENTITY.name ? CORE_IDENTITY.version : "unknown");
79
+ const secret = opts.issuer_secret ?? PUBLIC_DEV_MINT_KEY;
80
+ const keyId = opts.key_id ?? (secret === PUBLIC_DEV_MINT_KEY ? PUBLIC_DEV_MINT_KEY_ID : "configured-issuer");
81
+ const issuer_class = resolveIssuerClass(secret, keyId);
82
+ const markers = [
83
+ ...detectAgentRuntimeMarkers(opts.env),
84
+ ...(opts.agent_runtime_evidence?.markers ?? []),
85
+ ].filter((m, i, arr) => arr.indexOf(m) === i);
86
+ const host_claim_binding = resolveHostClaimBinding(opts, issuer_class, markers);
87
+ // Apply seal-bound policy updates before hashing sealed_manifest_digest so
88
+ // attestation covers the final identity/policy/content claims.
89
+ const sealedManifestBase = {
90
+ ...unpacked.manifest,
91
+ policy: {
92
+ ...unpacked.manifest.policy,
93
+ require_signatures: true,
94
+ require_minted: true,
95
+ trust_profile: opts.policy_profile ?? "minted",
96
+ },
97
+ };
98
+ const sealed_manifest_digest = sealedManifestDigest(sealedManifestBase);
99
+ const attestation = {
100
+ kind: "creation_attestation",
101
+ package_digest,
102
+ sealed_manifest_digest,
103
+ skill_id: pkg.manifest.id,
104
+ skill_version: pkg.manifest.version,
105
+ minted_at: new Date().toISOString(),
106
+ agent: {
107
+ runtime: agentRuntime,
108
+ version: agentVersion,
109
+ key_id: keyId,
110
+ },
111
+ host: opts.host,
112
+ provider: opts.provider,
113
+ model: opts.model,
114
+ deployment: opts.deployment,
115
+ endpoint: opts.endpoint,
116
+ host_claim_binding,
117
+ issuer_class,
118
+ agent_runtime_markers: markers.length ? markers : undefined,
119
+ journey: {
120
+ source_id: pkg.provenance?.compilation_report?.source_id,
121
+ source_hash: pkg.provenance?.proof &&
122
+ typeof pkg.provenance.proof === "object" &&
123
+ pkg.provenance.proof !== null &&
124
+ "source_hash" in pkg.provenance.proof
125
+ ? String(pkg.provenance.proof.source_hash)
126
+ : undefined,
127
+ recipe_id: pkg.provenance?.compilation_report?.recipe_id,
128
+ recipe_hash: pkg.provenance?.recipe &&
129
+ typeof pkg.provenance.recipe === "object" &&
130
+ pkg.provenance.recipe !== null &&
131
+ "hash" in pkg.provenance.recipe
132
+ ? String(pkg.provenance.recipe.hash)
133
+ : undefined,
134
+ proof_digest: pkg.provenance?.proof
135
+ ? sha256Digest(canonicalize(pkg.provenance.proof))
136
+ : undefined,
137
+ summary: pkg.provenance?.journey?.summary,
138
+ },
139
+ generation_usage: pkg.provenance?.generation_usage,
140
+ human_approvals: {
141
+ inputs: pkg.manifest.inputs.filter((i) => i.approved === true).map((i) => i.name),
142
+ permissions: pkg.manifest.permissions
143
+ .filter((p) => p.requires_consent)
144
+ .map((p) => p.side_effect_class),
145
+ actors: opts.actors ?? ["human"],
146
+ },
147
+ policy_profile: opts.policy_profile ?? "minted",
148
+ };
149
+ const payload = canonicalize(attestation);
150
+ const payloadDigest = sha256Digest(payload);
151
+ const sig = sha256Digest(`${secret}:${payloadDigest}`);
152
+ const dsse = {
153
+ payloadType: "application/vnd.dot-skill.creation-attestation+json",
154
+ payload_digest: payloadDigest,
155
+ signatures: [{ keyid: attestation.agent.key_id, sig }],
156
+ attestation,
157
+ };
158
+ const minted = {
159
+ ...unpacked.raw,
160
+ manifest: {
161
+ ...sealedManifestBase,
162
+ mint: {
163
+ mint_status: "minted",
164
+ minted_at: attestation.minted_at,
165
+ mint_issuer: attestation.agent.runtime,
166
+ content_id: package_digest,
167
+ },
168
+ attestation_digest: payloadDigest,
169
+ sealed_manifest_digest,
170
+ },
171
+ attestation,
172
+ signatures: {
173
+ "creation.dsse.json": dsse,
174
+ },
175
+ anchors: unpacked.raw.anchors ?? unpacked.manifest.anchors,
176
+ };
177
+ const packageBytes = packSkill(minted);
178
+ const verify = unpackSkill(packageBytes);
179
+ if (verify.manifest.package_digest !== package_digest) {
180
+ throw new Error(`Mint changed content digest (${verify.manifest.package_digest} != ${package_digest})`);
181
+ }
182
+ return { files: { ...minted, manifest: verify.manifest }, packageBytes, attestation };
183
+ }
184
+ export function addPermanenceAnchor(archive, anchor) {
185
+ const unpacked = unpackSkill(archive);
186
+ const package_digest = unpacked.manifest.package_digest;
187
+ const full = {
188
+ ...anchor,
189
+ package_digest: anchor.package_digest ?? package_digest,
190
+ };
191
+ if (full.package_digest !== package_digest) {
192
+ throw new Error("Anchor package_digest must match skill package_digest");
193
+ }
194
+ const anchors = [...(unpacked.manifest.anchors ?? []), full];
195
+ const files = {
196
+ ...unpacked.raw,
197
+ manifest: {
198
+ ...unpacked.manifest,
199
+ anchors,
200
+ },
201
+ anchors,
202
+ signatures: {
203
+ ...(unpacked.raw.signatures ?? {}),
204
+ [`anchors/${anchors.length}-${full.kind}.json`]: full,
205
+ },
206
+ };
207
+ return packSkill(files);
208
+ }
209
+ function extractAttestation(unpacked) {
210
+ const envelope = unpacked.raw.signatures?.["creation.dsse.json"];
211
+ const attestation = envelope?.attestation ?? unpacked.raw.attestation;
212
+ return { attestation, envelope };
213
+ }
214
+ function classifyTrustState(attestation, signedOk) {
215
+ if (!attestation || !signedOk)
216
+ return "untrusted";
217
+ if (attestation.issuer_class === "public_dev_hmac")
218
+ return "development";
219
+ if (attestation.host_claim_binding === "verified_issuer")
220
+ return "verified_issuer";
221
+ return "self_reported";
222
+ }
223
+ export function verifyMintTrust(archive, profile = "minted", issuer_secret_or_opts) {
224
+ const opts = typeof issuer_secret_or_opts === "string"
225
+ ? { issuer_secret: issuer_secret_or_opts }
226
+ : (issuer_secret_or_opts ?? {});
227
+ const base = validatePackageBytes(archive);
228
+ const issues = [...base.issues];
229
+ const unpacked = unpackSkill(archive);
230
+ const mintStatus = unpacked.manifest.mint?.mint_status ?? "draft";
231
+ const { attestation, envelope } = extractAttestation(unpacked);
232
+ let signedOk = false;
233
+ if (profile !== "open") {
234
+ if (mintStatus !== "minted") {
235
+ issues.push({
236
+ severity: "error",
237
+ code: "not_minted",
238
+ message: "Trust profile requires mint_status=minted",
239
+ });
240
+ }
241
+ if (!attestation) {
242
+ issues.push({
243
+ severity: "error",
244
+ code: "missing_attestation",
245
+ message: "Minted skills require CreationAttestation",
246
+ });
247
+ }
248
+ else if (!envelope?.signatures?.[0]?.sig) {
249
+ issues.push({
250
+ severity: "error",
251
+ code: "missing_attestation_signature",
252
+ message: "Minted trust profile requires a signed DSSE attestation envelope",
253
+ });
254
+ }
255
+ else if (attestation.package_digest !== unpacked.manifest.package_digest) {
256
+ issues.push({
257
+ severity: "error",
258
+ code: "attestation_digest_mismatch",
259
+ message: "Attestation package_digest does not match manifest",
260
+ });
261
+ }
262
+ else {
263
+ const expectedSealed = sealedManifestDigest(unpacked.manifest);
264
+ const sealed = attestation.sealed_manifest_digest ?? unpacked.manifest.sealed_manifest_digest;
265
+ if (!sealed) {
266
+ issues.push({
267
+ severity: "error",
268
+ code: "missing_sealed_manifest_digest",
269
+ message: "Attestation must bind sealed_manifest_digest over identity/policy/content claims",
270
+ });
271
+ }
272
+ else if (sealed !== expectedSealed) {
273
+ issues.push({
274
+ severity: "error",
275
+ code: "sealed_manifest_digest_mismatch",
276
+ message: "sealed_manifest_digest does not match current manifest claims",
277
+ });
278
+ }
279
+ if (unpacked.manifest.sealed_manifest_digest &&
280
+ unpacked.manifest.sealed_manifest_digest !== sealed) {
281
+ issues.push({
282
+ severity: "error",
283
+ code: "manifest_sealed_digest_mismatch",
284
+ message: "Manifest sealed_manifest_digest does not match attestation",
285
+ });
286
+ }
287
+ const payloadDigest = sha256Digest(canonicalize(attestation));
288
+ if (envelope.payload_digest !== payloadDigest) {
289
+ issues.push({
290
+ severity: "error",
291
+ code: "attestation_payload_digest",
292
+ message: "DSSE payload_digest does not match CreationAttestation",
293
+ });
294
+ }
295
+ const issuerClass = attestation.issuer_class ??
296
+ (attestation.agent.key_id === PUBLIC_DEV_MINT_KEY_ID
297
+ ? "public_dev_hmac"
298
+ : "configured_hmac");
299
+ // Fail closed: public-dev HMAC is never production trust unless explicitly allowed.
300
+ if (issuerClass === "public_dev_hmac" && !opts.allow_development_issuer) {
301
+ issues.push({
302
+ severity: "error",
303
+ code: "public_dev_issuer_untrusted",
304
+ message: "Seal uses the public development HMAC key — not production trust. " +
305
+ "Pass allow_development_issuer only for local testing, or mint with a configured issuer secret.",
306
+ });
307
+ }
308
+ const hostBinding = attestation.host_claim_binding ?? "self_reported";
309
+ if (hostBinding === "self_reported" &&
310
+ !opts.allow_self_reported &&
311
+ !opts.allow_development_issuer) {
312
+ issues.push({
313
+ severity: "error",
314
+ code: "self_reported_host_untrusted",
315
+ message: "Host/model claims are self_reported (e.g. SKILL_HOST env alone). " +
316
+ "Not treated as verified_issuer trust.",
317
+ });
318
+ }
319
+ const secret = opts.issuer_secret ??
320
+ (issuerClass === "public_dev_hmac" ? PUBLIC_DEV_MINT_KEY : undefined);
321
+ if (!secret) {
322
+ issues.push({
323
+ severity: "error",
324
+ code: "issuer_secret_required",
325
+ message: "Configured issuer seal requires a matching issuer_secret in the trust store",
326
+ });
327
+ }
328
+ else {
329
+ const expected = sha256Digest(`${secret}:${payloadDigest}`);
330
+ const sig = envelope?.signatures?.[0]?.sig;
331
+ if (sig !== expected) {
332
+ issues.push({
333
+ severity: "error",
334
+ code: "attestation_sig_invalid",
335
+ message: "CreationAttestation signature failed verification",
336
+ });
337
+ }
338
+ else {
339
+ signedOk = true;
340
+ }
341
+ }
342
+ if (issuerClass === "public_dev_hmac") {
343
+ issues.push({
344
+ severity: "warning",
345
+ code: "development_attestation",
346
+ message: "Attestation uses the public development key — labeled development, never production identity",
347
+ });
348
+ }
349
+ }
350
+ }
351
+ else if (attestation && envelope?.signatures?.[0]?.sig) {
352
+ // open profile: still classify if a seal is present
353
+ const payloadDigest = sha256Digest(canonicalize(attestation));
354
+ const secret = opts.issuer_secret ?? PUBLIC_DEV_MINT_KEY;
355
+ signedOk = envelope.signatures[0].sig === sha256Digest(`${secret}:${payloadDigest}`);
356
+ }
357
+ if (profile === "anchored") {
358
+ const anchors = unpacked.manifest.anchors ?? [];
359
+ if (!anchors.length) {
360
+ issues.push({
361
+ severity: "error",
362
+ code: "anchor_required",
363
+ message: "Trust profile requires at least one PermanenceAnchor",
364
+ });
365
+ }
366
+ }
367
+ if (profile.startsWith("issuer:")) {
368
+ const want = profile.slice("issuer:".length);
369
+ if (attestation?.agent.runtime !== want && attestation?.agent.key_id !== want) {
370
+ issues.push({
371
+ severity: "error",
372
+ code: "issuer_mismatch",
373
+ message: `Attestation issuer does not match ${profile}`,
374
+ });
375
+ }
376
+ }
377
+ const trust_state = classifyTrustState(attestation, signedOk);
378
+ return {
379
+ ok: !issues.some((i) => i.severity === "error"),
380
+ issues,
381
+ attestation,
382
+ trust_state,
383
+ };
384
+ }
385
+ /**
386
+ * TrustView from skill.json + signatures + digests only — no compile, no model body ingest.
387
+ */
388
+ export function inspectTrustView(archive) {
389
+ const base = validatePackageBytes(archive);
390
+ const warnings = [];
391
+ if (!base.manifest) {
392
+ return {
393
+ trust_state: "untrusted",
394
+ mint_status: "draft",
395
+ signed: false,
396
+ package_digest: "",
397
+ label: "INVALID — package failed validation",
398
+ warnings: [],
399
+ issues: base.issues,
400
+ };
401
+ }
402
+ const unpacked = unpackSkill(archive);
403
+ const m = unpacked.manifest;
404
+ const mint_status = m.mint?.mint_status ?? "draft";
405
+ const { attestation, envelope } = extractAttestation(unpacked);
406
+ const signed = Boolean(envelope?.signatures?.[0]?.sig && attestation);
407
+ let trust_state = "untrusted";
408
+ let label = "UNSIGNED / OPEN — untrusted";
409
+ if (mint_status === "draft" || !signed) {
410
+ trust_state = "untrusted";
411
+ label = "UNSIGNED / OPEN — untrusted (do not execute without --allow-untrusted)";
412
+ warnings.push("Package has no verified creation seal");
413
+ }
414
+ else {
415
+ const verify = verifyMintTrust(archive, "minted", {
416
+ allow_development_issuer: true,
417
+ allow_self_reported: true,
418
+ });
419
+ trust_state = verify.trust_state;
420
+ if (trust_state === "development") {
421
+ label = "DEVELOPMENT seal (public-dev HMAC) — not production trust";
422
+ warnings.push("Public development HMAC is forgeable; treat as untrusted for production execute");
423
+ }
424
+ else if (trust_state === "self_reported") {
425
+ label = "SELF-REPORTED agent host claims — signed but not verified_issuer";
426
+ warnings.push("Host/provider/model are self-asserted; local LLMs can lie about authorship");
427
+ }
428
+ else if (trust_state === "verified_issuer") {
429
+ label = "VERIFIED ISSUER seal — host claims bound by configured issuer";
430
+ warnings.push("Issuer key authenticity is established; model honesty (esp. local LLMs) remains a residual risk");
431
+ }
432
+ else {
433
+ label = "UNTRUSTED — seal present but verification failed";
434
+ }
435
+ for (const issue of verify.issues) {
436
+ if (issue.severity === "warning")
437
+ warnings.push(issue.message);
438
+ }
439
+ }
440
+ const expectedSealed = sealedManifestDigest(m);
441
+ return {
442
+ trust_state,
443
+ mint_status,
444
+ signed,
445
+ issuer: attestation?.agent.runtime ?? m.mint?.mint_issuer,
446
+ issuer_class: attestation?.issuer_class,
447
+ host_claim_binding: attestation?.host_claim_binding,
448
+ agent: attestation
449
+ ? {
450
+ host: attestation.host,
451
+ provider: attestation.provider,
452
+ model: attestation.model,
453
+ runtime: attestation.agent.runtime,
454
+ version: attestation.agent.version,
455
+ key_id: attestation.agent.key_id,
456
+ deployment: attestation.deployment,
457
+ markers: attestation.agent_runtime_markers,
458
+ }
459
+ : undefined,
460
+ package_digest: m.package_digest,
461
+ sealed_manifest_digest: attestation?.sealed_manifest_digest ?? m.sealed_manifest_digest ?? expectedSealed,
462
+ attestation_digest: m.attestation_digest,
463
+ label,
464
+ warnings,
465
+ issues: base.issues,
466
+ };
467
+ }
package/dist/pack.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { KnowledgeItem, SkillManifest, SkillPackageFiles, Workflow, CompilationReport } from "@skillerr/protocol";
2
+ export interface PackOptions {
3
+ recomputeDigests?: boolean;
4
+ }
5
+ export declare function buildFileMap(pkg: SkillPackageFiles): Record<string, Uint8Array>;
6
+ /**
7
+ * Content index covers every file except `skill.json` and `signatures/**`.
8
+ * `package_digest` is the digest of that index (RFC8785 JCS + SHA-256).
9
+ */
10
+ export declare function finalizeManifest(base: Omit<SkillManifest, "content" | "package_digest"> & Partial<Pick<SkillManifest, "content" | "package_digest">>, files: Record<string, Uint8Array>): SkillManifest;
11
+ export declare function packSkill(pkg: SkillPackageFiles, _opts?: PackOptions): Uint8Array;
12
+ export interface UnpackResult {
13
+ files: Record<string, Uint8Array>;
14
+ manifest: SkillManifest;
15
+ workflow: Workflow;
16
+ knowledge: KnowledgeItem[];
17
+ compilation_report?: CompilationReport;
18
+ raw: SkillPackageFiles;
19
+ }
20
+ export declare function unpackSkill(archive: Uint8Array): UnpackResult;
package/dist/pack.js ADDED
@@ -0,0 +1,191 @@
1
+ import { zipSync, unzipSync, strToU8, strFromU8 } from "fflate";
2
+ import { packageDigestFromContent, sha256Digest } from "./hash.js";
3
+ import { assertSafePaths, MAX_COMPRESSION_RATIO, MAX_ENTRIES, MAX_UNCOMPRESSED_BYTES, normalizePath, } from "./paths.js";
4
+ function toBytes(data) {
5
+ return typeof data === "string" ? strToU8(data) : data;
6
+ }
7
+ function textEncode(obj) {
8
+ return strToU8(JSON.stringify(obj, null, 2) + "\n");
9
+ }
10
+ export function buildFileMap(pkg) {
11
+ const files = {};
12
+ files["workflow.json"] = textEncode(pkg.workflow);
13
+ for (const item of pkg.knowledge) {
14
+ files[`knowledge/${item.id}.json`] = textEncode(item);
15
+ }
16
+ if (pkg.prompts) {
17
+ for (const [name, body] of Object.entries(pkg.prompts)) {
18
+ files[`prompts/${normalizePath(name)}`] = toBytes(body);
19
+ }
20
+ }
21
+ if (pkg.resources) {
22
+ for (const [name, body] of Object.entries(pkg.resources)) {
23
+ files[`resources/${normalizePath(name)}`] = toBytes(body);
24
+ }
25
+ }
26
+ if (pkg.artifacts) {
27
+ for (const [name, body] of Object.entries(pkg.artifacts)) {
28
+ files[`artifacts/${normalizePath(name)}`] = toBytes(body);
29
+ }
30
+ }
31
+ if (pkg.provenance?.recipe) {
32
+ files["provenance/recipe.json"] = textEncode(pkg.provenance.recipe);
33
+ }
34
+ if (pkg.provenance?.source) {
35
+ files["provenance/source.json"] = textEncode(pkg.provenance.source);
36
+ }
37
+ if (pkg.provenance?.journey) {
38
+ files["provenance/journey.json"] = textEncode(pkg.provenance.journey);
39
+ }
40
+ if (pkg.provenance?.generation_usage) {
41
+ files["provenance/generation_usage.json"] = textEncode(pkg.provenance.generation_usage);
42
+ }
43
+ if (pkg.provenance?.proof) {
44
+ files["provenance/proof.json"] = textEncode(pkg.provenance.proof);
45
+ }
46
+ if (pkg.provenance?.compilation_report) {
47
+ files["provenance/compilation_report.json"] = textEncode(pkg.provenance.compilation_report);
48
+ }
49
+ if (pkg.attestation && !pkg.signatures?.["creation.dsse.json"]) {
50
+ files["signatures/creation.attestation.json"] = textEncode(pkg.attestation);
51
+ }
52
+ if (pkg.signatures) {
53
+ for (const [name, body] of Object.entries(pkg.signatures)) {
54
+ files[`signatures/${normalizePath(name)}`] = textEncode(body);
55
+ }
56
+ }
57
+ if (pkg.anchors) {
58
+ pkg.anchors.forEach((anchor, i) => {
59
+ const path = `signatures/anchors/${i}-${anchor.kind}.json`;
60
+ if (!files[path])
61
+ files[path] = textEncode(anchor);
62
+ });
63
+ }
64
+ return files;
65
+ }
66
+ /**
67
+ * Content index covers every file except `skill.json` and `signatures/**`.
68
+ * `package_digest` is the digest of that index (RFC8785 JCS + SHA-256).
69
+ */
70
+ export function finalizeManifest(base, files) {
71
+ const content = Object.keys(files)
72
+ .filter((p) => p !== "skill.json" && !p.startsWith("signatures/"))
73
+ .sort()
74
+ .map((path) => ({
75
+ path,
76
+ digest: sha256Digest(files[path]),
77
+ bytes: files[path].byteLength,
78
+ }));
79
+ return {
80
+ ...base,
81
+ mint: base.mint ?? { mint_status: "draft" },
82
+ content,
83
+ package_digest: packageDigestFromContent(content),
84
+ };
85
+ }
86
+ export function packSkill(pkg, _opts = {}) {
87
+ const files = buildFileMap(pkg);
88
+ const manifest = finalizeManifest(pkg.manifest, files);
89
+ files["skill.json"] = textEncode(manifest);
90
+ assertSafePaths(Object.keys(files));
91
+ if (Object.keys(files).length > MAX_ENTRIES) {
92
+ throw new Error(`Too many entries: ${Object.keys(files).length}`);
93
+ }
94
+ let total = 0;
95
+ for (const bytes of Object.values(files))
96
+ total += bytes.byteLength;
97
+ if (total > MAX_UNCOMPRESSED_BYTES) {
98
+ throw new Error(`Package too large: ${total} bytes`);
99
+ }
100
+ return zipSync(files, { level: 6 });
101
+ }
102
+ export function unpackSkill(archive) {
103
+ if (archive.byteLength > MAX_UNCOMPRESSED_BYTES * 2) {
104
+ throw new Error("Archive too large to unpack");
105
+ }
106
+ const unzipped = unzipSync(archive);
107
+ const paths = Object.keys(unzipped);
108
+ if (paths.length > MAX_ENTRIES)
109
+ throw new Error("Too many zip entries");
110
+ assertSafePaths(paths);
111
+ let uncompressed = 0;
112
+ for (const data of Object.values(unzipped)) {
113
+ uncompressed += data.byteLength;
114
+ if (uncompressed > MAX_UNCOMPRESSED_BYTES) {
115
+ throw new Error("Uncompressed size exceeds limit");
116
+ }
117
+ const ratio = archive.byteLength > 0 ? uncompressed / archive.byteLength : 0;
118
+ if (ratio > MAX_COMPRESSION_RATIO && uncompressed > 1_000_000) {
119
+ throw new Error("Suspicious compression ratio");
120
+ }
121
+ }
122
+ const skillJson = unzipped["skill.json"];
123
+ if (!skillJson)
124
+ throw new Error("Missing skill.json");
125
+ const workflowJson = unzipped["workflow.json"];
126
+ if (!workflowJson)
127
+ throw new Error("Missing workflow.json");
128
+ const manifest = JSON.parse(strFromU8(skillJson));
129
+ const workflow = JSON.parse(strFromU8(workflowJson));
130
+ const knowledge = [];
131
+ for (const [path, data] of Object.entries(unzipped)) {
132
+ if (path.startsWith("knowledge/") && path.endsWith(".json")) {
133
+ knowledge.push(JSON.parse(strFromU8(data)));
134
+ }
135
+ }
136
+ let compilation_report;
137
+ if (unzipped["provenance/compilation_report.json"]) {
138
+ compilation_report = JSON.parse(strFromU8(unzipped["provenance/compilation_report.json"]));
139
+ }
140
+ const prompts = {};
141
+ const resources = {};
142
+ const artifacts = {};
143
+ const signatures = {};
144
+ for (const [path, data] of Object.entries(unzipped)) {
145
+ if (path.startsWith("prompts/"))
146
+ prompts[path.slice("prompts/".length)] = strFromU8(data);
147
+ if (path.startsWith("resources/"))
148
+ resources[path.slice("resources/".length)] = data;
149
+ if (path.startsWith("artifacts/"))
150
+ artifacts[path.slice("artifacts/".length)] = data;
151
+ if (path.startsWith("signatures/") && path.endsWith(".json")) {
152
+ signatures[path.slice("signatures/".length)] = JSON.parse(strFromU8(data));
153
+ }
154
+ }
155
+ const creation = signatures["creation.dsse.json"];
156
+ const attestation = creation?.attestation ??
157
+ signatures["creation.attestation.json"];
158
+ const anchorsFromSig = Object.entries(signatures)
159
+ .filter(([k]) => k.startsWith("anchors/"))
160
+ .map(([, v]) => v);
161
+ const raw = {
162
+ manifest,
163
+ workflow,
164
+ knowledge,
165
+ prompts,
166
+ artifacts,
167
+ resources,
168
+ provenance: {
169
+ recipe: unzipped["provenance/recipe.json"]
170
+ ? JSON.parse(strFromU8(unzipped["provenance/recipe.json"]))
171
+ : undefined,
172
+ source: unzipped["provenance/source.json"]
173
+ ? JSON.parse(strFromU8(unzipped["provenance/source.json"]))
174
+ : undefined,
175
+ journey: unzipped["provenance/journey.json"]
176
+ ? JSON.parse(strFromU8(unzipped["provenance/journey.json"]))
177
+ : undefined,
178
+ generation_usage: unzipped["provenance/generation_usage.json"]
179
+ ? JSON.parse(strFromU8(unzipped["provenance/generation_usage.json"]))
180
+ : undefined,
181
+ proof: unzipped["provenance/proof.json"]
182
+ ? JSON.parse(strFromU8(unzipped["provenance/proof.json"]))
183
+ : undefined,
184
+ compilation_report,
185
+ },
186
+ signatures,
187
+ attestation,
188
+ anchors: manifest.anchors?.length ? manifest.anchors : anchorsFromSig,
189
+ };
190
+ return { files: unzipped, manifest, workflow, knowledge, compilation_report, raw };
191
+ }
@@ -0,0 +1,5 @@
1
+ export declare function normalizePath(path: string): string;
2
+ export declare function assertSafePaths(paths: string[]): void;
3
+ export declare const MAX_ENTRIES = 10000;
4
+ export declare const MAX_UNCOMPRESSED_BYTES: number;
5
+ export declare const MAX_COMPRESSION_RATIO = 100;