Skip to content

D760 — concept_transfer is a new tool that moves concepts between KBs under both git locks, target first

Decision. concept_transfer(target_kb, moves[]) moves concepts, with their assets, from the KB it is called on to another KB mounted on the same server. It is a tool of its own, not a target_kb on concept_move. It takes both KBs' git locks in one global order (the KBs' root paths), validates everything, writes and commits the target, then removes from and commits the source, then pushes both. A target-step failure discards the target tree and leaves the source alone; a source-step failure discards the source tree and undoes the target commit with a compensating revert commit, never a reset. Links to a moved page that stay behind in the source become plain text (the label is kept). Files are copied, byte for byte, never renamed. The source commit carries Moved-To: <kb>, the target commit Moved-From: <kb>.

Why. Nothing touched two KBs at once, so moving pages meant re-emitting each through the model, re-uploading binaries as base64 and deleting the originals. concept_move's policy class, audit keys and gitWrap all assume one KB, and tool names and semantics are a public contract (D101): widening it would have changed what a grant on it means. Target first means the content exists on the target before it disappears from the source, so a crash between the two leaves a duplicate, never a loss. The lock order is global because k.mu is not reentrant and A → B can run beside B → A. Copy, not os.Rename, because two KB roots may sit on different filesystems (EXDEV) and a rename would remove the source before the target is committed.

Alternatives rejected. - target_kb on concept_move: one tool with two policy shapes and a changed audit record. - A cross-KB link syntax for the links left behind: it would have to be understood by lint, the graph, the Atlas and every client, for a feature used rarely. - Source first, then target: a failure in between loses content. - A hard reset of the target after a source failure: the target commit may already be fetched, and history is append-only. - Applying the write gate (D350) to a transfer: it rolls a tree back after the fact, which with two KBs and a commit in between has no coherent meaning. Findings are reported per KB instead and the tool takes no accept_findings.

Consequences. concept_transfer is the one write tool outside gitWrap; it reuses its sync, trailer and push steps (syncInForWrite, commitTrailerBlock, pushAfterCommit). It needs a server that mounts both KBs (Deps.KBByName; single-KB stdio answers that it cannot), both KBs git-backed with auto-commit (the undo path is git), and on the routed endpoint a target inside the connection's KB set. The caller needs a whole-KB write grant on the source and a write grant on each destination slot of the target. Placeholder keys the target does not declare, sops material and secret_refs that do not travel are reported, not refused. Skills (artifact_move) are out of scope and a follow-up on the same mechanism.

Safety rules from review. Success of a commit is decided by HEAD moving, not by the SHA CommitOpAs returns; when the outcome is unknown nothing is discarded or reverted. The target is reverted only after the source tree was discarded: otherwise the target copy stays (a duplicate is recoverable, a loss is not). An expanded concept holding gitignored files is refused. Links from pages that stay to a moved asset are reported, not rewritten. Target-first push ordering is best effort: a failed target push does not stop the source push, and debounced pushes run per KB.