Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions .claude/skills/production-scan/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -589,20 +589,42 @@ re-scans don't clobber):
| `verdictHandle` / `quip` *(optional)* | `verdict_handle` / `verdict_quip` | stored verbatim by the API (no validator rule; keep ≤500 as skill-side discipline) — lets the hosted page adopt the scan's own voice (renderer support tracked website-side) |

```bash
# First upload of an id — no auth. The 201 body includes a ONE-TIME `writeToken`;
# save it (rule below) — overwriting this id later requires it.
curl -X POST <base>/api/reports -H "Content-Type: application/json" -d @.production-scan/api-payload.json
# Re-submitting an id we already claimed (same-day re-scan) — send the saved token:
curl -X POST <base>/api/reports -H "Content-Type: application/json" \
-H "Authorization: Bearer <writeToken>" -d @.production-scan/api-payload.json
```
**Write token (authorship binding, API side 2026-07-03):** the first POST for an id
*claims* it — the 201 body carries a one-time `writeToken`, and every later POST of
the **same id** must send it as `Authorization: Bearer <writeToken>` or the API
answers 401 (so strangers can't rewrite a hosted report they didn't publish).
Handling is mandatory:
- **Before POSTing**, look the id up in `.production-scan/write-tokens.json`; if a
token exists, attach the `Authorization` header.
- **On 201**, if the body contains `writeToken`, merge `{"<id>": "<writeToken>"}` into
`.production-scan/write-tokens.json` (create the file if missing). The token is never
shown again; losing it means this id can no longer be overwritten (recovery: `-2`).

**Submission semantics — one payload, at most 3 delivery attempts:**
- Retry (backoff 1s/2s/4s, 10s timeout each) ONLY on network error / timeout / 5xx.
- **429** → stop immediately (Retry-After can be up to an hour); fall back per Step 9.5.
- **400** → the payload violates the table above; fix the payload — the corrected body
is a NEW submission, never re-send an identical body. Reserved-id 400 → append `-2`
to the id and resubmit once.
- **401** → the id is claimed and we don't hold its token (write-tokens.json lost, or
someone else published under this id first): append `-2` to the id — a fresh claim,
same recovery as the reserved-id 400 — and resubmit once. Never re-send an
identical 401 body.
- **413** → drop `markdown` from the payload and resubmit once.
On 201, use the returned URL; anything else → the Step 9.5 fallback. The per-IP budget
is 20 POSTs/hour — one scan = one logical submission.

**`.production-scan/` must be gitignored by default** (add it to `.gitignore`); it
carries bounded evidence only — **never raw secret values or full file contents**.
carries bounded evidence only — **never raw secret values or full file contents** —
plus `write-tokens.json`, the per-id overwrite credentials for the hosted reports
(one more reason the directory stays out of git).
The user opts in to committing a report. Keeping timestamped JSON lets a later scan
diff against the previous one ("fixed 2, 1 new since <date>"). The fix flow and
re-scan read **only the JSON** — Markdown is a human-only leaf.
Expand Down Expand Up @@ -1272,7 +1294,10 @@ shareable without manual deployment.
composed id — the date suffix must survive); if the result starts with a
non-alphanumeric, prefix `r`; if it equals a reserved sample id, append `-2`
- The date suffix separates scans from different days; **a same-day re-scan of the
same repo reuses the id and OVERWRITES the hosted report (latest wins — deliberate)**
same repo reuses the id and OVERWRITES the hosted report (latest wins — deliberate)**,
authenticated by the id's `writeToken` from `.production-scan/write-tokens.json`
(Step 7 write-token rule); without it the API answers 401 and the re-scan
publishes under `<id>-2`
- Reports are stored **permanently** (product decision 2026-07-02) — share links never expire
- **How the URL is produced (MANDATORY production flow with SAFETY checks):**
1. After building `scan-report.json`, determine the base URL using the detection logic above
Expand Down Expand Up @@ -1332,7 +1357,9 @@ shareable without manual deployment.
- This is THE conversion point - never skip

✅ **HTML Report URL (Step 9.5)**
- POST once (≤3 delivery attempts); 201 → hosted URL is the last line
- POST once (≤3 delivery attempts); 201 → hosted URL is the last line; persist any
returned `writeToken` to `.production-scan/write-tokens.json` and send it as a
Bearer header when re-POSTing the same id (Step 7 write-token rule)
- Any failure → local-fallback line + cause (honest URL rule; no dead links)
- Auto-detect base (First Tree → production, FIRST_TREE_BASE_URL, else localhost)
- Production: `https://first-tree.ai/production-scan/r/<owner>-<repo>-<yyyymmdd>`
Expand Down