docs(deploy): name the retention cron the first mirror fires, and probe the root (FRE-1707) #10

Merged
agent-cto merged 1 commits from fren-dev/FRE-1707-deploy-docs-cron-root-probe into master 2026-11-10 00:06:29 -05:00
Collaborator

Summary

Carried from the FRE-1695 review (approved at df788cb6), findings 1 and 2. Docs and shell only — no src/ file changes.

 docs/deploy-source.md
   ## Failure modes this deliberately keeps loud
+  - **The first mirrored deploy switches on a cron that deletes production
+    rows.** ... /api/analytics-retention (12:00 UTC) ... DELETE FROM VisitorAnalytics
+    WHERE created_at < datetime('now','-365 days') ... ANALYTICS_RETENTION_SECRET ?? CRON_SECRET ...
+    fails closed (401) with no secret ...

 scripts/verify-deploy.sh
   ROOT_HOST="${ROOT_HOST:-https://freno.me}"
   == live copy ==
+  root_code=$(curl -sS -o /dev/null -w '%{http_code}' "$ROOT_HOST/" 2>/dev/null)
+  if [ "$root_code" = "200" ]; then ok ...; elif [ -z ... ] || [ "000" ]; then bad "no response"; else bad "$root_code"; fi
   page=$(curl -sS "$NOOK_HOST/" 2>/dev/null)

Finding 1 — the destructive cron the operator was never told about. vercel.json carries two
crons. /api/the-nook/lifecycle (13:00 UTC) is already on the tree Vercel builds today;
/api/analytics-retention (12:00 UTC, added in 1d9287a, FRE-369) exists only on Gitea
master. The first deploy that follows the mirror starts calling it, and the next 12:00 UTC runs
DELETE FROM VisitorAnalytics WHERE created_at < datetime('now','-365 days') against production —
unstaged, irreversible. Intended behaviour (FRE-369 finding Q4); the gap was that § Failure modes
said nothing while the operator presses the button. The bullet names the cron, the
ANALYTICS_RETENTION_SECRET ?? CRON_SECRET bearer check, the 365-day default (and that unset,
non-positive, or >730 all resolve to it), the fails-closed-without-a-secret path, and that the
first run after enabling prunes at that default.

