@dot-skill/core 0.4.2 → 0.5.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/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # `@dot-skill/core`
2
+
3
+ Compile, pack, validate, and mint `.skill` packages for the [Open `.skill` Protocol](https://github.com/dot-skill/dot-skill).
4
+
5
+ Turns a **SkillSource** / **SkillContract** into a deterministic ZIP with digests, privacy scrubbing, completeness gates, and optional mint attestation.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i @dot-skill/core
11
+ ```
12
+
13
+ ## Capabilities
14
+
15
+ | Function | Role |
16
+ |----------|------|
17
+ | Compile | SkillSource → package files; release profile refuses if incomplete |
18
+ | Pack / unpack | Deterministic ZIP container |
19
+ | Validate | Structure + SHA-256 digests |
20
+ | Mint | Creation attestation (reference HMAC is development-only) |
21
+ | Inspect helpers | Manifest and seal visibility without execution |
22
+
23
+ ## Profiles
24
+
25
+ - **`continuity`** — handoff draft; soft gaps allowed; not mintable
26
+ - **`release`** — complete or `compile_refused`; mintable when approved
27
+
28
+ ## Related
29
+
30
+ - [`@dot-skill/protocol`](https://www.npmjs.com/package/@dot-skill/protocol) — types & contract
31
+ - [`@dot-skill/runtime`](https://www.npmjs.com/package/@dot-skill/runtime) — inspect / dry-run / execute
32
+ - [`skillerr`](https://www.npmjs.com/package/skillerr) — public install (`skill` CLI)
33
+
34
+ Docs: [PROTOCOL.md](https://github.com/dot-skill/dot-skill/blob/main/docs/PROTOCOL.md) · [MINT.md](https://github.com/dot-skill/dot-skill/blob/main/docs/MINT.md)
35
+
36
+ ## License
37
+
38
+ MIT
package/dist/compile.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import { DEFAULT_SKILL_POLICY, CONTAINER_VERSION, PROTOCOL_VERSION, WORKFLOW_DIALECT_VERSION, isValidAgentHost, recipeToSkillSource, } from "@dot-skill/protocol";
2
+ import { DEFAULT_SKILL_POLICY, CONTAINER_VERSION, PROTOCOL_VERSION, WORKFLOW_DIALECT_VERSION, assessSkillContract, isValidAgentHost, recipeToSkillSource, } from "@dot-skill/protocol";
3
3
  import { packSkill, finalizeManifest, buildFileMap } from "./pack.js";
4
4
  const PLACEHOLDER_RE = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}|<([A-Z][A-Z0-9_]+)>|\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
5
5
  const SECRET_PATTERNS = [
@@ -135,14 +135,391 @@ export function assessCompleteness(source, opts) {
135
135
  complete: requiredMissing.length === 0,
136
136
  present,
137
137
  missing: requiredMissing,
138
- hints: requiredMissing.map((m) => HINTS[m]),
138
+ hints: requiredMissing.map((m) => HINTS[m] ?? `Complete the ${m} declaration.`),
139
139
  };
140
140
  }
141
141
  function sectionHasWorkflowAction(sections) {
142
142
  return sections.some((s) => ["integration", "prompt", "implementation_note", "workflow_note", "code", "config"].includes(s.type));
143
143
  }
144
+ function declaredItems(declaration) {
145
+ if (!declaration)
146
+ return [];
147
+ return declaration.status === "specified" ? declaration.items ?? [] : [];
148
+ }
149
+ function contractCompleteness(assessment, profile) {
150
+ const missing = [...new Set(assessment.issues.map((issue) => {
151
+ const root = issue.field.split(".")[0];
152
+ return (root === "contract" ? "semantic_contract" : root);
153
+ }))];
154
+ const contractParts = [
155
+ "semantic_contract",
156
+ "intent",
157
+ "triggers",
158
+ "inputs",
159
+ "preconditions",
160
+ "steps",
161
+ "branches",
162
+ "human_decisions",
163
+ "capabilities",
164
+ "permissions",
165
+ "forbidden_actions",
166
+ "outputs",
167
+ "recovery",
168
+ "verification",
169
+ "corrections",
170
+ "provenance",
171
+ ];
172
+ return {
173
+ kind: "completeness_report",
174
+ profile,
175
+ complete: assessment.complete,
176
+ present: contractParts.filter((part) => !missing.includes(part)),
177
+ missing,
178
+ hints: assessment.issues.map((issue) => `${issue.field}: ${issue.fix}`),
179
+ };
180
+ }
181
+ function compileContractStep(step) {
182
+ const base = {
183
+ id: step.id,
184
+ title: step.title,
185
+ optional: step.optional,
186
+ next: step.next,
187
+ on_fail: step.on_failure,
188
+ };
189
+ switch (step.kind) {
190
+ case "instruct":
191
+ return { ...base, kind: "instruct", text: step.instruction ?? "" };
192
+ case "prompt":
193
+ return { ...base, kind: "prompt", template: step.instruction ?? "" };
194
+ case "tool":
195
+ return {
196
+ ...base,
197
+ kind: "tool",
198
+ capability: step.capability ?? "",
199
+ arguments: step.arguments,
200
+ argument_bindings: step.argument_bindings,
201
+ result_as: step.result_as,
202
+ };
203
+ case "transform":
204
+ return {
205
+ ...base,
206
+ kind: "transform",
207
+ expression: step.instruction ?? "identity",
208
+ result_as: step.result_as,
209
+ };
210
+ case "checkpoint":
211
+ return { ...base, kind: "checkpoint", message: step.instruction };
212
+ case "human_decision":
213
+ return {
214
+ ...base,
215
+ kind: "human_decision",
216
+ prompt: step.instruction ?? step.decision ?? step.title,
217
+ result_as: step.result_as,
218
+ };
219
+ case "verify":
220
+ return { ...base, kind: "verify", assertions: step.assertions ?? [] };
221
+ case "emit":
222
+ return {
223
+ ...base,
224
+ kind: "emit",
225
+ output: step.output ?? "",
226
+ from: step.from ?? "",
227
+ };
228
+ }
229
+ }
230
+ function compileNativeContract(source, opts, profile) {
231
+ const contract = source.contract;
232
+ if (!isValidAgentHost(source.agent.host) || !source.journey?.summary?.trim()) {
233
+ const missing = [];
234
+ const hints = [];
235
+ if (!isValidAgentHost(source.agent.host)) {
236
+ missing.push("agent_context");
237
+ hints.push(HINTS.agent_context);
238
+ }
239
+ if (!source.journey?.summary?.trim()) {
240
+ missing.push("journey");
241
+ hints.push(HINTS.journey);
242
+ }
243
+ throw new CompileRefusalError({
244
+ kind: "completeness_report",
245
+ profile,
246
+ complete: false,
247
+ present: [],
248
+ missing,
249
+ hints,
250
+ });
251
+ }
252
+ const assessment = assessSkillContract(contract, profile);
253
+ const completeness = contractCompleteness(assessment, profile);
254
+ if (!assessment.complete && !opts.allow_incomplete && profile === "release") {
255
+ throw new CompileRefusalError(completeness);
256
+ }
257
+ const skillId = opts.skill_id ?? shortId("skl");
258
+ const inputs = declaredItems(contract.inputs).map((input) => ({
259
+ name: input.name,
260
+ description: input.description,
261
+ schema: structuredClone(input.schema),
262
+ required: input.required,
263
+ ...(input.default !== undefined ? { default: structuredClone(input.default) } : {}),
264
+ sensitivity: input.sensitivity,
265
+ source: input.source,
266
+ ask_when: input.ask_when,
267
+ approval: input.approval,
268
+ // Contract review approves the slot definition; human_before_use remains a runtime gate.
269
+ approved: true,
270
+ }));
271
+ const outputs = declaredItems(contract.outputs).map((output) => ({
272
+ name: output.name,
273
+ description: output.description,
274
+ schema: structuredClone(output.schema),
275
+ required: output.required,
276
+ media_type: output.media_type,
277
+ assert: output.assertions,
278
+ }));
279
+ const capabilities = declaredItems(contract.capabilities).map((capability) => ({
280
+ ...structuredClone(capability),
281
+ }));
282
+ const permissions = declaredItems(contract.permissions).map((permission) => ({
283
+ side_effect_class: permission.side_effect_class,
284
+ description: permission.description,
285
+ paths: permission.paths,
286
+ hosts: permission.hosts,
287
+ requires_consent: permission.consent === "explicit_human",
288
+ }));
289
+ const knowledge = source.sections.map((section) => ({
290
+ kind: "knowledge",
291
+ id: `k_${section.id.replace(/[^a-zA-Z0-9]/g, "").slice(0, 16)}`,
292
+ type: knowledgeTypeFor(section),
293
+ title: section.title,
294
+ body: redactSecrets(section.body),
295
+ fidelity: "exact",
296
+ pinned: true,
297
+ sensitivity: section.sensitivity === "public" ? "public" : "private",
298
+ provenance: [{ kind: "section", id: section.id, revision: section.revision, hash: source.hash }],
299
+ }));
300
+ const steps = declaredItems(contract.steps).map(compileContractStep);
301
+ for (let i = 0; i < steps.length - 1; i++) {
302
+ if (!steps[i].next)
303
+ steps[i].next = steps[i + 1].id;
304
+ }
305
+ let entrypoint = steps[0]?.id;
306
+ for (const branch of declaredItems(contract.branches)) {
307
+ const branchStep = {
308
+ id: branch.id,
309
+ kind: "branch",
310
+ title: `Conditional branch: ${branch.condition}`,
311
+ cases: [{ when: branch.condition, goto: branch.then }],
312
+ else: branch.otherwise,
313
+ };
314
+ steps.push(branchStep);
315
+ if (branch.after_step) {
316
+ const parent = steps.find((step) => step.id === branch.after_step);
317
+ if (parent)
318
+ parent.next = branch.id;
319
+ }
320
+ }
321
+ for (const decision of declaredItems(contract.human_decisions)) {
322
+ const decisionStep = {
323
+ id: decision.id,
324
+ kind: "human_decision",
325
+ title: decision.prompt,
326
+ prompt: decision.prompt,
327
+ choices: decision.choices,
328
+ result_as: decision.id,
329
+ next: decision.required_before,
330
+ };
331
+ for (const step of steps) {
332
+ if (step.next === decision.required_before)
333
+ step.next = decision.id;
334
+ }
335
+ if (entrypoint === decision.required_before)
336
+ entrypoint = decision.id;
337
+ steps.push(decisionStep);
338
+ }
339
+ for (const input of declaredItems(contract.inputs).filter((item) => item.approval === "human_before_use")) {
340
+ const id = `approve_input_${slug(input.name)}`;
341
+ steps.push({
342
+ id,
343
+ kind: "human_decision",
344
+ title: `Approve input ${input.name}`,
345
+ prompt: `Approve use of input ${input.name}: ${input.description}`,
346
+ choices: ["approve", "deny"],
347
+ result_as: id,
348
+ next: entrypoint,
349
+ });
350
+ entrypoint = id;
351
+ }
352
+ for (const edge of declaredItems(contract.recovery)) {
353
+ const from = steps.find((step) => step.id === edge.from_step);
354
+ if (from && edge.goto)
355
+ from.on_fail = edge.goto;
356
+ }
357
+ if (declaredItems(contract.preconditions).length) {
358
+ const id = "contract_preconditions";
359
+ steps.push({
360
+ id,
361
+ kind: "verify",
362
+ title: "Verify contract preconditions",
363
+ assertions: declaredItems(contract.preconditions).map((item) => `precondition:${item.id}`),
364
+ next: entrypoint,
365
+ });
366
+ entrypoint = id;
367
+ }
368
+ const verification = declaredItems(contract.verification);
369
+ if (verification.length) {
370
+ const id = "contract_verification";
371
+ const terminal = steps.find((step) => !step.next && step.id !== id);
372
+ if (terminal)
373
+ terminal.next = id;
374
+ steps.push({
375
+ id,
376
+ kind: "verify",
377
+ title: "Verify domain assertions",
378
+ assertions: verification.map((item) => `contract_assertion:${item.id}`),
379
+ });
380
+ if (!entrypoint)
381
+ entrypoint = id;
382
+ }
383
+ if (!entrypoint) {
384
+ const id = "contract_noop";
385
+ steps.push({
386
+ id,
387
+ kind: "instruct",
388
+ title: "No operational steps declared",
389
+ text: contract.intent,
390
+ });
391
+ entrypoint = id;
392
+ }
393
+ const constraints = declaredItems(contract.forbidden_actions).map((forbidden) => ({
394
+ kind: "steering_constraint",
395
+ id: forbidden.id,
396
+ verb: "reject",
397
+ effect: "forbidden",
398
+ statement: forbidden.description,
399
+ }));
400
+ const report = {
401
+ kind: "compilation_report",
402
+ skill_id: skillId,
403
+ source_id: source.id,
404
+ profile,
405
+ created_at: new Date().toISOString(),
406
+ mappings: [],
407
+ inferred_inputs: [],
408
+ issues: assessment.issues.map((issue) => ({
409
+ severity: profile === "release" ? "error" : "warning",
410
+ code: `contract_${issue.code}`,
411
+ message: `${issue.field}: ${issue.message}`,
412
+ related: [issue.field],
413
+ })),
414
+ pending_approvals: [],
415
+ approved: contract.provenance.human_review.status === "reviewed",
416
+ completeness,
417
+ semantic_contract: "native_0.5",
418
+ };
419
+ const safeJourney = {
420
+ ...source.journey,
421
+ summary: redactSecrets(source.journey.summary),
422
+ open_questions: source.journey.open_questions?.map(redactSecrets),
423
+ decisions: source.journey.decisions?.map(redactSecrets),
424
+ redacted: true,
425
+ };
426
+ const files = {
427
+ manifest: {
428
+ kind: "dot-skill",
429
+ id: skillId,
430
+ version: opts.version ?? "1.0.0",
431
+ title: opts.title ?? contract.title,
432
+ description: opts.description ?? source.summary ?? contract.intent,
433
+ intent: contract.intent,
434
+ contract: structuredClone(contract),
435
+ triggers: declaredItems(contract.triggers),
436
+ preconditions: structuredClone(contract.preconditions),
437
+ branches: structuredClone(contract.branches),
438
+ human_decisions: structuredClone(contract.human_decisions),
439
+ forbidden_actions: structuredClone(contract.forbidden_actions),
440
+ recovery: structuredClone(contract.recovery),
441
+ verification: structuredClone(contract.verification),
442
+ corrections: structuredClone(contract.corrections),
443
+ authors: source.actor ? [source.actor] : undefined,
444
+ container_version: CONTAINER_VERSION,
445
+ protocol_version: PROTOCOL_VERSION,
446
+ entrypoint,
447
+ inputs,
448
+ outputs,
449
+ capabilities,
450
+ permissions,
451
+ policy: { ...DEFAULT_SKILL_POLICY },
452
+ content: [],
453
+ package_digest: "sha256:" + "0".repeat(64),
454
+ provenance_mode: opts.provenance_mode ?? (profile === "continuity" ? "redacted" : "full"),
455
+ compile_profile: profile,
456
+ completeness,
457
+ package_sensitivity: contract.sensitivity,
458
+ mint: { mint_status: "draft" },
459
+ needs_human_review: profile === "continuity" || contract.provenance.human_review.status !== "reviewed",
460
+ },
461
+ workflow: {
462
+ kind: "workflow",
463
+ dialect_version: WORKFLOW_DIALECT_VERSION,
464
+ entrypoint,
465
+ steps,
466
+ constraints,
467
+ preconditions: structuredClone(contract.preconditions),
468
+ branches: structuredClone(contract.branches),
469
+ human_decisions: structuredClone(contract.human_decisions),
470
+ recovery: structuredClone(contract.recovery),
471
+ verification: structuredClone(contract.verification),
472
+ },
473
+ knowledge,
474
+ provenance: {
475
+ source: opts.provenance_mode === "proof_only"
476
+ ? undefined
477
+ : {
478
+ id: source.id,
479
+ hash: source.hash,
480
+ title: source.title,
481
+ contract: structuredClone(contract),
482
+ agent: {
483
+ ...source.agent,
484
+ endpoint: source.agent.endpoint
485
+ ? redactSecrets(source.agent.endpoint)
486
+ : undefined,
487
+ },
488
+ section_ids: source.sections.map((section) => `${section.id}@${section.revision}`),
489
+ },
490
+ journey: safeJourney,
491
+ generation_usage: opts.generation_usage ?? source.generation_usage,
492
+ proof: { source_id: source.id, source_hash: source.hash },
493
+ compilation_report: report,
494
+ },
495
+ };
496
+ const fileMap = buildFileMap(files);
497
+ files.manifest = finalizeManifest(files.manifest, fileMap);
498
+ const packageBytes = packSkill(files);
499
+ return {
500
+ files,
501
+ report,
502
+ packageBytes,
503
+ pending_approvals: [],
504
+ completeness,
505
+ };
506
+ }
144
507
  export function compileSkillSource(source, opts = {}) {
145
508
  const profile = opts.profile ?? "release";
509
+ if (source.contract)
510
+ return compileNativeContract(source, opts, profile);
511
+ if (profile === "release") {
512
+ throw new CompileRefusalError({
513
+ kind: "completeness_report",
514
+ profile,
515
+ complete: false,
516
+ present: [],
517
+ missing: ["semantic_contract"],
518
+ hints: [
519
+ "Legacy 0.4 SkillSource/Recipe text is a lossy adapter. Add a protocol 0.5 SkillContract and assess it before release compile; use continuity only for migration.",
520
+ ],
521
+ });
522
+ }
146
523
  const skillId = opts.skill_id ?? shortId("skl");
147
524
  const version = opts.version ?? "1.0.0";
148
525
  const issues = [];
@@ -380,8 +757,12 @@ export function compileSkillSource(source, opts = {}) {
380
757
  hasInputsDeclared,
381
758
  pendingApprovals: pending,
382
759
  });
383
- if (!completeness.complete && !opts.allow_incomplete && profile === "release") {
384
- throw new CompileRefusalError(completeness);
760
+ if (!source.contract) {
761
+ completeness.complete = false;
762
+ if (!completeness.missing.includes("semantic_contract")) {
763
+ completeness.missing.push("semantic_contract");
764
+ }
765
+ completeness.hints.push("Add a 0.5 SkillContract. Legacy text was retained for continuity but structured semantics are unknown.");
385
766
  }
386
767
  if (!completeness.complete && profile === "continuity") {
387
768
  // continuity still requires hard parts
@@ -429,6 +810,13 @@ export function compileSkillSource(source, opts = {}) {
429
810
  pending_approvals: pending,
430
811
  approved: pending.length === 0,
431
812
  completeness,
813
+ semantic_contract: "legacy_lossy",
814
+ losses: [
815
+ "intent/triggers may be inferred from title or summary",
816
+ "inputs inferred from placeholders lose original schema semantics",
817
+ "preconditions, branches, outputs, recovery, and verification may remain prose",
818
+ "human review cannot be reconstructed from legacy text",
819
+ ],
432
820
  };
433
821
  const files = {
434
822
  manifest: {
@@ -440,7 +828,7 @@ export function compileSkillSource(source, opts = {}) {
440
828
  source.summary ??
441
829
  `Skill compiled from source ${source.id}`,
442
830
  intent: source.intent ?? source.summary,
443
- triggers: [source.title],
831
+ triggers: [{ id: "legacy_title", description: source.title }],
444
832
  authors: source.actor ? [source.actor] : undefined,
445
833
  container_version: CONTAINER_VERSION,
446
834
  protocol_version: PROTOCOL_VERSION,
@@ -476,6 +864,7 @@ export function compileSkillSource(source, opts = {}) {
476
864
  package_sensitivity: source.sensitivity,
477
865
  mint: { mint_status: "draft" },
478
866
  needs_human_review: pending.length > 0 || profile === "continuity",
867
+ legacy: true,
479
868
  },
480
869
  workflow: {
481
870
  kind: "workflow",
package/dist/hash.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { SealedManifestClaims, SkillManifest } from "@dot-skill/protocol";
1
2
  /** RFC 8785-inspired JSON Canonicalization for I-JSON-compatible objects. */
2
3
  export declare function canonicalize(value: unknown): string;
3
4
  export declare function sha256Hex(data: string | Uint8Array): string;
@@ -6,3 +7,12 @@ export declare function packageDigestFromContent(content: Array<{
6
7
  path: string;
7
8
  digest: string;
8
9
  }>): string;
10
+ /** Public development HMAC key — forgeable; never production trust. */
11
+ export declare const PUBLIC_DEV_MINT_KEY = "dot-skill-dev-mint-key";
12
+ export declare const PUBLIC_DEV_MINT_KEY_ID = "dot-skill-dev-mint-key";
13
+ /**
14
+ * Build the claim set covered by the creation seal.
15
+ * Binds identity, intent, permissions, policy, capabilities, inputs, and content digests.
16
+ */
17
+ export declare function buildSealedManifestClaims(manifest: SkillManifest): SealedManifestClaims;
18
+ export declare function sealedManifestDigest(manifest: SkillManifest): string;
package/dist/hash.js CHANGED
@@ -50,3 +50,73 @@ export function packageDigestFromContent(content) {
50
50
  }
51
51
  return sha256Digest(canonicalize({ paths }));
52
52
  }
53
+ /** Public development HMAC key — forgeable; never production trust. */
54
+ export const PUBLIC_DEV_MINT_KEY = "dot-skill-dev-mint-key";
55
+ export const PUBLIC_DEV_MINT_KEY_ID = "dot-skill-dev-mint-key";
56
+ /**
57
+ * Build the claim set covered by the creation seal.
58
+ * Binds identity, intent, permissions, policy, capabilities, inputs, and content digests.
59
+ */
60
+ export function buildSealedManifestClaims(manifest) {
61
+ const claims = {
62
+ id: manifest.id,
63
+ version: manifest.version,
64
+ title: manifest.title,
65
+ intent: manifest.intent,
66
+ description: manifest.description,
67
+ package_digest: manifest.package_digest,
68
+ permissions: [...manifest.permissions]
69
+ .map((p) => ({
70
+ side_effect_class: p.side_effect_class,
71
+ description: p.description,
72
+ paths: p.paths,
73
+ hosts: p.hosts,
74
+ requires_consent: p.requires_consent,
75
+ }))
76
+ .sort((a, b) => `${a.side_effect_class}:${a.description}`.localeCompare(`${b.side_effect_class}:${b.description}`)),
77
+ policy: {
78
+ require_signatures: manifest.policy.require_signatures,
79
+ require_minted: manifest.policy.require_minted,
80
+ require_anchor: manifest.policy.require_anchor,
81
+ allow_network: manifest.policy.allow_network,
82
+ filesystem_roots: manifest.policy.filesystem_roots
83
+ ? [...manifest.policy.filesystem_roots].sort()
84
+ : undefined,
85
+ consent_for: [...manifest.policy.consent_for].sort(),
86
+ trust_profile: manifest.policy.trust_profile,
87
+ max_tool_calls: manifest.policy.max_tool_calls,
88
+ max_runtime_ms: manifest.policy.max_runtime_ms,
89
+ fail_on_unsupported_step: manifest.policy.fail_on_unsupported_step,
90
+ },
91
+ capabilities: [...manifest.capabilities]
92
+ .map((c) => ({
93
+ name: c.name,
94
+ side_effect_class: c.side_effect_class,
95
+ required: c.required,
96
+ }))
97
+ .sort((a, b) => a.name.localeCompare(b.name)),
98
+ inputs: [...manifest.inputs]
99
+ .map((i) => ({
100
+ name: i.name,
101
+ sensitivity: i.sensitivity,
102
+ required: i.required,
103
+ source: i.source,
104
+ }))
105
+ .sort((a, b) => a.name.localeCompare(b.name)),
106
+ content: [...manifest.content]
107
+ .map((c) => ({ path: c.path, digest: c.digest, media_type: c.media_type, bytes: c.bytes }))
108
+ .sort((a, b) => a.path.localeCompare(b.path)),
109
+ };
110
+ if (manifest.contract) {
111
+ claims.contract = {
112
+ title: manifest.contract.title,
113
+ intent: manifest.contract.intent,
114
+ skill_kind: manifest.contract.skill_kind,
115
+ sensitivity: manifest.contract.sensitivity,
116
+ };
117
+ }
118
+ return claims;
119
+ }
120
+ export function sealedManifestDigest(manifest) {
121
+ return sha256Digest(canonicalize(buildSealedManifestClaims(manifest)));
122
+ }
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  /** @dot-skill/core — pack, unpack, validate, mint, compile .skill packages. */
2
- export { canonicalize, sha256Hex, sha256Digest, packageDigestFromContent } from "./hash.js";
2
+ export { canonicalize, sha256Hex, sha256Digest, packageDigestFromContent, buildSealedManifestClaims, sealedManifestDigest, PUBLIC_DEV_MINT_KEY, PUBLIC_DEV_MINT_KEY_ID, } from "./hash.js";
3
3
  export { normalizePath, assertSafePaths, MAX_ENTRIES, MAX_UNCOMPRESSED_BYTES, MAX_COMPRESSION_RATIO, } from "./paths.js";
4
4
  export { buildFileMap, finalizeManifest, packSkill, unpackSkill, } from "./pack.js";
5
5
  export type { PackOptions, UnpackResult } from "./pack.js";
6
6
  export { validateManifestShape, validateWorkflowShape, validatePackageBytes, inspectSkill, } from "./validate.js";
7
7
  export type { ValidationIssue, ValidationResult } from "./validate.js";
8
8
  export { migrateLegacySkill, toSkillMdAdapter } from "./migrate.js";
9
- export { mintSkillPackage, addPermanenceAnchor, verifyMintTrust, } from "./mint.js";
10
- export type { MintOptions } from "./mint.js";
9
+ export { mintSkillPackage, addPermanenceAnchor, verifyMintTrust, inspectTrustView, } from "./mint.js";
10
+ export type { MintOptions, VerifyMintTrustOptions } from "./mint.js";
11
11
  export { compileSkillSource, compileRecipeToSkill, approveCompilation, assessCompleteness, redactSecrets, CompileRefusalError, } from "./compile.js";
12
12
  export type { CompileOptions, CompileResult } from "./compile.js";
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /** @dot-skill/core — pack, unpack, validate, mint, compile .skill packages. */
2
- export { canonicalize, sha256Hex, sha256Digest, packageDigestFromContent } from "./hash.js";
2
+ export { canonicalize, sha256Hex, sha256Digest, packageDigestFromContent, buildSealedManifestClaims, sealedManifestDigest, PUBLIC_DEV_MINT_KEY, PUBLIC_DEV_MINT_KEY_ID, } from "./hash.js";
3
3
  export { normalizePath, assertSafePaths, MAX_ENTRIES, MAX_UNCOMPRESSED_BYTES, MAX_COMPRESSION_RATIO, } from "./paths.js";
4
4
  export { buildFileMap, finalizeManifest, packSkill, unpackSkill, } from "./pack.js";
5
5
  export { validateManifestShape, validateWorkflowShape, validatePackageBytes, inspectSkill, } from "./validate.js";
6
6
  export { migrateLegacySkill, toSkillMdAdapter } from "./migrate.js";
7
- export { mintSkillPackage, addPermanenceAnchor, verifyMintTrust, } from "./mint.js";
7
+ export { mintSkillPackage, addPermanenceAnchor, verifyMintTrust, inspectTrustView, } from "./mint.js";
8
8
  export { compileSkillSource, compileRecipeToSkill, approveCompilation, assessCompleteness, redactSecrets, CompileRefusalError, } from "./compile.js";
package/dist/mint.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CreationAttestation, PermanenceAnchor, SkillPackageFiles, TrustProfile } from "@dot-skill/protocol";
1
+ import type { CreationAttestation, HostClaimBinding, PermanenceAnchor, SkillPackageFiles, TrustProfile, TrustState, TrustView } from "@dot-skill/protocol";
2
2
  import { type ValidationIssue } from "./validate.js";
3
3
  export interface MintOptions {
4
4
  host: string;
@@ -11,16 +11,47 @@ export interface MintOptions {
11
11
  endpoint?: string;
12
12
  actors?: string[];
13
13
  /**
14
- * HMAC-style digest seal for development/testing only.
15
- * REFERENCE IMPLEMENTATION ONLYnot production PKI.
16
- * Default key is `dot-skill-dev-mint-key` and must be replaced in production.
14
+ * HMAC-style digest seal.
15
+ * Omit / use the public constant only for local development never production trust.
17
16
  */
18
17
  issuer_secret?: string;
19
18
  policy_profile?: TrustProfile;
19
+ /**
20
+ * Evidence that mint was invoked from an agent runtime path (not a bare human shell
21
+ * that only exported SKILL_HOST). Markers are still locally spoofable; residual risk
22
+ * for local LLMs remains.
23
+ */
24
+ agent_runtime_evidence?: {
25
+ markers?: string[];
26
+ session_id?: string;
27
+ };
28
+ /**
29
+ * Only set when a non-public issuer key authenticates the host/model claim.
30
+ * Default is always self_reported.
31
+ */
32
+ host_claim_binding?: HostClaimBinding;
33
+ /** Env snapshot for marker detection (defaults to process.env). */
34
+ env?: Record<string, string | undefined>;
35
+ }
36
+ export interface VerifyMintTrustOptions {
37
+ issuer_secret?: string;
38
+ /**
39
+ * When true, public-dev HMAC may pass structural verification as trust_state=development.
40
+ * Production execute paths must leave this false (fail closed).
41
+ */
42
+ allow_development_issuer?: boolean;
43
+ /** Accept self_reported host binding as ok for the requested profile. Default false for minted+. */
44
+ allow_self_reported?: boolean;
20
45
  }
21
46
  /**
22
47
  * Seal a draft package as minted.
23
48
  * Content under signatures/ may change; package_digest (content) stays fixed after finalize.
49
+ *
50
+ * Trust rules:
51
+ * - Forbidden hosts (human/cli/shell/manual/…) refuse mint entirely.
52
+ * - Env-only SKILL_HOST without agent runtime markers still mints, but as self_reported
53
+ * + public_dev_hmac — never production trust.
54
+ * - Seal binds sealed_manifest_digest (identity + policy + content claims).
24
55
  */
25
56
  export declare function mintSkillPackage(pkg: SkillPackageFiles, opts: MintOptions): {
26
57
  files: SkillPackageFiles;
@@ -30,8 +61,13 @@ export declare function mintSkillPackage(pkg: SkillPackageFiles, opts: MintOptio
30
61
  export declare function addPermanenceAnchor(archive: Uint8Array, anchor: Omit<PermanenceAnchor, "package_digest"> & {
31
62
  package_digest?: string;
32
63
  }): Uint8Array;
33
- export declare function verifyMintTrust(archive: Uint8Array, profile?: TrustProfile, issuer_secret?: string): {
64
+ export declare function verifyMintTrust(archive: Uint8Array, profile?: TrustProfile, issuer_secret_or_opts?: string | VerifyMintTrustOptions): {
34
65
  ok: boolean;
35
66
  issues: ValidationIssue[];
36
67
  attestation?: CreationAttestation;
68
+ trust_state: TrustState;
37
69
  };
70
+ /**
71
+ * TrustView from skill.json + signatures + digests only — no compile, no model body ingest.
72
+ */
73
+ export declare function inspectTrustView(archive: Uint8Array): TrustView;
package/dist/mint.js CHANGED
@@ -1,14 +1,48 @@
1
- import { isValidAgentHost } from "@dot-skill/protocol";
2
- import { canonicalize, sha256Digest } from "./hash.js";
1
+ import { readFileSync } from "node:fs";
2
+ import { detectAgentRuntimeMarkers, hasAgentRuntimeEvidence, isValidAgentHost, } from "@dot-skill/protocol";
3
+ import { canonicalize, PUBLIC_DEV_MINT_KEY, PUBLIC_DEV_MINT_KEY_ID, sealedManifestDigest, sha256Digest, } from "./hash.js";
3
4
  import { packSkill, unpackSkill } from "./pack.js";
4
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 @dot-skill/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
+ }
5
32
  /**
6
33
  * Seal a draft package as minted.
7
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).
8
41
  */
9
42
  export function mintSkillPackage(pkg, opts) {
10
43
  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, …`);
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.`);
12
46
  }
13
47
  if (pkg.manifest.needs_human_review) {
14
48
  throw new Error("Cannot mint while needs_human_review is true — approve inputs/permissions first");
@@ -22,6 +56,7 @@ export function mintSkillPackage(pkg, opts) {
22
56
  const report = pkg.provenance?.compilation_report;
23
57
  if (!report ||
24
58
  report.profile !== "release" ||
59
+ report.semantic_contract !== "native_0.5" ||
25
60
  !report.completeness.complete ||
26
61
  !report.approved ||
27
62
  report.pending_approvals.length > 0) {
@@ -39,22 +74,48 @@ export function mintSkillPackage(pkg, opts) {
39
74
  });
40
75
  const unpacked = unpackSkill(draftBytes);
41
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);
42
99
  const attestation = {
43
100
  kind: "creation_attestation",
44
101
  package_digest,
102
+ sealed_manifest_digest,
45
103
  skill_id: pkg.manifest.id,
46
104
  skill_version: pkg.manifest.version,
47
105
  minted_at: new Date().toISOString(),
48
106
  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",
107
+ runtime: agentRuntime,
108
+ version: agentVersion,
109
+ key_id: keyId,
52
110
  },
53
111
  host: opts.host,
54
112
  provider: opts.provider,
55
113
  model: opts.model,
56
114
  deployment: opts.deployment,
57
115
  endpoint: opts.endpoint,
116
+ host_claim_binding,
117
+ issuer_class,
118
+ agent_runtime_markers: markers.length ? markers : undefined,
58
119
  journey: {
59
120
  source_id: pkg.provenance?.compilation_report?.source_id,
60
121
  source_hash: pkg.provenance?.proof &&
@@ -87,12 +148,6 @@ export function mintSkillPackage(pkg, opts) {
87
148
  };
88
149
  const payload = canonicalize(attestation);
89
150
  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
151
  const sig = sha256Digest(`${secret}:${payloadDigest}`);
97
152
  const dsse = {
98
153
  payloadType: "application/vnd.dot-skill.creation-attestation+json",
@@ -103,7 +158,7 @@ export function mintSkillPackage(pkg, opts) {
103
158
  const minted = {
104
159
  ...unpacked.raw,
105
160
  manifest: {
106
- ...unpacked.manifest,
161
+ ...sealedManifestBase,
107
162
  mint: {
108
163
  mint_status: "minted",
109
164
  minted_at: attestation.minted_at,
@@ -111,12 +166,7 @@ export function mintSkillPackage(pkg, opts) {
111
166
  content_id: package_digest,
112
167
  },
113
168
  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
- },
169
+ sealed_manifest_digest,
120
170
  },
121
171
  attestation,
122
172
  signatures: {
@@ -156,14 +206,30 @@ export function addPermanenceAnchor(archive, anchor) {
156
206
  };
157
207
  return packSkill(files);
158
208
  }
159
- export function verifyMintTrust(archive, profile = "minted", issuer_secret) {
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 ?? {});
160
227
  const base = validatePackageBytes(archive);
161
228
  const issues = [...base.issues];
162
229
  const unpacked = unpackSkill(archive);
163
230
  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"];
231
+ const { attestation, envelope } = extractAttestation(unpacked);
232
+ let signedOk = false;
167
233
  if (profile !== "open") {
168
234
  if (mintStatus !== "minted") {
169
235
  issues.push({
@@ -194,6 +260,30 @@ export function verifyMintTrust(archive, profile = "minted", issuer_secret) {
194
260
  });
195
261
  }
196
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
+ }
197
287
  const payloadDigest = sha256Digest(canonicalize(attestation));
198
288
  if (envelope.payload_digest !== payloadDigest) {
199
289
  issues.push({
@@ -202,26 +292,68 @@ export function verifyMintTrust(archive, profile = "minted", issuer_secret) {
202
292
  message: "DSSE payload_digest does not match CreationAttestation",
203
293
  });
204
294
  }
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) {
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) {
210
301
  issues.push({
211
302
  severity: "error",
212
- code: "attestation_sig_invalid",
213
- message: "CreationAttestation signature failed verification",
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.",
214
306
  });
215
307
  }
216
- if (!issuer_secret && attestation.agent.key_id === "dot-skill-dev-mint-key") {
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") {
217
343
  issues.push({
218
344
  severity: "warning",
219
345
  code: "development_attestation",
220
- message: "Attestation uses the public development key; provenance is self-asserted, not trusted identity",
346
+ message: "Attestation uses the public development key labeled development, never production identity",
221
347
  });
222
348
  }
223
349
  }
224
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
+ }
225
357
  if (profile === "anchored") {
226
358
  const anchors = unpacked.manifest.anchors ?? [];
227
359
  if (!anchors.length) {
@@ -242,9 +374,94 @@ export function verifyMintTrust(archive, profile = "minted", issuer_secret) {
242
374
  });
243
375
  }
244
376
  }
377
+ const trust_state = classifyTrustState(attestation, signedOk);
245
378
  return {
246
379
  ok: !issues.some((i) => i.severity === "error"),
247
380
  issues,
248
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,
249
466
  };
250
467
  }
@@ -21,12 +21,16 @@ export declare function inspectSkill(archive: Uint8Array): {
21
21
  version: string;
22
22
  title: string;
23
23
  description: string;
24
+ intent?: string;
24
25
  inputs: string[];
25
26
  permissions: string[];
26
27
  capabilities: string[];
27
28
  package_digest: string;
29
+ sealed_manifest_digest?: string;
28
30
  mint_status?: string;
29
31
  needs_human_review?: boolean;
32
+ trust_label?: string;
33
+ trust_state?: string;
30
34
  };
31
35
  issues: ValidationIssue[];
32
36
  };
package/dist/validate.js CHANGED
@@ -1,4 +1,4 @@
1
- import { CONTAINER_VERSION, PROTOCOL_VERSION, WORKFLOW_DIALECT_VERSION, } from "@dot-skill/protocol";
1
+ import { assessSkillContract, CONTAINER_VERSION, PROTOCOL_VERSION, WORKFLOW_DIALECT_VERSION, } from "@dot-skill/protocol";
2
2
  import { packageDigestFromContent, sha256Digest } from "./hash.js";
3
3
  import { unpackSkill } from "./pack.js";
4
4
  export function validateManifestShape(manifest) {
@@ -38,6 +38,25 @@ export function validateManifestShape(manifest) {
38
38
  message: `Unsupported protocol_version ${manifest.protocol_version}; expected ${PROTOCOL_VERSION}`,
39
39
  });
40
40
  }
41
+ if (manifest.compile_profile === "release") {
42
+ if (!manifest.contract) {
43
+ issues.push({
44
+ severity: "error",
45
+ code: "release_contract_missing",
46
+ message: "Release packages require a native 0.5 authoring contract",
47
+ });
48
+ }
49
+ else {
50
+ for (const issue of assessSkillContract(manifest.contract, "release").issues) {
51
+ issues.push({
52
+ severity: "error",
53
+ code: `contract_${issue.code}`,
54
+ message: `${issue.field}: ${issue.message}`,
55
+ path: issue.field,
56
+ });
57
+ }
58
+ }
59
+ }
41
60
  if (manifest.mint?.mint_status === "minted") {
42
61
  if (manifest.compile_profile !== "release") {
43
62
  issues.push({
@@ -94,6 +113,23 @@ export function validateWorkflowShape(workflow, entrypoint) {
94
113
  message: "Each step needs id and kind",
95
114
  });
96
115
  }
116
+ const refs = [
117
+ ...(typeof step.next === "string" ? [step.next] : step.next ?? []),
118
+ ...(step.on_fail ? [step.on_fail] : []),
119
+ ...(step.kind === "branch"
120
+ ? [...step.cases.map((branch) => branch.goto), ...(step.else ? [step.else] : [])]
121
+ : []),
122
+ ];
123
+ for (const ref of refs) {
124
+ if (!ids.has(ref)) {
125
+ issues.push({
126
+ severity: "error",
127
+ code: "step_reference_missing",
128
+ message: `Step ${step.id} references missing step ${ref}`,
129
+ path: step.id,
130
+ });
131
+ }
132
+ }
97
133
  }
98
134
  return issues;
99
135
  }
@@ -191,11 +227,30 @@ export function inspectSkill(archive) {
191
227
  permissions: [],
192
228
  capabilities: [],
193
229
  package_digest: "",
230
+ trust_label: "INVALID",
231
+ trust_state: "untrusted",
194
232
  },
195
233
  issues: result.issues,
196
234
  };
197
235
  }
198
236
  const m = result.manifest;
237
+ const mint_status = m.mint?.mint_status ?? "draft";
238
+ const signed = Boolean((result.manifest &&
239
+ Object.keys(
240
+ // signatures live outside validate result — infer from mint + attestation_digest
241
+ m.attestation_digest ? { signed: true } : {}).length) ||
242
+ mint_status === "minted");
243
+ // Lightweight label without full TrustView import cycle; CLI --trust uses inspectTrustView.
244
+ let trust_label = "UNSIGNED / OPEN — untrusted";
245
+ let trust_state = "untrusted";
246
+ if (mint_status === "minted" && m.attestation_digest) {
247
+ trust_label = "SEALED — use skill inspect --trust for issuer/host details";
248
+ trust_state = "self_reported";
249
+ }
250
+ else if (!signed || mint_status === "draft") {
251
+ trust_label = "UNSIGNED / OPEN — untrusted";
252
+ trust_state = "untrusted";
253
+ }
199
254
  return {
200
255
  ok: result.ok,
201
256
  summary: {
@@ -203,12 +258,16 @@ export function inspectSkill(archive) {
203
258
  version: m.version,
204
259
  title: m.title,
205
260
  description: m.description,
261
+ intent: m.intent,
206
262
  inputs: m.inputs.filter((i) => i.required).map((i) => i.name),
207
263
  permissions: m.permissions.map((p) => p.side_effect_class),
208
264
  capabilities: m.capabilities.map((c) => c.name),
209
265
  package_digest: m.package_digest,
210
- mint_status: m.mint?.mint_status ?? "draft",
266
+ sealed_manifest_digest: m.sealed_manifest_digest,
267
+ mint_status,
211
268
  needs_human_review: m.needs_human_review,
269
+ trust_label,
270
+ trust_state,
212
271
  },
213
272
  issues: result.issues,
214
273
  };
package/package.json CHANGED
@@ -1,12 +1,21 @@
1
1
  {
2
2
  "name": "@dot-skill/core",
3
- "version": "0.4.2",
4
- "description": "Open .skill Protocol — pack, validate, compile, mint",
3
+ "version": "0.5.0",
4
+ "description": "Compile, pack, validate, and mint portable .skill packages",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Bharat Dudeja",
8
8
  "url": "https://github.com/bharatdudeja13-cmd"
9
9
  },
10
+ "keywords": [
11
+ "skill",
12
+ "dot-skill",
13
+ ".skill",
14
+ "ai-agents",
15
+ "agent-skills",
16
+ "compile",
17
+ "mint"
18
+ ],
10
19
  "type": "module",
11
20
  "main": "./dist/index.js",
12
21
  "types": "./dist/index.d.ts",
@@ -18,8 +27,14 @@
18
27
  "url": "https://github.com/dot-skill/dot-skill.git",
19
28
  "directory": "packages/core"
20
29
  },
30
+ "homepage": "https://github.com/dot-skill/dot-skill#readme",
31
+ "bugs": {
32
+ "url": "https://github.com/dot-skill/dot-skill/issues"
33
+ },
21
34
  "files": [
22
- "dist"
35
+ "dist",
36
+ "README.md",
37
+ "LICENSE"
23
38
  ],
24
39
  "exports": {
25
40
  ".": {
@@ -33,7 +48,7 @@
33
48
  "clean": "rm -rf dist"
34
49
  },
35
50
  "dependencies": {
36
- "@dot-skill/protocol": "^0.4.2",
51
+ "@dot-skill/protocol": "^0.5.0",
37
52
  "fflate": "^0.8.2"
38
53
  },
39
54
  "devDependencies": {