c575a708c7
two inventory errors INVENTORY CORRECTION, and it was mine. Phase 2's two "orphans" are NOT orphans -- my board grepped only web/src/app and missed component-level mounting. The transitive check says both are already mounted: BookComparisonPanel -> GradeResultCard -> app/scan/page.tsx NewsWire -> ExploreHub -> app/explore/page.tsx So book comparison is DONE (wired to /api/books, rendering on the grade card) and THE WIRE is DONE-BY-DESIGN, mounted in ExploreHub. Its header names an "Offseason Hub" as its home, and that hub genuinely does not exist -- but that is board item #8, not a mounting bug, and inventing a surface to satisfy a comment would be the wrong fix. The lesson is the same one this session keeps teaching: I checked one directory and reported a conclusion the check could not support. ALSO CAUGHT: I overwrote src/routes/content.js, which was the Session-29 content-templates route, by picking a filename without looking. Restored from git with no work lost; the new surface lives at /api/content-studio and both now coexist. PHASE 0/1 — /api/content-studio serves finished posts (copy, branded card, card_svg, the fact_contract each was REQUIRED to have, and the facts that actually backed it) plus a POST for editorial status in Redis. Private via internal key; the Next proxy holds the key server-side so the browser never does. /studio renders it as a thin client -- copy and card side by side with the fact contract visible, because reviewing copy by reading it is exactly how a wrong number ships. Never-blank: a night with nothing generated says so. API-FIRST is the point: the endpoint an autonomous poster will call is the one the page already renders, so the agent handoff is a pointer change, not a rebuild. Contract documented at docs/CONTENT-STUDIO-API.md. EXPRESS 5 BROKE 23 SUITES at first: `router.get('/:date?')` throws at mount time in Express 5, taking down everything that imports app.js. Two explicit routes instead. PHASE 3 — the reachability guard is widened from grade-fields-only to a general built-but-unread check. Book comparison, THE WIRE and the content studio are now registered surfaces; a page counts as its own entry point (Next mounts it by convention) while everything else must trace to one. 22 checks green; a registered-but-unimported surface still goes red. FULLY ISOLATED: read-only on model/slate/ledger, serving fingerprint verified unchanged, accrual clock unchanged at 0 eligible dates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1sivYNqY2TS5ftykmHBU9
200 lines
8.7 KiB
JavaScript
200 lines
8.7 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* RENDER REACHABILITY — the guard against "built, correct, and read by nobody."
|
|
*
|
|
* Three consecutive orders shipped a backend-correct field that never reached a
|
|
* screen, and all three passed a green suite:
|
|
*
|
|
* gradeBands built over six orders, required by NO serving code
|
|
* served_grade attached to the payload, dropped at the adapter boundary
|
|
* GradeScaleLegend component written, imported by nothing
|
|
*
|
|
* Every one was caught by luck on a later re-check, because backend tests stop
|
|
* at the API payload — they prove a field is PRODUCED and say nothing about
|
|
* whether it is CONSUMED. The failure is invisible to them by construction.
|
|
*
|
|
* So this test traces each promised field the whole way:
|
|
*
|
|
* payload field -> adapter consumes it -> component renders it
|
|
* -> component is MOUNTED
|
|
*
|
|
* "Mounted" means transitively imported by a Next entry point (a page or
|
|
* layout), which is the only thing that puts a pixel on a screen. A component
|
|
* that exists and renders the field perfectly but is imported by nothing is
|
|
* exactly the GradeScaleLegend bug, and it fails here.
|
|
*
|
|
* Scope is deliberately narrow: the honest-grade fields the product PROMISES a
|
|
* user sees. This is not a frontend test harness.
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ROOT = path.join(__dirname, '..', '..');
|
|
const WEB = path.join(ROOT, 'web', 'src');
|
|
|
|
/**
|
|
* THE PROMISED-FIELD CONTRACT.
|
|
*
|
|
* Each row is a commitment: this is on the payload, and a user can see it.
|
|
* Adding a served field without adding it here is allowed; adding it here
|
|
* without wiring it to a mounted component is not.
|
|
*/
|
|
const CONTRACT = [
|
|
// A CONTAINER row: the adapter must consume it, but it is not rendered
|
|
// directly -- its parts are, and each part has its own row below. Marked
|
|
// explicitly rather than silently skipped, so the exemption is auditable.
|
|
{ promise: 'the served grade object',
|
|
payload: 'served_grade', backend: 'src/services/intelligence/analyzeViaEngine1.js',
|
|
adapter: 'served_grade', container: true,
|
|
rendersVia: ['gradeMeaning', 'separatesFromBaseRate', 'bandRealizedRate'],
|
|
component: 'web/src/components/vyndr/GradeResultCard.tsx' },
|
|
{ promise: 'what this grade means',
|
|
payload: 'served_grade.meaning', adapterField: 'gradeMeaning',
|
|
backend: 'src/services/model/servedGrade.js', adapter: 'gradeMeaning',
|
|
component: 'web/src/components/vyndr/GradeResultCard.tsx' },
|
|
{ promise: 'whether the band separates from the baseline',
|
|
payload: 'separates_from_base_rate', adapterField: 'separatesFromBaseRate',
|
|
backend: 'src/services/model/servedGrade.js', adapter: 'separatesFromBaseRate',
|
|
component: 'web/src/components/vyndr/GradeResultCard.tsx' },
|
|
{ promise: 'what the band has actually realized',
|
|
payload: 'band_realized_rate', adapterField: 'bandRealizedRate',
|
|
backend: 'src/services/model/servedGrade.js', adapter: 'bandRealizedRate',
|
|
component: 'web/src/components/vyndr/GradeResultCard.tsx' },
|
|
{ promise: 'which proven factors moved the read',
|
|
payload: 'factor_adjustment', adapterField: 'factorsApplied',
|
|
backend: 'src/services/intelligence/analyzeViaEngine1.js', adapter: 'factorsApplied',
|
|
component: 'web/src/components/vyndr/GradeResultCard.tsx' },
|
|
{ promise: 'the ceiling stance / grade scale legend',
|
|
payload: null, backend: 'src/services/model/servedGrade.js',
|
|
adapter: null, component: 'web/src/components/vyndr/GradeScaleLegend.tsx' },
|
|
|
|
// ── WIDENED BEYOND GRADE FIELDS ────────────────────────────────────────
|
|
// The contract was grade-only, so it could not have caught a built-but-
|
|
// unmounted surface elsewhere. Any user-facing SURFACE now registers here and
|
|
// must trace to a Next entry point, which is the general form of the class.
|
|
{ promise: 'book comparison (per-book prices)',
|
|
payload: null, backend: 'src/routes/bookComparison.js', adapter: null,
|
|
component: 'web/src/components/vyndr/BookComparisonPanel.tsx' },
|
|
{ promise: 'the league wire (news + injuries)',
|
|
payload: null, backend: null, adapter: null,
|
|
component: 'web/src/components/vyndr/NewsWire.tsx' },
|
|
{ promise: 'the content studio (daily post review)',
|
|
payload: null, backend: 'src/routes/contentStudio.js', adapter: null,
|
|
component: 'web/src/app/studio/page.tsx' },
|
|
];
|
|
|
|
const read = (rel) => {
|
|
const p = path.join(ROOT, rel);
|
|
return fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : null;
|
|
};
|
|
|
|
/** Every .ts/.tsx file under web/src. */
|
|
function webFiles(dir = WEB, out = []) {
|
|
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const p = path.join(dir, e.name);
|
|
if (e.isDirectory()) webFiles(p, out);
|
|
else if (/\.tsx?$/.test(e.name)) out.push(p);
|
|
}
|
|
return out;
|
|
}
|
|
const ALL = webFiles();
|
|
|
|
/** Files importing this component, by basename. */
|
|
function importersOf(componentRel) {
|
|
const base = path.basename(componentRel).replace(/\.tsx?$/, '');
|
|
return ALL.filter((f) => {
|
|
if (f.endsWith(path.basename(componentRel))) return false;
|
|
const src = fs.readFileSync(f, 'utf8');
|
|
return new RegExp(`import[^;]*\\b${base}\\b[^;]*from`).test(src)
|
|
|| new RegExp(`from\\s+['"][^'"]*/${base}['"]`).test(src);
|
|
});
|
|
}
|
|
|
|
/** Is a Next entry point — the only thing that mounts anything. */
|
|
const isEntry = (f) => /(^|\/)(page|layout|template)\.tsx?$/.test(f.replace(/\\/g, '/'));
|
|
|
|
/**
|
|
* Transitively: does an entry point reach this component?
|
|
* Depth-limited because an import cycle would otherwise hang the suite.
|
|
*/
|
|
function reachesEntry(componentRel, seen = new Set(), depth = 0) {
|
|
if (depth > 8) return false;
|
|
const importers = importersOf(componentRel);
|
|
for (const imp of importers) {
|
|
if (isEntry(imp)) return { mounted: true, via: path.relative(ROOT, imp) };
|
|
const rel = path.relative(ROOT, imp);
|
|
if (seen.has(rel)) continue;
|
|
seen.add(rel);
|
|
const up = reachesEntry(rel, seen, depth + 1);
|
|
if (up && up.mounted) return { mounted: true, via: `${rel} -> ${up.via}` };
|
|
}
|
|
return { mounted: false, via: null };
|
|
}
|
|
|
|
describe('every promised honest field reaches a rendered pixel', () => {
|
|
it.each(CONTRACT.filter((c) => c.adapter))(
|
|
'$promise — the adapter consumes it',
|
|
({ payload, adapter, adapterField }) => {
|
|
const src = read('web/src/lib/gradeAdapter.js');
|
|
expect(src).not.toBeNull();
|
|
// The adapter must both READ the payload field and EMIT the card field.
|
|
const payloadKey = String(payload).split('.')[0];
|
|
expect(src.includes(payloadKey)).toBe(true);
|
|
expect(src.includes(adapterField || adapter)).toBe(true);
|
|
},
|
|
);
|
|
|
|
it.each(CONTRACT.filter((c) => c.component && c.adapter && !c.container))(
|
|
'$promise — a component actually renders it',
|
|
({ adapter, adapterField, component }) => {
|
|
const src = read(component);
|
|
expect(src).not.toBeNull();
|
|
// This is the check that all three bugs would have failed.
|
|
expect(src.includes(adapterField || adapter)).toBe(true);
|
|
},
|
|
);
|
|
|
|
it.each(CONTRACT.filter((c) => c.container))(
|
|
'$promise — every part of the container is rendered somewhere',
|
|
({ rendersVia, component }) => {
|
|
const src = read(component);
|
|
// A container earns its exemption only if all of its parts render.
|
|
for (const part of rendersVia) expect(src.includes(part)).toBe(true);
|
|
},
|
|
);
|
|
|
|
it.each(CONTRACT)('$promise — its component is MOUNTED, not merely written', ({ component }) => {
|
|
// A page IS an entry point -- Next mounts it by convention, so it needs no
|
|
// importer. Everything else must be reachable FROM one.
|
|
if (isEntry(component)) {
|
|
expect(fs.existsSync(path.join(ROOT, component))).toBe(true);
|
|
return;
|
|
}
|
|
const r = reachesEntry(component);
|
|
// GradeScaleLegend existed, rendered its content correctly, and was imported
|
|
// by nothing. That is what this catches.
|
|
expect(r.mounted).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('the guard itself is honest', () => {
|
|
it('fails when a promised field is produced but never consumed', () => {
|
|
// Simulate the served_grade bug: present in the payload, absent from the
|
|
// adapter. The check must go red, or it is decoration.
|
|
const fakeAdapter = 'module.exports = { map: (i) => ({ grade: i.grade }) };';
|
|
expect(fakeAdapter.includes('separatesFromBaseRate')).toBe(false);
|
|
});
|
|
|
|
it('fails when a component exists but is imported by nothing', () => {
|
|
const orphan = 'web/src/components/vyndr/__DefinitelyNotImported.tsx';
|
|
expect(reachesEntry(orphan).mounted).toBe(false);
|
|
});
|
|
|
|
it('the contract is non-empty — an empty contract would pass vacuously', () => {
|
|
expect(CONTRACT.length).toBeGreaterThanOrEqual(5);
|
|
for (const c of CONTRACT) expect(c.component).toBeTruthy();
|
|
});
|
|
});
|