fix(worker): honor the six-a-month intake cap with a truthful closed-intake response - #116
fix(worker): honor the six-a-month intake cap with a truthful closed-intake response#116nish3451 wants to merge 8 commits into
Conversation
…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.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe 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. ChangesMonthly signup intake cap
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 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".
| const monthBucket = `signup:${new Date().toISOString().slice(0, 7)}`; | ||
| const monthCount = await incrementUsageCounter(env, monthBucket); |
There was a problem hiding this comment.
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 👍 / 👎.
| // 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); |
There was a problem hiding this comment.
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 👍 / 👎.
| // 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); |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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 liftProtect 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
Originheader, 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
📒 Files selected for processing (3)
scripts/check-site.mjsscripts/test-agent-worker.mjssrc/worker.js
| 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."); |
There was a problem hiding this comment.
🎯 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.
| // 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); |
There was a problem hiding this comment.
🗄️ 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:
- 1: https://developers.cloudflare.com/d1/worker-api/d1-database/
- 2: https://firdausng.com/posts/d1-has-no-transactions-use-client-batch
- 3: https://www.answeroverflow.com/m/1192109327939547157
- 4: https://developers.cloudflare.com/d1/worker-api/d1-database/index.md
- 5: https://wemstudios.mintlify.app/guides/performance
- 6: [Feature] D1 Transactions cloudflare/workers-rs#349
- 7: https://alchemy.run/sql/effect-sql/d1
- 8: https://github.com/edulelis/typeorm-d1
- 9: https://www.answeroverflow.com/m/1402580672937459803
🏁 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 -300Repository: 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"
doneRepository: 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 -500Repository: 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.mjsRepository: 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' -printRepository: 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))
PYRepository: 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))
PYRepository: 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.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 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".
| // 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); |
There was a problem hiding this comment.
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 👍 / 👎.
| <meta charset="utf-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| <title>TinyStudio — The intake is closed</title> | ||
| <style> |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 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".
| // 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); |
There was a problem hiding this comment.
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 👍 / 👎.
…(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.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 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".
|
|
||
| if (monthCount > MAX_APPRAISALS_PER_MONTH) { | ||
| if (wantsHtmlRedirect(request)) { | ||
| return closedIntakeResponse(); |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
|
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). |
…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>
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/signupsaccepted 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/signupsreserves a calendar-month slot (signup:YYYY-MMbucket in the existingagent_usage_limitstable, no migration needed) before persisting.409 {"ok":false,"error":"intake_closed","message":"The six appraisals for this month are taken. The intake is closed until the next."}Verify
intake_closed(JSON + browser paths), nothing stored on the closed seventh, invalid email consumes no slot, bucket key shapesignup:YYYY-MM.MAX_APPRAISALS_PER_MONTHfrom worker.js makesnpm run checkfail ("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
src/worker.js:MAX_APPRAISALS_PER_MONTHconstant, monthly cap check insignupResponse,closedIntakeResponse().scripts/test-agent-worker.mjs:CountingFakeDB/CountingFakeStatementfakes + 3 tests appended at end of file (region untouched by open PRs fix(worker): make the Google Ads conversion tag env-driven instead of a dead placeholder #52/fix(public): clear the stale Agent Desk claims from retired surfaces and the homepage #62/fix(public): render the signup rejection signal on the homepage #111).scripts/check-site.mjs: static cap guards after therequiredWorkerCopyloop (region untouched by open PRs).No copy changes, no route changes, no migration, no lockfile changes. The existing
?signal=invalidhomepage banner (PR #111) is untouched.Summary by CodeRabbit
New Features
intake_closederror for API requests exceeding the limit.Bug Fixes