This release includes 2 breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
Affected surfaces
Summary
AI summaryUpdates Internal, Tests, and New error classes across a mixed release.
Full changelog
Cold-adopter Phase 1 v4 dogfood against v0.14.0 closed clean on substrate-portability
- package-only sufficiency, but surfaced four findings on contract-vs-runtime parity —
cases where the language reference / spec described a guarantee that only lint
enforced, leaving callers who bypassed lint (execute_skill({source})) silently
slipping through. v0.14.1 makes the runtime the load-bearing enforcement boundary;
lint stays advisory.
Per the "discipline-only contracts are bugs" invariant (banked this release as
sibling to the substrate-portability architecture invariant): when the spec
classifies an op or feature as requiring authorization/validation/sequencing,
runtime enforcement is mandatory — every layer that knows the contract should
enforce it.
Breaking-ish: mutation gate now throws at runtime
$ data_write / file_write / $ <mutating-name-tool> ops now throw
UnconfirmedMutationError at the runtime boundary unless one of three
authorization signals is present:
# Autonomous: trueskill-header (cron/agent-fired skills)- A preceding
??/ask(...)in the same target (interactive confirmation) approved="reason"per-op kwarg (per-op intent marker)
Pre-v0.14.1 these signals were lint-warned but runtime-permitted; lint-tier
warnings don't block execution, and execute_skill({source}) bypassed lint
preflight entirely. v0.14.1 closes both bypass paths:
- Layer A (load-bearing) —
execOpschecks each op before dispatch. - Layer B (regression guard) —
execOpInnerre-checks at the$/
file_writedispatch sites. Same predicate, fail-closed defaultauthState.
Migration: cron / agent-fired skills with mutation ops need # Autonomous: true.
Bundled example skills updated; corpus authors set the header once at the top.
Pattern-shape (mutation classes): $ data_write MCP dispatch, $ <tool> with
mutating-name prefix (write_, update_, delete_, archive_, prune_,
deploy_, etc.), file_write(...) runtime-intrinsic.
Breaking-ish: substrate-side strict filters on $ data_read
DataStoreMcpConnector now enforces every non-base filter key against the
wrapped DataStore.manifest().supported_filters, throwing
UnsupportedFilterError for unknowns. Closes the silent-scope-leak class
where pre-v0.14.1 substrates dropped unsupported filters (e.g., a skill
passing vault="team" against a substrate without vault semantics got
unfiltered results back, with no signal that the scoping was ignored).
Per-call opt-out: permissive_filters: true on the query acknowledges
"unknown keys are advisory; substrate may ignore them" — for cases where
the author knows their substrate is permissive by design.
Substrate implementor responsibility: declare every filter your query()
actually honors in manifest().supported_filters. Omissions become
"this filter is rejected at the bridge."
Docs
runtime_capabilities.shellExecution.descriptionrewritten to lead with
canonicalshell(command="...")syntax; legacy@ <cmd>/@ unsafe <body>
noted as deprecated-symbol-op aliases that parse identically.scaffold/config.toml— stale@@reference replaced with canonical
shell(command=..., unsafe=true)form.- README MCP-surface section —
execute_skillnow distinguishes
{skill_name}mode (subject to v0.9.0 hash-token approval gate) vs{source}
mode (ad-hoc inline; never persisted; bypasses SkillStore + approval gate);
explicitly calls out that v0.14.1 mutation gate applies in both modes. docs/adopter-playbook.md— substrate-neutral rewrite. New "What the
runtime promises connector implementors" section flat-lists the 5 typed
contract methods + explicit "what's NOT in the contracts" boundary (vaults,
namespaces, expiration, auth — all implementor concerns). Case 1 vs Case 2
table updated. Mutation-gate + strict-filters added to the "things the
playbook should be honest about" list.
Examples sweep — v0.14.1 contract compliance
examples/onboarding-scaffold/bootstrap.ts— wiresdata_writebridge
alongsidedata_read(matches bundledsrc/bootstrap.tspattern; one
bridge instance, two connector names).examples/onboarding-scaffold/file-data-store.ts—manifest()declares
supported_filters: []with comment explaining the substring scorer
doesn't filter on fields; teaches the v0.14.1 strict-default contract.examples/connectors/DataStoreTemplate/—manifest()+query()docs
explain v0.14.1 strict-filters enforcement at the bridge and how
supported_filtersis now load-bearing.examples/custom-bootstrap.example.ts— commented wiring example mirrors
the bundled both-names pattern.examples/skillscripts/—feedback-sentiment-scan,service-health-watch,
classify-support-ticketrewritten to v0.14.1 contract (# Autonomous: true;
canonical$ data_write content=... tags=[...]; envelope${X.items}
iteration; tokens recomputed).cut-release-tagshell-quote hygiene
warnings cleaned.examples/programmatic-trace-demo.mjs+docs/sqlite-skill-store.md—
legacy! <text>shorthand replaced with canonicalemit(text="...").
New error classes (src/errors.ts)
UnconfirmedMutationError extends OpError—opShape("data_write" /
"mutating_tool" / "file_write") +detail(tool name or path) +
requiredSignals[]+ concrete one-line suggestion in remediation.UnsupportedFilterError extends OpError—unsupportedKeys[]+
supportedKeys[]+substratename; remediation names 3 paths
(drop / opt out / switch substrate).
Internal
- New
src/mutation-gate.ts(~85 LOC):classifyMutation,
authorizationGranted,buildAuthorizationSuggestion,
MUTATING_TOOL_PATTERN. Single source of truth — both lint and runtime
call into the same classifier. - New
src/connectors/filter-enforcement.ts(~40 LOC):
enforceSupportedFilters(filters, supportedKeys, substrateName)helper +
base-shape-key allowlist. src/lint.tsUNCONFIRMED_MUTATIONrule refactored to call the shared
classifier (net -25 LOC).src/runtime.tsexecOps/execOp/execOpInnersignatures plumb
MutationAuthState; recursiveforeach/if/elsecalls pass the
same authState reference (sawConfirm propagates across nesting).src/connectors/data-store-mcp.tslazily cachessupported_filtersfrom
the wrapped substrate's manifest (one async call per bridge instance).- Narrow-core LOC ceiling bumped 9550 → 9700 per Scott's "ceiling is a
signal, not a budget" rule. File count (22) unchanged.
Tests
tests/v0.14.1-mutation-gate.test.ts— 8 cases: unauthorized throws + no
dispatch;approved=works;# Autonomous: trueworks; precedingask()
authorizes (sawConfirm path); mutating-name shape throws + no dispatch;
file_write paths.tests/v0.14.1-strict-filters.test.ts— 7 cases: unknown key throws +
no substrate call; error names keys;permissive_filters: truebypasses;
base keys never flagged; declared keys pass; empty manifest fails closed;
manifest fetched once / cached.- Test corpus migration: 4 test files (
file_write/$ data_writefixtures)
got# Autonomous: trueto keep passing under the new runtime gate.
Suite: 1296 passing (+8 since v0.14.0).
Breaking Changes
- $ data_write / file_write now throw UnconfirmedMutationError at runtime unless one of: # Autonomous: true skill header, preceding ask/?? confirmation, or approved="reason" per-op kwarg.
- DataStoreMcpConnector enforces every non-base filter against manifest().supported_filters, throwing UnsupportedFilterError for unknowns (opt‑out via permissive_filters:true).
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About Skillscript
All releases →Related context
Related tools
Earlier breaking changes
- v0.35.0 Editing `# Deadline:` on an approved skill drops it to Draft (signature invalidates).
Beta — feedback welcome: [email protected]