Resolves the review finding on c23a71d7: 'store add' silently deleted a
markerless target directory and reported it as recovered-partial, but the
code cannot distinguish its own interrupted-write debris from content the
operator placed by hand — and the USER root's entire contract is that
tooling never destroys operator content.
- addStoreEntry now throws typed STORE_TARGET_UNMARKED on an unmarked
target; deletion happens only when the caller passes { reclaim: true }.
- CLI: 'store add' gains --reclaim ('replace an existing UNMARKED target
directory; refuses without this flag').
- Status renamed recovered-partial -> reclaimed-unmarked so even the
opted-in path names what it did (fix 2 folded into fix 1).
- Spec: default-refusal test asserts operator content SURVIVES; opt-in
test asserts replacement; two CLI tests cover exit codes.
- Ordering test (second review round): 'reclaim can never destroy a
marked, vetted entry' — adds a vetted entry, re-adds with reclaim:true,
asserts STORE_ALREADY_PRESENT AND the original content + marker survive
on disk. Pins marker-check-before-reclaim-check against the
guard-clause-migrates-upward refactor; discrimination proven by
sabotaging the order (1 failed, exactly this test) and restoring (49/49).
- TOCTOU note added at assertSourceTreeHasNoSymlinks per review (known
check-then-use window, accepted for a local operator-run CLI).
Gates (settled set, rc-honest): store spec 49/49; package vitest 87 files
/ 1596 tests; package build+lint rc0; root build 25/25 + typecheck 45/45;
prettier --check rc0 on all four touched files.
c23a71d7 remains the reviewed object, untouched.
First command over the USER data root (~/.mosaic), per the HARNESS-HOMES
two-root split: ~/.config/mosaic is update-owned system space; ~/.mosaic is
user content that installs/updates never touch. The store is the vetting
boundary for plugins and skills.
- commands/store.ts: store add <kind> <name> <version> --from <dir> --by
<operator> [--notes] — copies real directory content (symlinks refused,
source must be outside the store) into <root>/<kind>s/<name>/<version>/
and writes store-entry.json LAST, so a partial write can never list as a
usable entry (a markerless dir is reclaimed with status
recovered-partial; an existing marker makes add append-only-refusing).
store list [--kind] [--name] — deterministic enumeration with typed
statuses: vetted | incomplete | invalid-metadata | foreign (surfaced,
never mutated).
- Name/version validated before any filesystem call; rich status enum over
booleans; env seam MOSAIC_USER_HOME for tests — modelled on skill.ts,
pointed at the user root instead of the system root.
- constants: DEFAULT_MOSAIC_USER_HOME. cli.ts: registration only.
- store.spec.ts: 45 tests — validation matrix, marker-last/append-only
semantics, symlink refusal (source link and nested), self-copy guard,
partial recovery, listing classification, CLI exit codes.
Gates (worktree, sb-it-1-dt): store spec 45/45; package build+typecheck+lint
green; package pnpm test vitest 87 files/1593 tests green — framework-shell
chain stops at invariant_r (host pi 0.84.2 vs recorded 0.84.1, inherited);
root build 25/25 + typecheck 45/45; prettier clean (diff-scanned).
Deferred to W-F6: activation/symlink-install into agent homes, version
pinning, network acquisition (add is local-path only, by design).