Skip to content

feat(api): backup integrity + restore-to-new-DB + destructive-ack + hobby_plus copy - #107

Merged
mastermanas805 merged 2 commits into
masterfrom
fix-h-api
May 14, 2026
Merged

mastermanas805 merged 2 commits into
masterfrom
fix-h-api

Conversation

@mastermanas805

Copy link
Copy Markdown
Member

Summary

Wave FIX-H closes BugBash B36 56-67 / A2 / Q45-Q50 / R6 — the backup/restore
correctness gaps:

Test plan

  • +5 new backup_test.go cases (ReplayBlocked, TargetNewDB, RequiresDestructiveAck, HobbyAgentActionPointsToHobbyPlus, CrossTenantBackupID_404)
  • All prior backup tests updated to include destructive_acknowledgment
  • Contract test enforces 4 new agent_action constants
  • go build ./... green
  • go test ./internal/handlers/ -run TestCreateRestore|TestRestore_|TestCreateBackup|TestListBackups|TestListRestores|TestAgentActionContract|TestCapab green
  • Worker-side companion PR: InstaNode-dev/worker fix-h-worker (scheduler widens to hobby_plus + _yearly, SHA-256 stream, refund call)

Deploy notes

  • Migration 043 adds sha256 TEXT to resource_backups. Idempotent (IF NOT EXISTS).
  • New internal endpoint requires WORKER_INTERNAL_JWT_SECRET (already wired for /internal/teams/:id/terminate).
  • The DO_NOT_TOUCH list was respected — no changes to email/, dpop.go, circuit/.

🤖 Generated with Claude Code

mastermanas805 and others added 2 commits May 14, 2026 15:40
…obby_plus copy

Wave FIX-H wraps up the BugBash B36 backup-correctness items so customer-
facing restore stops being one accidental retry away from a destroyed
database.

#57/#Q45 — restore replay guard
  Adds models.HasInflightRestore; a second POST /restore for the same
  resource while a prior row is pending/running returns 409
  restore_in_progress + AgentActionRestoreInflight. Fail-CLOSED on DB
  error (concurrent pg_restore --clean races itself).

#58/#A2 — restore to a new DB
  Accepts optional target_resource_id. Worker restores into the target
  (same team only); audit row carries both source + target ids. Skips
  the destructive-ack ceremony because the agent already opted into a
  fresh database.

#59 — backup integrity
  Migration 043 adds a nullable sha256 TEXT column to resource_backups.
  Worker stamps the digest at finalize; restore handler verifies before
  pg_restore. NULL on legacy rows is logged + accepted (fail-open on
  pre-043 data).

#64/#Q46 — cross-tenant 404
  GetBackupByIDForTeam joins resources to scope by team; cross-tenant
  backup_id guess now returns 404 backup_not_found instead of
  400 backup_resource_mismatch. Matches FIX-B tenant-isolation posture.

#65/#Q47 — refund quota on failure
  New internal endpoint POST /internal/teams/:id/backup-quota/refund
  (WORKER_INTERNAL_JWT_SECRET HS256, fail-closed when unset). The
  worker calls this when a MANUAL backup fails terminally so the
  team's daily manual-backups counter is credited back.

#66/#Q48 — hobby agent_action points to Hobby Plus
  AgentActionRestoreRequiresHobbyPlus added. The Hobby-tier restore 402
  now nudges Hobby Plus ($19/mo, restore enabled) instead of skipping
  the customer past the cheapest restore-enabled plan onto Pro ($49).

#67/#Q49 — destructive ack required for in-place restore
  In-place restore (no target_resource_id) now requires
  destructive_acknowledgment: true in the body. pg_restore --clean drops
  every table — refusing without an explicit ack prevents an agent
  testing a backup from wiping a live customer DB.

