Skip to content

feat(service): add restore command for the private state backup roundtrip - #132

Merged
nish3451 merged 1 commit into
mainfrom
fix/service-backup-restore-roundtrip
Aug 13, 2026
Merged

feat(service): add restore command for the private state backup roundtrip#132
nish3451 merged 1 commit into
mainfrom
fix/service-backup-restore-roundtrip

Conversation

@nish3451

Copy link
Copy Markdown
Collaborator

Provide and behaviorally test the restore half of the advertised backup roundtrip.

Changes

  • scripts/service-state-backup.mjs: new restore mode
    • Validates --input exactly like verify (outside repo, 0700 root, manifest + byte/mode match)
    • Takes the shared service queue lock on the target repo
    • Refuses any pre-existing canonical root (clean-clone restore only, no merge/overwrite)
    • Stages beside the target repo, re-verifies staged bytes against the manifest, then swaps each root atomically via rename in canonical order
    • Rolls back already-swapped roots and removes staging on any failure (no partial state)
  • scripts/test-service-engine.mjs:
    • Roundtrip now restores through the product command instead of cpSync
    • New coverage: missing input, missing/corrupted manifest data, existing-target refusal, byte+mode parity, re-backup equivalence, injected mid-swap interruption that fails closed and preserves the target

Validation

  • node scripts/test-service-engine.mjs{"status":"passed","checks":38}
  • Focused CLI smoke: create → restore → re-backup/verify → interrupted restore preserved target with zero staging leftovers
  • git diff --check clean

No package.json changes (owned by the strict-check packet).

…trip

service-state-backup.mjs gains a restore mode that validates the snapshot
exactly like verify, refuses any pre-existing canonical root (clean-clone
restore only), stages beside the target repo, and swaps each root
atomically via rename with full rollback on failure. The service-engine
test roundtrip now restores through the product command instead of
cpSync, and new tests cover missing/malformed input, existing-target
refusal, byte+mode parity, re-backup equivalence, and an injected
mid-swap interruption that fails closed and preserves the target.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@nish3451, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 9 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Free

Run ID: bec64a85-062b-4266-a179-81f602286894

📥 Commits

Reviewing files that changed from the base of the PR and between 67222dd and 018ac8f.

📒 Files selected for processing (2)
  • scripts/service-state-backup.mjs
  • scripts/test-service-engine.mjs

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 018ac8f138

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

for (const root of roots) {
const target = join(repoRoot, root)
mkdirSync(dirname(target), {recursive: true})
renameSync(join(staging, root), target)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make the multi-root swap recoverable after process termination

If the restore process is killed or the machine loses power after one renameSync but before all roots are swapped, the catch block never runs, leaving a partial mix of restored and absent canonical roots. A subsequent restore then refuses to run because the first restored root already exists, so recovery requires unsafe manual cleanup; the injected exception test does not cover this termination scenario. Use a durable restore journal/recovery path or a genuinely atomic commit boundary for the multi-root swap.

AGENTS.md reference: AGENTS.md:L2-L3

Useful? React with 👍 / 👎.

const repoRoot = realpathSync(resolve(repoPath))
const manifest = validateManifest(JSON.parse(readFileSync(join(verified.input, MANIFEST), "utf8")))
const roots = [...RECORD_ROOTS, OUTPUT_ROOT].filter(root => verified.roots.includes(root))
const release = acquireLock(queuePaths(repoRoot).lockDir)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the clean target when restore is rejected

When a verified restore later fails—for example because clients/ already exists—acquireLock has already created runs/service-engine/ for the queue lock, and releasing the lock removes only .lock. The command therefore mutates a target it reports as rejected, violating the zero-write/fail-closed restore behavior; the new tests miss this because snap() records files but ignores empty directories. Acquire the lock without leaving new parents behind, or remove parents created solely for the failed restore.

AGENTS.md reference: AGENTS.md:L2-L3

Useful? React with 👍 / 👎.

@nish3451
nish3451 merged commit 4a7f442 into main Aug 13, 2026
2 checks passed
nish3451 pushed a commit that referenced this pull request Aug 13, 2026
…le with main after PRs #135/#104/#99/#112/#100/#105)

The PR #130 branch had already been reconciled with origin/main through
c617364 (PRs #131 + #133/#134/#132/#116). Since then origin/main moved to
d4f3ef4 with six more merges (#135 #104 #99 #112 #100 #105). Pull them in
so PR #130 can be reviewed against current main without weakening the
capture/rollback/reverification implementation or the PR #131 fail-closed
missing-secret gate.

The merge resolved cleanly:
- PR #130 capture/rollback logic (publish-public-site.mjs canonical_deployment,
  rollbackTo, restored identity re-verify, accept-again) is intact.
- PR #130 test file (scripts/test-pages-release.mjs) is intact and wired
  into the npm ci/test scripts.
- PR #131 fail-loud missing-secret workflow step in
  .github/workflows/deploy-public-site.yml is preserved byte-for-byte.
- The new origin/main changes touch operator surfaces and the deploy
  bundle prep; none of them modify the publish-public-site.mjs rollback
  contract or the Pages token gate, so there were no conflict markers.
nish3451 added a commit that referenced this pull request Aug 20, 2026
…on main (#130/#132/#133) (#197)

Record the decision for the inbox backlog line
(tinystudio-in priority=normal AUG13-16). Investigation shows every item is
already in main:

- truthful-reversible-pages-release: LANDED via PR #130 (ea9eab2); release
  lane fails truthfully on missing credentials and rolls back bad uploads
  (scripts/test-pages-release.mjs passes 38/38).
- service-backup-restore-roundtrip: LANDED via PR #132 (4a7f442); restore
  mode in service-state-backup.mjs.
- operator-checks-strict: LANDED via PR #133 (c617364); named operator
  checks fail on blocked readiness with advisory aliases explicitly named.

No code change required; this closes the backlog by recording the decision.
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