Skip to content

fix(worker): honor the six-a-month intake cap with a truthful closed-intake response - #116

Closed
nish3451 wants to merge 8 commits into
mainfrom
fix/signup-monthly-cap-lane1
Closed

fix(worker): honor the six-a-month intake cap with a truthful closed-intake response#116
nish3451 wants to merge 8 commits into
mainfrom
fix/signup-monthly-cap-lane1

Conversation

@nish3451

@nish3451 nish3451 commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Intended outcome

The site publicly promises "Six a month. When the sixth is taken, the intake closes until the next." on five surfaces (homepage, /audit, /pricing, /agents, llms.txt), but /api/signups accepted every valid POST with a normal 201 — the seventh applicant got a success and no closed-intake signal, under-delivering a headline offer promise (backlog item 594).

This makes the code honor the promise:

  • /api/signups reserves a calendar-month slot (signup:YYYY-MM bucket in the existing agent_usage_limits table, no migration needed) before persisting.
  • The sixth signup in a calendar month is accepted.
  • Every later POST in the same month returns a truthful closed-intake response:
    • API clients: 409 {"ok":false,"error":"intake_closed","message":"The six appraisals for this month are taken. The intake is closed until the next."}
    • Browser form posts: a self-contained no-JS 409 page (same pattern as the retired-host pages) stating the six-a-month truth — no redirect to homepage machinery, so no new asset or JS.
  • The counter resets naturally on the first of the next month; invalid emails consume no slot.

Verify

npm test
npm run check
npm run check:render-blocking
npm run deploy:dry-run
  • 95 tests pass: 6 headings + 7 sitemap + 58 worker (3 new) + 16 UI + 8 contract.
  • New deterministic tests: six signups accepted then seventh rejected with intake_closed (JSON + browser paths), nothing stored on the closed seventh, invalid email consumes no slot, bucket key shape signup:YYYY-MM.
  • Guard proof: removing MAX_APPRAISALS_PER_MONTH from worker.js makes npm run check fail ("Worker must define MAX_APPRAISALS_PER_MONTH (the six-a-month cap).").
  • npm run check:render-blocking: all six pages PASS.
  • npm run deploy:dry-run: green (binding table shown, exits at dry-run).

Scope

