fix(write-protocol): thread pagesDirectory through the write, index, search, and chain paths - #160
Conversation
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — 93d223fc
This PR was opened by the trusted drewstone account.
This approval is provisional and was applied by the local stand-in because the pr-reviewer webhook host is unreachable (2026-08-21). CI on this head is fully green. The full PR reviewer audit re-runs via the resweep when the service returns and will publish findings if it detects issues.
…search, and chain paths
The reader accepted { pagesDirectory } while the write protocol and the file
transaction allowlist hardcoded knowledge/. One KnowledgePagesOptions now
drives applyKnowledgeWriteBlocks, buildKnowledgeIndex, writeKnowledgeIndex,
FileSystemSearchProvider, createRunScopedStores, and the CLI (--pages-dir).
normalizePagesDirectory refuses .., absolute paths, drive letters, control
characters, and the package-owned .agent-knowledge and raw trees. A file
transaction journals the pages directory it was prepared under so recovery
enforces the same allowlist. The default journal and allowlist are unchanged.
Closes #159
93d223f to
7c688ea
Compare
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — 7c688ea1
This PR was opened by the trusted drewstone account.
This approval is provisional and was applied by the local stand-in because the pr-reviewer webhook host is unreachable (2026-08-21). CI on this head is fully green. The full PR reviewer audit re-runs via the resweep when the service returns and will publish findings if it detects issues.
Why
isSafeKnowledgePath('kb/pages/q36/x.md')returnedfalsewhileloadKnowledgePages(root, { pagesDirectory })already read such a store. The read side was parameterized; the write protocol, the file-transaction allowlist, the indexer, the search provider, the run-scoped chain, and the CLI all hardcodedknowledge/. A lab whose store lives atkb/pages/<line>/could not use the safe-write protocol, index, search, or chain-read it.What
One option, one normalizer, default unchanged:
src/pages-directory.ts(new, leaf module):KnowledgePagesOptions { pagesDirectory? },DEFAULT_PAGES_DIRECTORY,normalizePagesDirectory. The normalizer refuses..,.and empty segments, absolute paths, drive letters, control characters, and the package-owned.agent-knowledgeandrawtrees, because the value is a write allowlist prefix as well as a read location.LoadKnowledgePagesOptionsis renamed toKnowledgePagesOptions(it drives writes now).applyKnowledgeWriteBlocks(root, text, { pagesDirectory })/applyKnowledgeWriteBlocksFileparse with[${dir}/]and commit with the samepagesDirectory.file-transaction.ts:assertKnowledgeMutationPath(path, pagesDirectory?)derives the allowlist from the validated directory (<dir>/,raw/, exact.agent-knowledge/sources.json);prepareKnowledgeFileTransaction/commitKnowledgeFileMutationstakepagesDirectory. The transaction journal records the canonicalpagesDirectoryit was prepared under (absent for the default) soinspect/recover/replay enforce the same allowlist without a caller-supplied value. The default journal is byte-identical; the default allowlist is byte-identical.buildKnowledgeIndex/writeKnowledgeIndex,FileSystemSearchProviderOptions(validated at construction, exposed asprovider.pagesDirectory),RunScopedStoresOptions(validated at creation, used byloadChainfor every origin) takepagesDirectory.--pages-dir <dir>onapply-write-blocks,index,search, and every other index-reading command.Proof (local, macOS, node 24.11.1)
pnpm run typecheck(src + contracts): cleanpnpm run lint: 226 files, no findingspnpm test(TMPDIR on a non-symlinked path): 681 passed, 16 skipped, 48 failed — all 48 aretests/kb-improvement/*and fail withexact knowledge candidate workflows require Linux directory descriptors, identical on a cleanorigin/maincheckout on this machine; CI (ubuntu) covers them.src/pages-directory.test.ts,tests/core.test.ts,tests/file-transaction.test.ts,tests/filesystem-search-provider.test.ts,src/run-scoped.test.ts,src/store.test.ts): 79 passed, 2 skipped.kb/pages/...while{ pagesDirectory }writes it and it lands in the scoped index and search while the default index stays empty;../escape,.agent-knowledge,raw,.refused on write; journal carriespagesDirectoryand recovery replays against it; default journal has nopagesDirectorykey; forged journalpagesDirectoryof.,..,./kb/, andkb+package.jsonentry all refused; provider and run-scoped chain readkb/pages.pnpm run build,pnpm run api:surface(+DEFAULT_PAGES_DIRECTORY, +KnowledgePagesOptions, +normalizePagesDirectory, -LoadKnowledgePagesOptions),pnpm run check:version-bump(major required, paid 8.0.10 -> 9.0.0),pnpm run verify:package: clean.apply-write-blockswithout the flag warnsunsafe pathand exits 2; with--pages-dir kb/pages/q36writes,index --pages-dirindexes 1 page,search --pages-dirranks it,--pages-dir ../xfails loud.Closes #159