All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Fold the Mos-elevated #797 Runtime-Ledger survival sentinel into #791 PR1 and
harden the .txt-format parity test per the accepted-format conditions.
- framework-manifest.txt: annotate the existing fleet/run/** operator carve-out
to name the #797 ledger (fleet/run/sessions/) so it reads as load-bearing.
The glob already matches the #797 spec exactly — no location divergence.
- HARD GATE (test-upgrade-manifest-guard.sh): seed a populated ledger
(events.ndjson journal + ledger.json projection, 0600 under 0700) as an
operator sentinel; assert byte-identical + mtime-unchanged + dir-perms
unchanged after a keep-mode upgrade. Relabel the prune check as the explicit
negative control. 48 -> 58 checks.
- Parity (manifest-parity.spec.ts): add format-edge fixtures driven through
BOTH resolvers via MANIFEST_FILE — comments/blanks/whitespace, duplicate and
overlapping globs (deny-wins), section/glob-ordering independence, and an
explicit UNKNOWN->operator negative probe; add ledger probe paths.
- manifest.spec.ts: isolate the carve-out's load-bearing value with a resolver
red->green — under a hypothetical fleet/** framework glob, the ledger is
pruned WITHOUT the fleet/run/** carve-out and protected WITH it (deny-wins).
Gates: typecheck, lint, format:check green; mosaic vitest 1069 passed;
HARD GATE 58/58; migration 21/21. Commits forward on 34e55d4a (no rebase).
Part of #791
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
126 lines
9.3 KiB
Markdown
126 lines
9.3 KiB
Markdown
# Scratchpad — #791 Upgrade config protection (ms-791 worker lane)
|
|
|
|
**Lane:** web1:ms-791 → reports to MS-LEAD (web1:mosaic-100). Do NOT contact Jason/Mos directly.
|
|
**Worktree:** `/home/hermes/agent-work/stack-agents-dir-791`, branch `feat/791-upgrade-config-protection`
|
|
off `origin/main` `9745bc3f` (verified exact head).
|
|
|
|
## Mission prompt (verbatim intent)
|
|
Protect operator-owned config under `~/.config/mosaic` from framework-upgrade wipes. Ratified
|
|
combination (Mos-approved, do NOT re-litigate): (b) strict ownership separation [PRIMARY] + (a)
|
|
transactional pre-update snapshot [safety net] + (d) regeneration-from-SSOT [recovery]. (c) periodic
|
|
timer DEFERRED. HARD GATE: unit test that an upgrade run touches NO path outside the manifest.
|
|
Design-first: write design doc, send to MS-LEAD, WAIT for confirmation before impl.
|
|
|
|
## Session 1 (2026-07-16) — Phase 1 design
|
|
|
|
### Evidence gathered (wipe mechanism, file/line)
|
|
- `mosaic update` → `update-checker.ts:509` `buildReseedCommand` → `bash install.sh`
|
|
(`MOSAIC_SYNC_ONLY=1`, `MOSAIC_INSTALL_MODE=keep`).
|
|
- Wipe = `packages/mosaic/framework/install.sh:199` `rsync -a --delete` + `PRESERVE_PATHS` denylist
|
|
(`install.sh:47`). cp-fallback `install.sh:223` `find ... -exec rm -rf`.
|
|
- Denylist gaps → WIPED: `agents/*.conf`, `policy/*.md`, `*.local.md`, harvester/SOP,
|
|
`tools/_lib/credentials.json`.
|
|
- Stale comment `update-checker.ts:492` claims `*.local` preserved — PRESERVE_PATHS has no such entry.
|
|
- TS path `file-adapter.ts:157` → `file-ops.ts:66` `syncDirectory` = non-destructive copy-overlay, BUT
|
|
its preserve list (`file-adapter.ts:164`) already DRIFTED from install.sh (missing `fleet/backlog`,
|
|
`fleet/roles.local`). Evidence for single shared manifest SSOT.
|
|
- Existing snapshot (`install.sh:76`) = /tmp, crash-trap only, deleted on success → inadequate; no
|
|
`mosaic restore`.
|
|
- `fleet-reconciler.ts:93,234` already has `regenerate-projections-from-roster` phase separate from
|
|
lifecycle → `mosaic fleet regen` = thin projection-only wrapper (no restart), no FCM-M4/M5 preemption.
|
|
|
|
### Design decisions
|
|
- **(b)** Invert to allow-list: shared `framework/framework-manifest.json` (framework globs +
|
|
operatorReserved carve-outs); resolve per-path, deny-wins; **UNKNOWN ⇒ operator (fail-safe)**.
|
|
Mechanism: drop `--delete`; non-deleting bulk copy + explicit manifest-scoped prune pass (iterate
|
|
framework globs only → operator/unknown structurally unreachable). Pure prune-planner fn for tests.
|
|
- **(a)** Snapshot to `~/.local/state/mosaic/backups/pre-update-<ts>/` 0700/0600, retention N=5,
|
|
post-sync verify+restore, `mosaic restore --list/--from`. No secret values in output.
|
|
- **(d)** `mosaic fleet regen` projection-only, preview-first, never restart.
|
|
- HARD GATE test includes a deliberately-unanticipated operator path to prove fail-safe default.
|
|
- **PR split:** PR1 manifest+guard (root fix, ships alone) → PR2 snapshot/restore (secrev) → PR3
|
|
regen+docs. PR2/PR3 depend on PR1.
|
|
|
|
### Status
|
|
Design doc written: `docs/design/791-upgrade-config-protection.md`. Sent to MS-LEAD.
|
|
|
|
## Session 1 (cont.) — MS-LEAD CONFIRMED → Phase 2 GO
|
|
All 4 asks approved. Binding conditions:
|
|
- TDD tests-first, red-first proof per PR; ≥85% new-code; co-located `*.spec.ts`; never `--no-verify`.
|
|
- HARD GATE test (§2.4, unanticipated sentinel survives byte-identical + mtime unchanged) = MERGE-BLOCKING for PR1.
|
|
- Manifest-completeness test (§6.2) required.
|
|
- Bash+TS read ONE shared `framework-manifest.json`; parity test (§6.1) required (closes #631 drift class).
|
|
- UNKNOWN⇒operator (rule 3) non-negotiable. Keep prune-planner PURE.
|
|
- `fleet regen`: NEVER restart; dry-run default, `--write` to apply; "never issues restart" test mandatory.
|
|
- Independent review every PR; PR2 dedicated secrev.
|
|
- One PR at a time through DAG. Report PR1 exact head + red→green evidence for review commission.
|
|
|
|
### Now: implementing PR1 (manifest + resolver + non-deleting sync + scoped prune + guard tests).
|
|
|
|
## Session 2 (2026-07-16) — PR1 built, tests-first, red→green proven
|
|
|
|
Deviation noted to MS-LEAD in PR: manifest is `framework-manifest.txt` (line-oriented), NOT `.json`.
|
|
Rationale: keep the bash installer free of a python3/jq dependency. The "ONE shared file, parity-
|
|
tested" requirement is honored — `manifest-parity.spec.ts` drives the bash resolver as a subprocess
|
|
and asserts byte-identical ownership vs the TS resolver over 34 probe paths spanning every class.
|
|
|
|
### PR1 artifacts
|
|
- SSOT: `packages/mosaic/framework/framework-manifest.txt` ([framework]/[operator], deny-wins, fail-safe).
|
|
- TS resolver: `src/framework/manifest.ts` (pure: parse/matchGlob/resolveOwnership/frameworkSubtreeRoots/
|
|
planPrune) + `manifest.spec.ts` (18 tests incl. planPrune property test + §6.2 completeness).
|
|
- Bash resolver: `framework/tools/_lib/manifest.sh` (compiled globs → fork-free `manifest_is_framework`;
|
|
CLI `resolve|subtree-roots|classify`). Sourced by install.sh.
|
|
- HARD GATE (§2.4): `framework/tools/quality/scripts/test-upgrade-manifest-guard.sh` — keep-mode reseed,
|
|
10 operator sentinels (incl. unanticipated `unknown-operator-dir/x`, `harvester/sop.md`,
|
|
`fleet/my-fleet.yaml`) survive byte-identical + mtime-unchanged; retired framework file pruned;
|
|
secret value absent from output. RED=31 fail (orig install.sh) → GREEN=48 pass (fixed).
|
|
- install.sh: keep mode now manifest-driven (`sync_framework_keep`, no `--delete`); overwrite unchanged.
|
|
PRESERVE_PATHS denylist deleted.
|
|
- TS sync: `file-ops.syncDirectory` gains `isOperatorOwned` guard; `file-adapter.syncFramework` derives
|
|
it from `loadManifest` — hardcoded (drifted) preservePaths deleted. Fixture uses the REAL manifest.
|
|
- Parity: `manifest-parity.spec.ts` (§6.1) — bash↔TS agree on 34 paths + subtree roots.
|
|
- Migration matrix `test-install-migration.sh`: F6 flipped — `my-fleet.yaml` now MUST survive (fail-safe).
|
|
- CI: new merge-blocking `upgrade-guard` step (`.woodpecker/ci.yml`) runs both bash suites (adds rsync).
|
|
- update-checker.ts reseed comment corrected to the manifest model.
|
|
|
|
### Gates (all green)
|
|
- `pnpm typecheck` ✓ · `pnpm lint` ✓ · `pnpm format:check` ✓
|
|
- Full mosaic vitest: 1062 passed (cli-smoke needs `pnpm build` first — build-artifact dep, not this change).
|
|
- HARD GATE 48/48 · migration 21/21 · parity 3/3 · manifest 18/18 · file-adapter 8/8.
|
|
|
|
### PR opened + reported (2026-07-16)
|
|
- **PR #802** http://git.mosaicstack.dev/mosaicstack/stack/pulls/802 — base `main`@`9745bc3f`,
|
|
head `34e55d4a` (commit `feat(mosaic): manifest-owned upgrade guard…`). 15 files, +1160/-142.
|
|
- Reported PR head + red→green evidence to MS-LEAD (web1:mosaic-100); queued (lead busy).
|
|
Standing by for the independent-review commission at head `34e55d4a`.
|
|
- **TWO items flagged to MS-LEAD for decision (awaiting reply):**
|
|
1. Deviation `.txt` vs `.json` — confirm accept (parity-tested) or convert to `.json`+jq.
|
|
2. `pr-create -i 791` appended `Fixes #791` → would auto-close the tracking issue on PR1 merge
|
|
while PR2/PR3 remain. Recommended edit to `Part of #791`; awaiting go-ahead to patch PR body.
|
|
- DO NOT start PR2/PR3 until PR1 merges (DAG; one PR at a time).
|
|
|
|
### MS-LEAD ruling → #797 ledger-survival sentinel folded into PR1 (2026-07-16)
|
|
MS-LEAD ruled both my decisions: (1) `.txt` format ACCEPTED (parity must be strict/merge-blocking incl.
|
|
format edge cases + negative probe); (2) trailer `Fixes #791`→`Part of #791` APPROVED (patched PR #802
|
|
body via Gitea API — tracking issue no longer auto-closes on PR1 merge). Plus Mos-ELEVATED merge-blocker
|
|
(spec `~/agent-work/planning/epic-796/791-ledger-survival-sentinel-SPEC.md`): #797 Runtime Session Ledger
|
|
must survive upgrade. Two coupled deliverables landed in PR1:
|
|
- (i) Carve-out: `fleet/run/**` was ALREADY an explicit `[operator]` entry — glob matches the spec's
|
|
pinned `fleet/run/**` EXACTLY, so NO divergence to route back to planner-opus. Strengthened its comment
|
|
to name the ledger (`fleet/run/sessions/` events.ndjson + ledger.json) so it is unmistakably load-bearing.
|
|
- (ii) HARD-GATE sentinel: seeded populated ledger (events.ndjson 3 events + ledger.json node+edge+gen,
|
|
0600 under 0700) into test-upgrade-manifest-guard.sh sentinels; asserts byte-identical + mtime-unchanged
|
|
+ dir-perms unchanged. Negative control (retired framework file IS pruned) relabeled explicitly.
|
|
HARD GATE now 58/58 (was 48).
|
|
- Decision-1 parity hardening: format-edge fixtures (comments/blanks/whitespace, duplicate+overlapping
|
|
globs deny-wins, section/glob-ordering independence) + explicit UNKNOWN→operator negative probe, driven
|
|
through BOTH resolvers via MANIFEST_FILE override. Parity 7/7 (was 3).
|
|
- RED-FIRST honesty note: the bash ledger sentinel stays GREEN even against the pre-fix installer (the
|
|
ledger was incidentally safe from the rsync --delete bug; overall pre-fix run 30/58 as expected). The
|
|
carve-out's TRUE load-bearing value (deny-wins if framework ownership ever broadens to `fleet/**`) is
|
|
isolated by a dedicated resolver-seam red→green in manifest.spec.ts: WITHOUT `fleet/run/**` operator
|
|
entry + hypothetical `fleet/**` framework → ledger resolves framework and planPrune DELETES it (RED);
|
|
WITH the carve-out → deny-wins → operator, unprunable (GREEN). manifest.spec.ts 21/21 (was 18).
|
|
- Gates all green: typecheck ✓ lint ✓ format:check ✓ · full mosaic vitest 1069 passed · HARD GATE 58/58
|
|
· migration 21/21. Committing FORWARD on the branch (NOT rebasing 34e55d4a out from under review).
|