Repository navigation
feat(knowledge): document lifecycle ops for Knowledge Base - #505
waterbro-8 wants to merge 3 commits into
Conversation
… files Add knowledge-only lifecycle routes, IPC, and DocsPanel actions so created documents are no longer stuck read-only. SKILL.md stays bound read-only. Chinese type/size/name display is covered by tests. Refs #347.
|
Blocking review on exact head
Reproduction evidence from an isolated application of this PR: Keep this Draft until the exact-head fixes and focused regressions are present. Product tracking: #347 |
…ale UI Guard every write/rename/archive/restore/delete path component, use temp-rename writes and no-replace moves, reject invalid archived query values, drop stale DocsPanel reads/mutations across position and archive views, and require a trusted IPC sender. Archived knowledge leaves doc-ref resolution and bound item counts. Refs #347.
PeterGuy326
left a comment
There was a problem hiding this comment.
APPROVE on exact head c5bf1e8b286a0db98a0a60e278a80c55dd0c186d. Reviewed the cumulative #347 lifecycle change after updating onto current main. Verified locally: npm run build, renderer typecheck, docs-lifecycle 4/4, docs-panel 7/7, CJK gate, desktop docs IPC 8/8, server docs 15/15, and diff check. Write/rename/archive/restore/delete remain confined to knowledge/; SKILL.md stays bound read-only; traversal/symlink/archive-root/collision paths fail closed; late reads/writes are scoped. This is the required write/lifecycle prerequisite for follow-up #511, not the Obsidian-style autosave editor itself. PR is still Draft and current-head hosted CI is in progress, so do not merge until the author marks Ready and all checks finish green.
|
Independent exact-head review is now APPROVED on |
|
@waterbro-8 Current exact head |
sun-970
left a comment
There was a problem hiding this comment.
Review — feat(knowledge): document lifecycle ops for Knowledge Base
Refs #347. CI is fully green on this head (both Node matrix jobs, both installers, both staging smokes, layout parity, CodeQL, dependency review, scorecard). This is a genuinely well-hardened change and most of my comments are about the description and two design points, not defects.
The path-safety work is the strongest part of this PR
I read every one of the 17 files. The filesystem hardening is thorough in a way that is unusual, and it is worth enumerating because the review will otherwise read as if I only found problems:
assertMutableKnowledgePathrejectsSKILL.mdand anything outsideknowledge/, so the bound package files are structurally unwritable — the read-only rule is enforced by path predicate, not by a UI affordance.- Two independent escape checks.
resolveArchivePathrejects.., absolute results, and any hidden segment;assertRealFileChainwalks each segment withlstatand refuses symlinks at every level, not just the leaf. That is the correct defence against a symlinked intermediate directory, which a naiverealpath-at-the-end check would miss. moveNoReplaceusesfs.link+unlinkrather thanrename. This is the right primitive:renamewould silently overwrite an existing destination, andlinkreturningEEXIST→409 docs_existsgives you no-clobber semantics for free. Archive/restore round-trips cannot destroy a file.atomicReplaceRegularFilewrites to awx-opened temp with mode0600, thenrenames, thenchmods, and unlinks the temp on every failure path. Correct crash-safety, and a real improvement over a plainwriteFile.withPositionLockserialises mutations per position. Given that archive/restore rename within the same tree, this is load-bearing rather than decorative.assertOrCreateRealArchiveRootre-lstats aftermkdirand treatsEEXISTas "verify what is actually there", so a pre-planted symlink named.owb-docs-archiveis rejected rather than followed.
Two things I want changed before merge
1. readOnly: true → false on the bound context source contradicts the PR's own rule.
{
locator: `positions/.../SKILL.md + knowledge/**`,
binding: "bound",
- readOnly: true,
+ readOnly: false,The description says "Bound package files (SKILL.md and non-knowledge/*) stay read-only." But this flag describes the SKILL.md + knowledge/** source as a whole, and it now reports readOnly: false. So SKILL.md — which assertMutableKnowledgePath explicitly refuses to write — is advertised to whatever consumes readOnly as mutable. Either:
- the flag is a coarse per-source hint (in which case say why
SKILL.md's true read-only-ness is carried by the path predicate and not by this field), or - the flag is an input to some other enforcement, in which case this is a real hole.
Whichever it is, the current state is that a boolean in the shared context-source contract and a 403 predicate say different things about the same file. Please resolve it explicitly, and if readOnly is informational, consider whether a per-glob or per-entry granularity is needed now that one source contains both mutable and immutable paths.
2. The IPC trust check is applied inconsistently, and this PR makes that pattern worse.
You added authorizeDocsIpcSender and used it on all five new handlers — good. But the existing owb:position:docs:list and owb:position:docs:read handlers still take _event and never check the sender:
ipcMain.handle("owb:position:docs:list", async (_event, positionId, options) => {
const validated = validateDocsListRequest(positionId, options);And owb:position:docs:create — which existed before, and which is now joined by five siblings that do check — is also still unguarded. So after this PR the docs module has a split trust model: the read path and the create path accept an untrusted sender while write/rename/archive/restore/delete reject one.
An attacker who can reach ipcRenderer can therefore still create a document (and read the whole tree) without the sender check, which is a strictly larger capability than the one you just gated. Please apply authorizeDocsIpcSender to docs:list, docs:read, and docs:create in this PR. The helper already exists, so this is three lines — and leaving the inconsistency in place makes the new guards look like they were added per-handler rather than as a boundary.
Description/record gaps
The body is a Summary + Why + What + a command list. It is missing:
Refs #347is correct but insufficient — #347 is "document lifecycle ops and Chinese localization for Knowledge Base". Note the title you wrote omits the localization half. Either state that localization is out of scope for this PR (and where it lands), or cover it.- Baseline is oddly specific. "Base:
58f407ba" plus "Exact head:4514398..." pins a base that has certainly moved. State the merge base branch and rebase;mergeable_stateis currentlydirty, so a reviewer cannot tell whether the pinned base is the cause. - No
File domains,Scope and non-goals,Security and compatibility,Known limitations,Risk and rollback, or validation ledger. For a change that adds five filesystem-mutating endpoints,Security and compatibilityis not optional. Specifically:context-sources.tschanges a field in a shared contract — isreadOnly: falsea breaking change for any existing consumer? Say so either way. - The verification list has no results. "
npm run build,npm run typecheck:renderer,node --test apps/server/dist/test/docs.test.js, …" — please add the observedN/Nfor each, matching#524in this repo, which does this properly. - "Not mixed with #465/#466" is a useful claim but it is not verifiable from the description. Make it a scope statement in the
Scope and non-goalsheading instead of a trailing note.
Design point worth a decision
Archive and restore both move the file out of / back into the live tree, and handleDocsList with archived=1 walks .owb-docs-archive. That means the archive is a directory inside the position directory, which is also the tree that handleDocsList walks unarchived. Two consequences I would like stated:
walkFileson the live tree — does it skip.owb-docs-archive? If it does not, archived documents appear in the normal list, which would make the archive invisible-but-duplicated rather than hidden.- The archive is per-position and undiscoverable except via the flag. If a position is deleted, do its archived documents go with it, and is that intended?
The tests presumably cover (1); I could not confirm it from the diff alone, and it is the kind of thing that is obvious to the author and invisible to a reviewer. One sentence in the description would close it.
Not blocking
validateDocsDeleteRequestnormalisesarchived: falseto omitted rather than passingfalsethrough, while the server'sparsePositionPathBodyaccepts an explicit boolean. Both are safe (the server treatsundefinedas live-tree delete, andfalseis not accepted on the path-request variants at all), but the asymmetry is the kind of thing that becomes a bug when a third caller appears. A note thatarchived: falseis intentionally equivalent to absent would help.- The zh/en locale additions are in step, which I checked — no untranslated keys on either side.
|
Maintenance decision (owner-authorized cleanup, #607): close as absorbed by main. The lifecycle implementation landed through #523; #564 adds expectedVersion protection for writes. Fresh current-main regressions pass 16/16 server document tests and 8/8 IPC tests, including lifecycle isolation, archive exclusion, no-clobber moves, sender authorization and stale-version writes. The old branch would regress the newer write contract. #347 is audited independently for lifecycle/localization closure; no customer acceptance is inferred from this PR closure. |
Summary
Independent #347 delivery from
58f407ba: knowledge files can be edited, renamed, archived, restored, and deleted with confirmation. Bound package files (SKILL.mdand non-knowledge/*) stay read-only.Why
Created knowledge documents were stuck read-only, so they accumulated with no lifecycle. This PR adds the missing HTTP/IPC/UI path without mixing Jev work.
What
POST /docs/write|rename|archive|restore|delete,GET /docs/list?archived=1, archived readsVerification
npm run buildnpm run typecheck:renderernode --test apps/server/dist/test/docs.test.jsnode --test apps/desktop/test/docs-ipc.test.cjsdocs-lifecycle,docs-panel,docs-create-ref,tree-management,doc-viewer,i18n,i18n-cjk-gateNotes
58f407ba45143982acb9efcf6b95a6bbd9ccb9f73dc421c1