Sponsor fact-check card (FRE-199, interaction a7cba232) answered 2026-11-05
20:21Z: 'Approve both — move to approved/'. Status blockquotes updated to
record the verdict and the date; page bodies untouched.
Five wave-2 candidates fetched live 2026-11-05 (awesome-macOS 19.4k ★,
1000.tools, uneed.best, devhunt.org; Product Hunt bot-walled and marked
on-screen-read). Copy inherits FRE-207's CTO-signed claims only; price line
stays 'Free 14-day trial', no number (G2). Submission gated on the wave-1
readback and G3/G4. Copy target was Fri 2026-11-13; ready 2026-11-05.
- agent-parallel-tracking/ — 'How do you keep track of multiple coding
agents?' — the multi-agent attention cluster (1xmzp2n, 1xh5cum, 1xdjb2g,
1xipsk0, 1xk0ghj); answered with documented per-session hook events
(session_id, typed Notification matchers) and the status line.
- agent-run-without-asking/ — 'Should you let your coding agent run without
asking permission?' — the autonomy cluster (1xexhwf, 1xk8imc, 1xk277d);
answered with the permission-mode ladder, the docs' own container/VM
warning, rule precedence, sandboxing pointer, PreToolUse deny.
Internal target Mon 2026-11-09; drafted 2026-11-05. All doc claims re-read
2026-11-05 against code.claude.com (permissions, hooks, statusline). Ban
compliance: no product of ours, no competitor, no handles, no price, no
invented figures.
Sponsor fact-check card (FRE-199, interaction 0101ee80) answered 2026-11-05
19:53Z: 'Approve both — move to approved/'. Status blockquotes updated to
record the verdict and the date; page bodies untouched.
- agent-done-signal: 'How do you know when your coding agent is done?' —
the zero-looks-like-success cluster (1xjb3j0, 1xm09fs, 1xmnz2c, HN fhub/
sadilek); Stop/Notification/StopFailure hooks cited to the agent's own
docs, re-read 2026-11-05. No product, no competitor, no price.
- agent-waiting-on-permission: 'Why is my coding agent sitting on a
permission prompt?' — the 1xhbc71 thread + invisible-permission cluster;
permission modes, /permissions, rule syntax, acceptEdits cited to the
permissions doc; undo-split attributed to 1xkoz06.
- newsletter-postmortem-pitch: item 2's shape, placeholders only; send
gated on FRE-202 or a CTO-confirmed direct relay-store read.
All drafts: sponsor fact-check moves pending/ -> approved/; nothing
publishes itself.
FRE-359 verdict B dropped the agent count instead of correcting it: the page
said thirteen while `AgentSpec.all` held eighteen, and the /agents table listed
13 of those rows. The count is a fact, not a claim, so this states it and makes
the table match the registry again.
- Landing island bullet and the marquee chip say 18 agents.
- /agents: title/description/ogTitle/H1 at eighteen, and the five missing rows
are on the page — Kimi, GitHub Copilot CLI, Crush, Cursor, Amp — with events
and gating taken from hook/setup.go, hook/normalize.go,
hook/normalize_hosts.go, Sources/NookCore/Agents/HookConfigInstaller.swift
and plugins/amp/index.ts. All five are Observe (kimi/copilot PreToolUse and
PermissionRequest fire before the host's own check; Crush documents one
event; a silent Cursor permission hook blocks; Amp's plugin API has no
permission-wait event).
- agents.test.ts asserts the roster as an equality with the 18-name registry
in registry order again, plus the count, instead of a subset check.
- remote-claims.test.ts keeps every FRE-323 remote removal guard and the parked
copy, and drops only the two completeness bans the corrected count
contradicts ("every coding agent", "Supported agents") — the ban existed
because the page claimed the set and listed 13 of 18.
- positioning.md: the coverage row is publishable at 18.
This is the count/roster half of FRE-997 only. The remote-SSH restore is held
by the Chief of Staff against FRE-346 §3 and is not in this commit: no remote
tile, breakdown, demo, chip, /agents remote paragraph or drafts deletion here.
Gate: `bun test src/routes/nook` 18 pass / 0 fail; `bun run build` succeeds.
The security review of 39d874a (FRE-369) found the address fix recorded
the wrong thing twice. Both are fixed here; the branch is the same one
under review, this commit does not rewrite it.
Q1 — the first x-forwarded-for hop is attacker-chosen text. In
client → Cloudflare → same-host proxy → app, both proxies *append*, so
the header arrives as `<client spoof…>, <true client>, <CF edge>` and
`split(",")[0]` took the sender's own invention — the loopback-peer gate
was sound, the hop selection defeated it. Preference is now
`cf-connecting-ip` (Cloudflare overwrites it, so a client value never
survives the edge), then x-forwarded-for counted from the right with one
trusted hop discounted, then the direct peer. Every candidate must parse
as an address. h3's `getRequestIP(…, { xForwardedFor: true })` has the
same leftmost flaw and is documented as not a fix.
Q4 — the table held full client addresses indefinitely. Every analytics
entry now passes through `truncateIpAddress` in `enrichAnalyticsEntry`
(IPv4 /24, IPv6 /56, unparseable dropped), so no writer can persist a
full address by accident. Retention got the schedule it never had:
`/api/analytics/retention` on a daily Vercel cron, Bearer-authenticated
and fail-closed, with the window parameterised (the old
`cleanupOldAnalytics` spliced the number into the SQL). Default 365 days
rather than the review's 90 so enabling the cron is not itself a
destructive event — the tighter window is an operational decision, named
on the task.
Also from the review: the module doc now forbids feeding this helper into
any security decision; `isLoopbackAddress` parses instead of
string-matching (`::ffff:127.9.9.9`, `0:0:0:0:0:0:0:1`); and `host` is
normalised (lowercased, port stripped) so `nook.freno.me:443` cannot
silently miss the `host = ?` scope. The weekly loopback caveat matches
the truncated shapes writers produce now, and says plainly that "unique
visitors" counts prefixes.
Gap 1 — click and served share one label: trackDownload now takes a
stage ("click" | "served") carried on a new VisitorAnalytics.stage
column; the Nook door and the tRPC proxy record clicks, the DMG file
route records served. summariseDownloads() and the /analytics downloads
block are untouched — the stage is a separate column, so the path
grouping cannot change.
Gap 2 — checkout start records nothing: the checkout route records a
created Stripe session through the shared trackRequest writer (same
referrer, campaign, country dimensions as every door) under its own
path, and STAGES.checkout reads it instead of the /checkout page proxy.
Gap 3 — loopback addresses, no host dimension: every writer now
resolves the client address through resolveClientIp, which trusts
x-forwarded-for only when the direct peer is the loopback production
proxy (trust boundary stated in request-client.ts), and records the
host on each row through the same site-context extractor routing uses.
stage and host reach the table through VISITOR_ANALYTICS_ADDED_COLUMNS;
the weekly job applies the same bootstrap before reading so it renders
seven stages against a database no deploy has migrated yet.
The weekly read splits downloadClick/dmgServed, scopes visits to
nook.freno.me, and computes its caveats from the window: pre-instrumentation
rows are stated and excluded, and the loopback warning now fires only
when rows recorded since the instrumentation still carry it.
`marketing/metrics/weekly.md` has been a placeholder since the marketing tree
landed — it promised a funnel and the one thing that moved, and nothing
produced it. This adds the producer: one command reads the live databases and
rewrites the file.
`scripts/weekly-metrics.ts` writes `marketing/metrics/weekly.md` from
`VisitorAnalytics` (visits, downloads, campaigns) and the-nook's `trials`,
`licenses`, `activations` (the paid funnel). It needs four env vars —
TURSO_DB_URL/TOKEN and NOOK_DB_URL/TOKEN — and imports no server env, so the
job runs from a checkout that has no Stripe or Apple secrets.
`src/server/weekly-metrics.ts` holds the queries and the renderer, taking both
connections as arguments; the tests run them against an in-memory SQLite shaped
like production, the same way the backfill suite does.
The campaign breakdown groups exactly as `getAnalyticsSummary.topCampaigns`
does (top 10, untagged excluded), so the weekly read and /analytics cannot
drift into different label sets.
The first real report says more about the instrumentation than about demand,
and the renderer computes that rather than asserting it:
- every addressed row the table has ever held (12,035 of 12,048) is loopback,
so "unique visitors" is one host and per-channel reach cannot be read;
- the landing door and the file route both write `/api/downloads/thenook`, so
"download click" and "DMG served" are one label — one served click writes two
rows, a failed click one, a Sparkle update one;
- `POST /api/the-nook/checkout` records nothing, so checkout start is only a
page-view proxy;
- `VisitorAnalytics` has no host dimension, so the Nook's `/` is freno.me's `/`.
Those three gaps are reported beside the numbers, not papered over. Week 0
(2026-10-22 → 2026-10-29): visit 61, download 156, trial 2, checkout 0,
purchase 0, activation 0 — and the one thing that moved is the download stage,
0 → 156, because the label was introduced that week.
Tests: `bun test src/server/weekly-metrics.test.ts` — 12 pass, 0 fail. The full
suite's 15 failures are pre-existing: this checkout's .env lacks
NOOK_LICENSE_PRIVATE_KEY, NOOK_STRIPE_SK/WEBHOOK/PRICE and LINEAGE_JWT_SECRET,
and every failure is that env validation.
Scripts/ is gitignored, so `scripts/weekly-metrics.ts` was added with `-f`, the
way the existing tracked scripts are.
FRE-202
copy.md said the sentences were "removed from the live site" and
rationale.md said "the landing page now says only what the product can back
today". Neither is true: nook.freno.me still serves all of them (HTTP 200,
re-fetched 2026-10-29T14:20Z) because the push is blocked on a write
credential (card 82d1e1b7). Both files now say committed-not-deployed, and
positioning.md's excluded-count row records FRE-359 verdict B and where the
count was removed.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The source-of-truth file still sold claims the board has since ruled on:
- 'thirteen agents' as a public coverage count — FRE-322 §1 counts 18 specs,
9 of which can raise an approval card; no count is publishable (FRE-203 plan-b).
- 'Price rises to 5 at 1.0' — withdrawn by the Chief of Staff on 2026-10-29
04:01; price stays $10 and is not a launch asset.
- 'macOS 14+' — the app cannot start below 14.2 (881190d, site 134f41e).
- Approved-claims table carried no launch sentence at all, and no row for the
remote-SSH hold or the banned absolute privacy sentences.
- Pointed at the-nook/docs/marketing-strategy.md, which no longer exists.
Now: the Plan B claim set, a per-claim status table with verdict pointers,
FRE-354's corrections to the hook/bridge sentence and the Sentry disclosure,
the iPhone TestFlight state, and an explicit excluded/banned list.
FRE-323. The one-click remote install is app-UI-only and needs hand-edited ~/.ssh/config plus a one-time ssh-copy-id (FRE-320); FRE-315 and FRE-322 hold the claim unverified and demo-gated.
- drop the Remote tile and its mock from the feature collage
- drop the "Any SSH box, one click away" breakdown, its demo and bullets
- drop "Works across every hooked agent, local or remote"
- drop the "One-click SSH setup" chip
- move the copy to marketing/drafts/remote-ssh/ and mark remote held in marketing/positioning.md
No replacement claim was added. Deploy waits on a push credential for the GitHub repo Vercel builds.
nook.freno.me/agents listed twelve agents and said "twelve" in its meta
description, OG title and intro, while the app's registry (AgentSpec.all)
gained Muse in 0.10.0 as its thirteenth. The page mirrors that registry
by hand from another repo, so nothing flagged the drift.
Adds the Muse card from its plugin manifest and registry row: the plugin
body at ~/.nook/plugins/muse installed through Muse's own plugins CLI,
its fourteen hook events, approvals answerable from a card (every tool
call under Verify), and this Mac only since remote hosts skip it. The
positioning claims table and copy move to thirteen with it.
Tests: src/routes/nook/agents.test.ts pins the roster to the registry's
names in order (red on master: Muse missing) and every stated count to
the number of entries (red when the intro says twelve again).
NOOK-336
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One voice, one place, so agent-drafted assets stop inventing framing
per artifact. positioning.md carries the wedge, the category-noun rule
(freno-dev/AGENTS.md competitor ban), claims each backed by code, the
honest privacy scoping and the voice/banned-phrasings list. The tree
adds calendar.json, per-channel briefs, drafts → pending → approved →
posted, metrics and listening — with the hard rule up front: approved/
is the only thing a human ever publishes from, nothing posts itself.
Issue: FRE-17