From 3ddacf269ad02493194217d709d7259d4f650e5f Mon Sep 17 00:00:00 2001 From: Aravind Date: Sat, 19 Sep 2026 23:54:36 +0530 Subject: [PATCH] fix(seed): emit the users array the backend seeder reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fixture generator was a version behind the seeder, and the gap was the `users` key. `internal/seeder` unmarshals `seed.json` into `{demoUser, users, entities}` and writes every account in `users`, falling back to `demoUser` alone when the key is absent. `fixtureFrom` never emitted it — its comment still claimed the Go loader "ignores the rest", which stopped being true when the seeder gained the field. So `npm run seed:check` reported stale on every run, and that is the seventh suite failure, the one that pre-dates the TypeScript migration. What made this worth investigating rather than regenerating: the committed fixture was NOT wrong. It carries two accounts — the demo administrator and `employer@krow.app` — and running `npm run seed:fixture` against the old generator would have written a fixture without them. `krow-backend/docs/deploy-9d3192a.md` says so in as many words, and tells anyone deploying not to run it. The accounts are not deleted from a live database by that — the only DELETE in the seeder is the shift-record prune, and `upsertUser`'s ON CONFLICT deliberately leaves `password_hash` alone. The damage lands on the next fresh environment, where `user_employer` would simply never be created, leaving nobody to sign in as to reach the employer console — which is the exact gap `seeder.go` records the account as having been added to close. The matching frontend half already existed, unmerged, on `feat/krow-employee-pages` (7a4b61d). That branch is a 53-file console restructure that renames the same files this migration renamed, so it is not mergeable here. Only the three files that carry the contract are taken: `EMPLOYER_USER`, `seedData.User` naming both accounts, and the generator emitting `users`. The proof is that nothing had to be written. With the generator corrected its output is byte-identical to the fixture already committed in krow-backend — 168,293 bytes, `cmp` clean — so `seed.json` was never opened for writing, and its md5 and mtime are untouched. typecheck 0 errors seed:check "seed.json is in step with src/api/seed.js", exit 0 npm test 1685/1691, up from 1684 — the fixture check now reads "24 applications, byte-identical". The remaining 6 are exactly the documented dca1842 baseline failures. lint exit 0, 0 errors, 289 warnings build exit 0, bundle 74d17e2d… unchanged The bundle being unchanged is itself a check: `demoUser.ts` IS in the production bundle, and `EMPLOYER_USER` is a new export of it. Nothing in the app imports it, so it is tree-shaken out and not one byte reaches the shipped code. No baseline artifact touched. krow-backend not modified. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 --- scripts/seed-fixture.mjs | 29 +++++++++++++++++++++++++---- src/api/demoUser.ts | 26 ++++++++++++++++++++++++++ src/api/seed.ts | 10 +++++++--- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/scripts/seed-fixture.mjs b/scripts/seed-fixture.mjs index 056f12e..4d9144c 100644 --- a/scripts/seed-fixture.mjs +++ b/scripts/seed-fixture.mjs @@ -31,10 +31,31 @@ export const GENERATED_NOTE = export function fixtureFrom(seedModule) { const { ShiftRecord, ...entities } = seedModule.seedData; - /* First key, so it is the first thing anyone opening the file reads. The Go - loader unmarshals into a struct of demoUser + entities and ignores the - rest, so this costs nothing on the reading side. */ - return { _generated: GENERATED_NOTE, demoUser: seedModule.DEMO_USER, entities }; + /* `_generated` first, so it is the first thing anyone opening the file reads. + + `users` is the channel the seeder actually reads — `internal/seeder` + unmarshals into `{demoUser, users, entities}` and writes every account in + `users`, falling back to `demoUser` alone when the key is absent. That + fallback is why `demoUser` stays beside it rather than being replaced: a + fixture written here still seeds correctly against a backend that predates + the list, it simply seeds one account instead of two. + + Emitting only `demoUser` is what this generator used to do, and it was a + version behind: the fixture on disk carries the employer account, the + seeder reads it from `users`, and regenerating without this key would drop + `employer@krow.app` from every fresh seed — leaving nobody to sign in as + to reach the employer console, which is the exact gap the account was + added to close. + + `entities.User` carries the same list. The seeder ignores it, and it is + emitted because `seedData` is copied wholesale; the two are the same array + rather than two places to keep in step. */ + return { + _generated: GENERATED_NOTE, + demoUser: seedModule.DEMO_USER, + users: seedModule.seedData.User, + entities, + }; } /** Serialised exactly as the committed file is: 2-space indent, no trailing newline. */ diff --git a/src/api/demoUser.ts b/src/api/demoUser.ts index 8721818..a5e9846 100644 --- a/src/api/demoUser.ts +++ b/src/api/demoUser.ts @@ -39,3 +39,29 @@ export const DEMO_USER: User = { emailDigest: true, }, }; + +/** + * The employer account, and the reason there is a second one at all. + * + * `policy.go` has always had three roles — admin, employer, talent — and the + * fixture had one administrator, so two thirds of the authorization table was + * never exercised and there was nobody to sign in as to reach the employer + * console. This account is what makes that path reachable. + * + * Unlike `DEMO_USER` this is not a default shape for the first render: nothing + * in the running app reads it. It exists so that `seedData.User` names both + * accounts, which is what the backend seeder writes. See `seed.ts`. + */ +export const EMPLOYER_USER: User = { + id: 'user_employer', + full_name: 'Jordan Blake', + email: 'employer@krow.app', + role: 'employer', + account_type: 'employer', + created_date: '2026-06-01T09:00:00.000Z', + preferences: { + owliverDefault: true, + compactDensity: false, + emailDigest: true, + }, +}; diff --git a/src/api/seed.ts b/src/api/seed.ts index 736d3a7..f2fc442 100644 --- a/src/api/seed.ts +++ b/src/api/seed.ts @@ -9,7 +9,7 @@ */ import { SHIFT_RECORDS } from './attendanceSeed'; -import { DEMO_USER } from './demoUser'; +import { DEMO_USER, EMPLOYER_USER } from './demoUser'; const iso = (date: string) => new Date(`${date}T09:00:00.000Z`).toISOString(); @@ -1990,8 +1990,12 @@ const USER_ACTIVITY = ACTIVITY_EVENTS.map(([event_type, user_email, user_name, a * candidate, interview and shift record below — into the production bundle. * The fixtures here are for the test scripts, and they still read * `seedData.User` and `DEMO_USER` from this module, so both keep working. + * + * `EMPLOYER_USER` travels the same way. It is seeded, never rendered: the + * backend writes both accounts so the employer console has somebody to sign + * in as, and nothing in the running app imports it. */ -export { DEMO_USER } from './demoUser'; +export { DEMO_USER, EMPLOYER_USER } from './demoUser'; /* ── Assignments ─────────────────────────────────────────────────────────── Who is on which position, and until when — the record that turns a hire into @@ -2051,5 +2055,5 @@ export const seedData = { Assignment: ASSIGNMENTS, ShiftRecord: SHIFT_RECORDS, Evidence: EVIDENCE, - User: [DEMO_USER], + User: [DEMO_USER, EMPLOYER_USER], };