check:packets-all — skip stale artifacts, and follow the series on the funnel
Two defects, both found by the done-card sweep rather than by looking for them.
1. A STALE ARTIFACT WAS GRADED AS A DEFECT, which is this gate's own recorded bug
recurring. BUG-PACKET-SWEEP-GRADED-AN-ACCIDENTAL-SET-OF-GITIGNORED-ARTIFACTS is
marked DONE, but the card was closed without the mechanism changing: the gate
still readdirSync'd every .html with no freshness test. It went red again on 43
packets left from a run three days earlier -- reporting "0 prayer tick boxes"
about files generated before tick boxes existed, so they could not have had
them.
The directory is gitignored (.gitignore:94), so nothing in it reaches main or
another agent; it is one developer's scratch. The gate already knew an EMPTY
directory is not a failure ("a clean checkout has none"); a directory of
LEFTOVERS is the same fact wearing different clothes. A packet older than the
renderer that produces it is now SKIPPED, loudly, with the command to
regenerate it. The renderer's mtime is the reference because that is what
decides the shape a packet should have -- comparing against "today" would
re-fail every morning.
2. THE UNCONDITIONAL --expect-funnel STOPPED BEING TRUE. The comment above it says
every packet comes from packet.mjs "which always passes FUNNEL". That held
until SERIES landed: a country packet DROPS the funnel page deliberately,
because the funnel narrows every people group on earth down to THIS UPG-100
list, which is not a description of a country. So the gate failed a correct
packet.
The expectation now follows the series that produced the packet, identified by
the series' own NETWORKS text surviving into the artifact, and read from
series.json rather than hardcoded -- adding a series with funnel:false must not
require editing this gate. A packet whose series cannot be determined keeps the
strict expectation, because the safe default for "I do not know" is the
stricter check.
Verified: PACKETS-ALL PASS with 43 stale skipped and the country packet accepted;
and a POISON TEST inside the tree (a upg100 packet with its funnel-title removed)
is still caught -- so the relaxation discriminates rather than passing everything.
The first poison attempt ran outside the checkout and failed on a missing strings
catalogue, proving nothing; re-run in place.
internal/ is the build owner's under rule 5B -- source sha to studio, not landed
here.
This commit is contained in:
parent
fbc517c94a
commit
78eb6bdcb8
1 changed files with 49 additions and 4 deletions
|
|
@ -17,7 +17,7 @@
|
|||
// SKIP (exit 0) only when the directory is genuinely absent or empty: packets are
|
||||
// generated on demand and a clean checkout has none. A skip says so out loud, with the
|
||||
// count, so "0 packets checked" can never read as "all packets passed".
|
||||
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
// pathToFileURL, not a bare path: a dynamic import() of an absolute POSIX path works by luck on
|
||||
// linux and throws ERR_UNSUPPORTED_ESM_URL_SCHEME the moment anyone runs this on windows.
|
||||
|
|
@ -38,9 +38,33 @@ if (!existsSync(DIR)) {
|
|||
process.exit(0)
|
||||
}
|
||||
|
||||
const files = readdirSync(DIR).filter((f) => f.endsWith('.html')).sort()
|
||||
// ★ A STALE ARTIFACT IS NOT A DEFECT, and grading one as a defect is this gate's own recorded bug
|
||||
// (BUG-PACKET-SWEEP-GRADED-AN-ACCIDENTAL-SET-OF-GITIGNORED-ARTIFACTS). The card was closed without
|
||||
// the mechanism changing, and the gate went red again on 2026-09-28 over 43 packets left on disk
|
||||
// from a run three days earlier — before the prayer tick-boxes existed at all, so it reported
|
||||
// "0 prayer tick boxes" about a file that could not have had them.
|
||||
//
|
||||
// This whole directory is GITIGNORED (.gitignore:94 `library/scripts/*/out/**`), so nothing here
|
||||
// ever reaches main or another agent: it is one developer's scratch. The gate already knows that an
|
||||
// EMPTY directory is not a failure ("a clean checkout has none"); a directory of LEFTOVERS is the
|
||||
// same fact wearing different clothes.
|
||||
//
|
||||
// So a packet older than the renderer that produces it is SKIPPED, loudly, rather than failed. The
|
||||
// renderer's own mtime is the reference because that is what decides the shape a packet should have
|
||||
// — comparing against "today" would re-fail every morning, and comparing against nothing is what
|
||||
// produced this bug twice.
|
||||
const RENDERER = resolve(HERE, '..', 'library/packet/packet-render.js')
|
||||
const rendererMtime = existsSync(RENDERER) ? statSync(RENDERER).mtimeMs : 0
|
||||
const all = readdirSync(DIR).filter((f) => f.endsWith('.html')).sort()
|
||||
const stale = all.filter((f) => statSync(join(DIR, f)).mtimeMs < rendererMtime)
|
||||
const files = all.filter((f) => !stale.includes(f))
|
||||
if (stale.length) {
|
||||
console.log(`PACKETS-ALL: skipping ${stale.length} packet(s) older than the renderer — regenerate`)
|
||||
console.log(` them (ONLY=<id> node library/scripts/posters/packet.mjs) or delete them; this`)
|
||||
console.log(` directory is gitignored scratch, so a leftover is not a defect.`)
|
||||
}
|
||||
if (!files.length) {
|
||||
console.log(`PACKETS-ALL: SKIP — ${DIR} contains no .html packets`)
|
||||
console.log(`PACKETS-ALL: SKIP — ${DIR} contains no .html packets newer than the renderer`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
|
|
@ -67,9 +91,30 @@ if (!EXPECT_FUNNEL) {
|
|||
console.log('PACKETS-ALL: ⚠ PACKETS_ALL_NO_FUNNEL=1 — page 2 is NOT required this run.')
|
||||
}
|
||||
|
||||
// ⚠ THE UNCONDITIONAL ABOVE STOPPED BEING TRUE, and this is the correction. The comment says every
|
||||
// packet comes from packet.mjs "which always passes FUNNEL" — that held until SERIES landed
|
||||
// (2026-09-28). A `country` packet DROPS the funnel page deliberately: the funnel narrows every
|
||||
// people group on earth down to THIS UPG-100 list, which is not a description of a country, and a
|
||||
// page of true numbers about the wrong thing is worse than no page.
|
||||
// So the expectation follows the SERIES that produced the packet, read from series.json rather than
|
||||
// hardcoded here — adding a series with funnel:false must not require editing this gate.
|
||||
// A packet whose series cannot be determined keeps the old unconditional expectation, because the
|
||||
// safe default for "I do not know" is the stricter check.
|
||||
const SERIES_PATH = resolve(HERE, '..', 'library/scripts/posters/series.json')
|
||||
const SERIES_DEFS = existsSync(SERIES_PATH) ? JSON.parse(readFileSync(SERIES_PATH, 'utf8')) : {}
|
||||
const NO_FUNNEL_MARKERS = Object.entries(SERIES_DEFS)
|
||||
.filter(([id, def]) => !id.startsWith('_') && def && def.funnel === false)
|
||||
.flatMap(([, def]) => (def.networks || []).map(([head]) => head).filter(Boolean))
|
||||
/** True when this packet's own text identifies it as a series that has no funnel page. */
|
||||
function expectsFunnel(html) {
|
||||
if (!NO_FUNNEL_MARKERS.length) return EXPECT_FUNNEL
|
||||
return !NO_FUNNEL_MARKERS.some((m) => html.includes(m)) && EXPECT_FUNNEL
|
||||
}
|
||||
|
||||
const failed = []
|
||||
for (const f of files) {
|
||||
const args = EXPECT_FUNNEL ? [gate, join(DIR, f), '--expect-funnel'] : [gate, join(DIR, f)]
|
||||
const wantFunnel = expectsFunnel(readFileSync(join(DIR, f), 'utf8'))
|
||||
const args = wantFunnel ? [gate, join(DIR, f), '--expect-funnel'] : [gate, join(DIR, f)]
|
||||
const res = spawnSync(process.execPath, args, { encoding: 'utf8' })
|
||||
const ok = res.status === 0
|
||||
console.log(` ${ok ? '✓' : '✗'} ${f}`)
|
||||
|
|
|
|||
Loading…
Reference in a new issue