core-data-structures 18 篇(core.md 因超长仍在产出、随后补)、 postmortem 3 篇与 RFC 前门 README 配对;流水线 + 二遍校验产出。 生成文件 docs/rfc/INDEX.md(gen-rfc-index 产物)列入排除。中文侧 页内锚点统一指向英文侧锚名,满足配对门禁的链接目标一致规则。
4.2 KiB
Post-mortem 0002: Filesystem snapshot tools were permanently disabled
English | 中文
Status: resolved
Executive summary
The ACP example attempted to enable filesystem plugins conditionally with disabled: !!js ..., but Cordis evaluates JavaScript expressions only inside plugin config. The raw expression object was truthy, so the filesystem stack was always disabled. Snapshot refresh then accepted UNKNOWN_TOOL results as new goldens. The fix uses an explicit filesystem overlay and adds static-config and snapshot-result guards.
Summary
The default ACP composition is intentionally bash-only because its sandbox cannot confine in-process filesystem providers. Filesystem snapshot scenarios still need read, write, and edit, so their plugins were placed in the default cordis.yml with a disabled expression intended to enable them only for full-access launches and snapshots.
Cordis Include parsed each !!js scalar into an expression object. The Loader recursively interpolated the plugin's config, but consumed entry metadata such as disabled directly. Every filesystem entry therefore saw a truthy object and remained disabled in every mode.
Impact
Seven filesystem scenarios and the mixed workspace-edit scenario called tools that were absent from the registry. Their structured session logs carried ToolNotFoundError with code UNKNOWN_TOOL, while stdout rendered generic failed tool cards. The snapshot suite passed because both surfaces matched the refreshed fixtures; it proved deterministic replay of the regression rather than successful filesystem behavior.
The live confined default did not gain unintended filesystem access. A naive interpolation fix would have created that risk: permission presets update bash sandbox and approval state at runtime, but cannot mount, unmount, or confine the filesystem stack.
Timeline
- PR #261 consolidated ACP compositions and refreshed the filesystem snapshots while introducing conditional filesystem entries.
- All unit, coverage, snapshot, documentation, build, and hygiene checks passed.
- Review of the refreshed filesystem goldens found generic failed cards and structured
UNKNOWN_TOOLresults. - A real Loader boot confirmed that every
disabledvalue remained an expression object and every filesystem fiber was absent.
Root cause
The implementation assumed !!js applied to an entire Loader entry. Its actual boundary is narrower: Entry._resolveConfig() interpolates only entry.options.config; Entry.disabled tests entry.options.disabled without interpolation. The YAML tag was syntactically valid, so loading produced no diagnostic.
The snapshot framework treated any deterministic transcript as valid behavior. Header pins verified the composed tool schemas, but the filesystem scenarios shared a pin from the default composition and therefore did not independently prove that their required tools were registered. Refresh rewrote the expected stdout and session logs before any semantic assertion rejected missing tools.
Guardrails added
- Filesystem scenarios boot
fs.cordis.yml, an explicit fixed full-access overlay with a paired replay config and its own request-header class. AGENTS.mdand the Cordis primer state that!!jsis valid only under pluginconfigand conditional composition uses overlays.verify-cordis-configparses repository Cordis YAML and rejects expression nodes in Loader entry metadata, including include patches and inserted entries.dsh-acp-snapshotrejects structuredUNKNOWN_TOOLresults in fresh runs and committed session fixtures before they can become accepted goldens.
Lessons
- A syntactically accepted configuration value is not necessarily evaluated at that location; document and verify interpolation boundaries.
- A snapshot refresh is fixture production, not correctness review. Semantic impossibilities such as a missing registered tool need assertions independent of the golden.
- Permission controls must describe only the capabilities they actually govern. Composition-time filesystem access cannot follow a runtime bash-only preset safely.