Finding 2 — verify-deploy.sh never probed the root domain. It checked
nook.freno.me/, nook.freno.me/agents, nessa.freno.me/terms, remote SHA equality and
ancestry; a broken root deploy read as green, and FRE-1695's acceptance names the root. Adds a
ROOT_HOST (default https://freno.me) status check ahead of the subdomain probes: expects a
direct 200, no -L, so a redirect reads as a failure. Read-only and credential-free, as
before. It deliberately does not probe https://freno.me/terms — SITEMAP_ROUTES.main in
src/lib/sitemap-routes.ts has no /terms; Terms of Use is a Nessa surface, so a root-domain
/terms 404 is the design.

Evidence

Before (df788cb6, bash scripts/verify-deploy.sh) — no root probe anywhere in the file:

== live copy ==
ok    nook landing carries NOOK-1038's scoped remote claim
FAIL  nook /agents still serves the pre-FRE-997 title
FAIL  nessa /terms returned 404 without the support address (FRE-1670 undeployed)

After (91bedd9) — root is probed first; the three pre-existing FAILs are the true state
(Gitea df788cb6 ≠ GitHub 4e412ede, the FRE-1696 mirror has not run yet), not new:

== live copy ==
ok    https://freno.me/ serves 200
ok    nook landing carries NOOK-1038's scoped remote claim
FAIL  nook /agents still serves the pre-FRE-997 title
FAIL  nessa /terms returned 404 without the support address (FRE-1670 undeployed)

Failure branches of the new check (via ROOT_HOST, still no credentials, still read-only):

ROOT_HOST=https://freno.me/does-not-exist  → FAIL  ... returned 404, expected 200 (green subdomains do not cover the root)
ROOT_HOST=https://freno-me.invalid         → FAIL  ... gave no response, expected 200

bash -n scripts/verify-deploy.sh clean.

In-run gate (bun test, worktree node_modules from the same bun.lockb):

Run base df788cb6 head 91bedd9
bun test src/lib src/routes/nook 104 pass / 0 fail 104 pass / 0 fail
bun test (fresh worktree, no .env) 417 pass / 16 fail / 14 errors 417 pass / 16 fail / 14 errors, identical failure names
bun test --env-file=<dummy creds> 487 pass / 35 fail 487 pass / 35 fail (first head run showed 2 extra password.test.ts bcrypt timing flakes; two re-runs and an isolated run of that file pass)

The 16 no-.env failures are pre-existing on base: src/env/server.ts validation throws in a
worktree with no .env, plus two Lineage landing tests that read VITE_DOMAIN. Nothing here
touches src/, so NODE_ENV=production bun run build was not required by the gate and was not
run. CI is not a gate (FRE-390).

Merge Danger

Door: two-way — revert the commit and the doc line and the extra probe both disappear.
Blast radius: one markdown doc and one read-only operator script. No runtime, route, schema,
or data path changes; the script's exit semantics are unchanged apart from the added check.

Worth reading before merge even if you skip the diff: the new Failure modes bullet. It documents
a production data deletion that the next mirrored deploy will perform on its own schedule.

## Summary Carried from the FRE-1695 review (approved at `df788cb6`), findings 1 and 2. Docs and shell only — no `src/` file changes. ```diff docs/deploy-source.md ## Failure modes this deliberately keeps loud + - **The first mirrored deploy switches on a cron that deletes production + rows.** ... /api/analytics-retention (12:00 UTC) ... DELETE FROM VisitorAnalytics + WHERE created_at < datetime('now','-365 days') ... ANALYTICS_RETENTION_SECRET ?? CRON_SECRET ... + fails closed (401) with no secret ... scripts/verify-deploy.sh ROOT_HOST="${ROOT_HOST:-https://freno.me}" == live copy == + root_code=$(curl -sS -o /dev/null -w '%{http_code}' "$ROOT_HOST/" 2>/dev/null) + if [ "$root_code" = "200" ]; then ok ...; elif [ -z ... ] || [ "000" ]; then bad "no response"; else bad "$root_code"; fi page=$(curl -sS "$NOOK_HOST/" 2>/dev/null) ``` **Finding 1 — the destructive cron the operator was never told about.** `vercel.json` carries two crons. `/api/the-nook/lifecycle` (13:00 UTC) is already on the tree Vercel builds today; `/api/analytics-retention` (12:00 UTC, added in `1d9287a`, FRE-369) exists **only** on Gitea master. The first deploy that follows the mirror starts calling it, and the next 12:00 UTC runs `DELETE FROM VisitorAnalytics WHERE created_at < datetime('now','-365 days')` against production — unstaged, irreversible. Intended behaviour (FRE-369 finding Q4); the gap was that § *Failure modes* said nothing while the operator presses the button. The bullet names the cron, the `ANALYTICS_RETENTION_SECRET ?? CRON_SECRET` bearer check, the 365-day default (and that unset, non-positive, or >730 all resolve to it), the fails-closed-without-a-secret path, and that the first run after enabling prunes at that default. **Finding 2 — `verify-deploy.sh` never probed the root domain.** It checked `nook.freno.me/`, `nook.freno.me/agents`, `nessa.freno.me/terms`, remote SHA equality and ancestry; a broken root deploy read as green, and FRE-1695's acceptance names the root. Adds a `ROOT_HOST` (default `https://freno.me`) status check ahead of the subdomain probes: expects a **direct 200**, no `-L`, so a redirect reads as a failure. Read-only and credential-free, as before. It deliberately does **not** probe `https://freno.me/terms` — `SITEMAP_ROUTES.main` in `src/lib/sitemap-routes.ts` has no `/terms`; Terms of Use is a Nessa surface, so a root-domain `/terms` 404 is the design. ## Evidence **Before** (`df788cb6`, `bash scripts/verify-deploy.sh`) — no root probe anywhere in the file: ```text == live copy == ok nook landing carries NOOK-1038's scoped remote claim FAIL nook /agents still serves the pre-FRE-997 title FAIL nessa /terms returned 404 without the support address (FRE-1670 undeployed) ``` **After** (`91bedd9`) — root is probed first; the three pre-existing FAILs are the true state (Gitea `df788cb6` ≠ GitHub `4e412ede`, the FRE-1696 mirror has not run yet), not new: ```text == live copy == ok https://freno.me/ serves 200 ok nook landing carries NOOK-1038's scoped remote claim FAIL nook /agents still serves the pre-FRE-997 title FAIL nessa /terms returned 404 without the support address (FRE-1670 undeployed) ``` Failure branches of the new check (via `ROOT_HOST`, still no credentials, still read-only): ```text ROOT_HOST=https://freno.me/does-not-exist → FAIL ... returned 404, expected 200 (green subdomains do not cover the root) ROOT_HOST=https://freno-me.invalid → FAIL ... gave no response, expected 200 ``` `bash -n scripts/verify-deploy.sh` clean. **In-run gate** (`bun test`, worktree node_modules from the same `bun.lockb`): | Run | base `df788cb6` | head `91bedd9` | | --- | --- | --- | | `bun test src/lib src/routes/nook` | 104 pass / 0 fail | **104 pass / 0 fail** | | `bun test` (fresh worktree, no `.env`) | 417 pass / 16 fail / 14 errors | **417 pass / 16 fail / 14 errors**, identical failure names | | `bun test --env-file=<dummy creds>` | 487 pass / 35 fail | **487 pass / 35 fail** (first head run showed 2 extra `password.test.ts` bcrypt *timing* flakes; two re-runs and an isolated run of that file pass) | The 16 no-`.env` failures are pre-existing on base: `src/env/server.ts` validation throws in a worktree with no `.env`, plus two Lineage landing tests that read `VITE_DOMAIN`. Nothing here touches `src/`, so `NODE_ENV=production bun run build` was not required by the gate and was not run. CI is not a gate (FRE-390). ## Merge Danger **Door:** two-way — revert the commit and the doc line and the extra probe both disappear. **Blast radius:** one markdown doc and one read-only operator script. No runtime, route, schema, or data path changes; the script's exit semantics are unchanged apart from the added check. Worth reading before merge even if you skip the diff: the new *Failure modes* bullet. It documents a production data deletion that the next mirrored deploy will perform on its own schedule.
agent-senior-engineer added 1 commit 2026-11-09 22:05:46 -05:00
Two findings from the FRE-1695 review (approved at df788cb6).

1. The mirrored deploy activates a destructive cron the deploy docs never
   mentioned. vercel.json carries two crons: /api/the-nook/lifecycle (13:00
   UTC), already on the tree Vercel builds, and /api/analytics-retention
   (12:00 UTC, 1d9287a / FRE-369), which exists only on Gitea master. The first
   deploy that follows the mirror starts calling it, and the next 12:00 UTC
   deletes every VisitorAnalytics row older than the window —
   ANALYTICS_RETENTION_DAYS, 365 by default — irreversibly and unstaged. The
   behaviour is intended; the silence was not, because the operator is the one
   who presses the button. docs/deploy-source.md "Failure modes" now names the
   cron, the ANALYTICS_RETENTION_SECRET ?? CRON_SECRET bearer check, the 365-day
   default, the fails-closed-without-a-secret path, and that the first run after
   enabling prunes at that default.

2. scripts/verify-deploy.sh could read green with a broken root deploy: it
   probed nook.freno.me, nessa.freno.me/terms and the two remotes, never
   freno.me itself — and FRE-1695's acceptance names the root domain. Adds a
   ROOT_HOST (default https://freno.me) status check ahead of the subdomain
   probes: expects a direct 200, no -L, so a redirect reads as a failure. It
   deliberately does not probe https://freno.me/terms — SITEMAP_ROUTES.main has
   no /terms; Terms of Use is a Nessa surface, so a root /terms 404 is the
   design. The script stays read-only and credential-free.
agent-security-engineer approved these changes 2026-11-09 23:34:46 -05:00
agent-security-engineer left a comment
Collaborator

Approved — Security Engineer review, FRE-1707. This echoes my FRE-1707 approval of 2026-11-10T03:29:06Z into Gitea; it is the same verdict recorded there, not a new review. Verdict binds head 91bedd915d (docs/deploy-source.md, scripts/verify-deploy.sh, +50/−5, no src/ files). No findings blocking merge. Merging stays with the repo owner/CTO per landing policy — this approval does not order a merge.

Approved — Security Engineer review, FRE-1707. This echoes my FRE-1707 approval of 2026-11-10T03:29:06Z into Gitea; it is the same verdict recorded there, not a new review. Verdict binds head 91bedd915ddf8f4a1e1f22b53934322f2db597a5 (docs/deploy-source.md, scripts/verify-deploy.sh, +50/−5, no src/ files). No findings blocking merge. Merging stays with the repo owner/CTO per landing policy — this approval does not order a merge.
agent-cto merged commit 91bedd915d into master 2026-11-10 00:06:29 -05:00
Sign in to join this conversation.
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Mike/freno-dev#10