test(baseline): recapture the three HTML baselines that held invented people

Commit A of the baseline remediation. The Owliver baseline is deliberately
untouched and is Commit B.

Five of the six failing checks were the `DEMO_FILL` removal. `hiringRecords`
used to pad the hires list with five invented people so Hired History read
as a history rather than as three rows, and the padding reached Analytics
too, where "total hires" counted eight against a database holding three.
Because these baselines render with queries disabled, the padding was ALL
they contained. The sixth is Candidates becoming the final-selection queue,
which changed what its empty state says.

Verified before regenerating rather than after. Hired History's tag counts,
baseline -> now: `<td` 40->0, `<span` 51->13, `<div` 78->39, `<p` 26->7,
`<svg` 20->8. Analytics lost 269 class attributes and 238 words to the same
cause; Candidates gained exactly one class, the description paragraph under
"Nobody is awaiting a decision". Every generated file was checked for the
five invented names (none), for the intended new copy (present) and for its
node identities before it was put in place.

Renamed, because a file called `pre-migration` holding post-`dca1842`
markup is a lie in the filename. `activity-page`, `candidates-analysis`,
`control-center`, `positions` and `talent-pool` still hold genuine
pre-migration markup, still pass, and keep the name that says so — so the
`PAGES` table now carries each baseline's FILENAME rather than deriving one
suffix for all five.

The migration proof was replaced, not discarded. `Hired History added only
identity wrappers` compared tag tallies to assert that migrating the page
added exactly two `<div>`s and changed nothing else. That was true, and it
was checkable only while the DATA was frozen as well: the predicate
subtracts one render from another, so removing the invented hires moved
every count and the arithmetic stopped describing wrappers. Recapturing
would not have rescued it — with the baseline equal to the render the delta
is zero and a predicate demanding two can never hold — so left in place it
would have stayed red for a new reason. Three checks read the render
directly instead and need no frozen file:

    Hired History wraps exactly the node types registered to wrap
    Hired History identities are unique and name composed nodes
    Hired History identity wrappers carry no styling

They say what the tally said: `UiTreeRenderer` encloses a type registered
`wrap: true` in `<div {...attrs}>`, every other type takes the attributes on
its own root element, and a wrapper carries identity and no styling.

Writing them first, against the OLD baseline, is what caught my own error:
the second check began as "one identity per composed node" and failed at
4 of 5. Hired History composes five nodes and `hired-extensions-top` is an
extension slot that renders nothing while no skill is attached to it, so a
one-to-one rule would have asserted that every slot is always filled. It
asserts uniqueness and no strays instead — a node addressed twice, or an
identity naming nothing the page composed, would break the editor's and
Owliver's ability to name a node. Had the baselines been recaptured first,
that mistake would have been invisible.

`carries node identity in the DOM` was documented as needing pre-migration
markup. It does not — it reads only the live render — so it is unchanged and
still passes. The dead `tally` helper is removed with its last caller.

  typecheck   0 errors
  lint        exit 0, 0 errors, 289 warnings
  npm test    1693/1693, exit 0 — was 1685/1691
  build       exit 0, bundle 74d17e2d… unchanged
  owliver     still DRIFTED — that is Commit B, untouched here

No file under `src/` changed, which is why the bundle hash cannot move.
`owliver-baseline.json` is byte-identical, the backend is untouched, and CI
configuration is unchanged. The suite total rises 1691 -> 1693 because one
assertion became three; CI asserts `passed == total` and a floor of 900,
both satisfied.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
This commit is contained in:
2026-09-20 00:52:53 +05:30
parent 3ddacf269a
commit 878a23532a
8 changed files with 136 additions and 63 deletions

View File

