Skip to content
This repository was archived by the owner on Sep 28, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
54337e5
fix(public): refresh the study figures to the 2026-08-12 scan
nish3451 Aug 12, 2026
a97da36
test(study): guard the daily-refresh promise against stale figures
nish3451 Aug 12, 2026
db69b93
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
b21a2da
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
79aa9f6
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
2e35acc
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
5d269b2
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
5fa87c0
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
cce417b
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
ce18637
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
efe9abd
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
4b19d73
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
0141e78
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
b0a2bae
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
28115cf
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
e01c789
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
dcad042
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
28d027c
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 12, 2026
a5ebba0
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
1e0b530
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
40cf96d
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
b6b3315
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
ded1d8b
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
6bd974d
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
d56a987
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
d3fe68f
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
b38fc57
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
70cb623
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
68caf4d
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
9032ef9
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
0474344
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
c21ad10
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
9fd8d69
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
a42b118
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
0b683aa
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
8830755
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
760f7a3
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
8030823
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
d1cc1cf
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
da3a6dc
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
e3c37c1
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
699bf02
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 13, 2026
0f429b3
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
Aug 18, 2026
4b668a3
Merge branch 'main' into fix/study-numbers-fresh-20260812-lane1
nish3451 Aug 19, 2026
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
"dev": "wrangler dev --remote --ip 127.0.0.1 --port 8788 --persist-to /tmp/tinystudio-agent-worker",
"check": "node scripts/check-site.mjs",
"check:render-blocking": "node scripts/check-render-blocking.mjs",
"test": "npm run check && npm run test:headings && npm run test:sitemap && npm run test:worker && npm run test:ui && npm run test:contract",
"test": "npm run check && npm run test:headings && npm run test:sitemap && npm run test:worker && npm run test:ui && npm run test:contract && npm run test:study",
"test:headings": "node --test scripts/test-heading-hierarchy.mjs",
"test:contract": "node --test scripts/test-product-contract.mjs",
"test:study": "node --test scripts/test-study-freshness.mjs",
"test:sitemap": "node --test scripts/test-sitemap.mjs",
"test:worker": "node --test scripts/test-agent-worker.mjs",
"test:ui": "node --test scripts/test-agent-ui.mjs",
Expand Down
8 changes: 4 additions & 4 deletions public/audit.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ <h1>We read your page <em>as a customer with intent reads it.</em></h1>

<div class="band">
<div class="bandgrid">
<div class="stat"><s><span data-study="no_faq">53</span></s> of <span data-study="readable">89</span></div>
<div class="stat"><s><span data-study="no_faq">55</span></s> of <span data-study="readable">90</span></div>
<div>
<h2>answer none of the questions a buyer asks before committing.</h2>
<p>We read <span data-study="readable_word">eighty-nine</span> of them — clinics, surgeons, dentists, spas, private members' clubs, property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. <span data-study="no_faq_word_cap">Fifty-three</span> settled nothing a buyer needs settled before they will commit — not what happens next, not who it is for, not what it costs to be wrong. <span data-study="no_price_word_cap">Fifty-seven</span> showed no price either, though we count that differently: withholding a figure is often a deliberate act, and discretion is part of what some of these businesses sell. Answering nothing at all is nobody's strategy. That is the difference we look for — the gap between what a business chose and what simply happened to it. We re-read the set continuously, so this number is today's.</p>
<div class="note">Method: automated read of the served homepage HTML, refreshed daily. <span data-study="attempted_word_cap">Ninety-nine</span> sites attempted across <span data-study="industries_word">thirteen</span> industries and <span data-study="geographies_word">eight</span> geographies; <span data-study="readable_word">eighty-nine</span> readable, <span data-study="blocked_word">ten</span> blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the faults.</div>
<p>We read <span data-study="readable_word">ninety</span> of them — clinics, surgeons, dentists, spas, private members' clubs, property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. <span data-study="no_faq_word_cap">Fifty-five</span> settled nothing a buyer needs settled before they will commit — not what happens next, not who it is for, not what it costs to be wrong. <span data-study="no_price_word_cap">Fifty-seven</span> showed no price either, though we count that differently: withholding a figure is often a deliberate act, and discretion is part of what some of these businesses sell. Answering nothing at all is nobody's strategy. That is the difference we look for — the gap between what a business chose and what simply happened to it. We re-read the set continuously, so this number is today's.</p>
<div class="note">Method: automated read of the served homepage HTML, refreshed daily. <span data-study="attempted_word_cap">Ninety-nine</span> sites attempted across <span data-study="industries_word">thirteen</span> industries and <span data-study="geographies_word">eight</span> geographies; <span data-study="readable_word">ninety</span> readable, <span data-study="blocked_word">nine</span> blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the faults.</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -140,7 +140,7 @@ <h2>Then it is entirely your call</h2>

