Skip to content

feat(cli,db): 2.S follow-up — media_gc_grace_days + save_to project-root; mark 2.S Done#53

Merged
cemililik merged 9 commits into
mainfrom
development
Jun 25, 2026
Merged

feat(cli,db): 2.S follow-up — media_gc_grace_days + save_to project-root; mark 2.S Done#53
cemililik merged 9 commits into
mainfrom
development

Conversation

@cemililik

@cemililik cemililik commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Post-merge follow-up to PR #52 (2.S — media host-wiring), in three parts.

1. Roadmap: mark 2.S Done (492ef13)

2.S merged (PR #52, 2026-06-25). Flipped the canonical status surfaces — current.md (Landed list + next pickup → 2.R), phase-2-cli.md (top status line, the 2.S section banner, the acceptance row, the remaining-build-order Status + Next/Lane table, the judgement-call prose), and CLAUDE.md. Checked off the deferred D-items 2.S discharged (durable fail-cost, D15/D17/D8 CLI wiring, the CAS-orphan sweep, the clean-terminal reclaim-retry).

2. Wire [defaults].media_gc_grace_days (8efbc28)

Closes the 2.S-deferred "forward-declared, not read" item. Added to the config Zod schema; resolve.ts resolves it last-writer-wins and normalizes DAYS → ms (mediaGcGraceMs); run/gate thread it into the host GC's graceMs (absent ⇒ the built-in 7-day DEFAULT_MEDIA_GC_GRACE_MS). config-spec.md "NOT YET WIRED" note dropped. (ADR-0042 §4c)

3. Persist runs.project_root for save_to resume (de9135f)

Closes the 2.S-deferred "resumer-cwd vs original-run project root" item. The runs.project_root column existed but was never written/read: run now persists the run's cwd at run-start (threaded openHistoryStoreRunHistoryStoreDeps.projectRoot); loadRunSnapshot returns it; gate re-jails save_to under snapshot.projectRoot ?? deps.global.cwd — a run started in dir A and resumed from B writes its deliverables under A (the realpath+commonpath jail holds either way). (ADR-0044 §2)

Left deferred (intentional): Item 2 — host-GC orchestration CLI-locality — stays at deferred-tasks.md (Phase-3+, to be promoted to @relavium/db when a 2nd host wires media GC; premature abstraction now, no 2nd consumer).

Validation

  • pnpm turbo run lint typecheck test build format:check — green across @relavium/shared · @relavium/db · relavium (cli 385, db +2 new tests).
  • Leakwatch: 0 findings across the touched source dirs.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added media_gc_grace_days to configure the media GC grace window.
    • Resume/history now persists and reuses the original project root to correctly scope save_to.
  • Bug Fixes

    • Run-end and terminal-event media cleanup now consistently apply the grace window (including optional grace when configured).
    • Terminal-event cleanup is more resilient, avoiding run failure due to GC errors.
  • Documentation

    • Updated config reference, plus roadmap/status docs to reflect the new media GC grace behavior.

cemililik and others added 3 commits June 25, 2026 17:56
…eck off discharged deferrals

PR #52 (2.S — media host-wiring) merged 2026-06-25. Post-merge follow-up (deliberately held per the
done-after-merge convention):

- current.md: 2.S added to the Landed list (✅ PR #52, behind ADR-0042–0046, no new ADR; read_media
  deferred to 2.M); next pickup flipped 2.S → 2.R.
