Skip to content

315 production audit readiness#316

Merged
InfinityBowman merged 14 commits into
mainfrom
315-production-audit-readiness
Jan 19, 2026
Merged

315 production audit readiness#316
InfinityBowman merged 14 commits into
mainfrom
315-production-audit-readiness

Conversation

@InfinityBowman
Copy link
Copy Markdown
Owner

@InfinityBowman InfinityBowman commented Jan 19, 2026

Summary by CodeRabbit

  • New Features

    • Production Readiness Audit plugin with phased audits, agent roles, and reporting templates.
    • Sentry error monitoring enabled for frontend and backend.
  • Improvements

    • Exponential-backoff retry wrappers for transient operations.
    • Rollback support for quota-protected inserts to handle race conditions.
    • Rate limiting added to magic-link authentication paths.
    • Non-blocking best-effort handling for PDF caching/cleanup.
    • Database recovery guidance for Cloudflare D1 Time Travel.

✏️ Tip: You can customize this high-level summary in your review settings.

@InfinityBowman InfinityBowman linked an issue Jan 19, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 19, 2026

📝 Walkthrough

Walkthrough

Adds a prod-readiness Claude plugin and documentation, integrates Sentry in frontend and Cloudflare Workers, implements a generic retry utility and Durable Object sync wrappers with rollback support, and replaces several PDF/study error handlers with bestEffort non-fatal wrappers.

Changes