<section id="confidential">
<h2>Why there are no logos on this site</h2>
<p class="lede">We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.<br><br><b>Market research is the opposite, and we share it freely.</b> The <span data-study="readable_word">eighty-nine</span>-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. <a class="xa1" href="/specimen">Read the specimen &rarr;</a></p>
<p class="lede">We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.<br><br><b>Market research is the opposite, and we share it freely.</b> The <span data-study="readable_word">ninety</span>-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. <a class="xa1" href="/specimen">Read the specimen &rarr;</a></p>
</section>

<footer>
Expand Down
8 changes: 4 additions & 4 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ <h1>Most of them leave <em>before they ever get in touch.</em></h1>
</header>

<section class="finding" id="finding">
<div class="stat"><s><span data-study="no_faq">53</span></s> of <span data-study="readable">89</span></div>
<div class="stat"><s><span data-study="no_faq">55</span></s> of <span data-study="readable">90</span></div>
<div class="body">
<h2>answer none of the questions a buyer asks before committing.</h2>
<p>We read <span data-study="readable_word">eighty-nine</span> of them — clinics, surgeons, dentists, spas, private members' clubs, property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. <span data-study="no_faq_word_cap">Fifty-three</span> settled nothing a buyer needs settled before they will commit — not what happens next, not who it is for, not what it costs to be wrong. <span data-study="no_price_word_cap">Fifty-seven</span> showed no price either, though we count that differently: withholding a figure is often a deliberate act, and discretion is part of what some of these businesses sell. Answering nothing at all is nobody's strategy. That is the difference we look for — the gap between what a business chose and what simply happened to it. We re-read the set continuously, so this number is today's.</p>
<p class="note">Method: automated read of the served homepage HTML, refreshed daily. <span data-study="attempted_word_cap">Ninety-nine</span> sites attempted across <span data-study="industries_word">thirteen</span> industries and <span data-study="geographies_word">eight</span> geographies; <span data-study="readable_word">eighty-nine</span> readable, <span data-study="blocked_word">ten</span> blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the faults.</p>
<p>We read <span data-study="readable_word">ninety</span> of them — clinics, surgeons, dentists, spas, private members' clubs, property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. <span data-study="no_faq_word_cap">Fifty-five</span> settled nothing a buyer needs settled before they will commit — not what happens next, not who it is for, not what it costs to be wrong. <span data-study="no_price_word_cap">Fifty-seven</span> showed no price either, though we count that differently: withholding a figure is often a deliberate act, and discretion is part of what some of these businesses sell. Answering nothing at all is nobody's strategy. That is the difference we look for — the gap between what a business chose and what simply happened to it. We re-read the set continuously, so this number is today's.</p>
<p class="note">Method: automated read of the served homepage HTML, refreshed daily. <span data-study="attempted_word_cap">Ninety-nine</span> sites attempted across <span data-study="industries_word">thirteen</span> industries and <span data-study="geographies_word">eight</span> geographies; <span data-study="readable_word">ninety</span> readable, <span data-study="blocked_word">nine</span> blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the faults.</p>
</div>
</section>

Expand Down Expand Up @@ -246,7 +246,7 @@ <h2>Before you ask</h2>

<section id="confidential">
<h2>Why there are no logos on this site</h2>
<p class="lede">We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.<br><br><b>Market research is the opposite, and we share it freely.</b> The <span data-study="readable_word">eighty-nine</span>-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. <a class="xi19" href="/specimen">Read the specimen &rarr;</a></p>
<p class="lede">We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.<br><br><b>Market research is the opposite, and we share it freely.</b> The <span data-study="readable_word">ninety</span>-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. <a class="xi19" href="/specimen">Read the specimen &rarr;</a></p>
</section>