- phase-2-cli.md: top status line, the 2.S section Status banner, the acceptance/outcome row, the
  remaining-build-order Status line + Next/Lane table (2.R now #1, 2.S moved to a ✅ row), the
  "four additive lanes" → three, and the 2.S-timing judgement-call prose all reflect 2.S Done + 2.R next.
- CLAUDE.md: status paragraph — 2.S landed (PR #52), next pickup 2.R.
- deferred-tasks.md: checked off the D-items 2.S discharged — durable fail-cost (node:failed/run:failed/
  run:cancelled), D15 (catalog load-check wired on run/gate), D17 (media_cost_estimate threaded), D8
  (resolveForEgress injected), the CAS-orphan sweep, and the clean-terminal reclaim-retry — each with PR #52;
  preamble updated. Still-open 2.S-created deferrals (save_to resumer-cwd, host-GC CLI-locality,
  media_gc_grace_days threading) left unchecked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…edia GC (2.S follow-up)

Closes the 2.S-deferred "media_gc_grace_days is forward-declared, not read" item. The grace window
before a zero-reference media handle's CAS bytes are reclaimed (ADR-0042 §4c) was documented in
config-spec.md but never read — the host GC always used the built-in 7-day DEFAULT_MEDIA_GC_GRACE_MS.

- shared/config.ts: add `media_gc_grace_days` (positiveInt, optional) to `[defaults]`.
- config/resolve.ts: resolve it last-writer-wins and normalize DAYS → ms as `mediaGcGraceMs`
  (undefined when absent at every layer ⇒ the GC's built-in default still applies).
- run.ts / gate.ts: thread `config.mediaGcGraceMs` into `sweepHostMediaBestEffort`'s `graceMs`
  (conditional-spread; the param already existed).
- config-spec.md: drop the "NOT YET WIRED" note; media-gc.ts comment updated.
- resolve.test.ts: DAYS → ms + last-writer-wins + absent⇒undefined; the empty-config and
  media-wiring EMPTY_CONFIG literals gain the new field.

Refs: ADR-0042

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e_to under the original run (2.S follow-up)

Closes the 2.S-deferred "save_to resumer-cwd vs original-run project root" item. On a `relavium gate`
resume, `save_to`'s jail root was the RESUMER's cwd, so a run started in dir A and resumed from B
wrote its deliverables under B/.relavium/runs/ (the realpath+commonpath jail still held — only the
destination differed). The `runs.project_root` column existed in the schema but was never written or read.

- @relavium/db: `RunHistoryStoreDeps` gains an optional `projectRoot`; the `run:started` insert persists
  it to `runs.project_root` (NULL when absent); `RunResumeSnapshot` + `loadRunSnapshot` now return it.
- apps/cli: `openHistoryStore` takes the run's cwd and threads it as `projectRoot`; `run` passes
  `deps.global.cwd`; `gate` re-jails `save_to` under `snapshot.projectRoot ?? deps.global.cwd` (a run
  started before the column was populated falls back to the resumer's cwd).
- Tests: run:started persists project_root and loadRunSnapshot reads it back (+ the NULL default);
  the open.e2e stub threads the projectRoot arg.

Refs: ADR-0044

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @cemililik, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: deba8d05-6de7-456a-9fa8-b71542e81b2a

📥 Commits

Reviewing files that changed from the base of the PR and between 15703f1 and e28aef9.

📒 Files selected for processing (9)
  • apps/cli/src/commands/gate.test.ts
  • apps/cli/src/commands/gate.ts
  • apps/cli/src/commands/run.test.ts
  • apps/cli/src/engine/media-gc.test.ts
  • apps/cli/src/engine/media-gc.ts
  • apps/cli/src/history/open.e2e.test.ts
  • docs/reference/contracts/config-spec.md
  • docs/roadmap/current.md
  • docs/roadmap/deferred-tasks.md
✅ Files skipped from review due to trivial changes (3)
  • docs/reference/contracts/config-spec.md
  • docs/roadmap/current.md
  • docs/roadmap/deferred-tasks.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/cli/src/commands/gate.ts
  • apps/cli/src/engine/media-gc.ts

📝 Walkthrough

Walkthrough

The CLI now records each run’s project root, reuses it on resume for media save paths, and resolves media_gc_grace_days into a millisecond grace value passed into terminal media garbage collection. Related status, contract, and roadmap docs were updated.

Changes

CLI media root and GC wiring

Layer / File(s) Summary
Config grace contract
packages/shared/src/config.ts, apps/cli/src/config/resolve.ts, apps/cli/src/config/resolve.test.ts
media_gc_grace_days is added to the shared schema, resolved to mediaGcGraceMs, and covered by precedence, conversion, and absence tests.
Persist project root in run history
packages/db/src/run-history-store.ts, packages/db/src/run-history-store.test.ts
projectRoot is persisted on run:started, returned in resume snapshots, and verified in store tests.
Thread project root through run and resume
apps/cli/src/commands/run.ts, apps/cli/src/history/open.ts, apps/cli/src/history/open.e2e.test.ts, apps/cli/src/commands/gate.ts, apps/cli/src/commands/gate.test.ts
projectRoot is threaded through the run store opener and resume path, and resumed media wiring uses the stored root for save_to.
Forward GC grace into sweeps
apps/cli/src/commands/run.ts, apps/cli/src/commands/gate.ts, apps/cli/src/engine/media-gc.ts, apps/cli/src/engine/media-gc.test.ts, apps/cli/src/engine/media-wiring.test.ts, apps/cli/src/commands/run.test.ts, apps/cli/src/commands/gate.test.ts
Terminal media GC calls now pass mediaGcGraceMs when present, and the helper plus tests cover gating, optional grace, and error swallowing.
Update status and roadmap docs
CLAUDE.md, docs/reference/contracts/config-spec.md, docs/roadmap/current.md, docs/roadmap/deferred-tasks.md, docs/roadmap/phases/phase-2-cli.md
Project status, config reference, and roadmap pages now reflect the completed media host-wiring work and updated lane/status text.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • HodeTech/Relavium#52: Extends the media host-wiring path and terminal media GC flow that this PR updates with project-root resume handling and grace-window propagation.

Poem

🐇 I hopped the trails of root and time,
With grace in days now turned to rhyme.
The save path found its proper nest,
And GC swept with gentler rest.
Hop hop—tidy burrows, all set!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: 2.S follow-up, media_gc_grace_days wiring, project-root save_to behavior, and marking 2.S done.
Docstring Coverage ✅ Passed Docstring coverage is 86.67% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch development

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the persistence of the original run's project root (project_root) to ensure that cross-process resumes (via relavium gate) correctly re-jail save_to deliverables under the original run's directory instead of the resumer's current working directory. It also wires up the media_gc_grace_days configuration, converting it from days to milliseconds and threading it into the host media garbage collector. Feedback on the PR suggests checking if the persisted snapshot.projectRoot actually exists on the filesystem before using it, falling back to the resumer's current working directory if the path is missing (e.g., in different environments or if the directory was deleted).

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread apps/cli/src/commands/gate.ts Outdated
// run-start), so a run started in dir A and resumed from B still writes its deliverables under A — not the
// resumer's cwd. A run started before that column was populated (`projectRoot === null`) falls back to the
// resumer's cwd; the realpath+commonpath jail holds under either root.
const saveToRoot = snapshot.projectRoot ?? deps.global.cwd;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If a run is resumed in a different environment or machine (e.g., in a CI/CD pipeline, a shared team environment, or if the original directory was deleted/moved), the persisted snapshot.projectRoot path might not exist. If the path does not exist, attempting to write deliverables via save_to will fail with a directory-not-found or permission error.

By dynamically importing node:fs and checking if snapshot.projectRoot exists, we can safely fall back to the resumer's current working directory (deps.global.cwd) as a robust fallback, ensuring the resume operation succeeds.

Suggested change
const saveToRoot = snapshot.projectRoot ?? deps.global.cwd;
const fs = await import('node:fs');
const saveToRoot =
snapshot.projectRoot && fs.existsSync(snapshot.projectRoot)
? snapshot.projectRoot
: deps.global.cwd;

cemililik and others added 2 commits June 25, 2026 18:47
The threaded openRunStore stub (de9135f) wrapped past the print width; prettier reflowed it to a
multi-line object. No logic change. (Caught by the root //#format:check, which a --filter turbo run
skips — now validated filter-free.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t run/gate cognitive complexity

Two review findings on the PR #53 follow-up.

Review (gate.ts): a resume's `save_to` scope root was the persisted absolute `runs.project_root`
unconditionally — but on a different machine / in CI, or after the original dir was deleted or moved,
that path may not exist, so the `save_to` write could fail (permission / surprising location). Add
`resolveSaveToRoot(projectRoot, resumerCwd)`: use the original root only when it still `existsSync` on
this machine, else fall back to the resumer's cwd (a `null` pre-column run already fell back). The
realpath+commonpath jail holds under either root — only the destination changes.

Sonar (gate.ts 17→, run.ts 16→ over the 15 limit): the near-identical run-end GC block (the
isTerminal/casRoot/db guard + the try/catch swallow + the graceMs conditional-spread) is extracted to
`sweepMediaAtTerminal` in media-gc.ts, so neither command body carries the branch/try — one home for the
"GC fires only on a real terminal, and never fails the run" guard (DRYs the duplication too). gate's
save_to-root ternary likewise moves into resolveSaveToRoot.

Tests: resolveSaveToRoot — original-exists / deleted-or-other-machine / null → the right root. The
existing run/gate GC tests (invoke / skip-on-paused / swallow-a-throw) still pin sweepMediaAtTerminal
through the call sites.

Refs: ADR-0042, ADR-0044

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/cli/src/commands/gate.ts`:
- Around line 75-76: The resolveSaveToRoot helper currently treats any existing
path as valid, so it can return projectRoot even when that path is a file
instead of a directory. Update resolveSaveToRoot in gate.ts to verify
projectRoot is both non-null and a directory before returning it, using the
existing path check logic around existsSync with a directory-specific test, and
otherwise fall back to resumerCwd.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 18858335-98ac-4b86-9124-0aeaa52835bb

📥 Commits

Reviewing files that changed from the base of the PR and between de9135f and 15703f1.

📒 Files selected for processing (5)
  • apps/cli/src/commands/gate.test.ts
  • apps/cli/src/commands/gate.ts
  • apps/cli/src/commands/run.ts
  • apps/cli/src/engine/media-gc.ts
  • apps/cli/src/history/open.e2e.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/cli/src/history/open.e2e.test.ts

Comment thread apps/cli/src/commands/gate.ts Outdated
cemililik and others added 4 commits June 25, 2026 19:13
… just an existing path

Review finding (rank 4, SECURITY-001/CORRECTNESS-002 + the inline comment). `resolveSaveToRoot`
used `existsSync(projectRoot)`, which returns true for a FILE (or symlink-to-non-dir) too. If
`runs.project_root` ever held a non-directory path (DB corruption, manual edit, a stale row, or the
original dir replaced by a file), the file path was returned as the `save_to` jail scope root, the
fallback to the resumer cwd was bypassed, and the downstream `mkdir`/write failed with an opaque
ENOTDIR instead of degrading gracefully. Fail-closed (the realpath+commonpath jail still prevents any
escape), but confusing.

- Replace existsSync with `statSync(projectRoot, { throwIfNoEntry: false })?.isDirectory() === true`
  — undefined for an absent path (no try/catch), false for a file/symlink-to-non-dir, so only a real
  directory wins; everything else falls back to the resumer cwd. No non-null assertion (the
  `projectRoot !== null` guard narrows it).
- JSDoc: document the directory requirement and the known-benign existsSync→write-time-realpath
  TOCTOU window (rank 6) — the symlink-swap changes only the destination, never containment.
- Tests: a FILE-at-path → resumer-cwd fallback case; the "gone" case now uses a collision-free
  removed-tmpdir path instead of a fixed name (rank 7 hermeticity).

Refs: ADR-0044 §2

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… project_root data flows

Review findings (rank 1/2/3/5/10/11/12 — all test-coverage gaps on the two NEW data flows this PR
introduced, plus two clarifying comments). The wiring was correct but unasserted end-to-end, so a
future refactor dropping a thread would pass the whole suite.

- sweepMediaAtTerminal direct unit test (rank 3, + rank 1 unit half): all guard arms (non-terminal /
  db=undefined / casRoot=undefined → no sweep), graceMs forwarded when set, the key OMITTED when
  undefined (conditional-spread, not graceMs:undefined), and a throwing sweep swallowed.
- graceMs command chain (rank 1): run.test asserts the default GC gets no graceMs, AND a project.toml
  with media_gc_grace_days=3 threads graceMs = 3·86_400_000 (DAYS → ms) into the sweep; gate.test
  asserts the default omits graceMs.
- project_root resume re-jail (rank 2): a gate.test seeds the paused run with project_root = dir A,
  resumes, invokes the captured host.mediaWrite, and asserts the deliverable lands under A/.relavium/
  runs/ — NOT under the resumer cwd — proving loadRunSnapshot.projectRoot → resolveSaveToRoot →
  the wiring. setupPausedRun gains an optional projectRoot.
- openRunStore wiring (rank 5): run.test asserts deps.global.cwd is passed as the third (projectRoot)
  arg; open.e2e asserts loadRunSnapshot reads runs.project_root back == FIXTURES after a real run.
- rank 10: the in-memory openRunStore stub names its dropped params `_homeDir`/`_projectRoot` + a note.
- rank 11/12 (no behavior change): comments noting the gate resume store omits projectRoot (no
  run:started re-emit) and that sweepMediaAtTerminal's db-guard serves run's optional-store path.

Refs: ADR-0042, ADR-0044

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…iving-doc dates

Review nits (rank 8, 9).

- config-spec.md: note media_gc_grace_days has a minimum of 1 (the positiveInt schema rejects 0), so a
  user trying 0 for immediate reclamation has a spec-side explanation for the Zod error.
- current.md / deferred-tasks.md: bump the "Last updated" headers to 2026-06-25 — both were
  substantively edited by the 2.S-Done flip / the deferred check-offs while the header stayed stale
  (current.md's body already references 2026-06-25).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st additions

- run.test.ts: revert the openRunStore stub to `(workflow) => ...` (a valid subtype of the 3-arg
  type) — the suggested `(workflow, _homeDir, _projectRoot)` tripped @typescript-eslint/no-unused-vars
  (this config doesn't ignore `_`-prefixed trailing args); the dropped-params intent stays in the comment.
- prettier-format gate.ts (the multi-line resolveSaveToRoot dir-check) + media-gc.test.ts (the
  sweepMediaAtTerminal import). No logic change. (Caught by the root //#format:check / cli lint, which a
  --filter turbo run skips — re-validated filter-free + with eslint.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cemililik
cemililik merged commit 958f7af into main Jun 25, 2026
9 checks passed
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant