Authored in a parallel session alongside the TypeScript migration; committed separately so the two never share a commit. No TypeScript migration file is included here. Candidates becomes the queue of hiring decisions waiting on a person, rather than a second Talent Pool listing every application the org ever took. Final selection is DERIVED - there is no `final_selection` value in the `application_status` enum and none is added. The fact it reads is the existence of an interview row, a NOT NULL foreign key, rather than `job_applications.interview_id`, which the schema keeps as an unconstrained soft reference precisely so it may dangle. `status = 'interview'` is set both when an interview is arranged and when one is completed, so status alone cannot tell a queue of people who have been interviewed from a queue of people merely booked in. Seats on a position are counted from the employment records instead of a stored column. A `filled` counter would be a second source of truth, and the day it disagreed with `staff` nothing could say which was lying. Someone who has left frees their seat, and over-hiring floors at zero rather than going negative. `DEMO_FILL` is gone. `hiringRecords.js` padded the hires list with five invented people so Hired History read as a history rather than as three rows; the padding reached Analytics too, where "total hires" counted eight against a database holding three. Hires now come only from `staff`. Both paths that file an application on somebody's behalf now carry `worker_profile_id`, the link back to the talent-pool record. The column is nullable, so omitting it saved cleanly and failed silently: the application belonged to an email address rather than to a person, and the hire it became could not be traced back to the profile it came from. `HiredChronology` used to `return null` with no hires, taking the `chronology` node identity out of the DOM with it - so on an honest empty dataset the section could not be addressed by Owliver or the layout editor at all. It now renders an empty state inside the section it keeps. Nine new checks cover the above; `npm test` reports 1684/1691. SIX SSR PARITY CHECKS FAIL ON PURPOSE, and `scripts/__baseline__/README.md` documents each with verified tag counts. Five are the `DEMO_FILL` removal: the Hired History and Analytics baselines were captured while the padding was in effect and, because they render with queries disabled, the padding is all they contain. The sixth is this change to what Candidates says. Do not regenerate those baselines to clear them - two of the checks exist to prove the UI node tree migration added exactly two `<div>`s, and 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 forever. The debt is held until the migration work lands, when both files are recaptured together. The seventh failure, the stale backend seed fixture, predates all of this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
164 lines
8.1 KiB
Markdown
164 lines
8.1 KiB
Markdown
# Owliver behaviour baseline
|
|
|
|
`owliver-baseline.json` records how the panel routed and answered before the
|
|
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
|
|
|
|
**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.
|
|
|
|
`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`.
|
|
|
|
`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:
|
|
|
|
<td 40 -> 0 five table rows of people who were never hired
|
|
<span 51 -> 13
|
|
<div 78 -> 39
|
|
<p 26 -> 7
|
|
<svg 20 -> 8
|
|
|
|
Every difference is content that was fabricated. No styling, ordering or
|
|
structural rule changed.
|
|
|
|
**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.
|
|
|
|
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.
|
|
|
|
**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
|
|
|
|
`application_status` has seven values. The fixture produced four: `applied`,
|
|
`ai_screened`, `interview`, `hired`. `shortlisted`, `rejected` and `assigned`
|
|
existed only in the schema, and `Assignment` shipped empty by design.
|
|
|
|
That gap was hiding real defects, all found the same week and none visible
|
|
against the old data:
|
|
|
|
- `atOrBeyond` ranks a status by its index in `STAGE_ORDER`, which listed five
|
|
of the seven. `rejected` and `assigned` scored -1 and dropped out of *every*
|
|
bucket including `applied`, so a position's funnel lost people and a role
|
|
whose candidates had all been assigned read as unfilled.
|
|
- The agent's `candidates_awaiting` excluded `hired` and `rejected` from "still
|
|
in the running", so somebody already working a shift was offered as a person
|
|
to chase.
|
|
- `insights.stalled` counted "screened and waiting on a decision" as
|
|
`status === 'ai_screened'` only, silently dropping the shortlisted — the
|
|
people that phrase most describes. Caught by this regeneration, not before it.
|
|
|
|
**The change:** three existing applications were converted, not added, so every
|
|
regression anchor holds — 24 applications, 9 scored averaging 76, 3 staff,
|
|
8 postings, all unchanged.
|
|
|
|
app_kevin applied → rejected (never scored; a hard requirement)
|
|
app_sofia ai_screened → shortlisted (the strongest screened candidate)
|
|
app_marco hired → assigned (hired, then rostered)
|
|
|
|
Plus one `Assignment` for Marco — the minimum that makes `assigned` real. Eight
|
|
of the nine positions still have none, so every reader still meets the empty
|
|
case.
|
|
|
|
**What drifted, verified before regenerating:** one number, in four prompts.
|
|
Kevin left `applied`, so the unscored count reads 9 where it read 10:
|
|
|
|
"Show the 10 applications waiting for review" → "…9 applications…"
|
|
"10 have no score" / "10 unscored applicants" / "10 unscored applications"
|
|
|
|
Nothing else moved. `1 waiting on a decision` briefly became a fallback prompt
|
|
and that was the `stalled` defect above — fixed in `insights.js` rather than
|
|
absorbed into the baseline, and the prompt came back on its own.
|
|
|
|
## 2026-08-27 — the local simulator was removed
|
|
|
|
The panel used to answer from two places: an agent, and ~3,300 lines of
|
|
browser-side templates. Two paths behind one avatar meant the same question got
|
|
different answers depending on phrasing, so the templates were deleted.
|
|
|
|
**What actually drifted, verified before regenerating:** one thing. The
|
|
"I do not have that on this page" decline used to list the page's capability
|
|
labels as bullets — those labels were the simulator's canned readings and went
|
|
with it. It now names the page's topics in a sentence.
|
|
|
|
doc shape: ["text","list","note"] → ["text","text","note"]
|
|
|
|
Every intent `kind` was unchanged. No routing moved, no skill matching changed.
|
|
That is why this regeneration was safe: the diff was read first, and it was one
|
|
cosmetic change on a path that only runs when no agent is configured at all.
|
|
|
|
## 2026-09-11 — Candidates became the final-selection queue
|
|
|
|
`candidates: shows the same words` now fails, and it is the only check this
|
|
change breaks.
|
|
|
|
The page used to list every application the org had ever taken — all seven
|
|
statuses at once — which made it a second Talent Pool rather than the queue of
|
|
hiring decisions waiting on a human. It now opens on final selection: interview
|
|
completed, not hired, not rejected. That is a change to what the page *says*, so
|
|
a check asserting the page says exactly what it said before was always going to
|
|
fail. There is no version of this work that leaves those words alone.
|
|
|
|
**What drifted, verified before leaving it failing.** The rendered delta is the
|
|
empty state and nothing else:
|
|
|
|
before "…0 of 0 candidates No candidates match your filters"
|
|
now "…0 of 0 candidates Nobody is awaiting a decision
|
|
Candidates arrive here once their interview is completed,
|
|
and leave once they are hired or declined."
|
|
|
|
Title, subtitle and toolbar meta are byte-identical. The old copy was not merely
|
|
different, it was untrue: with no filters applied there is nothing to clear, and
|
|
"no candidates match your filters" describes a filter that was never set.
|
|
|
|
Tag and class counts, baseline → now:
|
|
|
|
class="…" 26 -> 27 one inserted: the description paragraph
|
|
|
|
One insertion, nothing changed and nothing dropped — which is why
|
|
`candidates: paints the same styled elements, in the same order` and
|
|
`candidates: carries node identity in the DOM` both still pass. Those two are
|
|
the migration proof; only the words moved.
|
|
|
|
The new stage filter options cost nothing here. `FilterSelect` is a Radix
|
|
`Select`, so its options live in a portal that is closed in static markup, and
|
|
`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.
|