Skip to content

feat: add public anonymous one-page check as the proof-to-repair entry path - #66

Merged
nish3451 merged 1 commit into
mainfrom
seo-fix-kit/lane1-public-check-entry
Aug 9, 2026
Merged

nish3451 merged 1 commit into
mainfrom
seo-fix-kit/lane1-public-check-entry

Conversation

@nish3451

@nish3451 nish3451 commented Aug 9, 2026

Copy link
Copy Markdown
Owner

What

Turns the public sample into a truthful, searchable proof-to-repair entry path (lane 1, research 2026-08-08, rank 1).

New live surface

  • GET /check — indexable public entry page: paste one public URL, no account, no email, nothing stored.
  • POST /api/public-check — anonymous handler running the shared audit engine (Browser Run) against one page. Returns only live engine output: measured static-vs-rendered proof, guarded false positives, actionable findings when present, issue counts, engine version, scannedAt, and truthful next-step + no-ranking boundary copy.
  • Local dev twin in server/index.js with in-memory rate limits.

Rate limiting & safety

  • check:ip-hour (6/h), check:ip-day (15/d), check:target-hour (3/h), check:target-day (10/d) buckets in the existing audit_usage D1 table; per-network counters are IP-hashed, no PII.
  • Same URL guards as private audits: normalizeUrl + publicAuditUrlStatus + DNS resolvesToPrivateAddress (SSRF) before any render.

Truthfulness sweep (was: "anonymous public audits stay disabled")

  • /demo boundary copy updated (it now links to the live check), /methodology "What is live today", /packages live card, llms.txt, .well-known/skill.md, README intro + "What is live in this repo" + Cloudflare path section.
  • rootSitemap, public/sitemap.xml, llms.txt, Worker run_worker_first, and links from /, /demo, /methodology, /packages make the entry path searchable.

Verification

  • npm run check green locally (all suites + build), including new test:public-check (unit + real-engine fixture test that fails if the public payload drifts from live engine output).
  • Promise-audit suite extended with a new "README anonymous one-page check claim" test pinning route, buckets, limits, and boundary.
  • Live spot-check (audit:live-promise) extended with /check.
  • Real-browser mobile walk of /check on the local twin: 5/5 links 200, form check against https://example.com rendered proof + guards + findings + handoff, zero console errors, no horizontal scroll.

Rollback: remove the /check page + /api/public-check route and restore the copy lines; all other claims revert via git.

Summary by CodeRabbit

  • New Features

    • Added an anonymous /check page for one-page SEO checks without an account or stored report.
    • Added browser-rendered evidence, actionable findings, safeguards against false positives, usage limits, and private-access guidance.
    • Added secure API support with validation and protection against private or internal destinations.
    • Updated site navigation, sitemap, public messaging, and deployment routing.
  • Documentation

    • Documented the public check’s capabilities, limitations, rate limits, and privacy behavior.
  • Tests

    • Added coverage for page content, routing, validation, rate limits, and end-to-end check results.

…y path

The public sample (/demo) is now backed by a live, indexable entry path:
any visitor can paste one public URL and see browser-rendered proof without
an account, an email, or a stored report.

- /check: indexable public page with the URL form, proof-loop copy, the
  no-ranking boundary, and a measured handoff into private beta access.
- POST /api/public-check: anonymous handler that runs the shared audit
  engine against one page (Browser Run), returns measured static-vs-rendered
  proof, guarded false positives, actionable findings when present, issue
  counts, engine version, and truthful next-step/boundary copy. Nothing is
  stored; only per-network and per-site rate-limit counters (check:* buckets
  in audit_usage) are written.
- Local dev twin in server/index.js with in-memory rate limits.
- Copy stays truthful everywhere it previously said anonymous audits were
  unavailable: /demo, /methodology, /packages, llms.txt, skill.md, README.
- /check is in rootSitemap, public/sitemap.xml, llms.txt, and Worker
  run_worker_first, and linked from /, /demo, /methodology, and /packages.
- Tests: real-engine fixture test pins the response to live engine output;
  unit tests pin validation, quota buckets, response mapping, and page
  boundary copy; promise-audit and live-spot-check suites extended.