@@ -5,33 +5,20 @@ agent layer existed. `skill-check.mjs` asserts against it on every run.
Regenerating it is a deliberate act, and the reason belongs here.
## OUTSTANDING — the HTML baselines contain data that no longer exists
## 2026-09-20 — the three HTML baselines that held invented people were recaptured
**Six checks fail on purpose. Do not regenerate these baselines to clear them.**
Hired History paints the same styled elements, in the same order
Hired History shows the same words
Hired History added only identity wrappers
analytics: paints the same styled elements, in the same order
analytics: shows the same words
candidates: shows the same words
The first five are the `DEMO_FILL` removal, described immediately below. The
sixth is the final-selection queue and has its own dated entry further down.
Settled. The section that stood here said six checks failed on purpose and
listed the evidence; this is that debt being paid.
`hiringRecords.js` used to pad the hires list with five invented people
(`DEMO_FILL`) so Hired History read as a history rather than as three rows. The
padding applied to Analytics too, so "total hires" counted eight where the
database held three. It has been removed: hires now come only from `staff`.
padding reached Analytics too, where "total hires" counted eight against a
database holding three. It was removed, and because these baselines render with
queries disabled, the padding was *all* they contained. Candidates separately
became the final-selection queue, which changed what its empty state says.
`hired-history.pre-migration.html` and `analytics.pre-migration.html` were
captured **while the padding was in effect** — and because these baselines render
with queries disabled, the padding is *all* they contain. The Hired History
baseline is 31,453 characters holding all five invented names; the genuine empty
state is 11,947 and holds none.
**Verified before leaving them failing**, so the drift is known rather than
assumed. Tag counts, baseline → now:
Verified before regenerating, so the drift was known rather than assumed. Tag
counts for Hired History, baseline -> now:
<td 40 -> 0 five table rows of people who were never hired
<span 51 -> 13
@@ -39,25 +26,57 @@ assumed. Tag counts, baseline → now:
<p 26 -> 7
<svg 20 -> 8
Every difference is content that was fabricated. No styling, ordering or
structural rule changed.
Every difference was content that had been fabricated. Analytics lost 269 class
attributes and 238 words to the same cause. Candidates gained exactly one class
— the description paragraph under "Nobody is awaiting a decision". No styling
rule, ordering rule or structural rule changed on any of the three.
**Why they are not regenerated yet.** Two of them — `added only identity
wrappers` and `carries node identity in the DOM` — exist to prove the UI node
tree migration added exactly two `<div>`s and nothing else. That proof needs the
baseline to be *pre*-migration markup. Recapturing now would write *post*-
migration markup into a file named `pre-migration`, and the check would compare
the current render against itself and pass forever without proving anything.
**Recaptured and renamed**, because a file called `pre-migration` that holds
post-`dca1842` markup is a lie in the filename:
So the debt is held until the migration work lands, at which point both files
are recaptured together and this section is replaced by a dated entry saying so.
hired-history.pre-migration.html -> hired-history.render.html
analytics.pre-migration.html -> analytics.render.html
candidates.pre-migration.html -> candidates.render.html
`activity-page`, `candidates-analysis`, `control-center`, `positions` and
`talent-pool` still hold genuine pre-migration markup, still pass, and keep the
name that says so. The `PAGES` table in `skill-check.mjs` now carries each
baseline's FILENAME rather than deriving one suffix for all of them.
**The migration proof was not thrown away with the baseline.** One check —
`Hired History added only identity wrappers` — compared tag tallies to assert
that migrating the page added exactly two `<div>`s and changed nothing else.
That was true, and it was checkable only while the DATA was frozen as well: the
predicate subtracts one whole render from another, so removing the invented
hires moved every count and the arithmetic stopped describing wrappers at all.
Recapturing would not have rescued it — with the baseline equal to the render
the delta is zero, and a predicate demanding two can never hold again. Left in
place it would have stayed red for a new reason, which is worse than failing
for the old one.
It is replaced by three checks that read the render itself and need no frozen
file, so they keep holding as the page's content changes:
Hired History wraps exactly the node types registered to wrap
Hired History identities are unique and name composed nodes
Hired History identity wrappers carry no styling
Together these say what the tally said — `UiTreeRenderer` encloses a type
registered `wrap: true` in `<div {...attrs}>`, every other type takes the
attributes on its own root element, and a wrapper contributes identity and no
styling. Hired History composes five nodes; two are registered to wrap and
produce the two divs, two carry their identity on their own `<section>`, and
`hired-extensions-top` is an extension slot that renders nothing while no skill
is attached to it. The second check therefore asserts uniqueness and no strays
rather than one identity per composed node — a one-to-one rule would be
asserting that every extension slot is always filled.
`carries node identity in the DOM` was described here as needing pre-migration
markup. It does not: it reads only the live render, names `chronology` and
`records` directly, and is unaffected by any of this. It still passes.
`npm test` is 1693/1693.
**One related fix was made rather than deferred.** `HiredChronology` used to
`return null` with no hires, which took the `chronology` node identity out of the
DOM with it — so with an honest empty dataset the section could not be addressed
by Owliver or the layout editor at all, and the page said nothing where it most
needed to. It now renders an empty state inside the section, which the section
keeps. `carries node identity in the DOM` passes again because of it.
## 2026-08-27 — the seed gained the three statuses nothing exercised
@@ -156,8 +175,9 @@ The new stage filter options cost nothing here. `FilterSelect` is a Radix
`SelectValue` renders empty on the server — the baseline contains neither the
old option labels nor the new ones.
**Why it is not regenerated.** The same reason as the five above:
`candidates.pre-migration.html` is load-bearing for the UI node tree proof, and
recapturing it now would write post-migration markup into a file named
`pre-migration`. It is held until the migration work lands and all of these are
recaptured together.
**Regenerated on 2026-09-20**, with the other two — see the entry at the top of
this file. It was held until then because `candidates.pre-migration.html` was
load-bearing for the UI node tree proof, and recapturing it earlier would have
written post-migration markup into a file named `pre-migration`. The file is now
`candidates.render.html` and the proof it was holding up has been replaced by
three checks that read the render directly.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -8829,22 +8829,69 @@ console.log('\n── Candidates vs Talent Pool ──');
const classes = (html) => (html.match(/class="[^"]*"/g) || []).join('\n');
const words = (html) => html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
const tally = (html) => (html.match(/<\/?[a-z][a-z0-9-]*/gi) || [])
.map((t) => t.toLowerCase())
.reduce((acc, t) => ({ ...acc, [t]: (acc[t] || 0) + 1 }), {});
const hiredNow = stripControls(await draw('/src/pages/admin/HiredHistory.jsx', 'hired-history', '/admin/hired'));
const hiredBefore = readFileSync(join(ROOT, 'scripts/__baseline__/hired-history.pre-migration.html'), 'utf8');
const hiredBefore = readFileSync(join(ROOT, 'scripts/__baseline__/hired-history.render.html'), 'utf8');
record('Hired History paints the same styled elements, in the same order',
classes(hiredNow) === classes(hiredBefore));
record('Hired History shows the same words', words(hiredNow) === words(hiredBefore));
record('Hired History added only identity wrappers', (() => {
const b = tally(hiredBefore);
const n = tally(hiredNow);
const changed = [...new Set([...Object.keys(b), ...Object.keys(n)])].filter((t) => (b[t] || 0) !== (n[t] || 0));
return { pass: changed.join(',') === '<div,</div' && n['<div'] - b['<div'] === 2, detail: changed.join(', ') };
})().pass);
/**
* What the node tree adds to a page's markup, proved from the render itself.
*
* This replaces a tag tally against the captured baseline, which asserted
* that migrating Hired History added exactly two `<div>`s and changed
* nothing else. That was true, and it was only checkable while the page's
* DATA was frozen too: the comparison subtracts one whole render from
* another, so removing the invented hires moved every count and the
* arithmetic stopped describing the wrappers at all. Recapturing does not
* rescue it either — with the baseline equal to the render the tag delta is
* zero, and a predicate demanding a delta of two can never hold again.
*
* So the property is asserted directly instead of inferred from a
* subtraction. `UiTreeRenderer` gives a node its identity one of two ways:
* a type registered `wrap: true` is enclosed in `<div {...attrs}>`, and
* every other type takes the attributes on its own root element. The three
* checks below say exactly that, and they keep saying it as the page's
* content changes — which the tally could not.
*/
const hiredComposed = compMod.composePage('hired-history', ctx5).tree;
const hiredNodes = nodeMod5.walk(hiredComposed);
const hiredWrapped = hiredNodes.filter((n) => reg5.get(n.type)?.wrap === true);
/* The attribute list of every `<div>` that carries an identity. */
const hiredWrappers = [...hiredNow.matchAll(/<div([^>]*\bdata-ui-node="[^"]+"[^>]*)>/g)]
.map((m) => m[1]);
const hiredIds = [...hiredNow.matchAll(/data-ui-node="([^"]+)"/g)].map((m) => m[1]);
record('Hired History wraps exactly the node types registered to wrap',
hiredWrappers.length === hiredWrapped.length,
`${hiredWrappers.length} wrapper div(s) for ${hiredWrapped.length} wrapping type(s): `
+ `${hiredWrapped.map((n) => n.id).join(', ') || 'none'}`);
/* Unique, and every one a node the page actually composed.
NOT "one per composed node": an extension slot with no skills attached
renders nothing at all, which is correct and is why `hired-extensions-top`
has no identity here. Asserting a one-to-one mapping would be asserting
that every slot is always filled. What must hold is that nothing is
addressed twice and nothing is addressed that the page did not compose —
either would break the editor's and Owliver's ability to name a node. */
const hiredComposedIds = new Set(hiredNodes.map((n) => n.id));
const hiredDuplicated = hiredIds.filter((id, i) => hiredIds.indexOf(id) !== i);
const hiredStrays = hiredIds.filter((id) => !hiredComposedIds.has(id));
const hiredSilent = hiredNodes.filter((n) => !hiredIds.includes(n.id)).map((n) => `${n.id} (${n.type})`);
record('Hired History identities are unique and name composed nodes',
hiredDuplicated.length === 0 && hiredStrays.length === 0,
`${hiredIds.length} of ${hiredNodes.length} composed node(s) addressed`
+ `${hiredSilent.length ? `; rendering nothing: ${hiredSilent.join(', ')}` : ''}`
+ `${hiredDuplicated.length ? `; DUPLICATED: ${hiredDuplicated.join(', ')}` : ''}`
+ `${hiredStrays.length ? `; STRAY: ${hiredStrays.join(', ')}` : ''}`);
/* Identity, not presentation. A wrapper that picked up a class would be
styling the page from the tree, which is the one thing wrapping a node
must never do — it is why `nodeAttrs` emits `data-` attributes only. */
record('Hired History identity wrappers carry no styling',
hiredWrappers.every((attrs) => !/\bclass=/.test(attrs)),
hiredWrappers.map((a) => a.trim()).join(' | ') || 'no wrappers');
record('Hired History carries node identity in the DOM', (() => {
const ids = [...hiredNow.matchAll(/data-ui-node="([^"]+)"/g)].map((m) => m[1]);
return ids.includes('chronology') && ids.includes('records');
@@ -9073,11 +9120,17 @@ console.log('\n── Candidates vs Talent Pool ──');
const words = (h) => h.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
const PAGES = [
['candidates', '/src/pages/admin/Candidates.jsx', '/admin/candidates', 'candidates'],
['talent-pool', '/src/pages/admin/TalentPool.jsx', '/admin/talent-pool', 'talent-pool'],
['analytics', '/src/pages/admin/Analytics.jsx', '/admin/analytics', 'analytics'],
['candidates-analysis', '/src/pages/admin/CandidatesAnalysis.jsx', '/admin/candidates-analysis', 'candidates-analysis'],
['control-center', '/src/pages/admin/ControlCenter.jsx', '/admin', 'control-center'],
/* The fourth column is the baseline FILE, not a stem. Three pages were
recaptured after the final-selection work changed what they render and
are named `.render.html`; the other two still hold genuine
pre-migration markup and keep the name that says so. Deriving the
filename from the page would force both sets to share one suffix, and
whichever suffix won would be a lie about half of them. */
['candidates', '/src/pages/admin/Candidates.jsx', '/admin/candidates', 'candidates.render.html'],
['talent-pool', '/src/pages/admin/TalentPool.jsx', '/admin/talent-pool', 'talent-pool.pre-migration.html'],
['analytics', '/src/pages/admin/Analytics.jsx', '/admin/analytics', 'analytics.render.html'],
['candidates-analysis', '/src/pages/admin/CandidatesAnalysis.jsx', '/admin/candidates-analysis', 'candidates-analysis.pre-migration.html'],
['control-center', '/src/pages/admin/ControlCenter.jsx', '/admin', 'control-center.pre-migration.html'],
];
for (const [page, modulePath, route, baseline] of PAGES) {
@@ -9089,7 +9142,7 @@ console.log('\n── Candidates vs Talent Pool ──');
React.createElement(UiEditingProvider, { page },
React.createElement(Page))))
));
const before = readFileSync(join(ROOT, `scripts/__baseline__/${baseline}.pre-migration.html`), 'utf8');
const before = readFileSync(join(ROOT, `scripts/__baseline__/${baseline}`), 'utf8');
/**
* A migration may INSERT a wrapper class — grouping three siblings under