#Q50 — RPO/RTO on /capabilities
  Adds rpo_minutes + rto_minutes per tier in plans.yaml (anonymous/free
  = 0/0, hobby/hobby_plus = 1440/30, pro/team = 60/15) wired into the
  /api/v1/capabilities matrix via plans.Registry.RPOMinutes/RTOMinutes
  (added in instant.dev/common/plans#13).

Tests
  - +5 backup_test.go cases: ReplayBlocked, TargetNewDB,
    RequiresDestructiveAck, HobbyAgentActionPointsToHobbyPlus,
    CrossTenantBackupID_404
  - All prior backup tests updated to include destructive_acknowledgment
  - Contract test enforces the four new agent_action constants

DO NOT TOUCH list respected — no edits to email/, dpop.go, circuit/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mastermanas805
mastermanas805 merged commit a7a1643 into master May 14, 2026
mastermanas805 added a commit to InstaNode-dev/worker that referenced this pull request May 14, 2026
…quota on failure (#27)

Wave FIX-H worker companion to InstaNode-dev/api#107. Closes the worker-
side gaps in BugBash B36.

#56/#R6 — scheduler tier set
  The hardcoded `tier IN ('hobby','pro','growth','team')` silently
  excluded hobby_plus and every _yearly variant: paying hobby_plus /
  hobby_plus_yearly / pro_yearly customers received zero scheduled
  backups. The fix widens the IN list to every tier whose plans.yaml
  row has backup_retention_days > 0 (hobby, hobby_plus, pro, growth,
  team plus all _yearly counterparts) and treats hobby/hobby_plus as
  daily-slot cadence (canonicalTier strips _yearly).

#59 — SHA-256 integrity
  pg_dump → gzip output is now teed through a sha256.Hash via
  io.MultiWriter; the hex digest is stamped into the new
  resource_backups.sha256 column (migration 043 ships in api PR
  #107) at finalize. On failure the digest is NOT written so the
  restore handler treats it as "unknown integrity, skip the check".

#65/#Q47 — refund manual-backup counter on failure
  New WithRefundClient wires the runner to call
  POST /internal/teams/:id/backup-quota/refund (HS256 with
  WORKER_INTERNAL_JWT_SECRET) when a MANUAL backup row fails
  terminally. Scheduled-row failures skip the refund. Idempotent on
  the api side — a worker restart that re-processes the same backup
  id is a no-op.

Tests
  - TestScheduler_HobbyPlus_OnSlotInserts (regression for #56/#R6)
  - TestScheduler_YearlyVariants_BackupHourly
  - TestCanonicalTier
  - Existing TestRunner_HappyPath / _PgDumpFails / _ClaimRace updated
    to expect backup_kind + sha256 in the row shape

DO NOT TOUCH list respected — no edits to circuit/, apiclient internals
beyond using New() at the boundary.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mastermanas805 added a commit to InstaNode-dev/worker that referenced this pull request May 21, 2026
…quota on failure

Wave FIX-H worker companion to InstaNode-dev/api#107. Closes the worker-
side gaps in BugBash B36.

#56/#R6 — scheduler tier set
  The hardcoded `tier IN ('hobby','pro','growth','team')` silently
  excluded hobby_plus and every _yearly variant: paying hobby_plus /
  hobby_plus_yearly / pro_yearly customers received zero scheduled
  backups. The fix widens the IN list to every tier whose plans.yaml
  row has backup_retention_days > 0 (hobby, hobby_plus, pro, growth,
  team plus all _yearly counterparts) and treats hobby/hobby_plus as
  daily-slot cadence (canonicalTier strips _yearly).

#59 — SHA-256 integrity
  pg_dump → gzip output is now teed through a sha256.Hash via
  io.MultiWriter; the hex digest is stamped into the new
  resource_backups.sha256 column (migration 043 ships in api PR
  #107) at finalize. On failure the digest is NOT written so the
  restore handler treats it as "unknown integrity, skip the check".

#65/#Q47 — refund manual-backup counter on failure
  New WithRefundClient wires the runner to call
  POST /internal/teams/:id/backup-quota/refund (HS256 with
  WORKER_INTERNAL_JWT_SECRET) when a MANUAL backup row fails
  terminally. Scheduled-row failures skip the refund. Idempotent on
  the api side — a worker restart that re-processes the same backup
  id is a no-op.

Tests
  - TestScheduler_HobbyPlus_OnSlotInserts (regression for #56/#R6)
  - TestScheduler_YearlyVariants_BackupHourly
  - TestCanonicalTier
  - Existing TestRunner_HappyPath / _PgDumpFails / _ClaimRace updated
    to expect backup_kind + sha256 in the row shape

DO NOT TOUCH list respected — no edits to circuit/, apiclient internals
beyond using New() at the boundary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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