fix(spec-graph): hold the write-path invariants - #331
Open
Ivan Logutov (VanishJr) wants to merge 1 commit into
Open
fix(spec-graph): hold the write-path invariants#331Ivan Logutov (VanishJr) wants to merge 1 commit into
Ivan Logutov (VanishJr) wants to merge 1 commit into
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ivan Logutov (VanishJr)
requested review from
Olga Lavrichenko (OLavrik),
Rustam Sadykov (SBOne-Kenobi),
danyaberezun and
Rinat S (rsolmano)
as code owners
August 27, 2026 18:38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(spec-graph): hold the write-path invariants
The bug
spec_createpassed itspathparameter straight tojoin(root, path), so it wrote outside the project, through symlinks, into ignored directories and under non.mdnames, reportingCreated …for files that no other spec tool can read back.It also reported the caller's spelling rather than the path the index produces.
Before
After
The fix
coregainsresolveSpecPath(root, path), the single answer to whether the index could ever see a path, returning the canonical relative path the tool then reports. It requires root relative, inside the root,.md, outside the ignored directories, an existing root, and no symlink at any component beneath the root, checked per component withlstatbecause the walk never descends one.spec_createalso parses the bytes it is about to write, refuses them unless they read back as a spec (an emptyidserialized to a file that was not one), and writes withflag: "wx".Three more defects in the same module, each with a regression test:
spec_updatepicked the file's line ending from the whole text, so one CRLF anywhere in the body rewrote every line ending in the file. It now rewrites the frontmatter block alone and splices the body back byte for byte, BOM included.SpecIndexconsumedreaddirorder, so which file won a duplicateiddepended on the filesystem. The walk now sorts on NFC normalized names.spec_grepmatched lines still carrying a trailing\ror a leading BOM, so no anchored pattern matched a CRLF spec, and a non positivelimitreportedNo matches.while matches existed.Scope
A
realpathcontainment check was tried and withdrawn: it accepts a symlink that points back inside the root, and the walk skips that file anyway. Rejecting every symlinked component is both stricter and simpler.Further defects in this module are left out and want their own issues: an indented
---inside a multi line YAML scalar relocates the closing fence, list edits drop comments on the field they edit, a model supplied regex can stall the in process host,spec_updatecan overwrite an external edit landing inside the index's revalidation window, andspec_validatereports none of the schema rules the skill documents.Verifying
Before:
spec_createwithpath: "../evil.md"answersCreated ../evil.mdand the file exists outside the project. Withpath: "notes/spec.txt"it answersCreatedandspec_getthen answersNo spec with id.After: both are refused with the reason, and nothing is written.
Checklist
bun run lint,bun run typecheck,bun run testbun run e2e, orbun run e2e:fullwhen touching agent behavior)SPEC.md/ top-level specs updated to reflect any boundary, contract, or behavior change🤖 Generated with Claude Code