@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 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds an anonymous /check page and POST /api/public-check endpoint. It validates targets, blocks private addresses, enforces quotas, runs one-page audits, returns normalized proof, and updates routing, documentation, sitemap, and tests.

Changes

Anonymous public SEO check

Layer / File(s) Summary
Public check API and audit flow
worker/routes/public-check.js, server/index.js, worker/routes/public-check.test.mjs
Adds URL validation, private-address blocking, IP and target quotas, one-page auditing, normalized responses, and API error handling.
Public check page and result rendering
worker/routes/public-check.js, worker/routes/public-check.test.mjs
Adds the responsive /check page, URL form, browser-rendered evidence, findings, limitations, private-access links, and sanitized client rendering.
Worker and public-surface integration
worker/index.js, worker/routes/pages.js, shared/audit-engine.js, public/sitemap.xml, wrangler.jsonc, worker/routes/pages.test.mjs, shared/promise-audit.test.mjs
Routes the page and API, adds sitemap and asset handling, and updates public product messaging and routing contracts.
Documentation and validation coverage
README.md, public/.well-known/skill.md, package.json, scripts/live-promise-spot-check.mjs, scripts/live-promise-spot-check.test.mjs
Documents the anonymous check and adds it to automated and live promise validation.
Estimated code review effort: 4 (Complex) ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant PublicCheckAPI
  participant AuditEngine
  participant TargetPage
  Browser->>PublicCheckAPI: Submit page URL
  PublicCheckAPI->>PublicCheckAPI: Validate target and quota
  PublicCheckAPI->>AuditEngine: Run one-page audit
  AuditEngine->>TargetPage: Fetch rendered page
  AuditEngine-->>PublicCheckAPI: Return measurements and findings
  PublicCheckAPI-->>Browser: Return proof-backed JSON
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 describes the main change: adding a public anonymous one-page check as the entry path to repair workflows.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch seo-fix-kit/lane1-public-check-entry

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: 7005f2ce83

ℹ️ 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 ipHash = await requestIpHash(request);
const quota = await checkQuotaSet(env, publicCheckQuotaChecks(ipHash, hostname));

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 Charge quotas to the final redirected host

When the submitted URL redirects to another hostname, this charges every target quota to the original hostname even though shared/audit-engine.js follows the redirect and renders the destination. Attackers can therefore use wildcard subdomains or multiple redirectors to repeatedly hit one victim site without reaching that site's 3/hour or 10/day limits, defeating the protection this anonymous endpoint promises; reject cross-host redirects or also reserve quota for the final host before rendering.

AGENTS.md reference: AGENTS.md:L7-L7

Useful? React with 👍 / 👎.