Cohort / File(s) Summary
Prod-audit plugin & docs
.claude/plugins/README.md, .claude/plugins/prod-audit/.claude-plugin/plugin.json, .claude/plugins/prod-audit/README.md, .claude/plugins/prod-audit/commands/prod-audit.md
Adds a new Claude plugin manifest plus comprehensive production-readiness documentation and a 9-phase audit workflow.
Prod-audit agent specs
.claude/plugins/prod-audit/agents/*
Adds role specs for system-explorer, code-auditor, security-auditor, reliability-auditor, and ops-auditor with analysis approaches and output templates.
Prod-audit skills
.claude/plugins/prod-audit/skills/*/SKILL.md
Adds skill guides for data-safety, observability, and performance analysis with checklists and report templates.
Frontend Sentry & error logging
packages/web/.env.example, packages/web/package.json, packages/web/src/config/sentry.js, packages/web/src/main.jsx, packages/web/src/lib/errorLogger.js
Adds Sentry dependency and init, exposes capture/setUser utilities, integrates Sentry into errorLogger, and initializes Sentry at app startup.
Backend Sentry integration
packages/workers/package.json, packages/workers/src/index.ts, packages/workers/wrangler.jsonc
Adds @sentry/cloudflare, wraps exported app with Sentry.withSentry, and adds version metadata / dev SENTRY_DSN var.
Retry utilities & DO sync wrappers
packages/workers/src/lib/retry.ts, packages/workers/src/lib/syncWithRetry.ts
Adds generic withRetry (exponential backoff, jitter, logging) and retry-wrapped DO sync helpers (syncMemberWithRetry, syncProjectWithRetry) with retry predicates and scoped logging.
Quota rollback support
packages/workers/src/lib/quotaTransaction.ts, packages/workers/src/commands/projects/createProject.ts
Adds InsertRollbackMeta type and rollbackMeta support to insertWithQuotaCheck; createProject supplies rollback metadata for reverse-order cleanup on quota races.
Commands using syncWithRetry
packages/workers/src/commands/members/{addMember,removeMember,updateMemberRole}.ts
Replaces direct DO sync calls with syncMemberWithRetry and removes local try/catch logging, delegating retries to the wrapper.
Auth & billing adjustments
packages/workers/src/auth/routes.ts, packages/workers/src/routes/billing/helpers/orgContext.ts
Adds authRateLimit to /magic-link/* routes and verifies activeOrganizationId membership with domain error mapping on org resolution.
Frontend bestEffort wrappers for PDFs/studies
packages/web/src/components/project/ProjectView.jsx, packages/web/src/stores/projectActionsStore/{pdfs,studies}.js
Replaces several .catch handlers with bestEffort non-fatal wrappers for PDF cache/delete and cleanup flows, adding contextual logging.
Misc docs & config
.gitignore, packages/docs/guides/database.md, packages/landing/wrangler.jsonc
Adds ignore entry for worker-configuration.d.ts, Cloudflare D1 Time Travel recovery docs, and exposes VITE_SENTRY_DSN in landing wrangler config.

Sequence Diagram(s)

sequenceDiagram
  participant Cmd as Command (addMember)
  participant Retry as withRetry
  participant Sync as syncMemberToDO
  participant DO as DurableObject
  participant Logger as Scoped Logger

  Cmd->>Retry: syncMemberWithRetry(env, projectId, 'add', data)
  Retry->>Retry: attempt operation (up to 3 tries, backoff + jitter)
  Retry->>Sync: invoke syncMemberToDO(...)
  Sync->>DO: perform DO operation
  DO-->>Sync: success / error
  alt success
    Retry-->>Cmd: resolved
    Retry->>Logger: log post-retry success (if retried)
  else retryable error
    Retry->>Logger: log retry attempt and delay
    Retry->>Retry: sleep delay, retry
  else final failure
    Retry->>Logger: log final non-fatal failure
    Retry-->>Cmd: resolved (failure recorded, non-throwing)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #300: Modifies backend DO sync and member/project command code paths that this PR wraps with retry logic.
  • #292: Directly overlaps on Durable Object sync and retry/refactor work for members/projects.
  • #288: Touches worker app export/initialization, intersecting with the Sentry wrapping changes.
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title '315 production audit readiness' is vague and generic, using non-descriptive terms that don't convey meaningful information about the changeset beyond a ticket reference. Revise the title to clearly summarize the main change, e.g., 'Add production readiness audit plugin with 9-phase workflow' or 'Implement prod-audit Claude plugin and Sentry integration'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cccaf56 and 57ec2c8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • packages/landing/wrangler.jsonc
  • packages/web/package.json
  • packages/web/src/config/sentry.js
✅ Files skipped from review due to trivial changes (1)
  • packages/landing/wrangler.jsonc
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/src/config/sentry.js
  • packages/web/package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: corates
  • GitHub Check: prettier

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Jan 19, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
corates 57ec2c8 Commit Preview URL Jan 19 2026, 06:07 PM

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/workers/src/commands/members/removeMember.ts (1)

51-56: Database delete operation lacks error handling.

Per coding guidelines, database operations should be wrapped in try-catch blocks and return domain errors using createDomainError with SYSTEM_ERRORS.DB_ERROR. The delete operation on lines 51-53 is not wrapped.

Proposed fix
+  try {
     await db
       .delete(projectMembers)
       .where(and(eq(projectMembers.projectId, projectId), eq(projectMembers.userId, userId)));
+  } catch (err) {
+    throw createDomainError(SYSTEM_ERRORS.DB_ERROR, {
+      operation: 'remove_member',
+      cause: err instanceof Error ? err.message : String(err),
+    });
+  }

Note: This requires adding SYSTEM_ERRORS to the import from @corates/shared.

Based on coding guidelines: "Wrap database operations in try-catch blocks and return domain errors using createDomainError with SYSTEM_ERRORS.DB_ERROR".

packages/workers/src/commands/projects/createProject.ts (1)

110-136: Use syncProjectWithRetry instead of syncProjectToDO to handle sync failures with automatic retry.

The retry mechanism already exists in syncProjectWithRetry (3 attempts with exponential backoff), but this code is calling syncProjectToDO directly without it. On failure, D1 (the database) remains the source of truth and the DO will sync when clients reconnect; however, the sync should be attempted 3 times with backoff before accepting the eventual consistency model. Replace the syncProjectToDO call with syncProjectWithRetry for consistency with the application's designed failure handling.

🤖 Fix all issues with AI agents
In @.claude/plugins/prod-audit/skills/observability/SKILL.md:
- Line 54: The fenced code block in SKILL.md flagged by MD040 lacks a language
identifier; update the triple-backtick fence for that empty/text block to
include a language specifier (e.g., change ``` to ```text) so the markdown
linter recognizes the block type and the MD040 warning is resolved.
- Line 35: The fenced code block at the comment "Request context included" is
missing a language identifier (MD040); update that fenced block by adding a
language tag (for example use ```text or ```txt) immediately after the opening
backticks so the block becomes a labeled code/fenced block and satisfies the
linter requirement.
- Line 209: The markdown fenced code block currently has no language identifier
(violates MD040); update the closing/opening triple-backtick block in SKILL.md
to include a language tag (e.g., replace ``` with ```markdown) so the fenced
block is explicitly marked as markdown; locate the final fenced block (the
triple backticks near the end of the file) and add the language identifier.

In @.claude/plugins/prod-audit/skills/performance/SKILL.md:
- Line 242: The markdown has a fenced code block with no language identifier in
SKILL.md; update the opening fence from ``` to ```text for that block (and
similarly add language identifiers to any other bare fences) so markdownlint
MD040 is satisfied and the fenced block is explicitly labeled.

In `@packages/docs/guides/database.md`:
- Around line 814-950: The docs currently show examples using wrangler d1
time-travel export and wrangler d1 time-travel bookmark which are not in the
official Wrangler docs; update the guide to only use supported commands
(wrangler d1 time-travel info and wrangler d1 time-travel restore) or explicitly
note the required Wrangler version if those extra commands are from a specific
release. Replace or remove the `time-travel export` and `time-travel bookmark`
examples in the sections "Export Data from a Point in Time" and "Pre-Deployment
Backup Procedure", and either provide the documented alternative (e.g.,
instructions to export via a supported dump/query workflow or to create
bookmarks via the supported API/CLI) or add a version note that `time-travel
export`/`bookmark` require X Wrangler version.

In `@packages/web/.env.example`:
- Around line 12-16: The dotenv example has quoted empty values and ordering
that trigger dotenv-linter; update the Sentry entries by removing the
surrounding quotes from VITE_SENTRY_DSN and VITE_SENTRY_DEV and place them in
the correct expected order (ensure VITE_SENTRY_DSN appears before
VITE_SENTRY_DEV) so both keys are unquoted empty values (e.g., VITE_SENTRY_DSN=
and VITE_SENTRY_DEV=) to satisfy the linter.

In `@packages/web/package.json`:
- Line 50: The package now depends on "@sentry/browser": "^10.35.0" which
introduces breaking changes from 9.x; either pin the dependency back to a
compatible 9.x release or migrate the codebase to Sentry v10 by updating all
Sentry usages: search for the dependency string "@sentry/browser" and then
replace removed/renamed APIs (BaseClient → Client, hasTracingEnabled →
hasSpansEnabled), remove any FID/web-vital usages and switch to INP where
applicable, update IP inference/config options to use sendDefaultPii, and adjust
any OpenTelemetry-related integrations to v2 and remove reliance on the exported
logger; follow Sentry’s 9→10 migration guide while updating files that
initialize or configure Sentry (e.g., initSentry, sentry setup, tracing
wrappers) to ensure API and config changes are applied consistently.

In `@packages/web/src/config/sentry.js`:
- Around line 1-60: The beforeSend check treats import.meta.env.VITE_SENTRY_DEV
as a string, so the literal "false" is truthy and inverts behavior; fix by
normalizing once (e.g., add a constant like SENTRY_DEV =
import.meta.env.VITE_SENTRY_DEV === 'true') and use that boolean in initSentry's
beforeSend (replace !import.meta.env.VITE_SENTRY_DEV with !SENTRY_DEV), keeping
references to ENVIRONMENT and the initSentry/beforeSend flow intact.

In `@packages/workers/.env.example`:
- Around line 19-20: The .env.example defines VITE_SENTRY_DSN but the runtime
and wrangler expect SENTRY_DSN; update the example to rename the variable from
VITE_SENTRY_DSN to SENTRY_DSN so the environment key used by Sentry
initialization and wrangler matches the example, ensuring any references to
VITE_SENTRY_DSN are replaced with SENTRY_DSN (check for usages of
VITE_SENTRY_DSN and SENTRY_DSN in the workers code and wrangler config to
confirm consistency).

In `@packages/workers/src/index.ts`:
- Line 10: The Sentry initialization currently enables sendDefaultPii
unconditionally; add an explicit environment flag SENTRY_SEND_PII (default
false) and use it to set sendDefaultPii in the Sentry.init call so PII capture
is off unless SENTRY_SEND_PII=true, and add the SENTRY_SEND_PII entry with a
default false note to .env.example; locate the Sentry.init usage (imported as
Sentry from '@sentry/cloudflare') and replace the literal true/undefined value
with a boolean derived from process.env.SENTRY_SEND_PII.
- Around line 373-378: The Env interface is missing bindings for SENTRY_DSN and
CF_VERSION_METADATA which causes TypeScript errors when code reads
env.SENTRY_DSN and env.CF_VERSION_METADATA?.id; update the Env type declaration
(the Env symbol) to extend Cloudflare.Env with optional properties SENTRY_DSN?:
string and CF_VERSION_METADATA?: { id: string } so the compiler knows those
bindings exist and env.CF_VERSION_METADATA.id is typed.

In `@packages/workers/src/routes/billing/helpers/orgContext.ts`:
- Around line 32-59: Wrap the DB select calls that compute membership and
firstMembership in try/catch and convert any thrown errors into a domain error
via createDomainError(SYSTEM_ERRORS.DB_ERROR); specifically, surround the block
that queries member (the await db.select(...).where(...).get() that assigns
membership and the subsequent await db.select(...).limit(1).get() that assigns
firstMembership) with a try/catch, import/use createDomainError and
SYSTEM_ERRORS, and in the catch throw createDomainError(SYSTEM_ERRORS.DB_ERROR)
so callers receive the standardized domain error on DB failures.

In `@packages/workers/wrangler.jsonc`:
- Around line 64-65: Remove the hardcoded "SENTRY_DSN" entry from the shared
vars block (next to "APP_URL") and instead load it from a local/dev-only source:
either add it to a .wrangler.local.toml for local development or switch to
environment-specific values and secrets (follow the existing production pattern
using the wrangler secret put SENTRY_DSN --env production command). Update any
doc/comment in the config to instruct developers to place their dev DSN in
.wrangler.local.toml or set an env var, and ensure "SENTRY_DSN" is not committed
in the shared vars section.
🧹 Nitpick comments (9)
packages/workers/src/lib/retry.ts (2)

81-87: Minor: Redundant logging fields.

attempt and totalAttempts are identical here. Consider removing one or renaming for clarity.

       if (attempt > 1 && logger) {
         logger.info(`${operationName} succeeded after retries`, {
           attempt,
-          totalAttempts: attempt,
         });
       }

145-156: Jitter can exceed maxDelayMs.

The exponential value is capped at maxDelayMs, but jitter (up to 20%) is added afterward. This means the actual delay could reach 120% of maxDelayMs. If strict upper bounds are needed, cap the final result.

Proposed fix
   // Add 0-20% jitter to prevent thundering herd
   const jitter = exponential * 0.2 * Math.random();
 
-  return Math.round(exponential + jitter);
+  return Math.min(Math.round(exponential + jitter), config.maxDelayMs);
 }
packages/workers/src/lib/syncWithRetry.ts (2)

147-156: Regex may not match all error message formats.

The regex /:\s*(\d{3})\s/ requires whitespace after the status code. If error messages end with the status code or use different formatting (e.g., "failed: 503" without trailing space), the match will fail and the error will be retried unconditionally.

Consider making the trailing space optional:

-  const statusMatch = message.match(/:\s*(\d{3})\s/);
+  const statusMatch = message.match(/:\s*(\d{3})(?:\s|$)/);

14-38: Consider tighter typing for member/project data interfaces.

The index signatures ([key: string]: unknown) allow arbitrary properties. If the underlying syncMemberToDO and syncProjectToDO functions have specific expectations, stricter types would catch mismatches at compile time.

.claude/plugins/prod-audit/skills/data-safety/SKILL.md (1)

99-103: Minor wording tweak to avoid redundancy.
Consider “point‑in‑time restore” instead of “point in time.”

.claude/plugins/prod-audit/agents/ops-auditor.md (1)

123-182: Optional: Add language specifiers to fenced code blocks for consistency.

The output template blocks at lines 123-131, 135-144, and 148-155 are missing language specifiers. While these are example outputs rather than executable code, adding specifiers (e.g., text or markdown) would improve consistency and rendering across different markdown viewers.

♻️ Proposed formatting improvements
 **Deployment Assessment:**
-```
+```text

 Process: [Automated/Manual/Hybrid]
 **Migration Safety:**

-```
+```text

 | Migration Status    | Assessment       |
 **Operational Gaps:**

-```
+```text

 1. [Gap Title]

Based on static analysis hints, this improves markdown formatting consistency.

.claude/plugins/prod-audit/agents/system-explorer.md (1)

65-81: Optional: Add language specifier to output template block.

The system overview template block (lines 65-81) is missing a language specifier. Adding text would improve consistency.

♻️ Proposed formatting improvement
 **System Overview:**
-```
+```text

 Entry Points:

Note: The static analysis warning about "unused link reference" on line 73 is a false positive—[Database] is placeholder syntax in the template, not a markdown link reference.

Based on static analysis hints, with clarification on false positive.

packages/workers/src/lib/quotaTransaction.ts (1)

244-257: Best-effort rollback approach is reasonable, but partial failures may leave orphaned records.

Sequential deletes are correct for FK constraint ordering. However, if an early delete fails (e.g., projectMembers succeeds but projects fails), you'll have orphaned records. Consider whether this warrants alerting/monitoring beyond the current console.error logging.

Consider enhanced observability for partial rollback failures
         } catch (deleteErr) {
           // Log but continue - best effort rollback
           console.error(
             `[QuotaTransaction] Failed to rollback record during race condition cleanup:`,
             { table: table._.name, id, error: deleteErr },
           );
+          // TODO(agent): Consider emitting a metric or Sentry alert for partial rollback failures
+          // to enable monitoring of orphaned records that may need manual cleanup
         }
packages/web/src/main.jsx (1)

10-13: Clarify the Sentry init comment to state intent.

Consider phrasing the comment in terms of why this runs early. As per coding guidelines.

Proposed adjustment
-// Initialize Sentry early, before any other code runs
+// Initialize Sentry early to capture startup errors

// NOT: creditCardNumber, password, etc.
});

// Request context included
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language identifier to this fenced block (MD040).

🔧 Suggested change
-```
+```text
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

35-35: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In @.claude/plugins/prod-audit/skills/observability/SKILL.md at line 35, The
fenced code block at the comment "Request context included" is missing a
language identifier (MD040); update that fenced block by adding a language tag
(for example use ```text or ```txt) immediately after the opening backticks so
the block becomes a labeled code/fenced block and satisfies the linter
requirement.

// Sensitive data exposure
logger.info('User login', { password: user.password });

// Inconsistent format
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language identifier to this fenced block (MD040).

🔧 Suggested change
-```
+```text
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

54-54: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In @.claude/plugins/prod-audit/skills/observability/SKILL.md at line 54, The
fenced code block in SKILL.md flagged by MD040 lacks a language identifier;
update the triple-backtick fence for that empty/text block to include a language
specifier (e.g., change ``` to ```text) so the markdown linter recognizes the
block type and the MD040 warning is resolved.


## Report Structure

```markdown
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language identifier to this fenced block (MD040).

🔧 Suggested change
-```
+```markdown
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

209-209: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In @.claude/plugins/prod-audit/skills/observability/SKILL.md at line 209, The
markdown fenced code block currently has no language identifier (violates
MD040); update the closing/opening triple-backtick block in SKILL.md to include
a language tag (e.g., replace ``` with ```markdown) so the fenced block is
explicitly marked as markdown; locate the final fenced block (the triple
backticks near the end of the file) and add the language identifier.

5. **Check limits**: Verify resource limits and pagination
6. **Profile if possible**: Use actual timing data if available

```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a language identifier to the fenced block (MD040).
This keeps markdownlint clean.

🔧 Suggested change
-```
+```text
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

242-242: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In @.claude/plugins/prod-audit/skills/performance/SKILL.md at line 242, The
markdown has a fenced code block with no language identifier in SKILL.md; update
the opening fence from ``` to ```text for that block (and similarly add language
identifiers to any other bare fences) so markdownlint MD040 is satisfied and the
fenced block is explicitly labeled.

Comment on lines +814 to +950
## Database Recovery

Cloudflare D1 provides automatic point-in-time recovery through Time Travel. This section documents recovery procedures for common scenarios.

### Time Travel Overview

D1 automatically maintains a history of your database, allowing you to:

- Restore the entire database to a previous point in time
- Export data from a specific point in time
- Query historical data without restoring

**Retention:** 30 days on paid plans, 7 days on free tier.

### Recovery Commands

#### List Available Restore Points

```bash
# View recent bookmarks (automatic snapshots)
wrangler d1 time-travel info corates-db-prod --env production
```

#### Restore to a Point in Time

```bash
# Restore to a specific timestamp (ISO 8601 format)
wrangler d1 time-travel restore corates-db-prod \
--timestamp "2026-01-19T10:30:00Z" \
--env production

# Restore to a specific bookmark
wrangler d1 time-travel restore corates-db-prod \
--bookmark <bookmark-id> \
--env production
```

#### Export Data from a Point in Time

```bash
# Export SQL from a specific timestamp (for inspection before restore)
wrangler d1 time-travel export corates-db-prod \
--timestamp "2026-01-19T10:30:00Z" \
--output backup.sql \
--env production
```

### Common Recovery Scenarios

#### Scenario 1: Accidental Project Deletion

A user accidentally deletes an important project.

**Steps:**

1. Identify when the deletion occurred (check logs, ask user)
2. Export data from just before the deletion:
```bash
wrangler d1 time-travel export corates-db-prod \
--timestamp "2026-01-19T14:25:00Z" \
--output pre-deletion.sql \
--env production
```
3. Review the export to find the project data
4. Either restore the entire database or selectively re-insert the project

**Note:** If the project had associated R2 files (PDFs), those may not be recoverable as R2 does not have Time Travel.

#### Scenario 2: Bad Migration Rollback

A migration causes data corruption or unexpected behavior.

**Steps:**

1. Identify the timestamp just before the migration was applied
2. Restore to that point:
```bash
wrangler d1 time-travel restore corates-db-prod \
--timestamp "2026-01-19T09:00:00Z" \
--env production
```
3. Fix the migration in code
4. Redeploy with the corrected migration

**Important:** After restoring, you may need to redeploy the workers to ensure schema compatibility.

#### Scenario 3: Data Corruption Investigation

You suspect data was corrupted but need to investigate before restoring.

**Steps:**

1. Export data from multiple points in time:

```bash
# Export from yesterday
wrangler d1 time-travel export corates-db-prod \
--timestamp "2026-01-18T00:00:00Z" \
--output yesterday.sql \
--env production

# Export from current
wrangler d1 export corates-db-prod \
--output current.sql \
--env production
```

2. Compare the exports to identify what changed
3. Decide whether to restore or manually fix

### Pre-Deployment Backup Procedure

Before risky deployments (migrations, major updates), create a manual bookmark:

```bash
# Create a bookmark before deployment
wrangler d1 time-travel bookmark corates-db-prod \
--description "Pre-deployment backup 2026-01-19" \
--env production
```

This creates a named restore point you can easily reference later.

### Limitations

- **R2 files are not covered:** Time Travel only applies to D1. PDFs and avatars in R2 cannot be restored.
- **Durable Objects are not covered:** Y.js state in ProjectDoc Durable Objects has no Time Travel.
- **Restore is database-wide:** You cannot restore individual tables or rows; the entire database is restored.
- **Workers must be compatible:** After restoring to an older schema, ensure your deployed workers are compatible.

### Emergency Contacts

For D1 issues beyond Time Travel capabilities:

- Cloudflare Support (if on paid plan)
- Cloudflare Discord community

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Cloudflare D1 Time Travel retention period paid plan free tier 2025 2026

💡 Result:

Cloudflare D1 Time Travel retention:

  • Workers Paid plan: 30 days. [1]
  • Workers Free plan: 7 days. [2]

(If you need retention beyond 30 days, Cloudflare recommends exporting D1 to R2 or external storage.) [1]


🌐 Web query:

Wrangler D1 time-travel commands CLI documentation

💡 Result:

Wrap-up of Wrangler D1 Time Travel CLI commands

  • Commands:

    • wrangler d1 time-travel info — get a bookmark for a point-in-time (defaults to now). Supports --timestamp (Unix or RFC3339) and --json. [1][2]
    • wrangler d1 time-travel restore — restore the database to a bookmark or timestamp. Flags: --bookmark, --timestamp (Unix or RFC3339), --json. Restores overwrite the DB in place. [1][2]
  • Requirements & limits:

    • Wrangler v3.4.0+ required. Databases must use the production storage backend (time travel covers up to 30 days for paid plan; free plan shorter per docs). [2]
  • Example usage:

    • wrangler d1 time-travel info my_db --timestamp="2023-07-09T17:31:11+00:00".
    • wrangler d1 time-travel restore my_db --timestamp=1672531200
    • Or restore via bookmark: wrangler d1 time-travel restore my_db --bookmark=BOOKMARK_ID. [1][2]

Sources:
[1] Wrangler commands (D1) — Cloudflare docs.
[2] Time Travel and backups (D1) — Cloudflare docs.


Verify and correct Wrangler D1 time-travel commands.

The retention periods (30 days paid, 7 days free) are correct. However, the Wrangler CLI commands need review:

  • wrangler d1 time-travel info and wrangler d1 time-travel restore are documented and supported
  • wrangler d1 time-travel export and wrangler d1 time-travel bookmark do not appear in official Wrangler CLI documentation

Revise the examples to use only documented commands, or confirm these commands are available in a specific Wrangler version. If export/bookmark functionality is unavailable via those commands, document the correct alternatives.

🧰 Tools
🪛 LanguageTool

[style] ~822-~822: This phrase is redundant. Consider writing “point” or “time”.
Context: ...store the entire database to a previous point in time - Export data from a specific point in ...

(MOMENT_IN_TIME)


[style] ~823-~823: This phrase is redundant. Consider writing “point” or “time”.
Context: ...t in time - Export data from a specific point in time - Query historical data without restori...

(MOMENT_IN_TIME)


[style] ~837-~837: This phrase is redundant. Consider writing “Point” or “(time)”.
Context: ...--env production #### Restore to a Point in Time bash # Restore to a specific timest...

(MOMENT_IN_TIME)


[style] ~851-~851: This phrase is redundant. Consider writing “Point” or “(time)”.
Context: ...production #### Export Data from a Point in Time bash # Export SQL from a specific t...

(MOMENT_IN_TIME)


[style] ~906-~906: This phrase is redundant. Consider writing “points” or “times”.
Context: ...Steps:* 1. Export data from multiple points in time: ```bash # Export from yesterday...

(MOMENT_IN_TIME)

🤖 Prompt for AI Agents
In `@packages/docs/guides/database.md` around lines 814 - 950, The docs currently
show examples using wrangler d1 time-travel export and wrangler d1 time-travel
bookmark which are not in the official Wrangler docs; update the guide to only
use supported commands (wrangler d1 time-travel info and wrangler d1 time-travel
restore) or explicitly note the required Wrangler version if those extra
commands are from a specific release. Replace or remove the `time-travel export`
and `time-travel bookmark` examples in the sections "Export Data from a Point in
Time" and "Pre-Deployment Backup Procedure", and either provide the documented
alternative (e.g., instructions to export via a supported dump/query workflow or
to create bookmarks via the supported API/CLI) or add a version note that
`time-travel export`/`bookmark` require X Wrangler version.

Comment thread packages/workers/.env.example Outdated
Comment on lines +19 to +20
# Sentry DSN for error tracking
VITE_SENTRY_DSN=your-sentry-dsn
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "SENTRY_DSN|VITE_SENTRY_DSN" packages/workers

Repository: InfinityBowman/corates

Length of output: 504


Rename VITE_SENTRY_DSN to SENTRY_DSN in the .env.example.

The workers codebase and wrangler config reference SENTRY_DSN (without the Vite prefix), but the example file defines VITE_SENTRY_DSN. This mismatch will cause Sentry initialization to fail since the environment variable will not be found.

Proposed fix
-# Sentry DSN for error tracking
-VITE_SENTRY_DSN=your-sentry-dsn
+# Sentry DSN for error tracking
+SENTRY_DSN=your-sentry-dsn
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Sentry DSN for error tracking
VITE_SENTRY_DSN=your-sentry-dsn
# Sentry DSN for error tracking
SENTRY_DSN=your-sentry-dsn
🤖 Prompt for AI Agents
In `@packages/workers/.env.example` around lines 19 - 20, The .env.example defines
VITE_SENTRY_DSN but the runtime and wrangler expect SENTRY_DSN; update the
example to rename the variable from VITE_SENTRY_DSN to SENTRY_DSN so the
environment key used by Sentry initialization and wrangler matches the example,
ensuring any references to VITE_SENTRY_DSN are replaced with SENTRY_DSN (check
for usages of VITE_SENTRY_DSN and SENTRY_DSN in the workers code and wrangler
config to confirm consistency).

import { OpenAPIHono } from '@hono/zod-openapi';
import type { Context } from 'hono';
import type { ContentfulStatusCode } from 'hono/utils/http-status';
import * as Sentry from '@sentry/cloudflare';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the Sentry config in index.ts
head -20 packages/workers/src/index.ts && echo "---" && sed -n '360,390p' packages/workers/src/index.ts

Repository: InfinityBowman/corates

Length of output: 1790


🏁 Script executed:

# Find the Env type definition
find packages/workers -name "*.ts" -o -name "*.tsx" | xargs grep -l "interface Env\|type Env" | head -5

Repository: InfinityBowman/corates

Length of output: 138


🏁 Script executed:

# Search for SENTRY_SEND_PII or sendDefaultPii usage
rg "sendDefaultPii|SENTRY_SEND_PII" packages/workers

Repository: InfinityBowman/corates

Length of output: 121


🏁 Script executed:

cat -n packages/workers/src/types/env.ts

Repository: InfinityBowman/corates

Length of output: 351


🏁 Script executed:

# Check how Sentry DSN and other env vars are currently used
rg "SENTRY_DSN|ENVIRONMENT" packages/workers/src/types --context 3

Repository: InfinityBowman/corates

Length of output: 48


🏁 Script executed:

# Find worker-configuration.d.ts
find packages/workers -name "worker-configuration.d.ts" -o -name "*.d.ts" | grep -i worker

Repository: InfinityBowman/corates

Length of output: 48


🏁 Script executed:

# Search for Cloudflare.Env declaration
rg "namespace Env|interface Env" packages/workers --type ts --type tsx

Repository: InfinityBowman/corates

Length of output: 93


🏁 Script executed:

# Search broader for Env declaration
rg "interface Env|namespace Env" --type ts . | head -20

Repository: InfinityBowman/corates

Length of output: 133


🏁 Script executed:

# Check for d.ts files in repo root
fd ".d.ts" . --max-depth 2

Repository: InfinityBowman/corates

Length of output: 48


🏁 Script executed:

# Check wrangler config
find . -name "wrangler.toml" -o -name "wrangler.json" | head -5

Repository: InfinityBowman/corates

Length of output: 48


🏁 Script executed:

# Check if there are any environment/config examples or docs
find . -name "*.env*" -o -name "*.example" | grep -v node_modules | head -10

Repository: InfinityBowman/corates

Length of output: 157


🏁 Script executed:

# Look at how index.ts uses the env vars to understand what's currently available
sed -n '360,390p' packages/workers/src/index.ts

Repository: InfinityBowman/corates

Length of output: 884


🏁 Script executed:

# Check if there are other Sentry configurations in the codebase
rg "withSentry|Sentry\." packages/workers/src --context 2

Repository: InfinityBowman/corates

Length of output: 369


🏁 Script executed:

# Check workers .env.example to see current env var patterns
cat packages/workers/.env.example

Repository: InfinityBowman/corates

Length of output: 1567


Gate Sentry's PII capture behind an explicit environment variable

sendDefaultPii: true captures cookies, IPs, and auth headers in Sentry events without explicit consent—a compliance risk. Add SENTRY_SEND_PII to your environment configuration and default to false:

Suggested fix
-    sendDefaultPii: true,
+    sendDefaultPii: env.SENTRY_SEND_PII === 'true',

Also add to .env.example:

# Sentry PII capture (only enable if compliant with your data retention policy)
SENTRY_SEND_PII=false
🤖 Prompt for AI Agents
In `@packages/workers/src/index.ts` at line 10, The Sentry initialization
currently enables sendDefaultPii unconditionally; add an explicit environment
flag SENTRY_SEND_PII (default false) and use it to set sendDefaultPii in the
Sentry.init call so PII capture is off unless SENTRY_SEND_PII=true, and add the
SENTRY_SEND_PII entry with a default false note to .env.example; locate the
Sentry.init usage (imported as Sentry from '@sentry/cloudflare') and replace the
literal true/undefined value with a boolean derived from
process.env.SENTRY_SEND_PII.

Comment on lines +373 to +378
(env: Env) => ({
dsn: env.SENTRY_DSN || '',
release: env.CF_VERSION_METADATA?.id,
environment: env.ENVIRONMENT,
// Only enable if DSN is set
enabled: !!env.SENTRY_DSN,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Env includes new bindings
rg -n "interface Env|type Env" packages/workers/src/types.ts
rg -n "SENTRY_DSN|CF_VERSION_METADATA" packages/workers/src/types.ts

Repository: InfinityBowman/corates

Length of output: 205


🏁 Script executed:

#!/bin/bash
# Find Env type definition
find packages/workers -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs rg -l "interface Env|type Env" | head -20

Repository: InfinityBowman/corates

Length of output: 138


🏁 Script executed:

cat packages/workers/src/types/env.ts

Repository: InfinityBowman/corates

Length of output: 302


🏁 Script executed:

find packages/workers -name "worker-configuration.d.ts" -o -name "*.d.ts" | xargs grep -l "SENTRY_DSN\|CF_VERSION_METADATA" | head -10

Repository: InfinityBowman/corates

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Find the worker configuration file
find packages/workers -type f \( -name "*.d.ts" -o -name "wrangler.toml" \)
# Also search for where Cloudflare.Env is declared
rg -n "namespace Cloudflare|interface Env" packages/workers --type ts --type tsx

Repository: InfinityBowman/corates

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Find the worker configuration file
find packages/workers -type f \( -name "*.d.ts" -o -name "wrangler.toml" \)
# Also search for where Cloudflare.Env is declared
rg -n "namespace Cloudflare|interface Env" packages/workers

Repository: InfinityBowman/corates

Length of output: 134


🏁 Script executed:

#!/bin/bash
# Find wrangler config
find packages/workers -name "wrangler.toml" -o -name "wrangler*.json"

Repository: InfinityBowman/corates

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Search entire repo for wrangler config
find . -maxdepth 3 -name "wrangler.toml" -o -name "wrangler*.json"
# Also check what actual index.ts imports/requires
grep -n "SENTRY_DSN\|CF_VERSION_METADATA" packages/workers/src/index.ts | head -20

Repository: InfinityBowman/corates

Length of output: 181


🏁 Script executed:

#!/bin/bash
# Check package.json for `@cloudflare/workers-types`
grep -r "@cloudflare/workers-types" packages/workers
# Also look for any type definitions that might declare these env vars
rg -n "SENTRY_DSN|CF_VERSION_METADATA" . --type-list | head -5
# Search for where Env bindings might be declared
rg -n "declare.*SENTRY_DSN|declare.*CF_VERSION_METADATA" .

Repository: InfinityBowman/corates

Length of output: 645


🏁 Script executed:

#!/bin/bash
# Search for SENTRY_DSN and CF_VERSION_METADATA declarations
rg "SENTRY_DSN|CF_VERSION_METADATA" . --no-filename
# Look for global type declaration files
find packages/workers -name "*.d.ts" -o -name "global.d.ts" | head -10

Repository: InfinityBowman/corates

Length of output: 616


🏁 Script executed:

#!/bin/bash
# Check EnvWithOrigins example
cat -n packages/workers/src/config/origins.ts | head -30
# Check how Env is used in index.ts
head -50 packages/workers/src/index.ts | grep -n "import\|Env"

Repository: InfinityBowman/corates

Length of output: 2780


🏁 Script executed:

#!/bin/bash
# Check tsconfig.json for strict mode
grep -A 5 -B 5 "strict" packages/workers/tsconfig.json
# Check if there are any TS errors reported
cat packages/workers/tsconfig.json | grep -E "strict|noImplicitAny"

Repository: InfinityBowman/corates

Length of output: 392


Extend Env type to include SENTRY_DSN and CF_VERSION_METADATA bindings

The code accesses env.SENTRY_DSN and env.CF_VERSION_METADATA?.id (lines 374–378), but these are not typed in the Env interface. With TypeScript strict mode enabled, this will cause type errors. Extend the Env type in packages/workers/src/types/env.ts to include these properties:

export type Env = Cloudflare.Env & {
  SENTRY_DSN?: string;
  CF_VERSION_METADATA?: { id: string };
};
🤖 Prompt for AI Agents
In `@packages/workers/src/index.ts` around lines 373 - 378, The Env interface is
missing bindings for SENTRY_DSN and CF_VERSION_METADATA which causes TypeScript
errors when code reads env.SENTRY_DSN and env.CF_VERSION_METADATA?.id; update
the Env type declaration (the Env symbol) to extend Cloudflare.Env with optional
properties SENTRY_DSN?: string and CF_VERSION_METADATA?: { id: string } so the
compiler knows those bindings exist and env.CF_VERSION_METADATA.id is typed.

Comment thread packages/workers/src/routes/billing/helpers/orgContext.ts Outdated
Comment on lines +64 to +65
"APP_URL": "http://localhost:5173",
"SENTRY_DSN": "https://3ddf61abb06680bffc1ebc7f540f05de@o4510738063818752.ingest.us.sentry.io/4510738126274560"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n packages/workers/wrangler.jsonc | head -150

Repository: InfinityBowman/corates

Length of output: 4870


🌐 Web query:

Sentry DSN public key sensitive information security best practices

💡 Result:

Short answer: the DSN "public key" is intentionally non‑secret (it's an identifier), but you should still follow Sentry best practices to prevent abuse and avoid sending sensitive data. Key actions:

  • Treat the DSN public key as an identifier (not a secret). exposure alone does not grant read/modify access to your Sentry data — it only allows sending events. [1][2][9]
  • Limit accepted sources: use Relay or Sentry project/client-key settings to enable/disable public keys and restrict allowed origins/domains so events from unexpected origins are rejected. [6][10]
  • Rotate/regen client keys if you suspect abuse and remove old keys via Project → Client Keys. [2][3]
  • Enforce server-side validation and rate limiting (Relay or your ingestion layer) to block spammy/fake events before they reach Sentry. [6][10]
  • Avoid sending PII/secrets to Sentry: enable Data Scrubbing, configure custom sensitive-field scrubbing, and disable IP storage if needed. Use the SDKs’ before-send hooks to scrub or drop events containing secrets. [4]
  • Limit account access: enable MFA, follow least-privilege for organization/project roles, and monitor audit logs. [4]
  • For high‑security use cases, consider using Relay (self‑hosted or managed) to validate/allowlist public keys and enforce additional policy before forwarding events. [6][10]
  • Monitor for anomalous event spikes and set up alerts to detect misuse quickly; if misused, rotate keys and block offending sources. [3][6]

Sources: Sentry Help Center / DSN explanation and public-key behavior [1][2], Sentry docs on SDK/DSN and protocol [9], Relay/project config for publicKeys and allowedDomains [6][10], Sentry Security & Data Scrubbing guidance [4].


Move Sentry DSN to local development configuration

The DSN is stored in shared vars despite the "non-sensitive only" comment. While Sentry DSNs are public keys by design (exposure doesn't grant read/modify access), keeping development DSNs in committed config can cause unintended event spam. Either move to local .wrangler.local.toml or use environment-specific values, and follow the production pattern shown at line 127 which correctly uses wrangler secret put SENTRY_DSN --env production.

🤖 Prompt for AI Agents
In `@packages/workers/wrangler.jsonc` around lines 64 - 65, Remove the hardcoded
"SENTRY_DSN" entry from the shared vars block (next to "APP_URL") and instead
load it from a local/dev-only source: either add it to a .wrangler.local.toml
for local development or switch to environment-specific values and secrets
(follow the existing production pattern using the wrangler secret put SENTRY_DSN
--env production command). Update any doc/comment in the config to instruct
developers to place their dev DSN in .wrangler.local.toml or set an env var, and
ensure "SENTRY_DSN" is not committed in the shared vars section.

@InfinityBowman InfinityBowman merged commit 5cc7f54 into main Jan 19, 2026
2 of 3 checks passed
@InfinityBowman InfinityBowman deleted the 315-production-audit-readiness branch January 19, 2026 18:06
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.

Production Audit readiness

2 participants