<footer>
Expand Down
2 changes: 1 addition & 1 deletion public/pricing.html
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ <h2>The appraisal costs you an email. The rest is a decision you can make later.

<section id="confidential">
<h2>Why there are no logos on this site</h2>
<p class="lede">We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.<br><br><b>Market research is the opposite, and we share it freely.</b> The <span data-study="readable_word">eighty-nine</span>-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. <a class="xp1" href="/specimen">Read the specimen &rarr;</a></p>
<p class="lede">We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.<br><br><b>Market research is the opposite, and we share it freely.</b> The <span data-study="readable_word">ninety</span>-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. <a class="xp1" href="/specimen">Read the specimen &rarr;</a></p>
</section>

<footer>
Expand Down
2 changes: 1 addition & 1 deletion public/specimen.html
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ <h3>Two passes not run</h3>

<section>
<h2>Confidentiality</h2>
<p class="lede">This clinic is not a client and has not been named. Client work is never published — no logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us. That is why there is no wall of names on this site, and there never will be.<br><br>The <span data-study="readable_word">eighty-nine</span>-site study behind this audit is a different thing entirely: public homepages, no client relationship, and yours on request in full — thirteen industries, eight geographies. Research we will hand over; client work we will not.</p>
<p class="lede">This clinic is not a client and has not been named. Client work is never published — no logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us. That is why there is no wall of names on this site, and there never will be.<br><br>The <span data-study="readable_word">ninety</span>-site study behind this audit is a different thing entirely: public homepages, no client relationship, and yours on request in full — thirteen industries, eight geographies. Research we will hand over; client work we will not.</p>
</section>
</div>

