skillscript-runtime 0.20.2 → 0.21.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.
@@ -76,7 +76,10 @@ async function refresh() {
76
76
  state.blockedShellAttempts = blocked;
77
77
  state.lastUpdate = ts;
78
78
  renderSecuredBanner();
79
- document.getElementById("poll-status").textContent = `last updated ${ts.toLocaleTimeString()}`;
79
+ // v0.21.0 surface the running runtime version next to the poll timestamp.
80
+ const ver = state.capabilities?.runtimeVersion;
81
+ document.getElementById("poll-status").textContent =
82
+ `last updated ${ts.toLocaleTimeString()}${ver ? ` · skillscript v${ver}` : ""}`;
80
83
  renderCurrentView();
81
84
  } catch (err) {
82
85
  document.getElementById("poll-status").textContent = `poll failed: ${err.message}`;
@@ -350,7 +353,7 @@ function renderSkills() {
350
353
  ${state.skills.map((s) => `
351
354
  <tr onclick="window.location.hash='#skill/${encodeURIComponent(s.name)}'">
352
355
  <td><strong>${esc(s.name)}</strong></td>
353
- <td><span class="badge ${esc(s.status)}">${esc(s.status)}</span></td>
356
+ <td>${skillStatusBadge(s)}</td>
354
357
  <td>${esc(s.description ?? "—")}</td>
355
358
  <td><code>${esc(s.version?.slice(0, 8) ?? "—")}</code></td>
356
359
  </tr>
@@ -361,16 +364,28 @@ function renderSkills() {
361
364
  `;
362
365
  }
363
366
 
367
+ // v0.21.0 — gate-aware status badge (adopter finding 576632ca). A legacy skill
368
+ // stored `Approved` but failing the secured gate (e.g. a v1 stamp) WON'T run —
369
+ // badging it a plain green "Approved" while the Approvals tab lists it as
370
+ // "needs approval" reads as a contradiction. Show "re-approval needed" here too
371
+ // so both views agree on the truth (what the gate will actually do).
372
+ function skillStatusBadge(s) {
373
+ if (s.status === "Approved" && s.gate_ok === false) {
374
+ return `<span class="badge error" title="Approved status, but the body lacks a valid signature — won't run until re-approved">re-approval needed</span>`;
375
+ }
376
+ return `<span class="badge ${esc(s.status)}">${esc(s.status)}</span>`;
377
+ }
378
+
364
379
  async function renderSkillDetail(name) {
365
380
  try {
366
- // v0.13.3 — source moved out of skill_metadata into dedicated skill_read.
381
+ // v0.13.3 — source moved out of skill_preflight into dedicated skill_read.
367
382
  // Call both in parallel; skill_read may fail if the skill name doesn't
368
383
  // resolve (load() throws), so guard it to keep the detail view rendering.
369
384
  const [meta, readResult] = await Promise.all([
370
- callTool("skill_metadata", { name }),
385
+ callTool("skill_preflight", { name }),
371
386
  callTool("skill_read", { name }).catch(() => null),
372
387
  ]);
373
- const { metadata, versions, recent_fires, approval } = meta;
388
+ const { metadata, versions, recent_fires, approval, contract } = meta;
374
389
  const source = readResult?.source ?? null;
375
390
  const metrics = state.metrics?.perSkill?.[name];
376
391
  const triggersForSkill = state.triggers.filter((t) => t.skillName === name);
@@ -397,6 +412,8 @@ async function renderSkillDetail(name) {
397
412
  ${renderStatusActions(name, metadata, approval)}
398
413
  </section>
399
414
 
415
+ ${renderEffectfulFootprintPanel(contract?.effectful_footprint)}
416
+
400
417
  ${source ? renderSecuritySignalsPanel(source) : ""}
401
418
 
402
419
  <section>
@@ -626,6 +643,49 @@ function renderHighlightedSkillBody(source) {
626
643
  // showing aggregated counts so a reviewer knows WHAT to look for before
627
644
  // scanning the body. Pairs with renderHighlightedSkillBody() (the
628
645
  // WHERE).
646
+ // v0.21.0 — the authoritative "what does it touch" least-privilege checklist
647
+ // for the human approver. Unlike renderSecuritySignalsPanel (regex over the
648
+ // source), this is the AST-derived effectful_footprint from skill_preflight —
649
+ // the SAME op enumeration the capability gate authorizes — so it's the truth of
650
+ // what the skill can do when signed, not a textual approximation. Surfaced right
651
+ // at the approve action so the operator sees the surface they're signing off.
652
+ function renderEffectfulFootprintPanel(fp) {
653
+ if (!fp) return ""; // source not loadable / parse failed → no contract to show
654
+ const items = [];
655
+ if (fp.connectors.length > 0) {
656
+ items.push(`<li class="sig-medium-text">Dispatches to ${fp.connectors.length} MCP connector${fp.connectors.length === 1 ? "" : "s"}: ${fp.connectors.map((c) => `<code>${esc(c)}</code>`).join(", ")}</li>`);
657
+ }
658
+ if (fp.builtins.length > 0) {
659
+ items.push(`<li class="sig-info-text">Uses ${fp.builtins.length} built-in op${fp.builtins.length === 1 ? "" : "s"}: ${fp.builtins.map((b) => `<code>${esc(b)}</code>`).join(", ")}</li>`);
660
+ }
661
+ if (fp.file_writes > 0) {
662
+ items.push(`<li class="sig-high-text">${fp.file_writes} <code>file_write</code> op${fp.file_writes === 1 ? "" : "s"} <small>(writes to the filesystem allowlist)</small></li>`);
663
+ }
664
+ if (fp.file_reads > 0) {
665
+ items.push(`<li class="sig-medium-text">${fp.file_reads} <code>file_read</code> op${fp.file_reads === 1 ? "" : "s"}</li>`);
666
+ }
667
+ if (fp.unsafe_shell > 0) {
668
+ items.push(`<li class="sig-high-text">${fp.unsafe_shell} unsafe (full-bash) shell op${fp.unsafe_shell === 1 ? "" : "s"}</li>`);
669
+ }
670
+ const safeShellBins = fp.shell_binaries.filter((b) => b !== "bash" || fp.unsafe_shell === 0);
671
+ if (safeShellBins.length > 0) {
672
+ items.push(`<li class="sig-medium-text">Runs shell ${safeShellBins.length === 1 ? "binary" : "binaries"}: ${safeShellBins.map((b) => `<code>${esc(b)}</code>`).join(", ")} <small>(allowlist-gated)</small></li>`);
673
+ }
674
+ if (fp.notifies > 0) {
675
+ items.push(`<li class="sig-medium-text">${fp.notifies} <code>notify</code> agent-wake op${fp.notifies === 1 ? "" : "s"}</li>`);
676
+ }
677
+ const body = items.length === 0
678
+ ? `<p class="empty">Pure — no connector, shell, file, or notify ops. Nothing effectful to authorize.</p>`
679
+ : `<ul class="security-signals">${items.join("")}</ul>
680
+ <p><small>This is the AST-derived footprint the capability gate authorizes — the surface this skill can touch once approved. Confirm every line is least-privilege before signing.</small></p>`;
681
+ return `
682
+ <section>
683
+ <h2>What this skill touches</h2>
684
+ ${body}
685
+ </section>
686
+ `;
687
+ }
688
+
629
689
  function renderSecuritySignalsPanel(source) {
630
690
  const sig = collectSecuritySignals(source);
631
691
  const items = [];
@@ -957,7 +1017,7 @@ async function loadComposeContract(detailsEl) {
957
1017
  panel.dataset.loaded = "true";
958
1018
  const [kind, name] = detailsEl.dataset.composeRef.split(":");
959
1019
  try {
960
- const meta = await callTool("skill_metadata", { name });
1020
+ const meta = await callTool("skill_preflight", { name });
961
1021
  const m = meta.metadata;
962
1022
  // For inline (data-only), also fetch the body since it bakes at
963
1023
  // compile time — reviewer sees what literally lands in the
@@ -1,3 +1,12 @@
1
1
  import type { Registry } from "./connectors/registry.js";
2
+ /**
3
+ * The canonical agent-usage block, delivered as the MCP server's `instructions`
4
+ * field at `initialize` so every connecting agent learns the workflow with no
5
+ * CLAUDE.md copy-paste. This static string is the shipped source of truth —
6
+ * backend-agnostic (no AMP / substrate references) so it serves dogfood +
7
+ * external adopters from one place. Keep the dev-agent CLAUDE.md and
8
+ * docs/adopter-agent-guide.md REFERENCING this, not forking divergent copies.
9
+ */
10
+ export declare const SKILLSCRIPT_USAGE_INSTRUCTIONS = "A Skillscript runtime is wired over MCP. Skills are compiled, approved, reusable procedures \u2014 run them, don't re-derive.\n\n**Session start:** `skill_list()` \u2192 your skills, grouped: `skills` (invoke directly) \u00B7 `receives` (push context to you) \u00B7 `headless` (cron/event-fired; maintain, don't invoke). Know them before routine work.\n\n**Routine task \u2014 if a skill fits, use it, don't re-reason:**\n- `skill_preflight({name})` \u2192 vars, returns, requires, effectful footprint, cleared-to-run. (skill_list entries already carry this contract.)\n- `execute_skill({name})` \u2192 run end-to-end.\n- `compile_skill({name})` \u2192 preview the plan, no side effects.\n\n**Repeating a routine? Capture it as a skill.** What you can script = the wired connectors, models, and allowed shell binaries from `runtime_capabilities()` \u2014 that list is your menu. When you catch yourself re-running work over any of them, author it. If something you'd need isn't wired yet, ask the operator to add it, then capture it.\n- `help({topic})` \u2192 the language (ops, frontmatter, connectors, lint-codes).\n- Draft \u2192 `lint_skill({source})` \u2192 `compile_skill({source})` \u2192 `skill_write({name, source})`.\n- It lands **Draft** \u2014 you can't self-approve; a human does. Treat anything you authored as not-yet-runnable until approved.";
2
11
  export declare function helpResponse(topic: string | null, runtimeVersion: string, registry?: Registry): Record<string, unknown>;
3
12
  //# sourceMappingURL=help-content.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"help-content.d.ts","sourceRoot":"","sources":["../src/help-content.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AA24BzD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,cAAc,EAAE,MAAM,EACtB,QAAQ,CAAC,EAAE,QAAQ,GAClB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBzB"}
1
+ {"version":3,"file":"help-content.d.ts","sourceRoot":"","sources":["../src/help-content.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,k1CAYkF,CAAC;AA24B9H,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,cAAc,EAAE,MAAM,EACtB,QAAQ,CAAC,EAAE,QAAQ,GAClB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBzB"}
@@ -13,6 +13,27 @@
13
13
  //
14
14
  // Token estimates per topic are approximate; help() output is intended
15
15
  // for an agent's working context, not for human reading.
16
+ /**
17
+ * The canonical agent-usage block, delivered as the MCP server's `instructions`
18
+ * field at `initialize` so every connecting agent learns the workflow with no
19
+ * CLAUDE.md copy-paste. This static string is the shipped source of truth —
20
+ * backend-agnostic (no AMP / substrate references) so it serves dogfood +
21
+ * external adopters from one place. Keep the dev-agent CLAUDE.md and
22
+ * docs/adopter-agent-guide.md REFERENCING this, not forking divergent copies.
23
+ */
24
+ export const SKILLSCRIPT_USAGE_INSTRUCTIONS = `A Skillscript runtime is wired over MCP. Skills are compiled, approved, reusable procedures — run them, don't re-derive.
25
+
26
+ **Session start:** \`skill_list()\` → your skills, grouped: \`skills\` (invoke directly) · \`receives\` (push context to you) · \`headless\` (cron/event-fired; maintain, don't invoke). Know them before routine work.
27
+
28
+ **Routine task — if a skill fits, use it, don't re-reason:**
29
+ - \`skill_preflight({name})\` → vars, returns, requires, effectful footprint, cleared-to-run. (skill_list entries already carry this contract.)
30
+ - \`execute_skill({name})\` → run end-to-end.
31
+ - \`compile_skill({name})\` → preview the plan, no side effects.
32
+
33
+ **Repeating a routine? Capture it as a skill.** What you can script = the wired connectors, models, and allowed shell binaries from \`runtime_capabilities()\` — that list is your menu. When you catch yourself re-running work over any of them, author it. If something you'd need isn't wired yet, ask the operator to add it, then capture it.
34
+ - \`help({topic})\` → the language (ops, frontmatter, connectors, lint-codes).
35
+ - Draft → \`lint_skill({source})\` → \`compile_skill({source})\` → \`skill_write({name, source})\`.
36
+ - It lands **Draft** — you can't self-approve; a human does. Treat anything you authored as not-yet-runnable until approved.`;
16
37
  const QUICKSTART = `# Skillscript — quickstart
17
38
 
18
39
  Skillscript is a declarative language for authoring agent workflows. A
@@ -1 +1 @@
1
- {"version":3,"file":"help-content.js","sourceRoot":"","sources":["../src/help-content.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"help-content.js","sourceRoot":"","sources":["../src/help-content.ts"],"names":[],"mappings":"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"}
@@ -14,23 +14,24 @@ import type { Registry } from "./connectors/registry.js";
14
14
  * protocol conforms to MCP — real MCP clients (Claude Desktop, Cursor,
15
15
  * future tools) can consume the server unchanged.
16
16
  *
17
- * Surface: tools wrapping existing T6 primitives.
17
+ * Surface: tools wrapping existing T6 primitives, ordered by the cold-author
18
+ * workflow loop the descriptions teach (learn → discover → draft → commit →
19
+ * approve → run → automate → observe).
18
20
  *
19
- * skill_list({filter?}) SkillCatalog (v0.9.8 pre-grouped by audience)
20
- * skill_metadata({name}) metadata + version history + recent_fires + approval (no source — see skill_read)
21
- * skill_read({name, version?}) {name, version, status, source} (v0.13.3 symmetric peer to skill_write)
22
- * data_read({id, store?}) PortableData | null (v0.13.8 direct lookup; no data_write MCP by design)
23
- * skill_status({name, new_state})→ SkillStatus update (write)
24
- * list_triggers({filter?}) TriggerRegistration[]
25
- * register_trigger({...}) TriggerRegistration (write)
26
- * unregister_trigger({trigger_id})→ boolean (write)
27
- * health_metrics({filter?}) HealthMetrics
28
- * runtime_capabilities({include?})→ wired connectors + shell-exec mode (v0.2.1)
29
- * lint_skill({source?|name}) diagnostics across tiers (v0.2.3)
30
- * compile_skill({source?|name, inputs?})→ rendered artifact + errors (v0.2.3)
31
- * skill_write({name, source, overwrite?})→ commit to SkillStore (write, v0.2.3)
32
- * execute_skill({skill_name, inputs?, mechanical?})→ run + return result (write, v0.2.8)
33
- * help({topic?}) → cold-agent language discovery (read, v0.2.8)
21
+ * LEARN help({topic?}) language quickstart + deep topics
22
+ * runtime_capabilities({include?})→ wired connectors + shell-exec mode
23
+ * DISCOVER skill_list({filter?}) SkillCatalog (grouped by audience; entries carry the full contract)
24
+ * skill_preflight({name}) one skill's contract: takes/returns/requires/touches + approval + lifecycle
25
+ * skill_read({name, version?}) {name, version, status, source} (the body itself)
26
+ * data_read({id, store?}) PortableData | null (direct lookup; no data_write MCP by design)
27
+ * DRAFT lint_skill({source?|name}) tiered diagnostics (inner loop)
28
+ * compile_skill({source?|name, inputs?})→ rendered artifact + errors + exec order (pre-commit)
29
+ * COMMIT skill_write({name, source, overwrite?})→ store the body (write; secured mode forces unsigned→Draft)
30
+ * APPROVE skill_status({name, new_state}) Draft/Approved/Disabled (write; secured promote needs a signature)
31
+ * RUN execute_skill({name|source, inputs?, mechanical?})→ run + return result (write)
32
+ * AUTOMATE register_trigger / list_triggers / set_trigger_enabled / unregister_trigger → autonomous dispatch
33
+ * OBSERVE health_metrics({filter?}) per-skill/connector aggregates
34
+ * blocked_shell_attempts() allowlist-refused shell ops (observe→promote loop)
34
35
  */
35
36
  export interface JsonRpcRequest {
36
37
  jsonrpc: "2.0";
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
@@ -4,10 +4,11 @@ import { healthMetrics } from "./metrics.js";
4
4
  import { lint } from "./lint.js";
5
5
  import { compile } from "./compile.js";
6
6
  import { parse as parseSkill } from "./parser.js";
7
+ import { extractEffectfulFootprint } from "./skill-surface.js";
7
8
  import { listKnownConnectorClasses } from "./connectors/config.js";
8
9
  import { LintFailureError, MissingSkillReferenceError, OpError } from "./errors.js";
9
10
  import { executeSkillByName, executeSkillFromSource, RecursionDepthExceededError, SkillNotFoundForCompositionError, } from "./composition.js";
10
- import { helpResponse } from "./help-content.js";
11
+ import { helpResponse, SKILLSCRIPT_USAGE_INSTRUCTIONS } from "./help-content.js";
11
12
  import { RUNTIME_VERSION } from "./version.js";
12
13
  import { evaluateApprovalGate, isSecuredMode, hasApprovalPublicKey } from "./approval.js";
13
14
  import { forceDraftStatus } from "./connectors/skill-store-mcp.js";
@@ -44,6 +45,9 @@ export class McpServer {
44
45
  protocolVersion: MCP_PROTOCOL_VERSION,
45
46
  capabilities: { tools: {} },
46
47
  serverInfo: { name: SERVER_NAME, version: this.version },
48
+ // Canonical agent-usage block — every connecting agent learns the
49
+ // workflow at session start, no CLAUDE.md copy-paste required.
50
+ instructions: SKILLSCRIPT_USAGE_INSTRUCTIONS,
47
51
  },
48
52
  };
49
53
  case "tools/list":
@@ -118,7 +122,7 @@ export class McpServer {
118
122
  registerBuiltinTools() {
119
123
  this.registerTool({
120
124
  name: "skill_list",
121
- description: "Discover skills in the configured SkillStore (v0.9.8). Returns a `SkillCatalog` pre-grouped by audience-derived category: `receives` (skills that push to the calling agent via `# Output: agent:`), `skills` (skills the agent can invoke), `headless` (admin-view only). Category derived from each skill's `# Output:` declarations. Filter by audience / status / trigger_kind / domain_tags / name_prefix / author (AND-composed). Default: audience=\"agent\", status=\"Approved\". v0.18.6 — `author` filter narrows to skills authored by a specific identity; entries carry `author` for substrates that track it (null when not).",
125
+ description: "Discover skills in the configured SkillStore. Returns a `SkillCatalog` pre-grouped by audience-derived category: `receives` (skills that push to the calling agent via `# Output: agent:`), `skills` (skills the agent can invoke), `headless` (admin-view only). Category derived from each skill's `# Output:` declarations. Filter by audience / status / trigger_kind / domain_tags / name_prefix / author (AND-composed). Default: audience=\"agent\", status=\"Approved\". Every entry carries the full preflight contract — `vars` (inputs), `returns` (exported vars), `requires` (capability needs), `effectful_footprint` (which connectors / shell binaries / file + notify ops it touches), plus `gate_ok` (cleared-to-run under the current mode) and `author` (when the substrate tracks it). So you can read a skill's whole I/O + effect contract here without a per-skill `skill_preflight` call; reach for `skill_preflight` when you want one skill's contract + version/lifecycle detail, or `skill_read` for the body.",
122
126
  inputSchema: {
123
127
  type: "object",
124
128
  properties: {
@@ -143,7 +147,7 @@ export class McpServer {
143
147
  });
144
148
  this.registerTool({
145
149
  name: "blocked_shell_attempts",
146
- description: "v0.18.9 — list shell op dispatches refused by the binary allowlist gate. Queries the trace store cross-skill, filters to op records carrying `blocked_reason: \"binary-not-allowed\"`. Returns flat list ready for the dashboard's observe→promote loop (operator sees what binaries skills tried to invoke; decides whether to add any to `SKILLSCRIPT_SHELL_ALLOWLIST`).",
150
+ description: "List shell op dispatches refused by the binary allowlist gate. Queries the trace store cross-skill, filtering to op records carrying `blocked_reason: \"binary-not-allowed\"`. Returns a flat list for the dashboard's observe→promote loop: the operator sees what binaries skills tried to invoke, then decides whether to add any to `SKILLSCRIPT_SHELL_ALLOWLIST`. Read-only.",
147
151
  inputSchema: {
148
152
  type: "object",
149
153
  properties: {
@@ -196,8 +200,8 @@ export class McpServer {
196
200
  },
197
201
  });
198
202
  this.registerTool({
199
- name: "skill_metadata",
200
- description: "Get metadata + version history + recent fires + approval-gate state for a specific skill by name. For the source body itself, call skill_read.",
203
+ name: "skill_preflight",
204
+ description: "PRE-EXECUTION CONTRACT CHECK call this BEFORE executing or composing a skill to see what it takes, returns, requires, and touches: its inputs (vars), exported variables (returns), capability requirements, and effectful footprint (which connectors / shell binaries / write + notify ops it dispatches). Also reports whether it's cleared to run (approval-gate state) + version/lifecycle. The least-privilege checklist for a human approver, and the contract surface for a cold author composing against it. Discover skills with skill_list (its entries already carry this same contract); call skill_preflight for one skill's contract plus version/lifecycle detail, or skill_read for the source body.",
201
205
  inputSchema: {
202
206
  type: "object",
203
207
  properties: {
@@ -219,15 +223,28 @@ export class McpServer {
219
223
  // stale-Approved skills (body edited since approval, token no
220
224
  // longer verifies). `null` when the source isn't loadable.
221
225
  let approval = null;
226
+ // v0.21.0 — the contract surface: what the skill TAKES (vars, via the
227
+ // parser's frontmatter), RETURNS (exported vars), REQUIRES (capability
228
+ // clauses), and TOUCHES (effectful footprint). Derived statically from
229
+ // the body. Null when the source isn't loadable.
230
+ let contract = null;
222
231
  if (loaded?.source !== undefined) {
223
232
  const g = evaluateApprovalGate(loaded.source);
224
233
  approval = g.ok ? { gate_ok: true } : { gate_ok: false, reason: g.reason };
234
+ const parsed = parseSkill(loaded.source);
235
+ contract = {
236
+ vars: parsed.vars.map((v) => v.name),
237
+ returns: parsed.returns,
238
+ requires: parsed.requires,
239
+ effectful_footprint: extractEffectfulFootprint(parsed),
240
+ };
225
241
  }
226
242
  return {
227
243
  metadata,
244
+ contract,
245
+ approval,
228
246
  versions,
229
247
  recent_fires,
230
- approval,
231
248
  };
232
249
  },
233
250
  });
@@ -277,7 +294,7 @@ export class McpServer {
277
294
  });
278
295
  this.registerTool({
279
296
  name: "skill_status",
280
- description: "Transition a skill's status. Valid states: Draft, Approved, Disabled. Write operation.",
297
+ description: "Transition a skill's lifecycle status: Draft, Approved, Disabled. Approved is the only status that executes and lets triggers fire; Draft is the editing state; Disabled retires a skill without deleting it (re-enable by transitioning back to Approved/Draft). In secured mode you CANNOT promote to Approved here without a valid signature — approve via the dashboard or `skillfile approve` (which signs the body); a bare status flip can't grant approval. Write operation.",
281
298
  inputSchema: {
282
299
  type: "object",
283
300
  properties: {
@@ -296,6 +313,19 @@ export class McpServer {
296
313
  if (!isSkillStatus(newState)) {
297
314
  throw new OpError(`\`skill_status\` requires \`new_state\` to be one of ${VALID_SKILL_STATUSES.map((s) => `"${s}"`).join(", ")}; got ${JSON.stringify(newState)}.`, "skill_status", `Pass \`new_state\` as one of: ${VALID_SKILL_STATUSES.join(" | ")}.`, name);
298
315
  }
316
+ // v0.21.0 — store-agnostic secured-mode closure (red-team 33bf53d3).
317
+ // skill_status cannot GRANT approval in secured mode: promotion to
318
+ // Approved is refused unless the stored body already carries a valid v3
319
+ // signature. The per-store guard (FilesystemSkillStore/SqliteSkillStore)
320
+ // didn't cover custom adopter stores (e.g. AMP-backed), so an agent could
321
+ // flip Draft→Approved with no signature — a forgeable trust-state lie.
322
+ // Enforce HERE, at the ingress, regardless of substrate.
323
+ if (newState === "Approved" && isSecuredMode()) {
324
+ const loaded = await this.deps.skillStore.load(name).catch(() => null);
325
+ if (loaded === null || !evaluateApprovalGate(loaded.source).ok) {
326
+ throw new OpError(`cannot promote '${name}' to Approved in secured mode — the skill carries no valid signature. Approve it via the dashboard or \`skillfile approve\` (which signs the body); a status change alone cannot grant approval.`, "skill_status", `Sign the skill with the operator's key, then it can be Approved.`, name);
327
+ }
328
+ }
299
329
  const result = await this.deps.skillStore.update_status(name, newState);
300
330
  // v0.19.1 — sync declarative triggers on status transition.
301
331
  // Approved → register the skill's declared triggers; Draft or
@@ -307,7 +337,7 @@ export class McpServer {
307
337
  });
308
338
  this.registerTool({
309
339
  name: "list_triggers",
310
- description: "List registered triggers. Optionally filter by skill name or trigger source.",
340
+ description: "List registered triggers — the autonomous-dispatch registry. Optionally filter by skill name or trigger source (cron / event). Read-only. Pairs with register_trigger / set_trigger_enabled / unregister_trigger to manage how Approved skills fire on their own.",
311
341
  inputSchema: {
312
342
  type: "object",
313
343
  properties: {
@@ -326,7 +356,7 @@ export class McpServer {
326
356
  });
327
357
  this.registerTool({
328
358
  name: "register_trigger",
329
- description: "Register a new trigger for a skill. Returns the registration. Write operation.",
359
+ description: "Register an autonomous-dispatch trigger for a skill — cron (time-based) or event (HTTP POST /event ingress, named). Only Approved skills fire; the scheduler re-verifies the approval gate at fire time, so registering a trigger never bypasses approval. Skills can also declare triggers inline via `# Triggers:` frontmatter (synced automatically on approval) — use this tool for imperative / dynamic registration. Returns the registration. Write operation.",
330
360
  inputSchema: {
331
361
  type: "object",
332
362
  properties: {
@@ -374,7 +404,7 @@ export class McpServer {
374
404
  });
375
405
  this.registerTool({
376
406
  name: "unregister_trigger",
377
- description: "Unregister a trigger by id. Returns boolean (true if removed, false if id not found). Write operation.",
407
+ description: "Remove a registered trigger by id (see list_triggers for ids). Returns true if removed, false if the id wasn't found. Write operation.",
378
408
  inputSchema: {
379
409
  type: "object",
380
410
  properties: { trigger_id: { type: "string" } },
@@ -387,7 +417,7 @@ export class McpServer {
387
417
  });
388
418
  this.registerTool({
389
419
  name: "set_trigger_enabled",
390
- description: "v0.9.0 — toggle a trigger's enabled state. Disabled triggers stay registered but the scheduler skips firing them (vacation / maintenance windows). State persists via the onTriggersChanged hook for imperative triggers. Returns the updated registration, or null if no trigger has that id. Write operation.",
420
+ description: "Toggle a trigger's enabled state without unregistering it — disabled triggers stay in the registry but the scheduler skips firing them (vacation / maintenance windows). State persists via the onTriggersChanged hook for imperative triggers. Returns the updated registration, or null if no trigger has that id. Write operation.",
391
421
  inputSchema: {
392
422
  type: "object",
393
423
  properties: {
@@ -449,7 +479,7 @@ export class McpServer {
449
479
  // ─── v0.2.3 — over-the-wire authoring lifecycle ────────────────────────
450
480
  this.registerTool({
451
481
  name: "lint_skill",
452
- description: "Run static lint against a skill source body or stored skill name. Returns diagnostics across tier-1 (errors that block compile), tier-2 (warnings), tier-3 (advisories). Read-only. Inner-loop affordance for cold authors iterating on a draft.",
482
+ description: "Run static lint against a skill source body or stored skill name. Returns diagnostics across tier-1 (errors that block compile), tier-2 (warnings), tier-3 (advisories). Read-only. The inner-loop affordance while drafting — lint as you iterate, then compile_skill for the full pre-commit check before skill_write.",
453
483
  inputSchema: {
454
484
  type: "object",
455
485
  properties: {
@@ -461,7 +491,7 @@ export class McpServer {
461
491
  });
462
492
  this.registerTool({
463
493
  name: "compile_skill",
464
- description: "Compile a skill source body or stored skill name. Returns the rendered artifact + parse/compile errors + resolved variables + topological execution order. Read-only. Pre-commit validation affordance.",
494
+ description: "Compile a skill source body or stored skill name. Returns the rendered artifact + parse/compile errors + resolved variables + topological execution order. Read-only. The pre-commit check after lint_skill passes and before skill_write — confirms the body compiles and shows the execution order without running effects (use execute_skill `mechanical: true` for a no-fire dispatch preview).",
465
495
  inputSchema: {
466
496
  type: "object",
467
497
  properties: {
@@ -478,7 +508,7 @@ export class McpServer {
478
508
  });
479
509
  this.registerTool({
480
510
  name: "skill_write",
481
- description: "Write a skill body into the configured SkillStore. Tier-1 lint runs at write time (SkillStore contract); throws on rejection. Returns version + content_hash. The `# Status:` header in the source body is honored — write with `# Status: Approved` and the skill lands Approved (otherwise defaults to Draft). Promote/demote later via skill_status. Write operation.",
511
+ description: "Write a skill body into the configured SkillStore. Tier-1 lint runs at write time (SkillStore contract) and throws on rejection run lint_skill / compile_skill first to iterate cleanly. The `# Status:` header is honored: `# Status: Approved` lands Approved in unsecured mode, but in secured mode an unsigned body is forced to Draft (a write can't grant approval). Promote later via skill_status, or the dashboard / `skillfile approve` in secured mode. Returns version + content_hash. Write operation.",
482
512
  inputSchema: {
483
513
  type: "object",
484
514
  properties: {
@@ -533,7 +563,7 @@ export class McpServer {
533
563
  async executeSkill(args, callerCtx = {}) {
534
564
  // v0.15.2 — `name` is the canonical kwarg; `skill_name` is a silent
535
565
  // back-compat alias. Aligns the surface with the other skill_* tools
536
- // (skill_read / skill_metadata / skill_status / skill_write all take
566
+ // (skill_read / skill_preflight / skill_status / skill_write all take
537
567
  // `name`). Per Perry signoff (thread 75abc8c0): silent alias — no
538
568
  // tier-3 advisory, no deprecation warn. If both are supplied with
539
569
  // different values, that's ambiguous; reject so the caller picks one.
@@ -817,7 +847,21 @@ export class McpServer {
817
847
  // posture (every write requires explicit human promotion regardless
818
848
  // of body claim) opt in via the flag at runtime startup. Per Perry's
819
849
  // `787b6b95` Option A.
820
- const bodyToStore = this.deps.forceAlwaysDraft === true ? forceDraftStatus(source) : source;
850
+ // v0.21.0 store-agnostic secured-mode closure (red-team 33bf53d3).
851
+ // skill_write cannot GRANT approval: if the body claims Approved without a
852
+ // valid v3 signature, force Draft — regardless of the SkillStore impl. The
853
+ // per-store guard didn't cover custom adopter stores (AMP-backed), letting an
854
+ // agent write status=Approved with no signature (a forgeable trust-state lie
855
+ // that misleads skill_list / skill_preflight / the dashboard). evaluateApproval
856
+ // -Gate is not-ok for any Draft OR unsigned-Approved body, so a genuine
857
+ // approve-flow write (signed body) is honored; everything else lands Draft.
858
+ let bodyToStore = source;
859
+ if (this.deps.forceAlwaysDraft === true) {
860
+ bodyToStore = forceDraftStatus(source);
861
+ }
862
+ else if (isSecuredMode() && !evaluateApprovalGate(source).ok) {
863
+ bodyToStore = forceDraftStatus(source);
864
+ }
821
865
  // v0.17.0 — thread host-attested caller identity into `store({author})`
822
866
  // so MCP-authored skills stamp `SkillMeta.author = <calling agent>`,
823
867
  // not the runtime's wiring identity. When ctx.callerIdentity is