No copy changes, no route changes, no migration, no lockfile changes. The existing ?signal=invalid homepage banner (PR #111) is untouched.

Summary by CodeRabbit

  • New Features

    • Added a limit of six appraisal signups per calendar month.
    • Displays a clear closed-intake page for browser requests after the limit is reached.
    • Returns a structured intake_closed error for API requests exceeding the limit.
  • Bug Fixes

    • Invalid email submissions no longer consume available signup capacity.
    • Signups rejected after the monthly limit are not saved.

…intake response

The site promises 'Six a month. When the sixth is taken, the intake closes
until the next.' on the homepage, /audit, /pricing, /agents and llms.txt,
but /api/signups accepted every valid POST with a normal 201: the seventh
applicant was told nothing and the public promise under-delivered.

The signup handler now reserves a calendar-month slot (signup:YYYY-MM
bucket in the existing agent_usage_limits table) before persisting. The
sixth signup passes; every later POST in the same month gets the truth:
a 409 intake_closed JSON error for API clients, or a self-contained
no-JS 409 page (same pattern as the retired-host pages) for browser form
posts. The counter resets on the first of the next month.

Deterministic tests cover the JSON and browser paths (six accepted,
seventh rejected, nothing stored), an invalid email consuming no slot,
and the month-bucket key shape. check-site.mjs gains static guards so
removing the cap fails npm run check.

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

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai

coderabbitai Bot commented Aug 11, 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 Plus

Run ID: c80b66c7-f73f-434c-9b6c-4acf81e640e7

📥 Commits

Reviewing files that changed from the base of the PR and between e326096 and 23403e1.

📒 Files selected for processing (1)
  • scripts/check-site.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/check-site.mjs

📝 Walkthrough

Walkthrough

The Worker now limits valid signups to six per calendar month. Later requests return HTTP 409 with HTML or JSON responses. Tests and static checks cover the cap, persistence behavior, and invalid emails.

Changes

Monthly signup intake cap

Layer / File(s) Summary
Closed-intake response contract
src/worker.js
The Worker defines the monthly limit and returns a security-header-protected, no-store HTML response when intake is closed.
Monthly signup reservation
src/worker.js
The Worker reserves a calendar-month usage slot before saving a valid signup. Requests after six signups receive either the closed-intake HTML response or a JSON intake_closed error.
Signup cap validation
scripts/test-agent-worker.mjs, scripts/check-site.mjs
Tests simulate monthly counters and verify six accepted signups, rejected seventh signups, unchanged persistence, and invalid-email handling. Static checks verify the required implementation markers.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SignupClient
  participant Worker
  participant Database
  SignupClient->>Worker: Submit valid signup
  Worker->>Database: Reserve monthly signup slot
  Database-->>Worker: Signup count
  alt Count is at most six
    Worker->>Database: Save signup
    Worker-->>SignupClient: Success response
  else Count exceeds six
    Worker-->>SignupClient: HTTP 409 intake_closed or closed-intake HTML
  end
Loading

Possibly related PRs

🚥 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 and concisely summarizes the main change: enforcing the six-a-month intake cap and returning a truthful closed-intake response.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 fix/signup-monthly-cap-lane1

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: e326096492

ℹ️ 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".

Comment thread src/worker.js
Comment on lines +413 to +414
const monthBucket = `signup:${new Date().toISOString().slice(0, 7)}`;
const monthCount = await incrementUsageCounter(env, monthBucket);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Seed the counter with this month's accepted signups

When this change is deployed after the first of a month, the new signup:YYYY-MM bucket does not exist and nothing backfills it from the signups already stored that month. If n requests were accepted before deployment, this starts at zero and accepts six more, allowing n + 6 appraisals during the month and immediately breaking the cap this change is meant to enforce.

Useful? React with 👍 / 👎.

Comment thread src/worker.js
// every request after it in the same month is told the truth: the intake
// is closed until the next.
const monthBucket = `signup:${new Date().toISOString().slice(0, 7)}`;
const monthCount = await incrementUsageCounter(env, monthBucket);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Do not spend slots on upserted duplicate submissions

Because the counter is incremented before saveEmailSignup, every repeat submission consumes another slot even though the subsequent ON CONFLICT(email) DO UPDATE leaves only one signup row. Reposting the same valid email six times therefore fills the global monthly quota while the database contains only one actionable request, causing the next genuine applicant to receive intake_closed; deduplicate the request before reserving capacity or count records that can actually be fulfilled.

Useful? React with 👍 / 👎.

Comment thread src/worker.js
// every request after it in the same month is told the truth: the intake
// is closed until the next.
const monthBucket = `signup:${new Date().toISOString().slice(0, 7)}`;
const monthCount = await incrementUsageCounter(env, monthBucket);

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 Reserve and persist each slot atomically

If saveEmailSignup fails after this independent counter statement succeeds—for example because of a transient D1 write failure—the request is not stored, but its monthly slot remains permanently consumed. Enough such failures make intake close before six appraisals have been accepted, so the reservation and signup write need a single transaction/batch or the reservation must be rolled back on failure.

Useful? React with 👍 / 👎.

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
src/worker.js (1)

401-414: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Protect monthly slots from unauthenticated exhaustion.

A caller only needs six syntactically valid email values to reserve every monthly slot. A direct HTTP client can also set an Origin header, so an origin check does not prevent this attack. This lets an attacker close the public intake before legitimate users submit requests.

Require a reliable pre-reservation control, such as verified email ownership, a bot challenge, or rate limiting on a trusted client signal.

🤖 Prompt for 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.

In `@src/worker.js` around lines 401 - 414, The monthly reservation in the intake
flow must not rely solely on syntactic email validation or a caller-controlled
Origin header. Before incrementUsageCounter is called for monthBucket, require a
reliable anti-abuse control such as verified email ownership, a bot challenge,
or rate limiting based on a trusted client signal; reject or defer requests that
do not satisfy it while preserving the existing monthly-cap behavior for
approved requests.
🤖 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 `@scripts/check-site.mjs`:
- Around line 184-197: Update the static guards in the check-site script to
validate executable signup-flow behavior rather than merely searching for
strings. Tie the MAX_APPRAISALS_PER_MONTH, signup: bucket, intake_closed
response, closedIntakeResponse invocation, and closed-intake message checks to
the relevant parsed Worker statements or call relationships, while preserving
the existing behavioral tests as the primary contract.

In `@src/worker.js`:
- Line 414: Update signupResponse to execute the monthly counter increment and
saveEmailSignup upsert atomically through env.DB.batch(), while preserving the
six-signup guard. Ensure a failed signup write rolls back the counter, and add a
regression test that forces the upsert failure and verifies the monthly counter
is unchanged.

---

Outside diff comments:
In `@src/worker.js`:
- Around line 401-414: The monthly reservation in the intake flow must not rely
solely on syntactic email validation or a caller-controlled Origin header.
Before incrementUsageCounter is called for monthBucket, require a reliable
anti-abuse control such as verified email ownership, a bot challenge, or rate
limiting based on a trusted client signal; reject or defer requests that do not
satisfy it while preserving the existing monthly-cap behavior for approved
requests.
🪄 Autofix

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 Plus

Run ID: 324796b0-1cac-451c-b9cf-d04afec6db41

📥 Commits

Reviewing files that changed from the base of the PR and between 18128e8 and e326096.

📒 Files selected for processing (3)
  • scripts/check-site.mjs
  • scripts/test-agent-worker.mjs
  • src/worker.js

Comment thread scripts/check-site.mjs
Comment on lines +184 to +197
if (!worker.includes("MAX_APPRAISALS_PER_MONTH")) {
failures.push("Worker must define MAX_APPRAISALS_PER_MONTH (the six-a-month cap).");
}
if (!worker.includes("signup:")) {
failures.push("Worker must key the monthly signup cap on a signup:YYYY-MM bucket.");
}
if (!worker.includes("\"intake_closed\"")) {
failures.push("Worker must expose the intake_closed error for API signup clients.");
}
if (!worker.includes("closedIntakeResponse()")) {
failures.push("Worker must serve the closed-intake page to browser form posts.");
}
if (!worker.includes("The six appraisals for this month are taken")) {
failures.push("Closed-intake page must state the six-a-month truth.");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the static guards check executable behavior.

These checks only search for text. For example, closedIntakeResponse() matches its function declaration even if signupResponse no longer calls it. The other strings can also remain in unused code or comments while the signup cap flow is removed.

Match the relevant executable statements or inspect the parsed Worker source. Keep the behavioral tests as the primary contract check.

🤖 Prompt for 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.

In `@scripts/check-site.mjs` around lines 184 - 197, Update the static guards in
the check-site script to validate executable signup-flow behavior rather than
merely searching for strings. Tie the MAX_APPRAISALS_PER_MONTH, signup: bucket,
intake_closed response, closedIntakeResponse invocation, and closed-intake
message checks to the relevant parsed Worker statements or call relationships,
while preserving the existing behavioral tests as the primary contract.

Comment thread src/worker.js
// every request after it in the same month is told the truth: the intake
// is closed until the next.
const monthBucket = `signup:${new Date().toISOString().slice(0, 7)}`;
const monthCount = await incrementUsageCounter(env, monthBucket);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

Cloudflare D1 documentation: Can D1Database.batch execute a monthly counter upsert and an email_signups upsert atomically, with rollback when either statement fails?

💡 Result:

Yes, Cloudflare D1's batch method can execute multiple SQL statements, such as a monthly counter upsert and an email_signups upsert, as an atomic operation [1][2]. According to official Cloudflare documentation, batched statements are executed as a single transaction [1][3]. If any statement within the provided sequence fails, the entire batch is aborted or rolled back [1][4][2]. Conversely, if all statements in the batch succeed, they are committed together [2]. Key points regarding this behavior include: - Atomicity: The batch API guarantees that multiple mutations are processed together, providing an all-or-nothing outcome [5][2]. - No Interactive Transactions: D1 does not support traditional session-based transactions (e.g., explicit BEGIN, COMMIT, or ROLLBACK commands) [6][7]. The batch method is the recommended way to perform atomic multi-statement operations [8][2]. - Constraints: Because the batch executes as a single transaction, you cannot use the results of one statement within the same batch to inform the parameters of subsequent statements in that same batch [9][6]. You must know all statements and their parameters upfront [8].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- plan files ---'
find .. -maxdepth 3 -type f \( -iname '*plan*' -o -name 'PLAN.md' \) -print 2>/dev/null | head -50

printf '%s\n' '--- worker outline ---'
ast-grep outline src/worker.js 2>/dev/null | head -200 || true

printf '%s\n' '--- relevant symbols and call sites ---'
rg -n -C 12 'incrementUsageCounter|saveEmailSignup|monthBucket|monthCount|email_signups|usage' src test tests 2>/dev/null | head -500

printf '%s\n' '--- tracked test files ---'
git ls-files | rg '(^|/)(test|tests|spec|__tests__)(/|\\.)|worker|d1|signup' | head -300

Repository: nish3451/TinyStudio.io

Length of output: 14546


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- signup flow ---'
sed -n '320,465p' src/worker.js

printf '%s\n' '--- schema and migrations ---'
for file in $(git ls-files 'migrations/*' 'schema*' '*.sql' | head -80); do
  printf '\n--- %s ---\n' "$file"
  cat -n "$file"
done

printf '%s\n' '--- worker test script ---'
cat -n scripts/test-agent-worker.mjs | head -260

printf '%s\n' '--- package and configuration ---'
for file in $(git ls-files 'package.json' 'wrangler.toml' 'wrangler.json' 'wrangler.jsonc' '*.config.*' | head -80); do
  printf '\n--- %s ---\n' "$file"
  cat -n "$file"
done

Repository: nish3451/TinyStudio.io

Length of output: 20397


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- signup-related tests and routes ---'
rg -n -C 18 'signup|signal_saved|intake_closed|MAX_APPRAISALS_PER_MONTH|/api/signup|saveEmailSignup' scripts src README.md docs 2>/dev/null | head -700

printf '%s\n' '--- database batch usage and exports ---'
rg -n -C 8 '\\.batch\\(|export default|async fetch|signupResponse' src scripts 2>/dev/null | head -400

printf '%s\n' '--- D1 API references in repository ---'
rg -n -i -C 5 'D1Database|transaction|atomic|rollback|batch' . --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' 2>/dev/null | head -500

Repository: nish3451/TinyStudio.io

Length of output: 48771


🏁 Script executed:

#!/bin/bash
set -eu

PLAN=$(find specs -type f -path '*/plan.md' -print -quit 2>/dev/null || true)
if [ -n "$PLAN" ]; then
  printf '%s\n' "--- $PLAN ---"
  cat -n "$PLAN"
else
  printf '%s\n' 'No specs plan found'
fi

printf '%s\n' '--- signup test block ---'
sed -n '990,1180p' scripts/test-agent-worker.mjs

Repository: nish3451/TinyStudio.io

Length of output: 11390


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- current product plan ---'
cat -n specs/004-website-appraisal/plan.md

printf '%s\n' '--- all plan paths ---'
find specs -type f -path '*/plan.md' -print

Repository: nish3451/TinyStudio.io

Length of output: 4253


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import sqlite3

schema = """
CREATE TABLE agent_usage_limits (
  bucket_key TEXT PRIMARY KEY,
  count INTEGER NOT NULL DEFAULT 0,
  first_seen_at TEXT NOT NULL,
  updated_at TEXT NOT NULL
);
CREATE TABLE email_signups (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  email TEXT NOT NULL UNIQUE,
  source TEXT NOT NULL,
  page_path TEXT NOT NULL,
  referer TEXT,
  user_agent TEXT,
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL,
  website TEXT
);
"""

counter_sql = """
INSERT INTO agent_usage_limits (bucket_key, count, first_seen_at, updated_at)
VALUES (?, 1, ?, ?)
ON CONFLICT(bucket_key) DO UPDATE SET
  count = count + 1,
  updated_at = excluded.updated_at
"""

signup_sql = """
INSERT INTO email_signups
  (email, source, page_path, referer, user_agent, created_at, updated_at, website)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
"""

def state(db):
    count = db.execute(
        "SELECT count FROM agent_usage_limits WHERE bucket_key = 'signup:2026-08'"
    ).fetchone()
    signup_count = db.execute("SELECT COUNT(*) FROM email_signups").fetchone()[0]
    return (count[0] if count else 0, signup_count)

# Model the current two-call flow. The second mutation fails.
db = sqlite3.connect(":memory:")
db.executescript(schema)
db.execute("BEGIN")
db.execute(counter_sql, ("signup:2026-08", "now", "now"))
db.commit()
try:
    db.execute(signup_sql, ("x@example.com", "agent-self-serve", "/", None, None, "now", "now", None))
    db.commit()
except sqlite3.IntegrityError:
    db.rollback()
print("current_flow_after_signup_failure:", state(db))

# Model both mutations in one transaction. The same failure rolls back the reservation.
db = sqlite3.connect(":memory:")
db.executescript(schema)
try:
    db.execute("BEGIN")
    db.execute(counter_sql, ("signup:2026-08", "now", "now"))
    db.execute(signup_sql, ("x@example.com", "agent-self-serve", "/", None, None, "now", "now", None))
    db.execute(signup_sql, ("x@example.com", "agent-self-serve", "/", None, None, "now", "now", None))
    db.commit()
except sqlite3.IntegrityError:
    db.rollback()
print("atomic_flow_after_signup_failure:", state(db))
PY

Repository: nish3451/TinyStudio.io

Length of output: 243


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import sqlite3

schema = """
CREATE TABLE agent_usage_limits (
  bucket_key TEXT PRIMARY KEY,
  count INTEGER NOT NULL DEFAULT 0,
  first_seen_at TEXT NOT NULL,
  updated_at TEXT NOT NULL
);
CREATE TABLE email_signups (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  email TEXT NOT NULL UNIQUE,
  source TEXT NOT NULL,
  page_path TEXT NOT NULL,
  referer TEXT,
  user_agent TEXT,
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL,
  website TEXT
);
CREATE TRIGGER fail_signup
BEFORE INSERT ON email_signups
BEGIN
  SELECT RAISE(ABORT, 'injected signup failure');
END;
"""

counter_sql = """
INSERT INTO agent_usage_limits (bucket_key, count, first_seen_at, updated_at)
VALUES (?, 1, ?, ?)
ON CONFLICT(bucket_key) DO UPDATE SET
  count = count + 1,
  updated_at = excluded.updated_at
"""

signup_sql = """
INSERT INTO email_signups
  (email, source, page_path, referer, user_agent, created_at, updated_at, website)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(email) DO UPDATE SET updated_at = excluded.updated_at
"""

def state(db):
    count = db.execute(
        "SELECT count FROM agent_usage_limits WHERE bucket_key = 'signup:2026-08'"
    ).fetchone()
    signup_count = db.execute("SELECT COUNT(*) FROM email_signups").fetchone()[0]
    return (count[0] if count else 0, signup_count)

def mutate(db):
    db.execute(counter_sql, ("signup:2026-08", "now", "now"))
    db.execute(signup_sql, ("x@example.com", "agent-self-serve", "/", None, None, "now", "now", None))

# Current flow: the counter is committed before saveEmailSignup runs.
db = sqlite3.connect(":memory:")
db.executescript(schema)
db.execute("BEGIN")
db.execute(counter_sql, ("signup:2026-08", "now", "now"))
db.commit()
try:
    db.execute("BEGIN")
    db.execute(signup_sql, ("x@example.com", "agent-self-serve", "/", None, None, "now", "now", None))
    db.commit()
except sqlite3.DatabaseError:
    db.rollback()
print("current_flow_after_signup_failure:", state(db))

# Atomic flow: both mutations share one transaction.
db = sqlite3.connect(":memory:")
db.executescript(schema)
try:
    db.execute("BEGIN")
    mutate(db)
    db.commit()
except sqlite3.DatabaseError:
    db.rollback()
print("atomic_flow_after_signup_failure:", state(db))
PY

Repository: nish3451/TinyStudio.io

Length of output: 243


Make the monthly reservation and signup upsert atomic. signupResponse increments the counter at src/worker.js:414 before saveEmailSignup persists the signup at src/worker.js:426. If the upsert fails, the counter remains incremented and the intake can close with fewer than six stored signups. Use env.DB.batch() for both mutations, preserve the six-signup guard, and add a regression test that forces the signup write to fail and asserts that the monthly counter remains unchanged.

🤖 Prompt for 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.

In `@src/worker.js` at line 414, Update signupResponse to execute the monthly
counter increment and saveEmailSignup upsert atomically through env.DB.batch(),
while preserving the six-signup guard. Ensure a failed signup write rolls back
the counter, and add a regression test that forces the upsert failure and
verifies the monthly counter is unchanged.

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

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@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: 9996aa5a4f

ℹ️ 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".

Comment thread src/worker.js
// every request after it in the same month is told the truth: the intake
// is closed until the next.
const monthBucket = `signup:${new Date().toISOString().slice(0, 7)}`;
const monthCount = await incrementUsageCounter(env, monthBucket);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject unusable websites before reserving capacity

For /api/signups callers that bypass the browser's required/pattern constraints, an omitted or malformed website makes normalizeWebsite return null, but a valid email still reaches this increment and consumes one of the six slots. Six such requests therefore close intake while persisting only non-actionable appraisal rows with no URL to inspect; validate the normalized website before reserving capacity.

Useful? React with 👍 / 👎.

Comment thread src/worker.js
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>TinyStudio — The intake is closed</title>
<style>

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 Move the closed-page styles behind the CSP

When the seventh browser form submission receives this page, withSecurityHeaders attaches the worker's style-src 'self' https://fonts.googleapis.com policy, which does not permit this inline <style> block. Browsers therefore discard all of these rules and render the intended self-contained closed-intake page unstyled; serve the CSS from an allowed asset or authorize this block with a nonce/hash.

Useful? React with 👍 / 👎.

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

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@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: 3563c49e8f

ℹ️ 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".

Comment thread src/worker.js
// every request after it in the same month is told the truth: the intake
// is closed until the next.
const monthBucket = `signup:${new Date().toISOString().slice(0, 7)}`;
const monthCount = await incrementUsageCounter(env, monthBucket);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Protect the global quota from cross-site submissions

Because /api/signups never calls validateAgentRequest and has no per-IP limit or human-verification gate, a third-party page can submit six cross-origin form POSTs with distinct syntactically valid emails and websites; each reaches this increment despite its hostile Origin, fills the single global bucket, and causes every legitimate applicant to receive intake_closed for the rest of the month. Apply origin/Sec-Fetch-Site validation and abuse resistance before allowing a request to reserve one of only six global slots.

Useful? React with 👍 / 👎.

nish3451 added a commit that referenced this pull request Aug 12, 2026
…(PR #22 conflict-locked, superseded by #28) against current GitHub state (2026-08-12) (#152)

Closes the review item "Two open PRs carry the same heading-hierarchy fix —
PR #22 is conflict-locked and superseded" (review queue, unreviewed-by-grok),
re-verified against the current GitHub state on 2026-08-12.

Verified facts (2026-08-12):
- PR #22 is CLOSED (2026-08-09), never merged, mergeStateStatus: DIRTY
  (conflict-locked) at close — the item's description was accurate.
- The superseding fix PR #28 is MERGED on main (2026-08-09, commit 7be3d8f);
  sibling duplicate #27 is also CLOSED.
- All 53 currently open PRs diff-reviewed: the only literal heading markup
  anywhere is one hierarchy-preserving h2 on /specimen in PR #107 (with its
  locked outline updated in scripts/test-heading-hierarchy.mjs) and one
  standalone h1 in PR #116's new closed-intake page — zero open PR retags
  an existing heading level, so no open PR duplicates the heading fix.
- Main has carried exactly one heading fix since 2026-08-09: the only
  heading-tag change since the merge is 2ae7504's two hierarchy-preserving
  homepage FAQ h3s (PR #102, locked outline updated). npm run check passes
  and npm test passes in full (test:headings 6/6) on current origin/main
  (ad9cee3).
- Site-side verification is current via docs/evidence/heading-hierarchy-2026-08-09.md
  (PR #74) with the 2026-08-12 re-verify in flight as PR #120.

Docs-only change; no code, no deployment.

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

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

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

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@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: 4074a89964

ℹ️ 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".

Comment thread src/worker.js

if (monthCount > MAX_APPRAISALS_PER_MONTH) {
if (wantsHtmlRedirect(request)) {
return closedIntakeResponse();

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 Keep overflow applicants on the promised next-month list

When a seventh legitimate browser submission arrives, this return occurs before saveEmailSignup, so the applicant's email and website are discarded. The existing homepage tells applicants that when the month is full, “you go on the list for the next one” (public/index.html:236-237), but this path leaves no record through which they can be contacted or scheduled next month; persist overflow as a waitlist entry without consuming a current-month slot, or revise the public contract accordingly.

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

Useful? React with 👍 / 👎.

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

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

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

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

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

@nish3451

Copy link
Copy Markdown
Collaborator Author

Closing as superseded by #245. This branch (fix/signup-monthly-cap-lane1) went CONFLICTING/DIRTY against main after main gained the daily rate-limit tests, the storage-failure honesty tests, and the APPRAISAL_SURFACE health labeling. #245 is the same change rebased onto current main with those conflicts resolved (keeps APPRAISAL_SURFACE, makes the cap counter fail closed to 503, adds the cap tests without colliding with main's signupRequest helper).

@nish3451 nish3451 closed this Aug 18, 2026
nish3451 pushed a commit that referenced this pull request Aug 18, 2026
nish3451 added a commit that referenced this pull request Aug 19, 2026
…intake response (rebase of #116) (#245)

* fix(worker): honor the six-a-month intake cap with a truthful closed-intake response

Rebase of PR #116 (fix/signup-monthly-cap-lane1) onto current main,
resolving conflicts with the daily rate-limit and storage-failure tests
added since the branch was cut. The cap counter write fails closed to 503
when D1 is unavailable, so the existing storage-failure honesty tests
still pass.

- src/worker.js: MAX_APPRAISALS_PER_MONTH constant, monthly cap check in
  signupResponse, closedIntakeResponse() self-contained 409 page.
- scripts/check-site.mjs: static source guards for the cap.
- scripts/test-agent-worker.mjs: CountingFakeDB/CountingFakeStatement +
  3 tests (JSON cap, browser cap, invalid-email-no-slot).

* docs(evidence): triage closeout for parked PRs #116/#128/#137 (2026-08-18)

---------

Co-authored-by: minimax-vps <minimax-vps@fleet.local>
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