Expand Down
47 changes: 47 additions & 0 deletions scripts/test-study-freshness.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// The public pages promise "refreshed daily" and "this number is today's"
// (index.html, audit.html, pricing.html, specimen.html). These guards make
// that promise durable:
//
// 1. render.py --check — the pages' data-study spans must match the newest
// snapshot in study/snapshots/. A partial import (snapshot without
// render, or render without snapshot) fails here.
// 2. Freshness — the newest snapshot must be recent enough to back the
// daily-refresh claim. The scan runs daily at 07:00 IST in the scan
// checkout and the snapshots are imported into this repo by hand, so a
// small window is allowed; a multi-day drift (the 2026-08-06 -> 08-12
// gap this guard was written against) turns red.
import { test } from "node:test";
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { readdirSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const HERE = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(HERE, "..");
const MAX_SNAPSHOT_AGE_DAYS = 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.

P2 Badge Enforce daily freshness for the daily claim

Even with correctly dated snapshots and a scheduled test run, this four-day allowance lets a scan/import run only every fourth day while the suite remains green, so the public pages can continue saying the figures are “today's” and “refreshed daily” for several days after the last scan. Require approximately daily data freshness, or weaken the public claim to match the tolerated cadence.

Useful? React with 👍 / 👎.


function newestSnapshotFile() {
const snaps = readdirSync(path.join(ROOT, "study", "snapshots"))
.filter((name) => name.endsWith(".json"))
.sort();
assert.ok(snaps.length > 0, "study/snapshots/ must contain at least one dated snapshot");
return snaps[snaps.length - 1];
}

test("public pages are rendered from the newest study snapshot", () => {
execFileSync("python3", ["study/render.py", "--check"], { cwd: ROOT, stdio: "pipe" });

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 Validate snapshots against the repository corpus

The imported snapshot can pass this check even though it was produced from a different study set: at this commit study/corpus.json contains 20,005 sites and study/scan.py's default seven-day slice for 2026-08-12 contains 2,858 sites, while 2026-08-12.json contains only 99 sites (just 19 overlap that expected slice). Because the check only compares the pages with whichever snapshot was copied in, artifacts from the stale scan checkout are accepted as today's repository study and publish incompatible results. Validate the snapshot's hosts or corpus identity against the expected current-corpus slice before rendering it.

Useful? React with 👍 / 👎.

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 Cover hard-coded study counts in the render check

When a future snapshot changes its industries or geographies totals, this check can still pass while public/specimen.html:130 continues to claim “thirteen industries, eight geographies,” because those values are outside data-study spans even though render.py already exposes industries_word and geographies_word. Tag or otherwise validate these remaining counts so the new synchronization test actually detects drift across every listed public page.

Useful? React with 👍 / 👎.

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 Validate snapshot totals against their rows

When a manually imported snapshot has incorrect or partially regenerated top-level totals, this check still passes because render.py --check only verifies that the pages repeat those totals; it never derives attempted, readable, no_price, no_faq, industries, or geographies from rows. In that import scenario the public pages can publish inaccurate figures while the new synchronization guard remains green, so validate the snapshot's summary fields against its row data before accepting the render.

Useful? React with 👍 / 👎.

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 Keep npm test within its documented runtime requirements

On Windows and minimal Node environments where python3 is not installed under that exact executable name, the newly mandatory npm test path fails with ENOENT before checking the pages. The repository documents npm test as a standard command and declares only Node in package.json, so either implement this comparison in Node or explicitly provision and document Python for every supported test environment.

Useful? React with 👍 / 👎.

});

test("the newest study snapshot is fresh enough to back the daily-refresh promise", () => {
const file = newestSnapshotFile();
const date = file.replace(/\.json$/, "");
const snapshotEnd = Date.parse(`${date}T23:59:59Z`);
Comment on lines +37 to +39

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 Derive freshness from the snapshot metadata

When a snapshot is manually imported under the wrong filename, this check treats the filename as its acquisition date and never inspects the payload's existing date or scanned_at fields. For example, renaming an older snapshot to today's date and re-rendering its figures makes both new tests pass while the public data is still stale. Validate that the filename matches the embedded date and calculate age from scanned_at so the guard verifies when the scan actually ran.

Useful? React with 👍 / 👎.

assert.ok(Number.isFinite(snapshotEnd), `snapshot filename ${file} must be an ISO date (YYYY-MM-DD.json)`);

const ageDays = (Date.now() - snapshotEnd) / 86_400_000;
assert.ok(
ageDays <= MAX_SNAPSHOT_AGE_DAYS,
Comment on lines +42 to +44

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 Schedule the time-based freshness check

When the repository has no pushes or pull requests for several days, this assertion is never reevaluated: the checked .github/workflows/ci.yml only triggers CI for pull_request and pushes to main, with no scheduled run. The public figures can therefore become indefinitely stale without producing the promised visible failure; add a scheduled workflow that runs test:study (and reports failures) independently of repository activity.

Useful? React with 👍 / 👎.

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 Reject future-dated snapshots

If the scan host's clock is wrong or a snapshot is accidentally imported with a future filename, ageDays becomes negative and this upper-bound-only assertion passes until days after that future date, allowing the freshness guard and rendered pages to trust an invalid snapshot. Validate that the parsed snapshot date is not later than the current UTC date before accepting its age.

Useful? React with 👍 / 👎.

`newest snapshot is ${date} (${Math.floor(ageDays)} days old) but the site promises "refreshed daily" — import the newer snapshots from the scan checkout and re-run study/render.py (max allowed age: ${MAX_SNAPSHOT_AGE_DAYS} days)`
Comment on lines +36 to +45

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 | 🟠 Major | ⚡ Quick win

Validate freshness from snapshot metadata.

Line 39 uses the end of the filename date instead of the scan time. A snapshot scanned at 01:30:29Z can remain valid for almost five days. A future-dated filename also produces a negative age and passes.

Read the selected JSON file. Validate that its date matches the filename. Calculate age from scanned_at. Reject scan times later than Date.now().

🤖 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/test-study-freshness.mjs` around lines 36 - 45, Update the freshness
test around newestSnapshotFile to read the selected snapshot JSON and use its
scanned_at timestamp instead of the filename date. Validate that the metadata
date matches the YYYY-MM-DD filename, reject invalid or future scanned_at
values, and calculate age from scanned_at while preserving the existing
maximum-age assertion.

);
});
Loading
Loading