@dot-skill/core 0.4.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.
- package/LICENSE +21 -0
- package/dist/compile.d.ts +52 -0
- package/dist/compile.js +588 -0
- package/dist/hash.d.ts +8 -0
- package/dist/hash.js +52 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +8 -0
- package/dist/migrate.d.ts +8 -0
- package/dist/migrate.js +116 -0
- package/dist/mint.d.ts +37 -0
- package/dist/mint.js +250 -0
- package/dist/pack.d.ts +20 -0
- package/dist/pack.js +191 -0
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +22 -0
- package/dist/validate.d.ts +32 -0
- package/dist/validate.js +215 -0
- package/package.json +43 -0
package/dist/hash.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/** RFC 8785-inspired JSON Canonicalization for I-JSON-compatible objects. */
|
|
3
|
+
export function canonicalize(value) {
|
|
4
|
+
return serialize(value);
|
|
5
|
+
}
|
|
6
|
+
function serialize(value) {
|
|
7
|
+
if (value === null)
|
|
8
|
+
return "null";
|
|
9
|
+
if (typeof value === "boolean")
|
|
10
|
+
return value ? "true" : "false";
|
|
11
|
+
if (typeof value === "number") {
|
|
12
|
+
if (!Number.isFinite(value)) {
|
|
13
|
+
throw new Error("JCS forbids non-finite numbers");
|
|
14
|
+
}
|
|
15
|
+
return JSON.stringify(value);
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === "string")
|
|
18
|
+
return JSON.stringify(value);
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
return `[${value.map((v) => serialize(v)).join(",")}]`;
|
|
21
|
+
}
|
|
22
|
+
if (typeof value === "object") {
|
|
23
|
+
const obj = value;
|
|
24
|
+
const keys = Object.keys(obj).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
25
|
+
const parts = [];
|
|
26
|
+
for (const key of keys) {
|
|
27
|
+
const v = obj[key];
|
|
28
|
+
if (v === undefined)
|
|
29
|
+
continue;
|
|
30
|
+
parts.push(`${JSON.stringify(key)}:${serialize(v)}`);
|
|
31
|
+
}
|
|
32
|
+
return `{${parts.join(",")}}`;
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Unsupported JSON value type: ${typeof value}`);
|
|
35
|
+
}
|
|
36
|
+
export function sha256Hex(data) {
|
|
37
|
+
const hash = createHash("sha256");
|
|
38
|
+
hash.update(typeof data === "string" ? Buffer.from(data, "utf8") : data);
|
|
39
|
+
return hash.digest("hex");
|
|
40
|
+
}
|
|
41
|
+
export function sha256Digest(data) {
|
|
42
|
+
return `sha256:${sha256Hex(data)}`;
|
|
43
|
+
}
|
|
44
|
+
export function packageDigestFromContent(content) {
|
|
45
|
+
const paths = {};
|
|
46
|
+
for (const entry of [...content].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
47
|
+
if (entry.path.startsWith("signatures/"))
|
|
48
|
+
continue;
|
|
49
|
+
paths[entry.path] = entry.digest;
|
|
50
|
+
}
|
|
51
|
+
return sha256Digest(canonicalize({ paths }));
|
|
52
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** @dot-skill/core — pack, unpack, validate, mint, compile .skill packages. */
|
|
2
|
+
export { canonicalize, sha256Hex, sha256Digest, packageDigestFromContent } from "./hash.js";
|
|
3
|
+
export { normalizePath, assertSafePaths, MAX_ENTRIES, MAX_UNCOMPRESSED_BYTES, MAX_COMPRESSION_RATIO, } from "./paths.js";
|
|
4
|
+
export { buildFileMap, finalizeManifest, packSkill, unpackSkill, } from "./pack.js";
|
|
5
|
+
export type { PackOptions, UnpackResult } from "./pack.js";
|
|
6
|
+
export { validateManifestShape, validateWorkflowShape, validatePackageBytes, inspectSkill, } from "./validate.js";
|
|
7
|
+
export type { ValidationIssue, ValidationResult } from "./validate.js";
|
|
8
|
+
export { migrateLegacySkill, toSkillMdAdapter } from "./migrate.js";
|
|
9
|
+
export { mintSkillPackage, addPermanenceAnchor, verifyMintTrust, } from "./mint.js";
|
|
10
|
+
export type { MintOptions } from "./mint.js";
|
|
11
|
+
export { compileSkillSource, compileRecipeToSkill, approveCompilation, assessCompleteness, redactSecrets, CompileRefusalError, } from "./compile.js";
|
|
12
|
+
export type { CompileOptions, CompileResult } from "./compile.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** @dot-skill/core — pack, unpack, validate, mint, compile .skill packages. */
|
|
2
|
+
export { canonicalize, sha256Hex, sha256Digest, packageDigestFromContent } from "./hash.js";
|
|
3
|
+
export { normalizePath, assertSafePaths, MAX_ENTRIES, MAX_UNCOMPRESSED_BYTES, MAX_COMPRESSION_RATIO, } from "./paths.js";
|
|
4
|
+
export { buildFileMap, finalizeManifest, packSkill, unpackSkill, } from "./pack.js";
|
|
5
|
+
export { validateManifestShape, validateWorkflowShape, validatePackageBytes, inspectSkill, } from "./validate.js";
|
|
6
|
+
export { migrateLegacySkill, toSkillMdAdapter } from "./migrate.js";
|
|
7
|
+
export { mintSkillPackage, addPermanenceAnchor, verifyMintTrust, } from "./mint.js";
|
|
8
|
+
export { compileSkillSource, compileRecipeToSkill, approveCompilation, assessCompleteness, redactSecrets, CompileRefusalError, } from "./compile.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Skill } from "@dot-skill/protocol";
|
|
2
|
+
import { type SkillPackageFiles } from "@dot-skill/protocol";
|
|
3
|
+
/** Convert legacy flat skill JSON into a draft `.skill` package (bytes). */
|
|
4
|
+
export declare function migrateLegacySkill(legacy: Skill): {
|
|
5
|
+
packageBytes: Uint8Array;
|
|
6
|
+
files: SkillPackageFiles;
|
|
7
|
+
};
|
|
8
|
+
export declare function toSkillMdAdapter(pkg: SkillPackageFiles): string;
|
package/dist/migrate.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { DEFAULT_SKILL_POLICY, CONTAINER_VERSION, PROTOCOL_VERSION, WORKFLOW_DIALECT_VERSION, } from "@dot-skill/protocol";
|
|
2
|
+
import { packSkill } from "./pack.js";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
/** Convert legacy flat skill JSON into a draft `.skill` package (bytes). */
|
|
5
|
+
export function migrateLegacySkill(legacy) {
|
|
6
|
+
const id = legacy.id || `skl_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
7
|
+
const knowledgeId = "k_legacy_body";
|
|
8
|
+
const files = {
|
|
9
|
+
manifest: {
|
|
10
|
+
kind: "dot-skill",
|
|
11
|
+
id,
|
|
12
|
+
version: legacy.version || "0.1.0",
|
|
13
|
+
title: legacy.title || "Migrated skill",
|
|
14
|
+
description: "Draft migrated from legacy flat skill format. Needs human review before release.",
|
|
15
|
+
container_version: CONTAINER_VERSION,
|
|
16
|
+
protocol_version: PROTOCOL_VERSION,
|
|
17
|
+
entrypoint: "s_instruct",
|
|
18
|
+
inputs: [],
|
|
19
|
+
outputs: [
|
|
20
|
+
{
|
|
21
|
+
name: "result",
|
|
22
|
+
schema: { type: "string" },
|
|
23
|
+
required: false,
|
|
24
|
+
description: "Unstructured result from legacy instruct skill",
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
capabilities: [],
|
|
28
|
+
permissions: [],
|
|
29
|
+
policy: { ...DEFAULT_SKILL_POLICY },
|
|
30
|
+
content: [],
|
|
31
|
+
package_digest: "sha256:" + "0".repeat(64),
|
|
32
|
+
provenance_mode: "proof_only",
|
|
33
|
+
legacy: true,
|
|
34
|
+
needs_human_review: true,
|
|
35
|
+
},
|
|
36
|
+
workflow: {
|
|
37
|
+
kind: "workflow",
|
|
38
|
+
dialect_version: WORKFLOW_DIALECT_VERSION,
|
|
39
|
+
entrypoint: "s_instruct",
|
|
40
|
+
steps: [
|
|
41
|
+
{
|
|
42
|
+
id: "s_instruct",
|
|
43
|
+
kind: "instruct",
|
|
44
|
+
title: "Legacy body",
|
|
45
|
+
text: legacy.body,
|
|
46
|
+
next: "s_emit",
|
|
47
|
+
provenance: [{ kind: "legacy_skill", id: legacy.id }],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "s_emit",
|
|
51
|
+
kind: "emit",
|
|
52
|
+
output: "result",
|
|
53
|
+
from: "s_instruct",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
knowledge: [
|
|
58
|
+
{
|
|
59
|
+
kind: "knowledge",
|
|
60
|
+
id: knowledgeId,
|
|
61
|
+
type: "reference",
|
|
62
|
+
title: "Legacy skill body",
|
|
63
|
+
body: legacy.body,
|
|
64
|
+
fidelity: "exact",
|
|
65
|
+
provenance: [{ kind: "legacy_skill", id: legacy.id }],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
provenance: {
|
|
69
|
+
proof: {
|
|
70
|
+
sources: legacy.sources,
|
|
71
|
+
exported_at: legacy.exported_at,
|
|
72
|
+
legacy_protocol_version: legacy.source_protocol_version,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
const packageBytes = packSkill(files);
|
|
77
|
+
return { packageBytes, files };
|
|
78
|
+
}
|
|
79
|
+
export function toSkillMdAdapter(pkg) {
|
|
80
|
+
const m = pkg.manifest;
|
|
81
|
+
const lines = [
|
|
82
|
+
"---",
|
|
83
|
+
`name: ${m.id.replace(/[^a-z0-9-]/gi, "-").toLowerCase().slice(0, 64)}`,
|
|
84
|
+
`description: ${m.description.slice(0, 1024)}`,
|
|
85
|
+
"metadata:",
|
|
86
|
+
" dot_skill_authoritative: true",
|
|
87
|
+
` skill_id: ${m.id}`,
|
|
88
|
+
` skill_version: ${m.version}`,
|
|
89
|
+
` package_digest: ${m.package_digest}`,
|
|
90
|
+
"---",
|
|
91
|
+
"",
|
|
92
|
+
`# ${m.title}`,
|
|
93
|
+
"",
|
|
94
|
+
m.intent ?? m.description,
|
|
95
|
+
"",
|
|
96
|
+
"## Inputs",
|
|
97
|
+
"",
|
|
98
|
+
];
|
|
99
|
+
for (const input of m.inputs) {
|
|
100
|
+
lines.push(`- **${input.name}** (${input.source}, ${input.required ? "required" : "optional"}): ${input.description}`);
|
|
101
|
+
}
|
|
102
|
+
if (m.inputs.length === 0)
|
|
103
|
+
lines.push("- (none)");
|
|
104
|
+
lines.push("", "## Workflow", "");
|
|
105
|
+
for (const step of pkg.workflow.steps) {
|
|
106
|
+
lines.push(`### ${step.id} (\`${step.kind}\`)`);
|
|
107
|
+
if ("text" in step && step.text)
|
|
108
|
+
lines.push(step.text, "");
|
|
109
|
+
if ("template" in step && step.template)
|
|
110
|
+
lines.push(step.template, "");
|
|
111
|
+
if ("prompt" in step && step.prompt)
|
|
112
|
+
lines.push(step.prompt, "");
|
|
113
|
+
}
|
|
114
|
+
lines.push("", "> This SKILL.md is a **lossy adapter**. The authoritative artifact is the `.skill` package.");
|
|
115
|
+
return lines.join("\n");
|
|
116
|
+
}
|
package/dist/mint.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CreationAttestation, PermanenceAnchor, SkillPackageFiles, TrustProfile } from "@dot-skill/protocol";
|
|
2
|
+
import { type ValidationIssue } from "./validate.js";
|
|
3
|
+
export interface MintOptions {
|
|
4
|
+
host: string;
|
|
5
|
+
provider?: string;
|
|
6
|
+
agent_runtime?: string;
|
|
7
|
+
agent_version?: string;
|
|
8
|
+
key_id?: string;
|
|
9
|
+
model?: string;
|
|
10
|
+
deployment?: "local" | "hosted" | "hybrid" | "unknown";
|
|
11
|
+
endpoint?: string;
|
|
12
|
+
actors?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* HMAC-style digest seal for development/testing only.
|
|
15
|
+
* REFERENCE IMPLEMENTATION ONLY — not production PKI.
|
|
16
|
+
* Default key is `dot-skill-dev-mint-key` and must be replaced in production.
|
|
17
|
+
*/
|
|
18
|
+
issuer_secret?: string;
|
|
19
|
+
policy_profile?: TrustProfile;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Seal a draft package as minted.
|
|
23
|
+
* Content under signatures/ may change; package_digest (content) stays fixed after finalize.
|
|
24
|
+
*/
|
|
25
|
+
export declare function mintSkillPackage(pkg: SkillPackageFiles, opts: MintOptions): {
|
|
26
|
+
files: SkillPackageFiles;
|
|
27
|
+
packageBytes: Uint8Array;
|
|
28
|
+
attestation: CreationAttestation;
|
|
29
|
+
};
|
|
30
|
+
export declare function addPermanenceAnchor(archive: Uint8Array, anchor: Omit<PermanenceAnchor, "package_digest"> & {
|
|
31
|
+
package_digest?: string;
|
|
32
|
+
}): Uint8Array;
|
|
33
|
+
export declare function verifyMintTrust(archive: Uint8Array, profile?: TrustProfile, issuer_secret?: string): {
|
|
34
|
+
ok: boolean;
|
|
35
|
+
issues: ValidationIssue[];
|
|
36
|
+
attestation?: CreationAttestation;
|
|
37
|
+
};
|
package/dist/mint.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { isValidAgentHost } from "@dot-skill/protocol";
|
|
2
|
+
import { canonicalize, sha256Digest } from "./hash.js";
|
|
3
|
+
import { packSkill, unpackSkill } from "./pack.js";
|
|
4
|
+
import { validatePackageBytes } from "./validate.js";
|
|
5
|
+
/**
|
|
6
|
+
* Seal a draft package as minted.
|
|
7
|
+
* Content under signatures/ may change; package_digest (content) stays fixed after finalize.
|
|
8
|
+
*/
|
|
9
|
+
export function mintSkillPackage(pkg, opts) {
|
|
10
|
+
if (!isValidAgentHost(opts.host)) {
|
|
11
|
+
throw new Error(`Mint host "${opts.host}" is not a valid AI agent host. Use cursor, ollama, lmstudio, llama-cpp, custom-agent, …`);
|
|
12
|
+
}
|
|
13
|
+
if (pkg.manifest.needs_human_review) {
|
|
14
|
+
throw new Error("Cannot mint while needs_human_review is true — approve inputs/permissions first");
|
|
15
|
+
}
|
|
16
|
+
if (pkg.manifest.compile_profile !== "release") {
|
|
17
|
+
throw new Error("Cannot mint: compile_profile must be release. Complete the journey and release compile first.");
|
|
18
|
+
}
|
|
19
|
+
if (!pkg.manifest.completeness?.complete) {
|
|
20
|
+
throw new Error(`Cannot mint incomplete skill. Missing: ${pkg.manifest.completeness?.missing.join(", ") || "completeness report"}`);
|
|
21
|
+
}
|
|
22
|
+
const report = pkg.provenance?.compilation_report;
|
|
23
|
+
if (!report ||
|
|
24
|
+
report.profile !== "release" ||
|
|
25
|
+
!report.completeness.complete ||
|
|
26
|
+
!report.approved ||
|
|
27
|
+
report.pending_approvals.length > 0) {
|
|
28
|
+
throw new Error("Cannot mint: approved release compilation report required");
|
|
29
|
+
}
|
|
30
|
+
const pending = pkg.manifest.inputs.filter((i) => i.required && i.approved !== true);
|
|
31
|
+
if (pending.length) {
|
|
32
|
+
throw new Error(`Cannot mint with unapproved inputs: ${pending.map((p) => p.name).join(", ")}`);
|
|
33
|
+
}
|
|
34
|
+
const draftBytes = packSkill({
|
|
35
|
+
...pkg,
|
|
36
|
+
signatures: undefined,
|
|
37
|
+
attestation: undefined,
|
|
38
|
+
anchors: pkg.anchors ?? pkg.manifest.anchors,
|
|
39
|
+
});
|
|
40
|
+
const unpacked = unpackSkill(draftBytes);
|
|
41
|
+
const package_digest = unpacked.manifest.package_digest;
|
|
42
|
+
const attestation = {
|
|
43
|
+
kind: "creation_attestation",
|
|
44
|
+
package_digest,
|
|
45
|
+
skill_id: pkg.manifest.id,
|
|
46
|
+
skill_version: pkg.manifest.version,
|
|
47
|
+
minted_at: new Date().toISOString(),
|
|
48
|
+
agent: {
|
|
49
|
+
runtime: opts.agent_runtime ?? "@dot-skill/runtime",
|
|
50
|
+
version: opts.agent_version ?? "0.4.0",
|
|
51
|
+
key_id: opts.key_id ?? "dot-skill-dev-mint-key",
|
|
52
|
+
},
|
|
53
|
+
host: opts.host,
|
|
54
|
+
provider: opts.provider,
|
|
55
|
+
model: opts.model,
|
|
56
|
+
deployment: opts.deployment,
|
|
57
|
+
endpoint: opts.endpoint,
|
|
58
|
+
journey: {
|
|
59
|
+
source_id: pkg.provenance?.compilation_report?.source_id,
|
|
60
|
+
source_hash: pkg.provenance?.proof &&
|
|
61
|
+
typeof pkg.provenance.proof === "object" &&
|
|
62
|
+
pkg.provenance.proof !== null &&
|
|
63
|
+
"source_hash" in pkg.provenance.proof
|
|
64
|
+
? String(pkg.provenance.proof.source_hash)
|
|
65
|
+
: undefined,
|
|
66
|
+
recipe_id: pkg.provenance?.compilation_report?.recipe_id,
|
|
67
|
+
recipe_hash: pkg.provenance?.recipe &&
|
|
68
|
+
typeof pkg.provenance.recipe === "object" &&
|
|
69
|
+
pkg.provenance.recipe !== null &&
|
|
70
|
+
"hash" in pkg.provenance.recipe
|
|
71
|
+
? String(pkg.provenance.recipe.hash)
|
|
72
|
+
: undefined,
|
|
73
|
+
proof_digest: pkg.provenance?.proof
|
|
74
|
+
? sha256Digest(canonicalize(pkg.provenance.proof))
|
|
75
|
+
: undefined,
|
|
76
|
+
summary: pkg.provenance?.journey?.summary,
|
|
77
|
+
},
|
|
78
|
+
generation_usage: pkg.provenance?.generation_usage,
|
|
79
|
+
human_approvals: {
|
|
80
|
+
inputs: pkg.manifest.inputs.filter((i) => i.approved === true).map((i) => i.name),
|
|
81
|
+
permissions: pkg.manifest.permissions
|
|
82
|
+
.filter((p) => p.requires_consent)
|
|
83
|
+
.map((p) => p.side_effect_class),
|
|
84
|
+
actors: opts.actors ?? ["human"],
|
|
85
|
+
},
|
|
86
|
+
policy_profile: opts.policy_profile ?? "minted",
|
|
87
|
+
};
|
|
88
|
+
const payload = canonicalize(attestation);
|
|
89
|
+
const payloadDigest = sha256Digest(payload);
|
|
90
|
+
/**
|
|
91
|
+
* REFERENCE IMPLEMENTATION ONLY.
|
|
92
|
+
* The default key "dot-skill-dev-mint-key" is a public constant for testing.
|
|
93
|
+
* Replace issuer_secret with a real private key in any production issuer.
|
|
94
|
+
*/
|
|
95
|
+
const secret = opts.issuer_secret ?? "dot-skill-dev-mint-key";
|
|
96
|
+
const sig = sha256Digest(`${secret}:${payloadDigest}`);
|
|
97
|
+
const dsse = {
|
|
98
|
+
payloadType: "application/vnd.dot-skill.creation-attestation+json",
|
|
99
|
+
payload_digest: payloadDigest,
|
|
100
|
+
signatures: [{ keyid: attestation.agent.key_id, sig }],
|
|
101
|
+
attestation,
|
|
102
|
+
};
|
|
103
|
+
const minted = {
|
|
104
|
+
...unpacked.raw,
|
|
105
|
+
manifest: {
|
|
106
|
+
...unpacked.manifest,
|
|
107
|
+
mint: {
|
|
108
|
+
mint_status: "minted",
|
|
109
|
+
minted_at: attestation.minted_at,
|
|
110
|
+
mint_issuer: attestation.agent.runtime,
|
|
111
|
+
content_id: package_digest,
|
|
112
|
+
},
|
|
113
|
+
attestation_digest: payloadDigest,
|
|
114
|
+
policy: {
|
|
115
|
+
...unpacked.manifest.policy,
|
|
116
|
+
require_signatures: true,
|
|
117
|
+
require_minted: true,
|
|
118
|
+
trust_profile: opts.policy_profile ?? "minted",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
attestation,
|
|
122
|
+
signatures: {
|
|
123
|
+
"creation.dsse.json": dsse,
|
|
124
|
+
},
|
|
125
|
+
anchors: unpacked.raw.anchors ?? unpacked.manifest.anchors,
|
|
126
|
+
};
|
|
127
|
+
const packageBytes = packSkill(minted);
|
|
128
|
+
const verify = unpackSkill(packageBytes);
|
|
129
|
+
if (verify.manifest.package_digest !== package_digest) {
|
|
130
|
+
throw new Error(`Mint changed content digest (${verify.manifest.package_digest} != ${package_digest})`);
|
|
131
|
+
}
|
|
132
|
+
return { files: { ...minted, manifest: verify.manifest }, packageBytes, attestation };
|
|
133
|
+
}
|
|
134
|
+
export function addPermanenceAnchor(archive, anchor) {
|
|
135
|
+
const unpacked = unpackSkill(archive);
|
|
136
|
+
const package_digest = unpacked.manifest.package_digest;
|
|
137
|
+
const full = {
|
|
138
|
+
...anchor,
|
|
139
|
+
package_digest: anchor.package_digest ?? package_digest,
|
|
140
|
+
};
|
|
141
|
+
if (full.package_digest !== package_digest) {
|
|
142
|
+
throw new Error("Anchor package_digest must match skill package_digest");
|
|
143
|
+
}
|
|
144
|
+
const anchors = [...(unpacked.manifest.anchors ?? []), full];
|
|
145
|
+
const files = {
|
|
146
|
+
...unpacked.raw,
|
|
147
|
+
manifest: {
|
|
148
|
+
...unpacked.manifest,
|
|
149
|
+
anchors,
|
|
150
|
+
},
|
|
151
|
+
anchors,
|
|
152
|
+
signatures: {
|
|
153
|
+
...(unpacked.raw.signatures ?? {}),
|
|
154
|
+
[`anchors/${anchors.length}-${full.kind}.json`]: full,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
return packSkill(files);
|
|
158
|
+
}
|
|
159
|
+
export function verifyMintTrust(archive, profile = "minted", issuer_secret) {
|
|
160
|
+
const base = validatePackageBytes(archive);
|
|
161
|
+
const issues = [...base.issues];
|
|
162
|
+
const unpacked = unpackSkill(archive);
|
|
163
|
+
const mintStatus = unpacked.manifest.mint?.mint_status ?? "draft";
|
|
164
|
+
const attestation = unpacked.raw.signatures?.["creation.dsse.json"]?.attestation
|
|
165
|
+
?? unpacked.raw.attestation;
|
|
166
|
+
const envelope = unpacked.raw.signatures?.["creation.dsse.json"];
|
|
167
|
+
if (profile !== "open") {
|
|
168
|
+
if (mintStatus !== "minted") {
|
|
169
|
+
issues.push({
|
|
170
|
+
severity: "error",
|
|
171
|
+
code: "not_minted",
|
|
172
|
+
message: "Trust profile requires mint_status=minted",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (!attestation) {
|
|
176
|
+
issues.push({
|
|
177
|
+
severity: "error",
|
|
178
|
+
code: "missing_attestation",
|
|
179
|
+
message: "Minted skills require CreationAttestation",
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
else if (!envelope?.signatures?.[0]?.sig) {
|
|
183
|
+
issues.push({
|
|
184
|
+
severity: "error",
|
|
185
|
+
code: "missing_attestation_signature",
|
|
186
|
+
message: "Minted trust profile requires a signed DSSE attestation envelope",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
else if (attestation.package_digest !== unpacked.manifest.package_digest) {
|
|
190
|
+
issues.push({
|
|
191
|
+
severity: "error",
|
|
192
|
+
code: "attestation_digest_mismatch",
|
|
193
|
+
message: "Attestation package_digest does not match manifest",
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const payloadDigest = sha256Digest(canonicalize(attestation));
|
|
198
|
+
if (envelope.payload_digest !== payloadDigest) {
|
|
199
|
+
issues.push({
|
|
200
|
+
severity: "error",
|
|
201
|
+
code: "attestation_payload_digest",
|
|
202
|
+
message: "DSSE payload_digest does not match CreationAttestation",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
// REFERENCE IMPLEMENTATION ONLY — replace with real PKI in production.
|
|
206
|
+
const secret = issuer_secret ?? "dot-skill-dev-mint-key";
|
|
207
|
+
const expected = sha256Digest(`${secret}:${payloadDigest}`);
|
|
208
|
+
const sig = envelope?.signatures?.[0]?.sig;
|
|
209
|
+
if (sig !== expected) {
|
|
210
|
+
issues.push({
|
|
211
|
+
severity: "error",
|
|
212
|
+
code: "attestation_sig_invalid",
|
|
213
|
+
message: "CreationAttestation signature failed verification",
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (!issuer_secret && attestation.agent.key_id === "dot-skill-dev-mint-key") {
|
|
217
|
+
issues.push({
|
|
218
|
+
severity: "warning",
|
|
219
|
+
code: "development_attestation",
|
|
220
|
+
message: "Attestation uses the public development key; provenance is self-asserted, not trusted identity",
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (profile === "anchored") {
|
|
226
|
+
const anchors = unpacked.manifest.anchors ?? [];
|
|
227
|
+
if (!anchors.length) {
|
|
228
|
+
issues.push({
|
|
229
|
+
severity: "error",
|
|
230
|
+
code: "anchor_required",
|
|
231
|
+
message: "Trust profile requires at least one PermanenceAnchor",
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (profile.startsWith("issuer:")) {
|
|
236
|
+
const want = profile.slice("issuer:".length);
|
|
237
|
+
if (attestation?.agent.runtime !== want && attestation?.agent.key_id !== want) {
|
|
238
|
+
issues.push({
|
|
239
|
+
severity: "error",
|
|
240
|
+
code: "issuer_mismatch",
|
|
241
|
+
message: `Attestation issuer does not match ${profile}`,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
ok: !issues.some((i) => i.severity === "error"),
|
|
247
|
+
issues,
|
|
248
|
+
attestation,
|
|
249
|
+
};
|
|
250
|
+
}
|
package/dist/pack.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { KnowledgeItem, SkillManifest, SkillPackageFiles, Workflow, CompilationReport } from "@dot-skill/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;
|