error: "Daily one-page check limit reached from this network. Try again tomorrow."
},
{
bucket: `check:target-hour:${hour.key}:${targetKey}`,

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 Hash target hosts before persisting quota buckets

For every successful validation, this writes the clear target hostname into audit_usage as part of both target bucket keys, while the new page, README, and agent documentation promise that nothing about the check is stored. Because these D1 rows persist beyond the request, checking a sensitive-but-public staging or customer domain leaves a durable record; hash the normalized target host as is already done for network identifiers, or narrow the public storage claim.

AGENTS.md reference: AGENTS.md:L7-L7

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

🧹 Nitpick comments (4)
worker/routes/public-check.test.mjs (2)

77-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add handler-level coverage for runPublicCheck.

The suite covers validatePublicCheckUrl, publicCheckQuotaChecks, buildPublicCheckResponse, and checkHtml. It does not cover runPublicCheck itself. The untested branches carry the security and abuse behavior of the route: the 400 private-address rejection, the 503 missing-WAITLIST_DB response, the 429 quota response with resetAt, and the 422 failure path. A test with a stubbed env and a fake Request would pin those status codes and keep the quota contract stable.

🤖 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 `@worker/routes/public-check.test.mjs` around lines 77 - 130, Add handler-level
tests for runPublicCheck using a stubbed env and fake Request, covering
private-address rejection (400), missing WAITLIST_DB (503), quota exhaustion
with resetAt (429), and check failure (422). Assert each response status and
relevant response payload fields, while keeping existing helper-level coverage
unchanged.

187-196: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the duplicated visibleWordCount helper.

This helper is identical to visibleWordCount in worker/routes/pages.test.mjs lines 159-168. Move it into one shared test helper module and import it in both files.

🤖 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 `@worker/routes/public-check.test.mjs` around lines 187 - 196, Extract the
duplicated visibleWordCount helper from the test files into a shared test-helper
module, then import and use that shared symbol in both
worker/routes/public-check.test.mjs and worker/routes/pages.test.mjs. Preserve
the helper’s current HTML-stripping and word-count behavior.
worker/routes/public-check.js (2)

180-189: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider a Retry-After hint and quota-neutral capacity failures.

The quota counters increment before the audit runs. If the browser pool returns BROWSER_BUSY, the caller loses one of six hourly checks for a failure the caller did not cause. Consider decrementing or skipping the counter for the 503 path, and adding a retry-after header on the 429 and 503 responses so clients can back off correctly.

🤖 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 `@worker/routes/public-check.js` around lines 180 - 189, Update the public
check error handling around the BROWSER_BUSY branch and the existing 429
response to include a Retry-After header with an appropriate backoff value.
Ensure capacity failures are quota-neutral by reversing or bypassing the quota
increment when BROWSER_BUSY occurs, while preserving the current 503 and 422
response behavior.

304-409: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Use a relative API path in the inline script.

The submit handler posts to the absolute URL ${origin}/api/public-check. The page is served from the same origin in every route, so a relative /api/public-check avoids a cross-origin request whenever the page is reached through an alternate hostname, for example www.seofixkit.com versus seofixkit.com. A relative path also removes one interpolation of untrusted host data into the script body on the Express path, where origin is built from the Host header.

♻️ Proposed change
-            const response = await fetch("${origin}/api/public-check", {
+            const response = await fetch("/api/public-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 `@worker/routes/public-check.js` around lines 304 - 409, Update the fetch call
in the submit handler to use the relative `/api/public-check` path instead of
interpolating `origin`. Leave the request method, headers, body, and response
handling unchanged.
🤖 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 `@README.md`:
- Line 44: Replace the broad “nothing stored” claim with explicit wording that
no report or page contents are stored, while retaining the existing rate-limit
behavior. Apply this wording consistently in the README public-check
description, public/.well-known/skill.md, and the /check response copy in the
public-check route.

In `@scripts/live-promise-spot-check.test.mjs`:
- Around line 39-41: Update the test around spotCheckPublicPages to assert that
every returned result has an empty failures list, rather than only checking
results.length. Keep the existing result-count assertion if needed, and ensure
failures from missing pages or expected strings cause the test to fail.

In `@worker/routes/public-check.js`:
- Around line 159-172: Reorder the route flow so the WAITLIST_DB guard and the
requestIpHash/checkQuotaSet quota enforcement using publicCheckQuotaChecks run
immediately after deriving hostname from validated.url, before
resolvesToPrivateAddress. Preserve the existing 429 response for exhausted
quotas, and only perform the private-address DNS check after the request has
passed quota validation.

---

Nitpick comments:
In `@worker/routes/public-check.js`:
- Around line 180-189: Update the public check error handling around the
BROWSER_BUSY branch and the existing 429 response to include a Retry-After
header with an appropriate backoff value. Ensure capacity failures are
quota-neutral by reversing or bypassing the quota increment when BROWSER_BUSY
occurs, while preserving the current 503 and 422 response behavior.
- Around line 304-409: Update the fetch call in the submit handler to use the
relative `/api/public-check` path instead of interpolating `origin`. Leave the
request method, headers, body, and response handling unchanged.

In `@worker/routes/public-check.test.mjs`:
- Around line 77-130: Add handler-level tests for runPublicCheck using a stubbed
env and fake Request, covering private-address rejection (400), missing
WAITLIST_DB (503), quota exhaustion with resetAt (429), and check failure (422).
Assert each response status and relevant response payload fields, while keeping
existing helper-level coverage unchanged.
- Around line 187-196: Extract the duplicated visibleWordCount helper from the
test files into a shared test-helper module, then import and use that shared
symbol in both worker/routes/public-check.test.mjs and
worker/routes/pages.test.mjs. Preserve the helper’s current HTML-stripping and
word-count behavior.
🪄 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: f2b8dcf2-8886-4c6e-bb57-321e8eced00f

📥 Commits

Reviewing files that changed from the base of the PR and between 07252c1 and 7005f2c.

📒 Files selected for processing (15)
  • README.md
  • package.json
  • public/.well-known/skill.md
  • public/sitemap.xml
  • scripts/live-promise-spot-check.mjs
  • scripts/live-promise-spot-check.test.mjs
  • server/index.js
  • shared/audit-engine.js
  • shared/promise-audit.test.mjs
  • worker/index.js
  • worker/routes/pages.js
  • worker/routes/pages.test.mjs
  • worker/routes/public-check.js
  • worker/routes/public-check.test.mjs
  • wrangler.jsonc

Comment thread README.md
- Founder-friendly React interface.
- Cloudflare Worker target using Workers Static Assets and Browser Run.
- Locked private-beta homepage with `/api/waitlist` and `/api/access/request` backed by D1.
- Public anonymous one-page URL check at `/check` and `POST /api/public-check`: real browser rendering of one public page, static-vs-rendered proof, guarded false positives, actionable findings when present, per-network and per-site rate limits, nothing stored, and a handoff into private beta access with no ranking promise.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Narrow the storage claim.

The rate-limit design stores hashed network and target identifiers in audit_usage, and README.md, Line 57 documents quota-bucket cleanup. The phrase nothing stored can mislead users about retention. State that no report or page contents are stored. Apply the same wording to public/.well-known/skill.md, Line 10, and the /check copy in worker/routes/public-check.js, Lines 192-351.

🤖 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 `@README.md` at line 44, Replace the broad “nothing stored” claim with explicit
wording that no report or page contents are stored, while retaining the existing
rate-limit behavior. Apply this wording consistently in the README public-check
description, public/.well-known/skill.md, and the /check response copy in the
public-check route.

Comment on lines 39 to +41
test("live spot-check passes against the shipped public page copy", async () => {
const results = await spotCheckPublicPages({ baseUrl: origin, fetcher: pageFetcher() });
assert.equal(results.length, 3);
assert.equal(results.length, 4);

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

Assert that all spot-check expectations pass.

spotCheckPublicPages returns failures, but this test only checks the number of result objects. It remains green when /check returns 404 or misses every expected string. Assert that every failure list is empty.

Suggested assertion
   assert.equal(results.length, 4);
+  assert.deepEqual(
+    results.flatMap(({ path, failures }) =>
+      failures.map((failure) => `${path}: ${failure}`)
+    ),
+    []
+  );
📝 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
test("live spot-check passes against the shipped public page copy", async () => {
const results = await spotCheckPublicPages({ baseUrl: origin, fetcher: pageFetcher() });
assert.equal(results.length, 3);
assert.equal(results.length, 4);
test("live spot-check passes against the shipped public page copy", async () => {
const results = await spotCheckPublicPages({ baseUrl: origin, fetcher: pageFetcher() });
assert.equal(results.length, 4);
assert.deepEqual(
results.flatMap(({ path, failures }) =>
failures.map((failure) => `${path}: ${failure}`)
),
[]
);
🤖 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/live-promise-spot-check.test.mjs` around lines 39 - 41, Update the
test around spotCheckPublicPages to assert that every returned result has an
empty failures list, rather than only checking results.length. Keep the existing
result-count assertion if needed, and ensure failures from missing pages or
expected strings cause the test to fail.

Comment on lines +159 to +172
const hostname = new URL(validated.url).hostname;
if (await resolvesToPrivateAddress(hostname)) {
return jsonNoStore({ error: "This URL points at a private or internal address and cannot be checked." }, 400);
}

if (!env.WAITLIST_DB) {
return jsonNoStore({ error: "Check storage is not configured." }, 503);
}

const ipHash = await requestIpHash(request);
const quota = await checkQuotaSet(env, publicCheckQuotaChecks(ipHash, hostname));
if (!quota.ok) {
return jsonNoStore({ error: quota.error, resetAt: quota.resetAt }, 429);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Enforce the quota before the DNS resolution check.

resolvesToPrivateAddress performs up to two outbound DNS-over-HTTPS requests to cloudflare-dns.com per call (see shared/url-safety.js lines 122-145). This route runs that check before any rate limit applies. An anonymous caller can therefore drive unlimited outbound DNS lookups with a cheap request loop, at zero quota cost and with no D1 write to record the attempt.

Move the WAITLIST_DB guard and the quota check above the private-address check. The quota key uses hostname, which is available from validated.url without resolution.

🛡️ Proposed reordering
   const hostname = new URL(validated.url).hostname;
-  if (await resolvesToPrivateAddress(hostname)) {
-    return jsonNoStore({ error: "This URL points at a private or internal address and cannot be checked." }, 400);
-  }
-
   if (!env.WAITLIST_DB) {
     return jsonNoStore({ error: "Check storage is not configured." }, 503);
   }
 
   const ipHash = await requestIpHash(request);
   const quota = await checkQuotaSet(env, publicCheckQuotaChecks(ipHash, hostname));
   if (!quota.ok) {
     return jsonNoStore({ error: quota.error, resetAt: quota.resetAt }, 429);
   }
+
+  if (await resolvesToPrivateAddress(hostname)) {
+    return jsonNoStore({ error: "This URL points at a private or internal address and cannot be checked." }, 400);
+  }
📝 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
const hostname = new URL(validated.url).hostname;
if (await resolvesToPrivateAddress(hostname)) {
return jsonNoStore({ error: "This URL points at a private or internal address and cannot be checked." }, 400);
}
if (!env.WAITLIST_DB) {
return jsonNoStore({ error: "Check storage is not configured." }, 503);
}
const ipHash = await requestIpHash(request);
const quota = await checkQuotaSet(env, publicCheckQuotaChecks(ipHash, hostname));
if (!quota.ok) {
return jsonNoStore({ error: quota.error, resetAt: quota.resetAt }, 429);
}
const hostname = new URL(validated.url).hostname;
if (!env.WAITLIST_DB) {
return jsonNoStore({ error: "Check storage is not configured." }, 503);
}
const ipHash = await requestIpHash(request);
const quota = await checkQuotaSet(env, publicCheckQuotaChecks(ipHash, hostname));
if (!quota.ok) {
return jsonNoStore({ error: quota.error, resetAt: quota.resetAt }, 429);
}
if (await resolvesToPrivateAddress(hostname)) {
return jsonNoStore({ error: "This URL points at a private or internal address and cannot be checked." }, 400);
}
🤖 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 `@worker/routes/public-check.js` around lines 159 - 172, Reorder the route flow
so the WAITLIST_DB guard and the requestIpHash/checkQuotaSet quota enforcement
using publicCheckQuotaChecks run immediately after deriving hostname from
validated.url, before resolvesToPrivateAddress. Preserve the existing 429
response for exhausted quotas, and only perform the private-address DNS check
after the request has passed quota validation.

@nish3451
nish3451 merged commit 4bd9868 into main Aug 9, 2026
2 checks passed
nish3451 added a commit that referenced this pull request Aug 9, 2026
)

The lane-2 promise audit found the live /check page fails the spot-check,
but the failure was misdiagnosed as copy drift. The deployed Worker
predates the /check route (commit 4bd9868, #66): /check is served by the
static-asset SPA fallback (id="root" shell) instead of the Worker route,
and POST /api/public-check returns 405.

The spot-check now detects the SPA fallback and reports it as a stale
deployment (deploy main, then rerun) instead of pointing at the copy or
the claim. Adds a test that worker-rendered pages are never flagged.
nish3451 added a commit that referenced this pull request Aug 10, 2026
GitHub-hosted runners fail at job start (account billing outage, known
class). The repo-scoped hardened runner on netcup-rs2000 is online; route
every hosted job at it, mirroring TinyStudio.io #66.
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