Files
krow_talent_app/scripts/skill-check.mjs
Aravind 02a2ab05ef chore(ts-migration): resolve source-text reads across .js/.jsx/.ts/.tsx
The check suite consumes source files through two entirely separate channels,
and Phase 0 only hardened one of them.

`withSourceResolution` wraps `ssrLoadModule`, which covers the 159 module loads.
It does not and cannot see the other 30 sites, which read source as TEXT through
`readFileSync` to assert structural facts - "the panel imports no local
suggestion ranker", "no runtime path writes a definition". Renaming
`base44Client.js` to `.ts` is what surfaced the difference: ENOENT in the middle
of a suite that had been passing, from a line no grep for `ssrLoadModule` would
ever have found.

`resolveSourcePath()` in `ssr-resolve.mjs` reuses the existing `candidatesFor()`
ordering - the written path first, then `.ts`, then `.tsx` - and returns a path
relative to the root, so every call site keeps its `join(ROOT, ...)` as it was.
An unresolvable path comes back unchanged, which keeps the two absence
assertions honest: a check proving `store.js` is GONE still asks about the path
it means, and now also notices if the file returns under another extension.

Thirty-seven lines change, each a one-for-one replacement. No assertion text, no
record() message, no ordering, no logic.

The audit found the reads in four shapes, and two of them a path grep cannot
see:

  - direct       readFileSync(join(ROOT, 'src/x.jsx'), 'utf8')
  - via a const  const P = join(ROOT, 'src/x.jsx')
  - dir + name   ['node.js', 'patch.js'].map((f) => readFileSync(join(ROOT, 'src/lib/ui', f)))
  - path array   for (const f of files) readFileSync(join(ROOT, f))

The third and fourth hide thirteen filenames in adjacent arrays, which is why
the first estimate of this work was twenty-two files and the real number is
thirty-five.

One directory scan also filtered `/\.jsx?$/` over `src/pages/admin` and
`src/components/agents`. That one does not crash - it quietly matches nothing
once those directories are TypeScript, and the check passes having inspected an
empty set. Widened to `/\.[jt]sx?$/`. A silent shrink is worse than a failure,
and CI's FLOOR of 900 would not have caught it.

Deliberately NOT touched, because they are correctly extension-specific: the
`dist/assets` filter reads built bundles, which are `.js` whatever the source
was; `scripts/stubs/react-hot-toast.js` and `scripts/browser-flows.js` are
scripts, not migrated source; and every comment that mentions a `.js` filename
says something true about a file that still has that name.

Proven rather than assumed. `npm test` reports 1684/1691 before and after, the
same seven failures. Then `src/api/base44Client.js` - the exact file whose
rename broke the suite - was renamed to `.ts`, the suite re-run, and the check
that reads it as text passed: "the app does not import the seed fixture". The
rename was reverted and the file verified byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
2026-09-17 22:56:45 +05:30

10867 lines
532 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Skill pipeline check — Owliver skills and Board (UI) skills.
*
* Runs the *real* module graph through Vite, so `import.meta.glob`, the `@/`
* alias and the Markdown loading all behave exactly as they do in the app. That
* matters: the reliability problem this script exists for was never in the
* Markdown, it was in what the pipeline did with a definition it could only
* partly read, and a mock of that pipeline would have reproduced none of it.
*
* node scripts/skill-check.mjs # against source, as the dev server sees it
* node scripts/skill-check.mjs --dist # also assert the built bundle carries every definition
*
* Exits non-zero on failure, so it can gate a build.
*/
import { readFileSync, readdirSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { createServer } from 'vite';
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { BASELINE_PATH, captureBaseline } from './owliver-capture.mjs';
import { buildFixture, FIXTURE_PATH } from './seed-fixture.mjs';
import { resolveSourcePath, withSourceResolution } from './ssr-resolve.mjs';
const ROOT = process.cwd();
const results = [];
const record = (name, pass, detail = '') => {
results.push({ name, pass, detail });
const mark = pass ? ' ok ' : ' FAIL ';
console.log(`[${mark}] ${name}${detail ? ` — ${detail}` : ''}`);
};
const server = await createServer({
root: ROOT,
server: { middlewareMode: true },
appType: 'custom',
logLevel: 'error',
/* `react-hot-toast` evaluates `goober`, which calls `document.createElement`
at import time — so loading it in Node takes the design-system barrel, and
with it the Owliver block renderers, down. Nothing here raises a toast, and
the stub is what lets the citation tests assert on RENDERED MARKUP rather
than on the block objects behind it. */
resolve: { alias: { 'react-hot-toast': join(ROOT, 'scripts/stubs/react-hot-toast.js') } },
});
/* Every `ssrLoadModule` path below names a file by its current extension. While
the TypeScript migration is under way that extension changes underneath them,
so the loader resolves .js/.jsx/.ts/.tsx rather than each call site having to
know which one a module is on today. The path as written is tried first. */
withSourceResolution(server, ROOT);
const reg = await server.ssrLoadModule('/src/lib/skills/registry.js');
const placement = await server.ssrLoadModule('/src/components/ai-assistant/placement.js');
const contextFor = (pageKey) => Object.entries(placement.PLACEMENT_ROUTES)
.find(([route]) => reg.pageKeyForRoute(route) === pageKey)?.[1] ?? null;
/* ── 1. Discovery ─────────────────────────────────────────────────────────── */
console.log('\n── Discovery ──');
const onDisk = readdirSync(join(ROOT, 'src/skills'))
.flatMap((dir) => readdirSync(join(ROOT, 'src/skills', dir)).map((f) => `${dir}/${f}`))
.filter((f) => f.endsWith('.md'));
record(
'every .md under src/skills registers',
reg.SKILLS.length === onDisk.length,
`${reg.SKILLS.length} registered / ${onDisk.length} files`
);
record(
'no duplicate skill ids',
new Set(reg.SKILLS.map((s) => s.id)).size === reg.SKILLS.length,
`${new Set(reg.SKILLS.map((s) => s.id)).size} unique ids`
);
record(
'every skill declares at least one page',
reg.SKILLS.every((s) => s.pages.length > 0)
);
/* Both managed lists are non-empty — the Workspace → Skills tabs. */
const uiFacet = reg.skillsWithFacet(reg.SKILLS, 'ui');
const owliverFacet = reg.skillsWithFacet(reg.SKILLS, 'owliver');
record('Owliver Skills list is populated', owliverFacet.length > 0, `${owliverFacet.length} skills`);
record(
'Board Skills list resolves without error',
Array.isArray(uiFacet),
`${uiFacet.length} skills declare a \`ui:\` block`
);
/* ── 2. Registration integrity: nothing half-loads in silence ─────────────── */
console.log('\n── Registration integrity ──');
const clean = reg.readSkillRegistry([]);
record(
'shipped registry reports no diagnostics',
clean.diagnostics.length === 0,
clean.diagnostics.map((d) => d.message).join(' | ') || 'none'
);
record(
'every shipped skill registered every capability it declared',
reg.SKILLS.every((s) => !(s.owliverErrors?.length || s.uiErrors?.length))
);
/* An unreadable stored definition must be REPORTED, not swallowed. */
const brokenYaml = `---\nid: broken\n name: bad indent\n---\n# Broken\n`;
const withBroken = reg.readSkillRegistry([{ path: 'custom/broken.md', raw: brokenYaml }]);
record(
'unreadable stored skill is reported, not silently dropped',
withBroken.diagnostics.some((d) => d.kind === 'unreadable'),
withBroken.diagnostics.find((d) => d.kind === 'unreadable')?.message ?? 'NO DIAGNOSTIC'
);
/* A definition with one bad capability keeps the good one AND says so. */
const partial = `---
id: partial-check
name: Partial Check
description: One resolvable capability and one that names no source.
pages:
- positions
status: active
owliver:
enabled: true
capabilities:
- summary
- flow
responses:
summary:
source: position.activity
periods:
- today
---
# Partial Check
`;
const withPartial = reg.readSkillRegistry([{ path: 'custom/partial.md', raw: partial }]);
const partialSkill = withPartial.skills.find((s) => s.id === 'partial-check');
record(
'partly-resolvable skill still registers its good capability',
partialSkill?.owliver.capabilities.includes('summary'),
`capabilities = ${JSON.stringify(partialSkill?.owliver.capabilities)}`
);
record(
'and the capability it LOST is reported',
withPartial.diagnostics.some((d) => d.kind === 'incomplete' && d.skillId === 'partial-check'),
withPartial.diagnostics.find((d) => d.kind === 'incomplete')?.message ?? 'NO DIAGNOSTIC'
);
/* Shadowing a built-in is allowed, but must be visible. */
const shadow = `---\nid: create-position\nname: Shadow\ndescription: d\npages:\n - positions\nstatus: active\n---\n\n# Shadow\n`;
const withShadow = reg.readSkillRegistry([{ path: 'custom/shadow.md', raw: shadow }]);
record(
'a custom skill overriding a built-in is reported',
withShadow.diagnostics.some((d) => d.kind === 'shadowed' && d.skillId === 'create-position'),
withShadow.diagnostics.find((d) => d.kind === 'shadowed')?.message ?? 'NO DIAGNOSTIC'
);
/* ── 3. Markdown parsing: no silent content loss ──────────────────────────── */
console.log('\n── Markdown parsing ──');
const cp = reg.SKILLS.find((s) => s.id === 'create-position');
record(
'wrapped bullets are read whole',
cp.capabilities.some((c) => c.endsWith('out of a single sentence.')),
cp.capabilities.find((c) => c.includes('single sentence')) ?? 'TRUNCATED'
);
record(
'conversation steps all parse',
cp.conversation.length === 7 && cp.conversation.every((s) => s.field && s.question),
`${cp.conversation.length} steps`
);
/* ── Every conversation step resolves in the registry it is bound to ─────── */
/*
* The check that catches the failure nobody else would.
*
* `stepsOf` filters a skill's steps down to the fields its registry defines.
* A typo in a field name — or a step added to the Markdown before the field
* exists in code — is therefore DROPPED SILENTLY: the flow asks fewer questions
* than the file lists, and a skill whose steps are all unknown asks none at all,
* jumps straight to the summary, and offers to write an empty record. Nothing
* errors, nothing logs, and the Markdown still reads correctly.
*
* It is also the failure a second conversation makes easy: `flow: employee-role`
* against position field names parses, validates, and produces a flow that asks
* nothing.
*/
const flowsModule = await server.ssrLoadModule('/src/lib/skills/flows/index.js');
const conversational = reg.SKILLS.filter((s) => (s.conversation || []).length);
record(
'every conversational skill declares a flow that exists',
conversational.every((s) => flowsModule.flowFor(s)),
conversational.filter((s) => !flowsModule.flowFor(s))
.map((s) => `${s.id} → ${s.flow ?? 'none'}`).join(', ')
|| `${conversational.length} skill(s): ${conversational.map((s) => `${s.id}→${s.flow}`).join(', ')}`
);
const unresolvedSteps = conversational.flatMap((skill) => {
const registry = flowsModule.flowFor(skill);
if (!registry) return [];
return (skill.conversation || [])
.filter((step) => !registry.fields[step.field])
.map((step) => `${skill.id}:${step.field}`);
});
record(
'...and every one of its steps resolves to a field that registry defines',
unresolvedSteps.length === 0,
unresolvedSteps.join(', ')
|| `${conversational.reduce((n, s) => n + s.conversation.length, 0)} steps all resolve`
);
/* A registry's commit vocabulary has to accept the chip it renders, or the
summary offers a button that falls through to "I did not catch that". */
const unspokenVerbs = Object.values(flowsModule.FLOWS).flatMap((registry) => registry.verbs
.filter((verb) => !verb.says.has(verb.chip.toLowerCase()))
.map((verb) => `${registry.id}:${verb.chip}`));
record(
'every commit chip is a phrase its own flow accepts',
unspokenVerbs.length === 0,
unspokenVerbs.join(', ') || 'all verbs answer their own chips'
);
const numbered = reg.parseSkill(
`---\nid: numbered\nname: Numbered\ndescription: d\npages:\n - positions\nstatus: active\n---\n\n`
+ `## Capabilities\n\n1. First instruction.\n2. Second instruction.\n3. Third instruction.\n`,
{ custom: true }
);
record(
'numbered instructions are preserved',
numbered.capabilities.length === 3,
JSON.stringify(numbered.capabilities)
);
/* A section ends at the next `##`, and a heading that merely starts with the
same word is a different heading. Both halves matter: the first is what stops
`Capabilities` swallowing the rest of the file, the second is what stops a
near-miss heading being read as the real one. */
const bounded = reg.parseSkill(
`---\nid: bounded\nname: Bounded\ndescription: d\npages:\n - positions\nstatus: active\n---\n\n`
+ `## Capabilities\n\n- one\n- two\n\n## Capabilities (v2)\n\n- three\n\n## Actions\n\n- four\n`,
{ custom: true }
);
record(
'a section stops at the next heading',
bounded.capabilities.length === 2,
JSON.stringify(bounded.capabilities)
);
/* Sub-headings (`###`) belong to the section they sit under, and must not end
it — `\n##\s` would match `### ` if the space were not required. */
const nested = reg.parseSkill(
`---\nid: nested\nname: Nested\ndescription: d\npages:\n - positions\nstatus: active\n---\n\n`
+ `## Capabilities\n\n- one\n\n### Detail\n\n- two\n\n## Actions\n\n- three\n`,
{ custom: true }
);
record(
'a `###` sub-heading does not truncate its section',
nested.capabilities.length === 2,
JSON.stringify(nested.capabilities)
);
/* Frontmatter of every shipped file parses and carries its required fields. */
let frontmatterOk = true;
const frontmatterDetail = [];
for (const s of reg.SKILLS) {
if (!s.id || !s.name || !s.pages.length) {
frontmatterOk = false;
frontmatterDetail.push(s.path);
}
}
record('every shipped skill has id, name and pages', frontmatterOk, frontmatterDetail.join(', '));
/* ── 4. Trigger / matching ────────────────────────────────────────────────── */
console.log('\n── Trigger matching ──');
const positions = contextFor('positions');
record('positions context resolves', Boolean(positions), String(positions));
const MATRIX = [
['create a position', 'create-position'],
['i want to hire', 'create-position'],
['post a job', 'create-position'],
['add a client', 'create-position'],
['show hiring activity', 'hiring-activity-assistant'],
['hiring flow', 'hiring-activity-assistant'],
['applications over time', 'hiring-activity-assistant'],
];
for (const [question, expected] of MATRIX) {
const got = reg.matchSkill(question, positions)?.id ?? null;
record(`trigger "${question}" -> ${expected}`, got === expected, got ?? 'no match');
}
/* Every claimant is enumerable, so a contested phrase is not invisible. */
const contender = `---
id: zz-contender
name: ZZ Contender
description: Also claims a phrase create-position owns.
pages:
- positions
status: active
triggers:
- create a position
owliver:
enabled: true
capabilities:
- summary
responses:
summary:
source: position.activity
periods:
- today
---
# ZZ Contender
`;
const contested = [{ path: 'custom/zz.md', raw: contender }];
const claimants = reg.matchSkills('create a position', positions, [], contested);
record(
'all trigger claimants are enumerable',
claimants.length === 2,
claimants.map((s) => s.id).join(', ')
);
record(
'a contested trigger is reported as a collision',
reg.readSkillRegistry(contested).diagnostics.some((d) => d.kind === 'trigger-collision'),
reg.readSkillRegistry(contested).diagnostics.find((d) => d.kind === 'trigger-collision')?.message ?? 'NO DIAGNOSTIC'
);
/* ── 5. Owliver suggestions ───────────────────────────────────────────────── */
console.log('\n── Suggestions ──');
const resolver = await server.ssrLoadModule('/src/lib/skills/owliverResolver.js');
const declared = resolver.owliverSuggestions(positions, [], [], {});
record(
'every declared suggestion names a capability the skill offers',
declared.every((c) => !c.skillCapability
|| reg.SKILLS.find((s) => s.id === c.skillId)?.owliver.capabilities.includes(c.skillCapability))
);
const byIntent = declared.map((c) => `${c.skillId}:${c.skillCapability}`);
record(
'no two declared suggestions resolve to one capability',
new Set(byIntent).size === byIntent.length,
byIntent.join(' | ')
);
const dynamic = await server.ssrLoadModule('/src/components/ai-assistant/dynamic.js');
/* The generator is exercised at three different data shapes: the labels must
track the data, and must never appear when the count behind them is zero. */
const factsFor = (n) => ({
postings: [], starvedPositions: new Array(n).fill({}), unscreened: new Array(n).fill({}),
scored: [], stalled: [], ranked: [], activity: [], missingCredentials: [],
});
const shapes = [0, 1, 7, 12];
let labelsOk = true;
const offenders = [];
for (const n of shapes) {
for (const chip of dynamic.buildPrompts('admin.positions', factsFor(n), null)) {
/* A complete intent asks or instructs. A raw fragment — "4 ready for
interview" — does neither, and is what this check exists to catch. */
const isIntent = /\?$/.test(chip.label) || /^(show|summari[sz]e|create|continue|compare|find|explain|what|which|who)\b/i.test(chip.label);
if (!isIntent) { labelsOk = false; offenders.push(`n=${n}: "${chip.label}"`); }
if (n === 0 && /\b0\b/.test(chip.label)) { labelsOk = false; offenders.push(`n=0 leaked a zero count: "${chip.label}"`); }
}
}
record('every generated label is a complete intent', labelsOk, offenders.join(' ; ') || 'all labels are intents');
/* Counts must move with the data rather than being baked in. */
const at7 = dynamic.buildPrompts('admin.positions', factsFor(7), null).map((c) => c.label);
const at12 = dynamic.buildPrompts('admin.positions', factsFor(12), null).map((c) => c.label);
record(
'labels are dynamic, not hardcoded',
at7.some((l) => l.includes('7')) && at12.some((l) => l.includes('12')) && !at7.some((l) => l.includes('12')),
`7 -> ${at7.find((l) => l.includes('7'))} | 12 -> ${at12.find((l) => l.includes('12'))}`
);
const atZero = dynamic.buildPrompts('admin.positions', factsFor(0), null).map((c) => c.label);
record(
'no count-bearing suggestion when the count is zero',
!atZero.some((l) => /\d/.test(l)),
atZero.join(' | ')
);
/**
* The workforce branch — the one the fragments came from.
*
* "4 ready for interview" and "2 strong for Event Server" were generated here,
* so a suggestion check that passes `null` for workforce proves nothing about
* them. Applications are dated today so the "applied today" branch is live too.
*/
const workforceAt = (readyCount) => {
const position = { id: 'p1', title: 'Event Server – Fine Dining', status: 'active', headcount: 5 };
const today = new Date();
const applications = Array.from({ length: readyCount }, (_, i) => ({
id: `a${i}`,
job_posting_id: 'p1',
email: `c${i}@example.com`,
applicant_name: `Candidate ${i}`,
status: i % 2 ? 'ai_screened' : 'shortlisted',
ai_score: 90,
created_date: today.toISOString(),
updated_date: today.toISOString(),
}));
return {
positions: [position],
currentPositionId: null,
context: { applications, today, profiles: [], staff: [], assignments: [] },
};
};
for (const n of [1, 4, 9]) {
const labels = dynamic
.buildPrompts('admin.positions', factsFor(2), workforceAt(n))
.map((c) => c.label);
const ready = labels.find((l) => /ready for interview/i.test(l));
record(
`workforce branch at ${n}: "ready for interview" is a complete question`,
Boolean(ready) && /\?$/.test(ready) && ready.includes(String(n)) && /^Which\b/.test(ready),
ready ?? 'NOT GENERATED'
);
/* Singular/plural has to track the count, or the fix trades one awkward
label for another. */
if (n === 1) {
record(
'a single candidate reads as one candidate',
ready === 'Which 1 candidate is ready for interview?',
ready ?? 'NOT GENERATED'
);
}
const fragment = labels.find((l) => /^\d+\s/.test(l));
record(
`workforce branch at ${n}: no bare status fragments`,
!fragment,
fragment ?? 'none'
);
}
/* ── 6. Authoring: what the editors save, and what they refuse ────────────── */
console.log('\n── Authoring ──');
const fields = await server.ssrLoadModule('/src/lib/skills/skillFields.js');
const templates = await server.ssrLoadModule('/src/lib/skills/customSkills.js');
const surfaces = await server.ssrLoadModule('/src/lib/skills/surfaces.js');
/* A template must produce something that works before it is edited. The Board
template used to default to a source its own default placement cannot read. */
record(
'the Board template validates as written',
reg.validateSkillSource(templates.uiSkillTemplate({
id: 'template-check', name: 'Template Check', pages: ['positions'],
})) === null,
reg.validateSkillSource(templates.uiSkillTemplate({
id: 'template-check', name: 'Template Check', pages: ['positions'],
})) || 'valid'
);
/* An Owliver block that can answer nothing is refused rather than saved and
then reported as a skill that does not work. */
record(
'an Owliver skill with no capabilities is refused',
Boolean(reg.validateSkillSource(templates.owliverSkillTemplate({
id: 'empty-owliver', name: 'Empty Owliver', pages: ['positions'],
}))),
reg.validateSkillSource(templates.owliverSkillTemplate({
id: 'empty-owliver', name: 'Empty Owliver', pages: ['positions'],
})) || 'ACCEPTED — should have been refused'
);
/* The section-can-never-resolve check, in both directions. */
const boardOn = (page, placement) => `---
id: context-check
name: Context Check
description: Reads position activity.
pages:
- ${page}
status: active
ui:
type: flow
placement: ${placement}
source: position.activity
---
# Context Check
`;
record(
'a position source on a page with no position is refused',
Boolean(reg.validateSkillSource(boardOn('analytics', 'after-header'))),
reg.validateSkillSource(boardOn('analytics', 'after-header')) || 'ACCEPTED — should have been refused'
);
record(
'the same source is refused above the Positions grid',
Boolean(reg.validateSkillSource(boardOn('positions', 'after-position-list'))),
reg.validateSkillSource(boardOn('positions', 'after-position-list')) || 'ACCEPTED — should have been refused'
);
record(
'and accepted inside a position card',
reg.validateSkillSource(boardOn('positions', 'after-position-card')) === null,
reg.validateSkillSource(boardOn('positions', 'after-position-card')) || 'accepted'
);
/* Owliver responses are deliberately NOT subject to that rule: an unmet need is
a question back, not a dead card. `hiring-activity-assistant` depends on it. */
record(
'a shipped Owliver skill reading one position still validates',
reg.validateSkillSource(
reg.SKILLS.find((s) => s.id === 'hiring-activity-assistant').markdown
) === null
);
record(
'every shipped definition still validates',
reg.SKILLS.every((s) => reg.validateSkillSource(s.markdown) === null),
reg.SKILLS.filter((s) => reg.validateSkillSource(s.markdown)).map((s) => s.id).join(', ') || 'all valid'
);
/* Reading a definition into the editor fields — the upload path. */
for (const skill of reg.SKILLS.filter((s) => s.kind === 'assistant')) {
const read = fields.owliverFieldsFromSource(skill.markdown);
record(
`\`${skill.id}\` reads back into the Owliver fields`,
read.id === skill.id && read.name === skill.name
&& read.pages.join(',') === skill.pages.join(','),
`${read.name} / ${read.pages.join(', ')}`
);
}
record(
'an unparseable file leaves the fields empty rather than throwing',
fields.owliverFieldsFromSource('not a definition').id === ''
);
/* Writing a field back — the half that used to do nothing. */
const original = reg.SKILLS.find((s) => s.id === 'hiring-activity-assistant').markdown;
const renamed = fields.patchFrontmatter(original, { name: 'Renamed Assistant' });
record(
'patching a field changes what the registry reads',
reg.parseSkill(renamed, { custom: true }).name === 'Renamed Assistant',
reg.parseSkill(renamed, { custom: true }).name
);
record(
'patching a field leaves the body untouched',
reg.parseSkill(renamed, { custom: true }).body === reg.parseSkill(original, { custom: true }).body
);
record(
'patching a field preserves frontmatter comments',
renamed.includes('# One suggestion per capability.')
);
record(
'patching a field changes nothing else',
original.split('\n').filter((l) => !l.startsWith('name:')).join('\n')
=== renamed.split('\n').filter((l) => !l.startsWith('name:')).join('\n')
);
record(
'a patched definition still validates',
reg.validateSkillSource(renamed) === null,
reg.validateSkillSource(renamed) || 'valid'
);
const repaged = fields.patchFrontmatter(original, { pages: ['analytics', 'activity'] });
record(
'patching a list replaces the whole block',
reg.parseSkill(repaged, { custom: true }).pages.join(',') === 'analytics,activity',
reg.parseSkill(repaged, { custom: true }).pages.join(',')
);
const restatused = fields.patchFrontmatter(original, { status: 'inactive' });
record(
'patching a key the file never declared adds it',
reg.parseSkill(restatused, { custom: true }).status === 'inactive'
);
/* Both keys, because that is what the editor's own field handler writes: the
registry reads capabilities as the union of the declared list and the keys of
`responses:`, so patching one without the other changes nothing. */
const recapped = fields.patchFrontmatter(original, {
'owliver.capabilities': ['summary'],
'owliver.responses': { summary: { source: 'position.activity', periods: ['today'] } },
});
record(
'patching a nested block rewrites only that block',
reg.parseSkill(recapped, { custom: true }).owliver.capabilities.join(',') === 'summary',
reg.parseSkill(recapped, { custom: true }).owliver.capabilities.join(',')
);
/* Asserted on the text, not on the parse: with `flow` no longer offered, the
registry correctly drops the suggestion that names it — which is the rule
working, not the patch reaching a sibling key it should not have. */
record(
'and leaves its siblings in the same block alone',
recapped.includes('Summarize hiring activity for this position')
&& recapped.includes('Show hiring activity as a flow')
&& recapped.includes(' enabled: true')
);
/* The two `ui:` shapes, told apart — the per-page form must not be overwritten
from four single-valued fields. */
record(
'the shorthand `ui:` form is recognised',
fields.uiShape(boardOn('positions', 'after-position-card')) === 'shorthand'
);
record(
'a definition with no `ui:` block reports none',
fields.uiShape(original) === 'none'
);
/* The picker and the validator read the same table. */
record(
'a position card supplies a position',
surfaces.contextSuppliedBy(['positions'], 'after-position-card').includes('positionId')
);
record(
'the Positions list supplies nothing',
surfaces.contextSuppliedBy(['positions'], 'after-position-list').length === 0
);
record(
'Analytics supplies nothing',
surfaces.contextSuppliedBy(['analytics'], 'after-header').length === 0
);
record(
'every surface declares what its placements provide',
surfaces.SKILL_SURFACES.every((s) => s.provides && typeof s.provides === 'object'),
surfaces.SKILL_SURFACES.filter((s) => !s.provides).map((s) => s.id).join(', ') || 'all declared'
);
/* ── 7. Upload hydration: a file, into the fields ─────────────────────────── */
console.log('\n── Upload hydration ──');
/**
* The shapes people actually upload.
*
* Every case here is a definition that arrived from outside the editors, which
* is the only way most definitions arrive. What is being asserted is not that
* the parser is lenient — it is that the three identity fields, the pages and
* the section a reader can see in the file are the ones the form shows.
*/
const COMPLETE = `---
id: hiring-activity-assistant
name: Hiring Activity Assistant
description: Answer questions about recent hiring activity on a position.
type: board
ui:
- page: Positions
placement: grid-card
source: position.activity
---
# Hiring Activity Assistant
`;
const NO_ID = `---
name: Hiring Activity Assistant
description: Answer questions about recent hiring activity.
pages:
- positions
---
# Hiring Activity Assistant
`;
const MULTI_PAGE = `---
id: hiring-activity
name: Hiring Activity
description: Hiring activity across the workspace.
ui:
- page: Positions
placement: grid-card
source: candidates.activity
- page: Analytics
placement: panel
source: hires.performance
---
# Hiring Activity
`;
/* Case 1 — a complete definition hydrates every field it declares. */
{
const f = fields.boardFieldsFromSource(COMPLETE);
record('upload: name hydrates', f.name === 'Hiring Activity Assistant', f.name || 'EMPTY');
record('upload: id hydrates', f.id === 'hiring-activity-assistant', f.id || 'EMPTY');
record(
'upload: description hydrates',
f.description === 'Answer questions about recent hiring activity on a position.',
f.description || 'EMPTY'
);
record('upload: pages come from the `ui:` entries', f.pages.join(',') === 'positions', f.pages.join(',') || 'EMPTY');
record('upload: `grid-card` resolves to a real placement', f.placement === 'after-position-card', f.placement || 'EMPTY');
record('upload: source hydrates', f.source === 'position.activity', f.source || 'EMPTY');
record('upload: an undeclared type is inferred from the source', f.type === 'flow', f.type || 'EMPTY');
record('upload: the definition validates as written', reg.validateSkillSource(COMPLETE) === null,
reg.validateSkillSource(COMPLETE) || 'valid');
record('upload: it is classified as a Board skill',
fields.facetsFromSource(COMPLETE).join(',') === 'ui', fields.facetsFromSource(COMPLETE).join(','));
/* The same file read by the other editor's reader — one pipeline, two views. */
const o = fields.owliverFieldsFromSource(COMPLETE);
record('upload: the Owliver reader hydrates the same identity',
o.id === f.id && o.name === f.name && o.description === f.description);
}
/* Case 2 — no `id:`, so it is slugged from the name and never invented. */
{
const f = fields.boardFieldsFromSource(NO_ID);
record('upload: a missing id slugs the name', f.id === 'hiring-activity-assistant', f.id || 'EMPTY');
record('upload: a missing id does not become the placeholder path', f.id !== 'custom');
record('upload: an explicit id is never replaced by a generated one',
fields.boardFieldsFromSource(COMPLETE).id === 'hiring-activity-assistant');
record('upload: a file with no frontmatter hydrates nothing',
fields.boardFieldsFromSource('# Just a heading\n').id === '');
}
/* Case 3 — several pages, and every entry survives a field edit. */
{
const f = fields.boardFieldsFromSource(MULTI_PAGE);
record('upload: every page in the list is reported', f.pages.join(',') === 'positions,analytics', f.pages.join(','));
const parsed = reg.parseSkill(MULTI_PAGE, { custom: true });
record('upload: every entry becomes a section',
parsed.ui.positions.sections.length === 1 && parsed.ui.analytics.sections.length === 1);
record('upload: each entry keeps its own source',
parsed.ui.positions.sections[0].source === 'candidates.activity'
&& parsed.ui.analytics.sections[0].source === 'hires.performance');
record('upload: a multi-page definition validates', reg.validateSkillSource(MULTI_PAGE) === null,
reg.validateSkillSource(MULTI_PAGE) || 'valid');
/* The fields must not be able to flatten it. */
record('upload: the section fields are read-only against a list', !fields.uiIsEditableFromFields(MULTI_PAGE));
record('upload: a single-section definition stays field-editable',
fields.uiIsEditableFromFields(templates.uiSkillTemplate({ id: 'x', name: 'X', pages: ['positions'] })));
/* Renaming is identity, not structure: it must still work, and must not
touch either entry. */
const renamedMulti = fields.patchFrontmatter(MULTI_PAGE, { name: 'Renamed Multi' });
const after = reg.parseSkill(renamedMulti, { custom: true });
record('upload: renaming a multi-page definition keeps both entries',
after.ui.positions?.sections.length === 1 && after.ui.analytics?.sections.length === 1,
Object.keys(after.ui).join(','));
record('upload: ...and actually renames it', after.name === 'Renamed Multi', after.name);
record('upload: ...and leaves the `ui:` text byte-identical',
renamedMulti.slice(renamedMulti.indexOf('ui:')) === MULTI_PAGE.slice(MULTI_PAGE.indexOf('ui:')));
}
/* Files as they actually arrive: from Windows, from a download, from paste. */
{
const dirty = {
'a byte-order mark': `${COMPLETE}`,
'CRLF line endings': COMPLETE.replace(/\n/g, '\r\n'),
'a BOM and CRLF': `${COMPLETE.replace(/\n/g, '\r\n')}`,
'a blank line above the fence': `\n\n${COMPLETE}`,
'trailing spaces on the fence': COMPLETE.replace(/^---$/gm, '--- '),
};
for (const [what, md] of Object.entries(dirty)) {
const f = fields.boardFieldsFromSource(md);
record(
`upload: a file with ${what} still hydrates`,
f.name === 'Hiring Activity Assistant' && f.id === 'hiring-activity-assistant'
&& f.pages.join(',') === 'positions',
`${f.name || 'EMPTY'} / ${f.id || 'EMPTY'} / ${f.pages.join(',') || 'EMPTY'}`
);
record(
`upload: ...and the registry reads it the same way`,
reg.parseSkill(md, { custom: true }).name === 'Hiring Activity Assistant',
reg.parseSkill(md, { custom: true }).name
);
record(`upload: ...and it validates`, reg.validateSkillSource(md) === null,
reg.validateSkillSource(md) || 'valid');
}
/* Normalising on the way in is what keeps `patchFrontmatter` safe: an
unrecognised fence would have it write a second one above the first. */
const patchedDirty = fields.patchFrontmatter(fields.normalizeUpload(`${COMPLETE}`), { name: 'Clean' });
record('upload: patching a normalised file writes one frontmatter block',
(patchedDirty.match(/^---$/gm) || []).length === 2,
`${(patchedDirty.match(/^---$/gm) || []).length} fences`);
record('upload: ...and it still parses', reg.parseSkill(patchedDirty, { custom: true }).name === 'Clean');
/* A file with nothing to read must be refused, not read as a blank skill. */
record('upload: a file with no fence is not readable', !fields.isReadableDefinition('# Just prose\n'));
record('upload: a real definition is readable', fields.isReadableDefinition(COMPLETE));
}
/* Page names and placements as they are written in the product, not as the
vocabulary spells them internally. */
record('upload: `Positions` resolves to the positions surface', surfaces.canonicalPage('Positions') === 'positions');
record('upload: `Talent Pool` resolves to the talent-pool surface', surfaces.canonicalPage('Talent Pool') === 'talent-pool');
record('upload: `panel` on Analytics resolves to a real placement',
surfaces.placementFor('analytics', 'panel') === 'after-header');
record('upload: an alias never resolves onto a surface that lacks it',
surfaces.placementFor('analytics', 'grid-card') === null);
record('upload: a canonical placement still resolves to itself',
surfaces.placementFor('positions', 'after-position-card') === 'after-position-card');
/* Case 4 — an existing definition reopened for editing. */
for (const skill of reg.SKILLS) {
const f = skill.facets?.includes('ui')
? fields.boardFieldsFromSource(skill.markdown)
: fields.owliverFieldsFromSource(skill.markdown);
record(
`reopening \`${skill.id}\` loads its identity unchanged`,
f.id === skill.id && f.name === skill.name && f.description === skill.description
&& f.pages.join(',') === skill.pages.join(','),
`${f.id} / ${f.name} / ${f.pages.join(', ')}`
);
}
/* Case 5 — a manual edit is not reverted by later synchronisation. */
{
/* The editor's own handler, in miniature: hydrate from the file, edit one
field, then edit an unrelated one. The first edit must survive the second. */
let draft = fields.owliverFieldsFromSource(COMPLETE);
let src = COMPLETE;
const edit = (patch) => {
draft = { ...draft, ...patch };
src = fields.patchFrontmatter(src, {
id: draft.id || undefined,
name: draft.name || undefined,
description: draft.description || undefined,
pages: draft.pages?.length && !fields.pagesAreDerived(src) ? draft.pages : undefined,
});
};
edit({ name: 'My Own Name' });
record('edit: a manual name reaches the artefact',
reg.parseSkill(src, { custom: true }).name === 'My Own Name');
edit({ description: 'My own description.' });
record('edit: a later edit does not revert the earlier one',
reg.parseSkill(src, { custom: true }).name === 'My Own Name',
reg.parseSkill(src, { custom: true }).name);
record('edit: ...and applies itself',
reg.parseSkill(src, { custom: true }).description === 'My own description.');
record('edit: ...and the `ui:` block is untouched throughout',
src.slice(src.indexOf('ui:')) === COMPLETE.slice(COMPLETE.indexOf('ui:')));
/* A second upload replaces the draft outright — it is a new source. */
const rehydrated = fields.owliverFieldsFromSource(MULTI_PAGE);
record('edit: a derived `pages:` is never written back',
!src.includes('\npages:'), src.includes('\npages:') ? 'pages: was inserted' : 'not written');
record('edit: a declared `pages:` still patches normally',
reg.parseSkill(
fields.patchFrontmatter(NO_ID, { pages: ['analytics'] }), { custom: true }
).pages.join(',') === 'analytics');
record('edit: an inherited trigger is not written into the file',
!src.includes('triggers:'), src.includes('triggers:') ? 'triggers: was materialised' : 'not written');
record('edit: a declared trigger is still read into the fields',
fields.owliverFieldsFromSource(
reg.SKILLS.find((x) => x.id === 'hiring-activity-assistant').markdown
).triggers.includes('hiring activity'));
record('edit: a definition with no triggers reads none',
fields.owliverFieldsFromSource(COMPLETE).triggers.length === 0,
JSON.stringify(fields.owliverFieldsFromSource(COMPLETE).triggers));
record('edit: a second upload hydrates from the new file',
rehydrated.id === 'hiring-activity' && rehydrated.name === 'Hiring Activity',
`${rehydrated.id} / ${rehydrated.name}`);
}
/* ── 8. Markdown ↔ manual equivalence ─────────────────────────────────────
The two authoring paths, asserted to produce one artefact.
A form that cannot express what the format can is not a shortcut to it: the
manual Owliver editor carried a single `source` shared by every selected
capability, so a capability chosen before a source was picked composed no
`responses:` block at all — and a definition whose capabilities all drop
registers, shows as active on its page, and contributes no chip. These check
that both doors compose the same definition, and that the one that cannot be
configured is refused rather than saved empty. */
console.log('\n── Markdown ↔ manual equivalence ──');
const dataResolver = await server.ssrLoadModule('/src/lib/skills/dataResolver.js');
const positionsContext = Object.entries(placement.PLACEMENT_ROUTES)
.find(([route]) => reg.pageKeyForRoute(route) === 'positions')?.[1];
/* The canonical model is what `parseSkill` makes of a definition. Comparing the
whole record would compare its Markdown too, which is the one thing the two
paths are allowed to differ on — so the runtime configuration is compared,
which is exactly what every consumer reads. */
const canonical = (skill) => JSON.parse(JSON.stringify({
id: skill.id,
name: skill.name,
description: skill.description,
status: skill.status,
pages: skill.pages,
kind: skill.kind,
triggers: skill.triggers,
ui: skill.ui,
owliver: skill.owliver,
}));
/* TEST A / B — the Board card, written both ways. */
const BOARD_MD = `---
id: board
name: Board
description: Helps Owliver understand, analyze, and act on the current task board.
pages:
- positions
status: active
ui:
type: card
placement: after-position-list-summary
title: Board
source: candidates.activity
periods:
- today
- last-7-days
- previous-month
---
# Board
`;
const boardManual = templates.uiSkillTemplate({
id: 'board',
name: 'Board',
description: 'Helps Owliver understand, analyze, and act on the current task board.',
pages: ['positions'],
type: 'card',
placement: 'after-position-list-summary',
title: 'Board',
source: 'candidates.activity',
periods: ['today', 'last-7-days', 'previous-month'],
});
record('TEST A: Markdown Board saves', reg.validateSkillSource(BOARD_MD) === null,
reg.validateSkillSource(BOARD_MD) || 'accepted');
record('TEST B: manual Board saves', reg.validateSkillSource(boardManual) === null,
reg.validateSkillSource(boardManual) || 'accepted');
const boardFromMd = reg.parseSkill(BOARD_MD, { custom: true });
const boardFromForm = reg.parseSkill(boardManual, { custom: true });
const boardSection = (skill) => Object.values(skill.ui || {}).flatMap((pg) => pg.sections || [])[0];
record('TEST A: one section, above the position list',
Object.values(boardFromMd.ui).flatMap((pg) => pg.sections).length === 1
&& boardSection(boardFromMd).placement === 'after-position-list-summary',
`${boardSection(boardFromMd).type}@${boardSection(boardFromMd).placement}`);
record('TEST A: the Board card contributes no Owliver capability',
boardFromMd.owliver.enabled === false && boardFromMd.owliver.capabilities.length === 0);
record('TEST A: no unresolvable section', reg.unresolvableSections(boardFromMd).length === 0,
reg.unresolvableSections(boardFromMd)[0]?.message || 'none');
record('TEST B: manual Board renders the identical section',
JSON.stringify(boardSection(boardFromForm)) === JSON.stringify(boardSection(boardFromMd)),
JSON.stringify(boardSection(boardFromForm)));
record('TEST B: manual Board keeps its title', boardSection(boardFromForm).title === 'Board',
String(boardSection(boardFromForm).title));
/* TEST C / D — the conversational skill, written both ways. */
const OWLIVER_MD = `---
id: owliver-conversation-test
name: Hiring Activity Test
description: Shows hiring activity from the Positions page.
pages:
- positions
status: active
triggers:
- hiring activity test
owliver:
enabled: true
suggestions:
- Show hiring activity
- Summarize hiring activity
capabilities:
- summary
responses:
summary:
source: candidates.activity
periods:
- today
- last-7-days
- previous-month
---
# Hiring Activity Test
`;
const owliverManual = templates.owliverSkillTemplate({
id: 'owliver-conversation-test',
name: 'Hiring Activity Test',
description: 'Shows hiring activity from the Positions page.',
pages: ['positions'],
suggestions: ['Show hiring activity', 'Summarize hiring activity'],
capabilities: ['summary'],
responses: {
summary: { source: 'candidates.activity', periods: ['today', 'last-7-days', 'previous-month'] },
},
});
record('TEST C: Markdown Owliver skill saves', reg.validateSkillSource(OWLIVER_MD) === null,
reg.validateSkillSource(OWLIVER_MD) || 'accepted');
record('TEST D: manual Owliver skill saves', reg.validateSkillSource(owliverManual) === null,
reg.validateSkillSource(owliverManual) || 'accepted');
for (const [label, source] of [['TEST C', OWLIVER_MD], ['TEST D', owliverManual]]) {
const custom = [{ path: 'custom/owliver-conversation-test.md', raw: source }];
const skill = reg.parseSkill(source, { custom: true });
record(`${label}: the capability keeps its source`,
skill.owliver.responses.summary?.source === 'candidates.activity',
skill.owliver.responses.summary?.source || 'dropped');
record(`${label}: the capability keeps its periods`,
JSON.stringify(skill.owliver.responses.summary?.periods)
=== JSON.stringify(['today', 'last-7-days', 'previous-month']),
JSON.stringify(skill.owliver.responses.summary?.periods));
record(`${label}: it draws no page card`,
Object.keys(skill.ui).length === 0, `${Object.keys(skill.ui).length} pages`);
const chips = resolver.owliverSuggestions(positionsContext, [], custom, {});
const mine = chips.filter((c) => c.skillId === 'owliver-conversation-test');
record(`${label}: its suggestion reaches the panel`,
mine.some((c) => c.label === 'Show hiring activity'),
JSON.stringify(chips.map((c) => c.label)));
record(`${label}: the suggestion answers without asking for a record`,
mine.every((c) => !c.deferred));
const owlSkills = resolver.owliverSkillsForContext(positionsContext, [], custom);
const matched = resolver.matchOwliverSkill('Show hiring activity', owlSkills);
record(`${label}: clicking it routes to this skill's summary`,
matched?.skill.id === 'owliver-conversation-test' && matched?.capability === 'summary' && matched?.exact,
`${matched?.skill.id}:${matched?.capability} exact=${matched?.exact}`);
const answered = resolver.resolveOwliverResponse({
skill: matched.skill,
capability: matched.capability,
question: 'Show hiring activity',
context: {
positions: [{ id: 'p1', title: 'Line Cook', status: 'active' }],
applications: [{ id: 'a1', job_posting_id: 'p1', created_date: new Date().toISOString() }],
},
now: new Date(),
});
record(`${label}: it answers with the configured reading`,
answered?.missing === null && resolver.summaryLines(answered.data).length === 3,
JSON.stringify(resolver.summaryLines(answered?.data || {})));
}
/* TEST H — the same skill through both doors, normalized, compared. */
record('TEST H: Markdown and manual normalize identically (Board)',
JSON.stringify(canonical(boardFromMd)) === JSON.stringify(canonical(boardFromForm)),
JSON.stringify(canonical(boardFromForm)).slice(0, 120));
record('TEST H: Markdown and manual normalize identically (Owliver)',
JSON.stringify(canonical(reg.parseSkill(OWLIVER_MD, { custom: true })))
=== JSON.stringify(canonical(reg.parseSkill(owliverManual, { custom: true }))),
JSON.stringify(canonical(reg.parseSkill(owliverManual, { custom: true }))).slice(0, 120));
/* TEST E — a capability with no source is refused, never saved empty. */
const noSourceFields = {
id: 'no-source-test',
name: 'No Source Test',
description: 'A capability with nothing to read.',
pages: ['positions'],
suggestions: ['Show hiring activity'],
capabilities: ['summary'],
responses: {},
};
const noSource = templates.owliverSkillTemplate(noSourceFields);
record('TEST E: an unconfigured capability is named by the form',
JSON.stringify(fields.unconfiguredCapabilities(noSourceFields)) === '["summary"]',
JSON.stringify(fields.unconfiguredCapabilities(noSourceFields)));
record('TEST E: and the save is refused',
/owliver\.responses\.summary/.test(reg.validateSkillSource(noSource) || ''),
reg.validateSkillSource(noSource) || 'ACCEPTED — a skill with no capability would register');
record('TEST E: the refused definition would have had zero capabilities',
reg.parseSkill(noSource, { custom: true }).owliver.capabilities.length === 0,
'which is why it is refused rather than stored');
/* TEST F — two capabilities, two different sources, neither inheriting. */
const twoSources = templates.owliverSkillTemplate({
id: 'two-source-test',
name: 'Two Source Test',
description: 'Two capabilities reading two sources.',
pages: ['positions'],
suggestions: ['Summarize workspace applications', 'List the open roles'],
capabilities: ['summary', 'list'],
responses: {
summary: { source: 'candidates.activity', periods: ['today'] },
list: { source: 'positions.demand', limit: 5 },
},
});
const twoSkill = reg.parseSkill(twoSources, { custom: true });
record('TEST F: manual save accepted', reg.validateSkillSource(twoSources) === null,
reg.validateSkillSource(twoSources) || 'accepted');
record('TEST F: each capability keeps its own source',
twoSkill.owliver.responses.summary?.source === 'candidates.activity'
&& twoSkill.owliver.responses.list?.source === 'positions.demand',
`summary→${twoSkill.owliver.responses.summary?.source}, list→${twoSkill.owliver.responses.list?.source}`);
record('TEST F: neither inherits the other\'s options',
JSON.stringify(twoSkill.owliver.responses.summary.periods) === '["today"]'
&& twoSkill.owliver.responses.list.limit === 5
&& twoSkill.owliver.responses.list.periods.length === 0,
`summary periods=${JSON.stringify(twoSkill.owliver.responses.summary.periods)}, list limit=${twoSkill.owliver.responses.list.limit}`);
record('TEST F: both are offered as suggestions',
resolver.owliverSuggestions(positionsContext, [], [{ path: 'custom/two.md', raw: twoSources }], {})
.filter((c) => c.skillId === 'two-source-test').length === 2);
/* TEST G — editing one field leaves every other configuration alone. */
const edited = fields.patchFrontmatter(
twoSources,
fields.owliverPatch(
{ ...fields.owliverFieldsFromSource(twoSources), description: 'A new description.' },
{ existing: twoSources }
)
);
const editedSkill = reg.parseSkill(edited, { custom: true });
record('TEST G: the edit lands', editedSkill.description === 'A new description.', editedSkill.description);
record('TEST G: every capability source survives the edit',
JSON.stringify(canonical(editedSkill).owliver.responses)
=== JSON.stringify(canonical(twoSkill).owliver.responses),
`summary→${editedSkill.owliver.responses.summary?.source}, list→${editedSkill.owliver.responses.list?.source}`);
record('TEST G: suggestions survive the edit',
JSON.stringify(editedSkill.owliver.suggestions) === JSON.stringify(twoSkill.owliver.suggestions));
const boardEdited = fields.patchFrontmatter(
boardManual,
fields.boardPatch(
{ ...fields.boardFieldsFromSource(boardManual), description: 'A new description.' },
{ existing: boardManual }
)
);
record('TEST G: Board UI configuration survives the edit',
JSON.stringify(boardSection(reg.parseSkill(boardEdited, { custom: true })))
=== JSON.stringify(boardSection(boardFromForm)),
JSON.stringify(boardSection(reg.parseSkill(boardEdited, { custom: true }))));
/* ── Editing a definition that is not indented with ASCII spaces ──────────
*
* Found in a browser, on a definition stored on a real account: changing the
* card title through Skill Configure appended a whole second `ui:` block, so
* every field under it appeared twice. The account's definition indents with
* U+00A0, which JavaScript's `\s` matches — the parser read the file correctly,
* every screen showed the right values, and only the writer disagreed, because
* `findKey` compared against literal ASCII spaces and concluded the keys it was
* asked to update did not exist.
*
* The Go port shares the parser's reading (`jsIsSpace` lists `0x00A0`), so this
* was never a parser disagreement and needs no parser change. The fixture below
* builds the indentation from an escape rather than a pasted character, so an
* editor that tidies whitespace cannot silently delete the thing under test.
*/
const NB = '\u00a0';
const NBSP_BOARD = [
'---',
'id: nbsp-board',
'name: NBSP Board',
'description: A definition indented with no-break spaces.',
'pages:',
`${NB}${NB}- positions`,
'status: active',
'ui:',
`${NB}${NB}type: card`,
`${NB}${NB}placement: after-position-list-summary`,
`${NB}${NB}title: Before`,
`${NB}${NB}source: candidates.activity`,
`${NB}${NB}periods:`,
`${NB}${NB}${NB}${NB}- today`,
'---',
'',
'# NBSP Board',
'',
'Body.',
'',
].join('\n');
const nbspEdited = fields.patchFrontmatter(
NBSP_BOARD,
fields.boardPatch(
{ ...fields.boardFieldsFromSource(NBSP_BOARD), title: 'BOARD UI TARGET TEST' },
{ existing: NBSP_BOARD }
)
);
const nbspFm = nbspEdited.split('---')[1];
const nbspCount = (pattern) => (nbspFm.match(pattern) || []).length;
const nbspParsed = reg.parseSkill(nbspEdited, { custom: true });
const nbspSection = nbspParsed?.ui?.positions?.sections?.[0] || null;
record('NBSP: editing the title replaces it rather than adding a second one',
nbspSection?.title === 'BOARD UI TARGET TEST' && !/title:\s*Before/.test(nbspFm),
nbspSection?.title);
for (const key of ['type', 'placement', 'title', 'source', 'periods']) {
record(`NBSP: \`${key}\` is not duplicated`,
nbspCount(new RegExp(`^\\s+${key}:`, 'gm')) === 1,
`${nbspCount(new RegExp(`^\\s+${key}:`, 'gm'))} occurrence(s)`);
}
record('NBSP: exactly one `ui:` block remains',
nbspCount(/^ui:/gm) === 1, `${nbspCount(/^ui:/gm)} block(s)`);
record('NBSP: the edited definition still parses',
Boolean(nbspParsed)
&& nbspParsed.id === 'nbsp-board'
&& (nbspParsed.ui?.positions?.sections || []).length === 1
&& nbspSection?.type === 'card'
&& nbspSection?.placement === 'after-position-list-summary'
&& nbspSection?.source === 'candidates.activity',
`${nbspSection?.type} at ${nbspSection?.placement} reading ${nbspSection?.source}`);
record('NBSP: `pages` is unchanged by the edit',
JSON.stringify(nbspParsed?.pages) === JSON.stringify(['positions']),
JSON.stringify(nbspParsed?.pages));
/* The half that must not move: ASCII definitions are the entire repository, and
widening what counts as an indent must leave them byte-identical. */
record('ASCII indentation is written exactly as it was before',
fields.patchFrontmatter(boardManual, { name: 'Renamed Board' })
=== fields.patchFrontmatter(boardManual, { name: 'Renamed Board' })
&& /^ {2}type:/m.test(fields.patchFrontmatter(boardManual, { name: 'Renamed Board' })),
'two-space indentation preserved');
/* TEST 6 — one source/shape resolver, shared by the pickers and the validator. */
record('shape compatibility: the resolver refuses what the normalizer refuses',
surfaces.sourcesForShape('list').every((src) => surfaces.sourceSupportsShape(src.id, 'list'))
&& !surfaces.sourcesForShape('list').some((src) => src.id === 'candidates.activity'),
`list sources: ${surfaces.sourcesForShape('list').map((s) => s.id).join(', ')}`);
record('shape compatibility: a list against candidates.activity is refused on save',
/cannot be shown as/.test(reg.validateSkillSource(templates.owliverSkillTemplate({
id: 'bad-shape-test',
name: 'Bad Shape Test',
description: 'A list of something with no list in it.',
pages: ['positions'],
capabilities: ['list'],
responses: { list: { source: 'candidates.activity' } },
})) || ''),
'the picker cannot offer it, and the save refuses it');
record('summary is prose, so every source suits it',
surfaces.shapeForCapability('summary') === null
&& surfaces.sourcesForShape(null).length === surfaces.DATA_SOURCES.length);
/* ── 9. The reported failure, reproduced end to end ───────────────────────── */
console.log('\n── Regression: manual Owliver skill on Positions ──');
/* Exactly the configuration from the report: Pages = Positions, one suggestion,
Summary, `candidates.activity`, three periods — composed the way the form
composes it, then read the way the panel reads it. */
const REGRESSION = templates.owliverSkillTemplate({
id: 'owliver-conversation-test',
name: 'Hiring Activity Test',
description: 'Shows hiring activity from the Positions page.',
pages: ['positions'],
suggestions: ['Show hiring activity'],
capabilities: ['summary'],
responses: {
summary: { source: 'candidates.activity', periods: ['today', 'last-7-days', 'previous-month'] },
},
});
const stored = [{ path: 'custom/owliver-conversation-test.md', raw: REGRESSION }];
record('the manual definition saves', reg.validateSkillSource(REGRESSION) === null,
reg.validateSkillSource(REGRESSION) || 'accepted');
record('it registers', reg.allSkills(stored).some((s) => s.id === 'owliver-conversation-test'));
record('it is attached to Positions',
reg.skillsForContext(positionsContext, [], stored).some((s) => s.id === 'owliver-conversation-test'));
record('it keeps a capability — the step that used to empty',
reg.parseSkill(REGRESSION, { custom: true }).owliver.capabilities.length === 1,
JSON.stringify(reg.parseSkill(REGRESSION, { custom: true }).owliver.capabilities));
record('it reaches owliverSkillsForContext',
resolver.owliverSkillsForContext(positionsContext, [], stored)
.some((s) => s.id === 'owliver-conversation-test'));
const regressionChips = resolver.owliverSuggestions(positionsContext, [], stored, {});
record('"Show hiring activity" is offered as a chip',
regressionChips.some((c) => c.label === 'Show hiring activity'),
JSON.stringify(regressionChips.map((c) => c.label)));
const clicked = resolver.matchOwliverSkill(
'Show hiring activity',
resolver.owliverSkillsForContext(positionsContext, [], stored)
);
record('clicking it invokes this skill\'s summary',
clicked?.skill.id === 'owliver-conversation-test' && clicked?.capability === 'summary',
`${clicked?.skill.id}:${clicked?.capability}`);
const now = new Date();
const answer = resolver.resolveOwliverResponse({
skill: clicked.skill,
capability: clicked.capability,
question: 'Show hiring activity',
context: {
positions: [{ id: 'p1', title: 'Line Cook', status: 'active' }],
applications: [
{ id: 'a1', job_posting_id: 'p1', created_date: now.toISOString() },
{ id: 'a2', job_posting_id: 'p1', created_date: new Date(now - 3 * 86400000).toISOString() },
],
},
now,
});
record('it answers with the configured source and periods',
answer?.missing === null && answer.section.source === 'candidates.activity'
&& answer.section.periods.length === 3,
`${answer?.section.source} over ${JSON.stringify(answer?.section.periods)}`);
record('the answer carries real figures',
resolver.summaryLines(answer.data).length === 3,
JSON.stringify(resolver.summaryLines(answer.data)));
record('and no `position.activity needs a position` is possible',
dataResolver.resolveSkillData(answer.section, { applications: [], positions: [] }, now).unavailable !== true,
'the source declares no context requirement');
/* Reload: what is stored is the Markdown, so the round trip through storage is
the round trip through the parser. */
const reloaded = reg.parseSkill(
fields.patchFrontmatter(REGRESSION, {}),
{ custom: true }
);
record('the source survives a reload',
reloaded.owliver.responses.summary?.source === 'candidates.activity',
reloaded.owliver.responses.summary?.source || 'lost');
record('the form reads it back unchanged',
JSON.stringify(fields.owliverFieldsFromSource(REGRESSION).responses)
=== JSON.stringify({ summary: { source: 'candidates.activity', periods: ['today', 'last-7-days', 'previous-month'], limit: null } }),
JSON.stringify(fields.owliverFieldsFromSource(REGRESSION).responses));
/* ── 10. Storage round trip ───────────────────────────────────────────────
The editors do not hand the registry a record; they write Markdown into
`preferences.customSkills`, which `base44Client` sends to the API and reads
back on the next load. "The source disappeared after reload" is a claim about
*that* path, so it is exercised here rather than assumed: the same module the
app runs, against the same requests it makes.
The API is stubbed, and only here. `base44Client` is production code and must
talk to the real backend — that is the whole point of it — so what is
replaced is the transport underneath it: `fetch`, answering the three
endpoints this section drives with an in-memory record. Everything above the
transport is the real thing: the same client, the same request builder, the
same envelope unwrapping, the same synchronous preferences accessor.
Node has no `localStorage` and no origin, so both are supplied. The origin is
why this used to fail outright: the client builds a same-origin path
(`/api/v1/...`), which `fetch` cannot resolve without one. */
console.log('\n── Storage round trip ──');
const store = new Map();
globalThis.localStorage = {
getItem: (k) => (store.has(k) ? store.get(k) : null),
setItem: (k, v) => store.set(k, String(v)),
removeItem: (k) => store.delete(k),
};
/* The user the stubbed API holds, and the requests it saw. Both are asserted
below: what the client sent matters as much as what it did with the reply. */
const apiUser = {
id: 'user_demo', full_name: 'Alex Rivera', email: 'demo@krow.app',
role: 'admin', account_type: 'employer',
preferences: { owliverDefault: true, compactDensity: false, emailDigest: true },
};
const apiCalls = [];
/* What GET /api/v1/owliver/suggestions answers with. Set per assertion, so a
test can say what the server returned and check what the panel made of it. */
let apiSuggestions = [];
/* Turns the suggestions route into one this backend does not have — the
deployment lag where the route is in the Go source and not yet on the host
the dev server proxies to. Everything else keeps answering, which is exactly
what makes that state confusing to diagnose by hand. */
let apiSuggestionsDeployed = true;
/* The collections the stubbed API serves, by resource path.
*
* Filled in by section 12 from `api/seed.js` — the shipped fixture, which is
* what it is for now that the running app reads PostgreSQL instead. Declared
* here because the stub is installed before that module is loaded, and empty
* until then so an accidental early read is an empty list rather than a lie.
*
* The paths are `httpClient.js`'s own RESOURCE_PATHS, which is a declared table
* rather than a derivation, so the two are written out the same way. */
const apiEntities = new Map();
/* The subset of the list contract this fixture implements: repeated parameters
* as `IN`, `-field` as descending, `limit` as a cap. Enough for the reads below
* and deliberately no more — a fuller reimplementation would be a second
* backend to keep honest. */
function serveList(records, params) {
let out = [...records];
for (const [key, values] of params) {
if (key === 'sort' || key === 'limit' || key === 'offset') continue;
out = out.filter((record) => values.includes(String(record[key])));
}
const sort = params.has('sort') ? String(params.get('sort')[0] ?? '') : '';
if (sort) {
const desc = sort.startsWith('-');
const field = desc ? sort.slice(1) : sort;
out.sort((a, b) => {
const [x, y] = [a[field], b[field]];
const cmp = x === y ? 0
: (typeof x === 'number' && typeof y === 'number') ? x - y
: String(x ?? '').localeCompare(String(y ?? ''));
return desc ? -cmp : cmp;
});
}
const limit = Number(params.get('limit')?.[0]);
return limit > 0 ? out.slice(0, limit) : out;
}
const jsonResponse = (data) => new Response(JSON.stringify({ data }), {
status: 200, headers: { 'Content-Type': 'application/json' },
});
globalThis.fetch = async (url, init = {}) => {
const method = init.method || 'GET';
const path = String(url);
apiCalls.push({ method, path, body: init.body ? JSON.parse(init.body) : null });
if (method === 'GET' && path === '/api/v1/me') return jsonResponse(apiUser);
if (method === 'PATCH' && path === '/api/v1/me/preferences') {
apiUser.preferences = { ...apiUser.preferences, ...JSON.parse(init.body) };
return jsonResponse(apiUser.preferences);
}
if (method === 'GET' && path.startsWith('/api/v1/owliver/suggestions')) {
if (!apiSuggestionsDeployed) {
return new Response(
JSON.stringify({ error: { code: 'not_found', message: 'not found' } }),
{ status: 404, headers: { 'Content-Type': 'application/json' } }
);
}
return jsonResponse({ suggestions: apiSuggestions });
}
const listed = /^\/api\/v1\/([a-z-]+)(?:\?(.*))?$/.exec(path);
if (method === 'GET' && listed && apiEntities.has(listed[1])) {
const params = new URLSearchParams(listed[2] || '');
const grouped = new Map();
for (const key of new Set(params.keys())) grouped.set(key, params.getAll(key));
return jsonResponse(serveList(apiEntities.get(listed[1]), grouped));
}
return new Response(
JSON.stringify({ error: { code: 'not_found', message: `no stub for ${method} ${path}` } }),
{ status: 404, headers: { 'Content-Type': 'application/json' } }
);
};
const { base44 } = await server.ssrLoadModule('/src/api/base44Client.js');
/* The client hydrates itself with GET /me at module load; wait for that to land
so the synchronous accessor below reads the server's answer rather than the
default shape. */
await base44.auth.me();
const custom = await server.ssrLoadModule('/src/lib/skills/customSkills.js');
/* Saving is what the editor's Save button does: compose, validate, upsert,
write the preference. */
const saved = custom.upsertCustomSkill(base44.auth.preferences().customSkills || [], REGRESSION);
const written = await base44.auth.updatePreferences({ customSkills: saved.next });
record('the definition is persisted', written.persisted, written.error?.message || 'written to storage');
/* Reload: a fresh read of the same key, exactly as a page load does. */
const rehydratedSkills = base44.auth.preferences().customSkills || [];
record('it survives the reload', rehydratedSkills.length === 1
&& String(rehydratedSkills[0].raw).includes('candidates.activity'),
`${rehydratedSkills.length} stored`);
const afterReload = reg.parseSkill(rehydratedSkills[0].raw, { custom: true });
record('its capability survives the reload',
afterReload.owliver.responses.summary?.source === 'candidates.activity',
afterReload.owliver.responses.summary?.source || 'lost');
record('and it still offers its chip after the reload',
resolver.owliverSuggestions(positionsContext, [], rehydratedSkills, {})
.some((c) => c.label === 'Show hiring activity'),
JSON.stringify(resolver.owliverSuggestions(positionsContext, [], rehydratedSkills, {}).map((c) => c.label)));
record('the editor reopens it with its source intact',
fields.owliverFieldsFromSource(rehydratedSkills[0].raw).responses.summary?.source === 'candidates.activity',
JSON.stringify(fields.owliverFieldsFromSource(rehydratedSkills[0].raw).responses));
/* The Board skill through the same path, stored beside it — the two subsystems
are independent, so both must survive one write. */
const bothStored = custom.upsertCustomSkill(rehydratedSkills, boardManual).next;
await base44.auth.updatePreferences({ customSkills: bothStored });
const finalStored = base44.auth.preferences().customSkills || [];
const finalRegistry = reg.readSkillRegistry(finalStored);
record('Board and Owliver skills coexist in storage', finalStored.length === 2,
`${finalStored.length} stored`);
record('neither produces a registry diagnostic',
!finalRegistry.diagnostics.some((d) => d.level === 'error'),
finalRegistry.diagnostics.filter((d) => d.level === 'error').map((d) => d.message).join(' | ') || 'clean');
record('the Board card draws once on Positions, and offers no chip',
reg.getSkillsForPage('positions', { customSources: finalStored })
.filter((sk) => sk.id === 'board')
.every((sk) => Object.values(sk.ui).flatMap((pg) => pg.sections).length === 1
&& sk.owliver.capabilities.length === 0));
record('the Owliver skill offers its chip, and draws no card',
resolver.owliverSuggestions(positionsContext, [], finalStored, {})
.some((c) => c.skillId === 'owliver-conversation-test')
&& Object.keys(reg.parseSkill(REGRESSION, { custom: true }).ui).length === 0);
/* ── 10b. Owliver suggestions come from the API ───────────────────────────
*
* The suggestion the panel offers is a claim that the reader could usefully ask
* something, and only the backend can make that claim: it knows the caller's
* role, and — with nothing typed — it ranks against what is actually in
* PostgreSQL. So the frontend's whole part in this is a request and a
* translation, and these checks are about exactly that boundary.
*
* What is asserted, in order: the request the client makes, and the chip the
* panel makes of the reply. Nothing here re-implements ranking, because nothing
* in `src/` does either — the point of the change these checks protect is that
* the local ranker is gone. `MISSING matchPrompts` below is the guard against
* it coming back.
*/
console.log('\n── Owliver suggestions from the API ──');
const serverSuggestions = await server.ssrLoadModule('/src/lib/skills/serverSuggestions.js');
/* ── The request ─────────────────────────────────────────────────────────── */
const requestFor = async (args) => {
const before = apiCalls.length;
await base44.owliver.suggestions(args);
return apiCalls.slice(before).map((c) => c.path);
};
const typedRequest = await requestFor({ page: 'positions', query: 'pipeline' });
record('a typed query is asked of the API',
typedRequest.length === 1
&& typedRequest[0].startsWith('/api/v1/owliver/suggestions')
&& typedRequest[0].includes('page=positions')
&& typedRequest[0].includes('query=pipeline'),
typedRequest.join(', ') || 'no request made');
/* Absent, not empty. `?query=` and no `query` at all are different requests:
the first says "the composer holds nothing", the second asks the data what to
suggest, and only the second reads the database. */
const untypedRequest = await requestFor({ page: 'positions' });
record('an empty composer asks the data, not for an empty string',
untypedRequest.length === 1 && !untypedRequest[0].includes('query='),
untypedRequest.join(', '));
record('a page-less request is not made at all',
(await requestFor({ query: 'pipeline' })).length === 0,
'nothing to ask about');
/* ── The reply, as chips ─────────────────────────────────────────────────── */
/* Two the Positions page declares and one it does not, so both branches of the
translation are exercised by one response. */
apiSuggestions = [
{ text: 'Which positions need attention?', intent: 'positions-attention' },
{ text: 'Show hiring activity as a flow', intent: 'hiring-operations', capability: 'flow' },
{ text: 'A reading this build has not shipped', intent: 'not-a-capability' },
];
const fromServer = await base44.owliver.suggestions({ page: 'positions', query: 'attention' });
const chips = serverSuggestions.suggestionChips(fromServer, positionsContext);
record('every suggestion the server sent becomes a chip',
chips.length === apiSuggestions.length, `${chips.length} of ${apiSuggestions.length}`);
record('...in the order the server ranked them',
chips.map((c) => c.label).join(' | ') === apiSuggestions.map((s) => s.text).join(' | '),
chips.map((c) => c.label).join(' | '));
record('...reading exactly what the server wrote',
chips.every((c, i) => c.label === apiSuggestions[i].text && c.prompt === apiSuggestions[i].text),
'label and prompt are the server text');
/* These two checks used to assert that a suggestion resolved to a LOCAL
capability — a canned reading the browser would run itself. Those readings
were the simulator, and they were deleted along with it. The property that
replaced them matters more: every suggestion now becomes a question, asked in
the server's own words, and nothing in between rewrites it. */
record('every suggestion becomes a question, in the server\'s own words',
chips.every((c, i) => c.prompt === apiSuggestions[i].text),
chips.map((c) => c.prompt).join(' | '));
/* `capability` on a server suggestion names a SECTION TYPE to draw, not
anything to run. It used to be at risk of being confused with the chip's own
capability field; there is no longer a second meaning to confuse it with, and
this asserts the remaining one still rides through untouched. */
record('a requested shape rides through as a shape, not as an instruction',
chips[1].shape === 'flow',
`shape=${chips[1].shape}`);
record('every chip says it came from the API',
chips.every((c) => c.source === 'api'));
apiSuggestions = [];
record('no suggestions is an empty row, not a fabricated one',
serverSuggestions.suggestionChips(
await base44.owliver.suggestions({ page: 'positions', query: 'sourdough' }),
positionsContext
).length === 0);
/* ── A backend without the route ─────────────────────────────────────────── */
/* The deployment lag this guards against is real and has happened: the route is
in the Go source and not yet on the host a dev server proxies to, which
answers 404 for it and 200 for everything else. Every keystroke would then be
a request that cannot succeed, and no later keystroke can change the answer. */
const hooks = await server.ssrLoadModule('/src/lib/krowHooks.js');
apiSuggestionsDeployed = false;
record('a suggestion request that 404s resolves to none, not an error',
(await hooks.fetchOwliverSuggestions({ page: 'positions', query: 'attention' })).length === 0);
const afterBreaker = apiCalls.length;
await hooks.fetchOwliverSuggestions({ page: 'positions', query: 'pipeline' });
record('...and stops asking a backend that does not have the route',
apiCalls.length === afterBreaker,
`${apiCalls.length - afterBreaker} request(s) after the 404`);
/* ── The local ranker is gone ────────────────────────────────────────────── */
/* Structural, because this is the property that decays quietly. The panel used
to rank a locally-built catalogue against the query — the same job the API
now does — and the failure mode of it returning is not an error but a second
opinion silently outranking the server's. */
record('the panel imports no local suggestion ranker',
!existsSync(join(ROOT, resolveSourcePath('src/components/ai-assistant/matchPrompts.js'))),
'matchPrompts.js removed');
const panelSource = readFileSync(join(ROOT, resolveSourcePath('src/components/ai-assistant/KrowAssistant.jsx')), 'utf8');
record('...and does not rank, score or sort suggestions itself',
!/rankPrompts|\.sort\(|score\(/.test(panelSource),
'no ranking in the panel');
record('...it asks the API instead',
/useOwliverSuggestions/.test(panelSource) && /suggestionChips/.test(panelSource));
/* The production data client must not carry the fixture. */
record('the app does not import the seed fixture',
!/from '\.\/seed'/.test(readFileSync(join(ROOT, resolveSourcePath('src/api/base44Client.js')), 'utf8')),
'base44Client reads demoUser.js, not seed.js');
record('the local entity store is gone',
!existsSync(join(ROOT, resolveSourcePath('src/api/store.js'))),
'store.js removed');
/* ── 11. A fresh Add Owliver Skill screen ─────────────────────────────────
What the form starts as, asserted at the state that drives the checkbox
rather than at the checkbox. `checked` is
`draft.capabilities.includes(capability.id)` and the per-capability
configuration is rendered under `{checked && …}`, so the array below is the
whole of "what is ticked" and "what is shown". */
console.log('\n── Fresh Add Owliver Skill ──');
const CAPS = surfaces.OWLIVER_CAPABILITIES.map((c) => c.id);
/* 1. NEW skill. `emptyDraft` is `EMPTY_OWLIVER_FIELDS`, which is what the
editor initializes to when there is no `:id` and nothing handed over. */
record('new skill: initial capabilities are []',
Array.isArray(fields.EMPTY_OWLIVER_FIELDS.capabilities)
&& fields.EMPTY_OWLIVER_FIELDS.capabilities.length === 0,
JSON.stringify(fields.EMPTY_OWLIVER_FIELDS.capabilities));
record('new skill: no response is pre-configured either',
Object.keys(fields.EMPTY_OWLIVER_FIELDS.responses).length === 0,
JSON.stringify(fields.EMPTY_OWLIVER_FIELDS.responses));
record(`new skill: all ${CAPS.length} capability checkboxes start unchecked`,
CAPS.every((id) => !fields.EMPTY_OWLIVER_FIELDS.capabilities.includes(id)),
CAPS.filter((id) => fields.EMPTY_OWLIVER_FIELDS.capabilities.includes(id)).join(', ') || 'none ticked');
/* The template the fresh screen composes must not declare capabilities either:
an author who types a name before ticking anything patches *that* file, and a
`capabilities:` key in it would tick boxes nobody chose. */
const freshTemplate = templates.owliverSkillTemplate({});
record('new skill: the composed template declares no capabilities',
!/^\s*capabilities:/m.test(freshTemplate),
freshTemplate.split('\n').filter((l) => /capabilit/i.test(l)).join(' / ') || 'no capabilities key');
record('new skill: reading that template back still yields []',
fields.owliverFieldsFromSource(freshTemplate).capabilities.length === 0,
JSON.stringify(fields.owliverFieldsFromSource(freshTemplate).capabilities));
/* 2. One capability, chosen. The editor's `update({ capabilities })` is a plain
state merge, so the assertion is on what that array then means. */
const pickedSummary = { ...fields.EMPTY_OWLIVER_FIELDS, capabilities: ['summary'] };
record('selecting Summary ticks exactly Summary',
JSON.stringify(pickedSummary.capabilities) === '["summary"]');
record('and only Summary renders a configuration block',
CAPS.filter((id) => pickedSummary.capabilities.includes(id)).length === 1,
CAPS.filter((id) => pickedSummary.capabilities.includes(id)).join(', '));
record('an unsourced Summary is invalid, and only Summary is reported',
JSON.stringify(fields.unconfiguredCapabilities(pickedSummary)) === '["summary"]',
JSON.stringify(fields.unconfiguredCapabilities(pickedSummary)));
/* 3. Choosing its source clears the error, and the definition saves. */
const sourcedSummary = {
...pickedSummary,
id: 'fresh-summary-test',
name: 'Fresh Summary Test',
description: 'One capability, configured.',
pages: ['positions'],
suggestions: ['Show hiring activity'],
responses: { summary: { source: 'candidates.activity', periods: ['today'] } },
};
record('choosing a source clears the validation error',
fields.unconfiguredCapabilities(sourcedSummary).length === 0,
JSON.stringify(fields.unconfiguredCapabilities(sourcedSummary)));
const freshSaved = templates.owliverSkillTemplate(sourcedSummary);
record('and the save succeeds', reg.validateSkillSource(freshSaved) === null,
reg.validateSkillSource(freshSaved) || 'accepted');
record('the saved definition declares only the chosen capability',
JSON.stringify(reg.parseSkill(freshSaved, { custom: true }).owliver.capabilities) === '["summary"]',
JSON.stringify(reg.parseSkill(freshSaved, { custom: true }).owliver.capabilities));
record('and its suggestion reaches the Positions panel',
resolver.owliverSuggestions(positionsContext, [], [{ path: 'custom/fresh.md', raw: freshSaved }], {})
.some((c) => c.skillId === 'fresh-summary-test'));
/* 4. Several, chosen by hand — only those, each validated on its own. */
const pickedTwo = {
...fields.EMPTY_OWLIVER_FIELDS,
capabilities: ['summary', 'list'],
responses: { summary: { source: 'candidates.activity' } },
};
record('selecting two capabilities renders exactly those two',
CAPS.filter((id) => pickedTwo.capabilities.includes(id)).join(',') === 'summary,list');
record('and only the unconfigured one is reported',
JSON.stringify(fields.unconfiguredCapabilities(pickedTwo)) === '["list"]',
JSON.stringify(fields.unconfiguredCapabilities(pickedTwo)));
/* 5. An EXISTING skill hydrates from its own definition, and from nothing else.
Including the capability that did *not* resolve: it is the one the author
opened the editor to fix, and dropping it from the form would delete it
from the file on the next save. */
const DECLARED_ONE = `---
id: declared-one
name: Declared One
description: Declares a single capability.
pages:
- positions
status: active
owliver:
enabled: true
suggestions:
- Show hiring activity
capabilities:
- summary
responses:
summary:
source: candidates.activity
---
# Declared One
`;
record('existing skill: hydrates exactly its declared capability',
JSON.stringify(fields.owliverFieldsFromSource(DECLARED_ONE).capabilities) === '["summary"]',
JSON.stringify(fields.owliverFieldsFromSource(DECLARED_ONE).capabilities));
record('existing skill: no new-skill default is applied on top',
CAPS.filter((id) => fields.owliverFieldsFromSource(DECLARED_ONE).capabilities.includes(id)).length === 1);
const HALF_BROKEN = DECLARED_ONE
.replace(' - summary\n', ' - summary\n - list\n')
.replace('id: declared-one', 'id: half-broken');
const halfFields = fields.owliverFieldsFromSource(HALF_BROKEN);
record('existing skill: a declared capability that lost its source still shows',
JSON.stringify(halfFields.capabilities) === '["summary","list"]',
JSON.stringify(halfFields.capabilities));
record('...unsourced, so it stays invalid rather than silently dropped',
JSON.stringify(fields.unconfiguredCapabilities(halfFields)) === '["list"]',
JSON.stringify(fields.unconfiguredCapabilities(halfFields)));
record('...and the configured one keeps its source through the round trip',
halfFields.responses.summary.source === 'candidates.activity',
halfFields.responses.summary.source);
record('...while the runtime still registers only what can answer',
JSON.stringify(reg.parseSkill(HALF_BROKEN, { custom: true }).owliver.capabilities) === '["summary"]',
JSON.stringify(reg.parseSkill(HALF_BROKEN, { custom: true }).owliver.capabilities));
/* A definition inheriting its reading from a `ui:` section still hydrates the
capability it actually offers — the union is declaration-first, not
declaration-only. */
const INHERITED = `---
id: inherited-cap
name: Inherited Cap
description: A capability reading the page section.
pages:
- positions
status: active
ui:
type: card
placement: after-position-list-summary
source: candidates.activity
owliver:
enabled: true
capabilities:
- card
---
# Inherited Cap
`;
record('a capability inheriting from `ui:` still hydrates with its source',
fields.owliverFieldsFromSource(INHERITED).responses.card?.source === 'candidates.activity',
fields.owliverFieldsFromSource(INHERITED).responses.card?.source || 'lost');
/* ── 12. Attendance and overtime data ─────────────────────────────────────
*
* Attendance is the one collection whose dates are anchored to now rather than
* written as calendar dates, and the one whose foreign keys have to line up
* with a narrative written months earlier. Both are easy to get wrong in ways
* that look fine: a shift pointing at a staff member who was never hired reads
* as a working feature until someone asks whose shift it was.
*
* These checks are about the data being *true* — that it joins, that it is
* windowable by the existing period machinery, and that the analyses over it
* find what is there and stay quiet about what is not.
*/
console.log('\n── Attendance and overtime data ──');
const attendance = await server.ssrLoadModule('/src/lib/attendance.js');
const shiftSeed = await server.ssrLoadModule('/src/api/attendanceSeed.js');
const seedModule = await server.ssrLoadModule('/src/api/seed.js');
const resolverModule = await server.ssrLoadModule('/src/lib/skills/dataResolver.js');
/* The stubbed API serves the shipped fixture from here on, so the reads below
go through the real client, the real request builder and the real envelope —
only the transport is replaced. See the note above the `fetch` stub. */
for (const [entity, path] of Object.entries({
JobPosting: 'job-postings', JobApplication: 'job-applications', AIInterview: 'ai-interviews',
Staff: 'staff', WorkerProfile: 'worker-profiles', Course: 'courses', Badge: 'badges',
LearningPath: 'learning-paths', Certification: 'certifications',
RoleCategory: 'role-categories', UserActivity: 'user-activity', Evidence: 'evidence',
User: 'users', Assignment: 'assignments', ShiftRecord: 'shift-records',
})) {
apiEntities.set(path, seedModule.seedData[entity] || []);
}
const SHIFTS = shiftSeed.SHIFT_RECORDS;
const SHIFT_STATUSES = ['present', 'late', 'absent', 'no_show', 'excused'];
record('shift records are seeded', SHIFTS.length > 0, `${SHIFTS.length} shifts`);
record('the seed registers them as `ShiftRecord`',
Array.isArray(seedModule.seedData.ShiftRecord) && seedModule.seedData.ShiftRecord.length === SHIFTS.length,
`${seedModule.seedData.ShiftRecord?.length ?? 0} in seedData`);
record('every shift has a unique id',
new Set(SHIFTS.map((s) => s.id)).size === SHIFTS.length);
record('every shift status is one the product recognises',
SHIFTS.every((s) => SHIFT_STATUSES.includes(s.status)),
[...new Set(SHIFTS.map((s) => s.status))].join(', '));
record('every shift was scheduled for real hours',
SHIFTS.every((s) => s.scheduled_hours > 0));
record('overtime is never negative',
SHIFTS.every((s) => s.overtime_hours >= 0));
/* ── The joins ───────────────────────────────────────────────────────────── */
const staffIds = new Set(seedModule.seedData.Staff.map((s) => s.id));
const postingsById = new Map(seedModule.seedData.JobPosting.map((p) => [p.id, p]));
record('every shift belongs to someone who was actually hired',
SHIFTS.every((s) => staffIds.has(s.staff_id)),
[...new Set(SHIFTS.map((s) => s.staff_id).filter((id) => !staffIds.has(id)))].join(', ') || 'all resolve');
record('every shift belongs to a real position',
SHIFTS.every((s) => postingsById.has(s.job_posting_id)),
[...new Set(SHIFTS.map((s) => s.job_posting_id).filter((id) => !postingsById.has(id)))].join(', ') || 'all resolve');
/* "Department" has to mean the same thing here as it does on Hired History and
Analytics, both of which read it off the posting — see the join in
`lib/hiringRecords.js`. A shift carrying its own category is a denormalized
copy, and a copy that disagrees is worse than no copy. */
record('a shift\'s department matches its position\'s',
SHIFTS.every((s) => postingsById.get(s.job_posting_id)?.role_category === s.role_category),
SHIFTS.filter((s) => postingsById.get(s.job_posting_id)?.role_category !== s.role_category)
.map((s) => s.id).join(', ') || 'all agree');
/**
* `created_date` is the instant the shift was worked.
*
* Load-bearing rather than incidental: `inPeriod` windows every collection on
* `created_date`, so if these drifted apart every period reading of attendance
* would come back empty and nothing would say why.
*/
record('a shift\'s created date is the instant it was worked',
SHIFTS.every((s) => s.created_date === s.scheduled_start));
/* ── Hours add up ────────────────────────────────────────────────────────── */
record('a missed shift is zero hours worked, not a short one',
SHIFTS.filter((s) => s.status === 'absent' || s.status === 'no_show')
.every((s) => s.actual_hours === 0 && s.overtime_hours === 0 && s.actual_start === null));
record('a worked shift\'s hours are its schedule, less lateness, plus overtime',
SHIFTS.filter((s) => s.status === 'present' || s.status === 'late').every((s) => {
const expected = s.scheduled_hours - s.minutes_late / 60 + s.overtime_hours;
return Math.abs(s.actual_hours - expected) < 0.02;
}));
record('a late shift is one that was turned up for',
SHIFTS.filter((s) => s.status === 'late').every((s) => s.minutes_late > 0 && s.actual_start !== null));
/* ── Reachable the way a page reaches it ─────────────────────────────────── */
const client = await server.ssrLoadModule('/src/api/base44Client.js');
const listed = await client.base44.entities.ShiftRecord.list('-created_date', 500);
record('shifts are queryable through the entity API',
listed.length === SHIFTS.length, `${listed.length} returned`);
record('...newest first, like every other collection',
listed.every((s, i) => i === 0
|| new Date(listed[i - 1].created_date).getTime() >= new Date(s.created_date).getTime()));
const filtered = await client.base44.entities.ShiftRecord.filter({ status: 'absent' });
record('...and filterable by field',
filtered.length > 0 && filtered.every((s) => s.status === 'absent'),
`${filtered.length} absences`);
/* ── The sources future skills will declare ──────────────────────────────── */
for (const id of ['workforce.attendance', 'workforce.overtime']) {
const source = surfaces.dataSourceFor(id);
record(`\`${id}\` is a declarable source`, Boolean(source));
record(`\`${id}\` needs no record in context`, source?.context === null);
record(`\`${id}\` supports every shape it declares`,
source.shapes.every((shape) => surfaces.sourceSupportsShape(id, shape)),
source.shapes.join(', '));
record(`\`${id}\` accepts the options it declares`,
(source.options || []).every((option) => surfaces.sourceSupportsOption(id, option)),
(source.options || []).join(', ') || 'none');
/* Honest emptiness. A source with nothing to read must say so rather than
reporting zeros that look like findings. */
const empty = resolverModule.resolveSkillData({ source: id }, { shifts: [] });
record(`\`${id}\` reports honestly when there are no shifts`,
empty.empty === true && Boolean(empty.emptyNote),
empty.emptyNote || 'NO NOTE');
const full = resolverModule.resolveSkillData({ source: id }, { shifts: SHIFTS });
record(`\`${id}\` reads the seeded shifts`,
full.empty === false && full.steps.length > 0 && full.items.length > 0,
`${full.steps.length} figures, ${full.items.length} rows`);
const periodic = resolverModule.resolveSkillData(
{ source: id, periods: ['last-7-days', 'previous-month'] }, { shifts: SHIFTS }
);
record(`\`${id}\` windows by period`,
periodic.steps.length === 2 && periodic.steps.some((s) => s.records.length > 0),
periodic.steps.map((s) => `${s.label}=${s.records.length}`).join(', '));
}
/* ── The analyses ────────────────────────────────────────────────────────── */
const summary = attendance.attendanceSummary(SHIFTS);
record('attendance counts every scheduled shift exactly once',
summary.worked + summary.missed === summary.scheduled,
`${summary.worked} worked + ${summary.missed} missed = ${summary.scheduled}`);
record('attendance rate is a percentage',
summary.attendanceRate >= 0 && summary.attendanceRate <= 100, `${summary.attendanceRate}%`);
record('punctuality is never better than attendance',
summary.punctualityRate <= summary.attendanceRate,
`${summary.punctualityRate}% punctual / ${summary.attendanceRate}% present`);
const overtime = attendance.overtimeSummary(SHIFTS);
record('overtime is the gap between hours worked and hours scheduled',
overtime.hours > 0 && overtime.hoursWorked > overtime.hoursScheduled - summary.minutesLate / 60,
`${overtime.hours}h overtime`);
const workers = attendance.attendanceByWorker(SHIFTS);
record('every worker on the roster is compared',
workers.length === new Set(SHIFTS.map((s) => s.staff_id)).size, `${workers.length} workers`);
record('workers are ordered worst attendance first',
workers.every((w, i) => i === 0 || workers[i - 1].attendanceRate <= w.attendanceRate),
workers.map((w) => `${w.name.split(' ')[0]} ${w.attendanceRate}%`).join(', '));
const overtimeWorkers = attendance.overtimeByWorker(SHIFTS);
record('overtime comparison is ordered most hours first',
overtimeWorkers.every((w, i) => i === 0 || overtimeWorkers[i - 1].hours >= w.hours),
overtimeWorkers.map((w) => `${w.name.split(' ')[0]} ${w.hours}h`).join(', '));
const departments = attendance.attendanceByDepartment(SHIFTS);
record('departments are compared too',
departments.length > 1 && departments.every((d) => d.people > 0),
departments.map((d) => `${d.name} ${d.attendanceRate}%`).join(', '));
const trend = attendance.weeklyTrend(SHIFTS);
record('the weekly trend runs oldest to newest',
trend.length >= 4 && trend.every((w, i) => i === 0
|| new Date(trend[i - 1].weekStart).getTime() < new Date(w.weekStart).getTime()),
`${trend.length} weeks`);
record('a week nobody was rostered for is not reported as 0% attendance',
trend.every((w) => w.scheduled > 0));
/* ── Anomalies: what it finds, and what it stays quiet about ─────────────── */
/* Anchored, not live.
*
* These shifts are generated by counting back from a day, and which of them
* land in a *complete* week depends on where that day sits in one. Asserting
* against the live set meant asserting against the calendar: the seeded
* overtime climb is detected on a Thursday and not on a Friday, so this check
* failed every Friday and Saturday and passed the rest of the week.
*
* The anchor is the same instant the rest of this file pins, and the same date
* is passed as `now` — shifts built around one day and bucketed around another
* describe two different windows, which is its own way to be wrong. */
const ANCHOR = new Date('2026-08-20T09:00:00.000Z');
const ANCHORED_SHIFTS = shiftSeed.buildShiftsAt(ANCHOR);
const findings = attendance.attendanceAnomalies(ANCHORED_SHIFTS, { now: ANCHOR });
record('the seeded attendance decline is found',
findings.some((f) => f.kind === 'attendance'),
findings.filter((f) => f.kind === 'attendance').map((f) => f.title).join(' | ') || 'NOT FOUND');
record('the seeded overtime climb is found',
findings.some((f) => f.kind === 'overtime'),
findings.find((f) => f.kind === 'overtime')?.detail || 'NOT FOUND');
/* And that it does not depend on the day, measured rather than assumed.
*
* Pinning the anchor makes this file deterministic; it does not make the
* product insensitive to the day, and it was not: the climb was reported Sunday
* through Thursday and vanished on Friday and Saturday, because the oldest week
* in the window is a partial one and thinned to two shifts as the week wore on,
* inflating the baseline every later week was compared against.
*
* Fixed in attendanceAnomalies by dropping a leading week rostered well below
* the usual. This is the number that keeps it fixed: a change that reintroduces
* a calendar-shaped blind spot fails here even though the pinned assertion
* above still passes. */
const detectableOn = Array.from({ length: 28 }, (_, i) => new Date(2026, 7, i + 1, 12))
.filter((day) => attendance
.attendanceAnomalies(shiftSeed.buildShiftsAt(day), { now: day })
.some((f) => f.kind === 'overtime')).length;
record('the seeded overtime climb is detectable on every day of the week',
detectableOn === 28,
`${detectableOn}/28 anchors`);
record('every finding carries the figures behind it',
findings.every((f) => f.title && f.detail && f.severity && f.metric));
record('findings are ordered most severe first',
findings.every((f, i) => i === 0
|| ({ high: 0, medium: 1, low: 2 })[findings[i - 1].severity] <= ({ high: 0, medium: 1, low: 2 })[f.severity]),
findings.map((f) => f.severity).join(' → '));
/**
* The check that matters most, and the one a detector like this usually fails.
*
* A rule that flags everything is worse than no rule: it trains its reader to
* skim, and the one finding that mattered goes past unread. So a workforce with
* nothing wrong must produce *nothing at all* — not a low-severity note, not a
* "no issues" finding.
*/
const flawless = SHIFTS.map((s) => ({
...s, status: 'present', minutes_late: 0, overtime_hours: 0, actual_hours: s.scheduled_hours,
}));
record('a workforce with nothing wrong produces no findings',
attendance.attendanceAnomalies(flawless).length === 0,
`${attendance.attendanceAnomalies(flawless).length} findings`);
record('no shift records produces no findings either',
attendance.attendanceAnomalies([]).length === 0);
/* One bad week is a bad week; it takes a second to be a direction. */
const oneOffWeek = SHIFTS.map((s) => {
const daysBack = Math.round((Date.now() - new Date(s.created_date).getTime()) / 86400000);
return daysBack > 21 && daysBack <= 28 ? { ...s, status: 'absent', actual_hours: 0, overtime_hours: 0 } : { ...s, status: 'present', minutes_late: 0, overtime_hours: 0, actual_hours: s.scheduled_hours };
});
record('a single bad week months ago is not reported as a current problem',
!attendance.attendanceAnomalies(oneOffWeek).some((f) => f.id === 'missed-shifts-rising'),
attendance.attendanceAnomalies(oneOffWeek).map((f) => f.id).join(', ') || 'none');
/* ── 13. Cross-domain data sources ────────────────────────────────────────
*
* Eight readings over records the workspace already holds. The failure these
* guard against is a source that *looks* like it works: one that returns
* plausible zeros when it has nothing to read, or that quietly assumes a field
* the data does not carry. Either produces a skill that appears to answer and
* is answering about nothing.
*
* So every source is checked twice — against the seed, and against an empty
* workspace — and the empty case has to say so rather than report zeros.
*/
console.log('\n── Cross-domain data sources ──');
const signalsModule = await server.ssrLoadModule('/src/lib/activitySignals.js');
const insightsModule = await server.ssrLoadModule('/src/components/ai-assistant/insights.js');
const expectedBaseline = JSON.parse(readFileSync(BASELINE_PATH, 'utf8'));
const SEED = seedModule.seedData;
/* The workspace a declared reading is checked against — the same collections the
panel hands a resolver at runtime. Declared once here because both the
analysis-skill and agent sections read against it. */
const AGENT_SKILL_CONTEXT = {
positions: SEED.JobPosting,
applications: SEED.JobApplication,
interviews: SEED.AIInterview,
staff: SEED.Staff,
profiles: SEED.WorkerProfile,
workerProfiles: SEED.WorkerProfile,
activity: SEED.UserActivity,
assignments: SEED.Assignment,
courses: SEED.Course,
shifts: SHIFTS,
trainingPaths: [],
};
const FULL_CONTEXT = {
positions: SEED.JobPosting,
applications: SEED.JobApplication,
interviews: SEED.AIInterview,
staff: SEED.Staff,
profiles: SEED.WorkerProfile,
workerProfiles: SEED.WorkerProfile,
activity: SEED.UserActivity,
assignments: SEED.Assignment,
shifts: SHIFTS,
};
const NEW_SOURCES = [
'positions.risk',
'candidates.quality',
'talent.pool',
'workforce.coverage',
'activity.signals',
'activity.breakdown',
'operations.risk',
'workspace.summary',
];
for (const id of NEW_SOURCES) {
const decl = surfaces.dataSourceFor(id);
record(`\`${id}\` is a declarable source`, Boolean(decl));
if (!decl) continue;
/* Every one of these is a workspace-level reading. A source that needed a
record in context could not be answered from a page that has none, and
would validate then fail at read time. */
record(`\`${id}\` needs no record in context`, decl.context === null);
record(`\`${id}\` supports every shape it declares`,
decl.shapes.every((shape) => surfaces.sourceSupportsShape(id, shape)),
decl.shapes.join(', '));
record(`\`${id}\` accepts the options it declares`,
(decl.options || []).every((option) => surfaces.sourceSupportsOption(id, option)),
(decl.options || []).join(', ') || 'none');
/* Reads the workspace as it actually is. */
const readFull = resolverModule.resolveSkillData({ source: id }, FULL_CONTEXT);
record(`\`${id}\` reads the seeded workspace`,
readFull.empty === false && (readFull.steps?.length > 0),
`${readFull.steps?.length ?? 0} figures, ${readFull.items?.length ?? 0} rows`);
record(`\`${id}\` returns figures, never prose`,
(readFull.steps || []).every((step) => typeof step.value === 'number'),
(readFull.steps || []).map((s) => `${s.title}=${s.value}`).join(', '));
/**
* The check that separates a working source from one that only looks like it.
*
* An empty workspace must produce `empty: true` and a note saying why — not
* a row of zeros, which reads as a measured result rather than an absent one.
*/
const readEmpty = resolverModule.resolveSkillData({ source: id }, {});
record(`\`${id}\` reports honestly when there is nothing to read`,
readEmpty.empty === true && Boolean(readEmpty.emptyNote),
readEmpty.emptyNote || 'NO NOTE');
record(`\`${id}\` never throws on a half-empty workspace`,
(() => {
try {
const partialRead = resolverModule.resolveSkillData({ source: id }, { positions: SEED.JobPosting });
return partialRead && typeof partialRead.empty === 'boolean';
} catch {
return false;
}
})());
}
/* ── Period filtering, where it applies ──────────────────────────────────── */
for (const id of ['candidates.quality', 'activity.breakdown']) {
const readPeriodic = resolverModule.resolveSkillData(
{ source: id, periods: ['previous-month'] }, FULL_CONTEXT
);
const all = resolverModule.resolveSkillData({ source: id }, FULL_CONTEXT);
record(`\`${id}\` windows by period`,
readPeriodic.total <= all.total,
`${readPeriodic.total} in the previous month / ${all.total} in total`);
/* Overlapping windows must not count the same record twice — `today` sits
inside `last-7-days`, and a naive concatenation would double it. */
const overlapping = resolverModule.resolveSkillData(
{ source: id, periods: ['today', 'last-7-days'] }, FULL_CONTEXT
);
const sevenOnly = resolverModule.resolveSkillData(
{ source: id, periods: ['last-7-days'] }, FULL_CONTEXT
);
record(`\`${id}\` does not double-count overlapping periods`,
overlapping.total === sevenOnly.total,
`${overlapping.total} vs ${sevenOnly.total}`);
}
/* ── `activity.signals` reuses the assistant's own detection ─────────────── */
/**
* The reuse assertion.
*
* `activitySignals` moved out of `buildFacts` so a data source could read it
* without a library importing from the component tree. The whole value of that
* move is that there is still exactly one implementation — "two unusual
* patterns" has to mean the same two in the greeting and on the card.
*/
const factSheet = insightsModule.buildFacts({
applications: SEED.JobApplication,
postings: SEED.JobPosting,
interviews: SEED.AIInterview,
staff: SEED.Staff,
profiles: SEED.WorkerProfile,
activity: SEED.UserActivity,
courses: SEED.Course,
profile: null,
user: seedModule.DEMO_USER,
today: new Date('2026-08-20T09:00:00.000Z'),
});
const direct = signalsModule.activitySignals(SEED.UserActivity, new Date('2026-08-20T09:00:00.000Z'));
record('the fact sheet and the extracted detection agree exactly',
JSON.stringify(factSheet.activitySignals) === JSON.stringify(direct),
JSON.stringify(direct.flags));
record('`PRIVILEGED_EVENTS` is still importable from insights.js',
JSON.stringify(insightsModule.PRIVILEGED_EVENTS) === JSON.stringify(signalsModule.PRIVILEGED_EVENTS),
JSON.stringify(insightsModule.PRIVILEGED_EVENTS));
const signalsRead = resolverModule.resolveSkillData({ source: 'activity.signals' }, FULL_CONTEXT);
record('the activity.signals source reports the same flags the greeting counts',
signalsRead.items.length === factSheet.activitySignals.flags.length,
`${signalsRead.items.length} signals`);
record('every signal is explained rather than named',
signalsRead.items.every((i) => i.title && i.detail && i.title !== i.id));
/* A workspace with nothing out of pattern is a real answer, and a different
one from having no log at all. */
const quiet = resolverModule.resolveSkillData({ source: 'activity.signals' }, { activity: [] });
record('an empty log and a quiet log are different answers',
quiet.emptyNote !== signalsRead.emptyNote);
/* ── `workforce.coverage` refuses to invent a headcount ──────────────────── */
/**
* No seeded position declares a headcount, and `demandFor` reports that rather
* than defaulting to one person per role. This check exists because the obvious
* shortcut — assume 1, report a fill rate — produces a confident percentage
* that means nothing, and nothing on screen would say so.
*/
const coverage = resolverModule.resolveSkillData({ source: 'workforce.coverage' }, FULL_CONTEXT);
const declaredStep = coverage.steps.find((s) => s.id === 'declared');
record('coverage says out loud when no role states a headcount',
declaredStep.value === 0 && /does not|not state|No open role/i.test(declaredStep.detail),
declaredStep.detail);
record('coverage still reports who has actually been hired',
coverage.steps.find((s) => s.id === 'covered').value > 0,
`${coverage.steps.find((s) => s.id === 'covered').value} roles have someone hired`);
record('a role with no headcount reports hires, not a fill percentage',
coverage.items.every((i) => i.declared || /headcount not stated/.test(i.detail)));
/* ── `positions.risk` distinguishes "nothing wrong" from "nothing posted" ── */
const noRoles = resolverModule.resolveSkillData({ source: 'positions.risk' }, { positions: [], applications: [] });
const healthy = resolverModule.resolveSkillData({ source: 'positions.risk' }, {
positions: SEED.JobPosting.filter((p) => p.status === 'active').slice(0, 1),
applications: SEED.JobApplication.map((a) => ({
...a, job_posting_id: SEED.JobPosting.find((p) => p.status === 'active').id, ai_score: 88, status: 'hired',
})),
});
record('positions.risk separates "no roles open" from "no roles at risk"',
noRoles.emptyNote !== healthy.emptyNote,
`${noRoles.emptyNote} / ${healthy.emptyNote}`);
/* ── `operations.risk` stays quiet when the operation is running ─────────── */
record('operations.risk finds the real backlog',
resolverModule.resolveSkillData({ source: 'operations.risk' }, FULL_CONTEXT).findings.length > 0,
resolverModule.resolveSkillData({ source: 'operations.risk' }, FULL_CONTEXT)
.findings.map((f) => f.id).join(', '));
const readClean = resolverModule.resolveSkillData({ source: 'operations.risk' }, {
/* Everything screened and decided, every role has applicants, every shift worked. */
applications: SEED.JobApplication.map((a) => ({ ...a, status: 'hired', ai_score: 90 })),
positions: SEED.JobPosting.filter((p) => p.status === 'active').map((p) => ({ ...p })),
shifts: SHIFTS.map((s) => ({ ...s, status: 'present' })),
});
record('operations.risk reports nothing when nothing is wrong',
readClean.findings.length === 0 && readClean.empty === true,
readClean.findings.map((f) => f.id).join(', ') || 'no findings');
/* ── `talent.pool` does not average away the unscored ───────────────────── */
/**
* Four of the nine seeded profiles have no score. Averaging them in as zero
* would report a healthy pool as poor, in exact proportion to how much of it
* nobody has assessed yet — a figure that gets worse as the pool grows.
*/
const pool = resolverModule.resolveSkillData({ source: 'talent.pool' }, FULL_CONTEXT);
const scoredProfiles = SEED.WorkerProfile.filter((p) => (p.krow_score || 0) > 0);
const expectedAvg = Math.round(
scoredProfiles.reduce((sum, p) => sum + p.krow_score, 0) / scoredProfiles.length
);
record('talent.pool averages only the profiles that have been scored',
pool.steps.find((s) => s.id === 'quality').value === expectedAvg,
`${pool.steps.find((s) => s.id === 'quality').value} vs ${expectedAvg} expected`);
record('...and says how many are not yet assessed',
/not yet assessed/.test(pool.steps.find((s) => s.id === 'scored').detail),
pool.steps.find((s) => s.id === 'scored').detail);
record('an unscored person reads as unscored, not as a zero score',
pool.items.filter((i) => i.value === 0).every((i) => /Not yet scored/.test(i.detail)));
/* ── `candidates.quality` reports coverage beside quality ────────────────── */
const quality = resolverModule.resolveSkillData({ source: 'candidates.quality' }, FULL_CONTEXT);
record('candidate quality reports how much of the pool was actually scored',
quality.steps.find((s) => s.id === 'coverage').value < 100,
quality.steps.find((s) => s.id === 'coverage').detail);
record('score bands add up to the number scored',
quality.bands.reduce((n, b) => n + b.value, 0)
=== SEED.JobApplication.filter((a) => a.ai_score > 0).length,
quality.bands.map((b) => `${b.label}=${b.value}`).join(', '));
/* ── Nothing here reaches for a record it was not given ─────────────────── */
/**
* A resolver that throws takes the page down; one that invents a default is
* worse, because it reports a figure nobody can trace. Every source is called
* with each collection missing in turn.
*/
const COLLECTIONS = ['positions', 'applications', 'interviews', 'staff', 'profiles', 'activity', 'shifts', 'assignments'];
let survived = true;
let culprit = '';
for (const id of NEW_SOURCES) {
for (const drop of COLLECTIONS) {
const withoutOne = { ...FULL_CONTEXT };
delete withoutOne[drop];
try {
const out = resolverModule.resolveSkillData({ source: id }, withoutOne);
if (!out || typeof out.empty !== 'boolean') { survived = false; culprit = `${id} without ${drop}`; }
} catch (error) {
survived = false;
culprit = `${id} without ${drop}: ${error.message}`;
}
}
}
record('every source survives any single collection being absent', survived, culprit || `${NEW_SOURCES.length} sources × ${COLLECTIONS.length} collections`);
/* ── 14. Analysis skills ──────────────────────────────────────────────────
*
* Thirteen definitions written against the sources built in the two previous
* phases. The failure they guard against is a definition that registers, looks
* complete, and answers nothing — a capability bound to a source that cannot be
* read, or a trigger that quietly takes a question another definition was
* written to answer.
*
* Every one is therefore executed, not merely parsed: each declared capability
* is resolved against the seeded workspace and has to come back with figures.
*/
console.log('\n── Analysis skills ──');
const ANALYSIS_SKILLS = [
'staffing-risk', 'attendance-analysis', 'overtime-analysis', 'candidate-analysis',
'talent-pool-analysis', 'workforce-analytics', 'anomaly-detection', 'activity-analysis',
'operational-risk', 'executive-summary', 'hiring-history-analysis', 'learning-analysis',
'hiring-pulse-analysis',
];
record('every analysis skill registers',
ANALYSIS_SKILLS.every((id) => reg.SKILLS.some((s) => s.id === id)),
ANALYSIS_SKILLS.filter((id) => !reg.SKILLS.some((s) => s.id === id)).join(', ') || `${ANALYSIS_SKILLS.length} skills`);
for (const id of ANALYSIS_SKILLS) {
const skill = reg.SKILLS.find((s) => s.id === id);
if (!skill) continue;
/* Written in the one format, read by the one parser. */
record(`\`${id}\` is a valid definition`,
reg.validateSkillSource(skill.markdown) === null,
reg.validateSkillSource(skill.markdown) || 'ok');
record(`\`${id}\` is an Owliver skill on real pages`,
skill.facets.includes('owliver')
&& skill.pages.length > 0
&& skill.pages.every((p) => surfaces.SUPPORTED_SKILL_PAGES.includes(surfaces.canonicalPage(p) || p)),
JSON.stringify(skill.pages));
/* The sections the brief asks every definition to carry. `Purpose` and
`Capabilities` are parsed into fields; the rest are prose the authoring
flow will later read back, so they are checked for presence rather than
for shape. */
record(`\`${id}\` states its purpose and capabilities`,
skill.purpose.length > 0 && skill.capabilities.length > 0,
`${skill.purpose.length} purpose, ${skill.capabilities.length} capabilities`);
record(`\`${id}\` documents its data, analysis, output and limitations`,
['## Data', '## Analysis', '## Output', '## Limitations'].every((h) => skill.body.includes(h)),
['Data', 'Analysis', 'Output', 'Limitations'].filter((h) => !skill.body.includes(`## ${h}`)).join(', ') || 'all four');
/* Every capability resolves, and comes back with figures rather than an
apology. This is what separates a definition that works from one that
merely parses. */
const failures = [];
for (const capability of skill.owliver.capabilities) {
const response = skill.owliver.responses[capability];
if (!response) { failures.push(`${capability}: no response`); continue; }
const declared = surfaces.dataSourceFor(response.source);
if (!declared) { failures.push(`${capability}: unknown source ${response.source}`); continue; }
/* Shape has to be one the source actually supports, or the definition
promises a rendering the data cannot produce. */
const shape = surfaces.shapeForCapability(capability);
if (shape && !surfaces.sourceSupportsShape(response.source, shape)) {
failures.push(`${capability}: ${response.source} cannot draw ${shape}`);
continue;
}
if (declared.context) continue; /* needs a record; asked for at runtime */
const read = dataResolver.resolveSkillData(response, AGENT_SKILL_CONTEXT);
if (read?.unavailable) failures.push(`${capability}: unreadable`);
else if (read?.empty && !read.emptyNote) failures.push(`${capability}: empty with no explanation`);
}
record(`\`${id}\` answers every capability it declares`,
failures.length === 0,
failures.join(' | ') || `${skill.owliver.capabilities.length} capabilities`);
/* Every suggestion names the capability it asks for. Without this a chip
falls through to the first declared capability, and two chips silently
become one answer — the lesson recorded in hiring-activity-assistant.md. */
record(`\`${id}\` names a capability on every suggestion`,
skill.owliver.suggestions.every((s) => s.capability
&& skill.owliver.capabilities.includes(s.capability)),
skill.owliver.suggestions.map((s) => `${s.label} → ${s.capability}`).join(' | '));
/* Its own triggers, claimed rather than inherited from its name. */
record(`\`${id}\` claims its own triggers`,
skill.declaredTriggers && skill.triggers.length > 0,
JSON.stringify(skill.triggers));
}
/* ── Reachable from the pages they name ──────────────────────────────────── */
const PAGE_EXPECTATIONS = {
'admin.controlCenter': ['executive-summary', 'staffing-risk', 'operational-risk', 'anomaly-detection', 'attendance-analysis', 'overtime-analysis', 'hiring-pulse-analysis'],
'admin.positions': ['staffing-risk'],
'admin.candidatesList': ['candidate-analysis'],
'admin.hiredHistory': ['hiring-history-analysis'],
'admin.talentPool': ['talent-pool-analysis'],
'admin.forge': ['learning-analysis'],
'admin.analytics': ['workforce-analytics', 'attendance-analysis', 'overtime-analysis', 'hiring-pulse-analysis'],
'admin.activity': ['activity-analysis', 'anomaly-detection', 'operational-risk'],
};
for (const [contextId, expected] of Object.entries(PAGE_EXPECTATIONS)) {
const available = reg.skillsForContext(contextId, [], []).map((s) => s.id);
record(`${contextId.replace('admin.', '')} offers its analysis skills`,
expected.every((id) => available.includes(id)),
expected.filter((id) => !available.includes(id)).join(', ') || `${expected.length} available`);
}
/**
* Profile keeps none, and that is correct.
*
* There is no data source about an account, so a skill there would be a
* placeholder. The emptiness is pinned so a later phase cannot quietly fill it
* to make a list look complete.
*/
record('the Profile page still carries no analysis skill',
reg.skillsForContext('admin.profile', [], []).length === 0,
JSON.stringify(reg.skillsForContext('admin.profile', [], []).map((s) => s.id)));
/* ── Triggers reach their skill, and take nothing that was not theirs ────── */
const TRIGGER_CASES = [
['admin.positions', 'Which roles are at risk?', 'staffing-risk'],
['admin.analytics', 'How is attendance this month?', 'attendance-analysis'],
['admin.analytics', 'How much overtime are we running?', 'overtime-analysis'],
['admin.candidatesList', 'What is the candidate quality like?', 'candidate-analysis'],
['admin.talentPool', 'How healthy is the talent pool?', 'talent-pool-analysis'],
['admin.analytics', 'Show me workforce coverage', 'workforce-analytics'],
['admin.activity', 'Is there anything unusual?', 'anomaly-detection'],
['admin.activity', 'Give me an event breakdown', 'activity-analysis'],
['admin.controlCenter', 'What is the operational risk?', 'operational-risk'],
['admin.controlCenter', 'Give me an executive summary', 'executive-summary'],
['admin.hiredHistory', 'What is our time to hire?', 'hiring-history-analysis'],
['admin.forge', 'How is training progress?', 'learning-analysis'],
['admin.controlCenter', 'What is the hiring pulse?', 'hiring-pulse-analysis'],
];
for (const [contextId, question, expected] of TRIGGER_CASES) {
const matched = reg.matchSkill(question, contextId, [], []);
record(`"${question}" reaches \`${expected}\``,
matched?.id === expected, matched?.id || 'no match');
}
/**
* The check that protects everything already shipped.
*
* Thirteen new definitions across shared pages is the likeliest way to take a
* question that an existing definition — or a page's own reader — was answering.
* Every baseline question is replayed on every page: whatever answered it before
* must still answer it.
*/
const stolen = [];
for (const contextId of Object.keys(expectedBaseline.contexts)) {
const before = expectedBaseline.contexts[contextId];
for (const intent of before.intents) {
const now = reg.matchSkill(intent.question, contextId, [], [])?.id ?? null;
if (now !== intent.matchedSkill) {
stolen.push(`${contextId} "${intent.question}": ${intent.matchedSkill ?? 'page reader'} → ${now}`);
}
}
}
record('no new skill takes a question something else was answering',
stolen.length === 0, stolen.join(' | ') || `${Object.keys(expectedBaseline.contexts).length} contexts replayed`);
/* ── The format stays suitable for non-technical authoring ──────────────── */
/**
* Every analysis definition round-trips through the field writer.
*
* The Markdown is the canonical representation, and a future authoring flow
* will compose it rather than replace it. That only holds if a definition can
* be read into fields and written back without losing what it said — so the
* property is asserted now, while there are thirteen definitions to test it
* against, rather than discovered later.
*/
const lossy = [];
for (const id of ANALYSIS_SKILLS) {
const skill = reg.SKILLS.find((s) => s.id === id);
if (!skill) continue;
const rewritten = fields.patchFrontmatter(skill.markdown, { description: skill.description });
const reparsedSkill = reg.parseSkill(rewritten, { custom: true });
if (reparsedSkill.body !== skill.body) lossy.push(`${id}: body`);
if (JSON.stringify(reparsedSkill.owliver) !== JSON.stringify(skill.owliver)) lossy.push(`${id}: owliver`);
if (JSON.stringify(reparsedSkill.pages) !== JSON.stringify(skill.pages)) lossy.push(`${id}: pages`);
}
record('an analysis definition survives being written back through the field writer',
lossy.length === 0, lossy.join(', ') || `${ANALYSIS_SKILLS.length} definitions`);
record('every analysis skill declares a category for grouping',
ANALYSIS_SKILLS.every((id) => reg.SKILLS.find((s) => s.id === id)?.category),
[...new Set(ANALYSIS_SKILLS.map((id) => reg.SKILLS.find((s) => s.id === id)?.category))].join(', '));
/* ── 15. Agent registry ───────────────────────────────────────────────────
*
* Agents are the layer above skills, and they are read by the *same* parser —
* `parseAgent` imports `parseFrontmatter` and the section readers from the
* skill registry rather than reimplementing them. These checks exist to keep
* that true, and to keep an agent honest about what it carries: an agent that
* names a skill nobody provides is a capability promised and not delivered,
* and it fails silently.
*/
console.log('\n── Agent registry ──');
const agentReg = await server.ssrLoadModule('/src/lib/agents/registry.js');
const agentFields = await server.ssrLoadModule('/src/lib/agents/agentFields.js');
const customAgents = await server.ssrLoadModule('/src/lib/agents/customAgents.js');
const vocab = await server.ssrLoadModule('/src/lib/agents/vocabulary.js');
const agentFiles = readdirSync(join(ROOT, 'src/agents')).filter((f) => f.endsWith('.md'));
record('every .md under src/agents registers',
agentReg.AGENTS.length === agentFiles.length,
`${agentReg.AGENTS.length} registered / ${agentFiles.length} files`);
record('no two agents share an id',
new Set(agentReg.AGENTS.map((a) => a.id)).size === agentReg.AGENTS.length);
const shippedAgents = agentReg.readAgentRegistry([]);
record('shipped agent registry reports no diagnostics',
shippedAgents.diagnostics.length === 0,
shippedAgents.diagnostics.map((d) => d.message).join(' | ') || 'none');
record('every shipped agent registered every field it declared',
agentReg.AGENTS.every((a) => !a.errors?.length),
agentReg.AGENTS.flatMap((a) => a.errors || []).join(' | ') || 'none');
/* The nine the product ships: one per Krow page, plus the root. */
const EXPECTED_AGENTS = {
'krow-workforce-agent': null, // covers every page
'control-center-agent': 'control-center',
'positions-agent': 'positions',
'candidates-agent': 'candidates',
'hired-history-agent': 'hired-history',
'talent-pool-agent': 'talent-pool',
'krow-forge-agent': 'krow-forge',
'analytics-agent': 'analytics',
'activity-agent': 'activity',
};
for (const [id, page] of Object.entries(EXPECTED_AGENTS)) {
const agent = agentReg.getAgent(agentReg.AGENTS, id);
record(`\`${id}\` is registered and published`,
Boolean(agent) && agent.status === 'published',
agent ? agent.status : 'MISSING');
if (agent && page) {
record(`\`${id}\` covers \`${page}\``, agent.pages.includes(page),
JSON.stringify(agent.pages));
}
}
/* The root agent reaches every surface a skill may name, so it can stand in on
a page whose own agent carries nothing. */
const root = agentReg.getAgent(agentReg.AGENTS, 'krow-workforce-agent');
record('the root agent covers every supported page',
surfaces.SUPPORTED_SKILL_PAGES.every((p) => root.pages.includes(p)),
`${root.pages.length}/${surfaces.SUPPORTED_SKILL_PAGES.length}`);
record('the root agent carries the other eight as subagents',
root.subagents.length === 8
&& root.subagents.every((s) => s !== root.id && EXPECTED_AGENTS[s] !== undefined),
JSON.stringify(root.subagents));
/* Every address an agent states must resolve. */
const registeredSkillIds = new Set(reg.SKILLS.map((s) => s.id));
const registeredAgentIds = new Set(agentReg.AGENTS.map((a) => a.id));
record('every skill an agent names exists in the skill registry',
agentReg.AGENTS.every((a) => a.skills.every((s) => registeredSkillIds.has(s))),
agentReg.AGENTS.flatMap((a) => a.skills.filter((s) => !registeredSkillIds.has(s))).join(', ') || 'all resolve');
record('every subagent an agent names exists',
agentReg.AGENTS.every((a) => a.subagents.every((s) => registeredAgentIds.has(s))));
record('every page an agent names is a real surface',
agentReg.AGENTS.every((a) => a.pages.every((p) => surfaces.SUPPORTED_SKILL_PAGES.includes(p))),
agentReg.AGENTS.flatMap((a) => a.pages.filter((p) => !surfaces.SUPPORTED_SKILL_PAGES.includes(p))).join(', ') || 'all resolve');
/**
* No agent carries a placeholder skill.
*
* This replaces an earlier check that pinned four agents at zero skills, which
* was the honest assertion while no skill existed for their pages: the
* temptation when building an agent UI is to invent one so the list looks
* populated. Those skills now exist and are real, so pinning zero would be
* pinning the wrong thing — but the property worth protecting is unchanged, and
* this states it directly instead of by proxy.
*
* A skill is real when it declares a capability whose response binds to a
* registered data source, and that source resolves against the seeded workspace
* without reporting itself unavailable. A definition that names a source the
* product does not have, or one that cannot be read, is a promise the agent
* cannot keep.
*/
const skillById = new Map(reg.SKILLS.map((s) => [s.id, s]));
const placeholders = [];
const unreadable = [];
for (const agent of agentReg.AGENTS) {
for (const skillId of agent.skills) {
const skill = skillById.get(skillId);
if (!skill) continue; /* already reported as `unattached` above */
const responses = Object.values(skill.owliver?.responses || {});
const sources = responses.map((r) => r.source).filter(Boolean);
/**
* Two kinds of real skill, and only one of them reads data.
*
* An *analysis* skill binds capabilities to data sources. An *action* skill
* — `create-position`, `forge-skill-management` — declares actions or a
* guided conversation instead, and carries no responses at all. Both are
* real; a definition that does neither is the placeholder this looks for.
*/
const doesSomething = sources.length > 0
|| (skill.actions || []).length > 0
|| (skill.conversation || []).length > 0;
if (!doesSomething || !sources.every((src) => surfaces.dataSourceFor(src))) {
placeholders.push(`${agent.id}/${skillId}`);
continue;
}
for (const response of responses) {
const declared = surfaces.dataSourceFor(response.source);
/* A source that needs a position or a candidate in context is *correctly*
unavailable when it is handed neither — `resolveEntity` asks the reader
which one at runtime. Only workspace-level sources can be read cold,
so only those are asserted here. */
if (declared?.context) continue;
const read = dataResolver.resolveSkillData(response, AGENT_SKILL_CONTEXT);
/* `unavailable` means the resolver has no reading for that source at all.
`empty` is fine and honest — the source read successfully and found
nothing. */
if (read?.unavailable) unreadable.push(`${agent.id}/${skillId}:${response.source}`);
}
}
}
record('no agent carries a skill without a real data source',
placeholders.length === 0, placeholders.join(', ') || 'every attached skill declares one');
record('every skill an agent carries reads a source the product can resolve',
unreadable.length === 0, unreadable.join(', ') || 'all resolve');
/* Every page agent now carries at least one skill of its own, except where the
product genuinely has none to give it. Stated as data rather than as a
number, so a future page with no source is visible rather than assumed. */
const withoutSkills = agentReg.AGENTS.filter((a) => a.skills.length === 0).map((a) => a.id);
record('every agent that has a skill available to it carries one',
withoutSkills.length === 0, withoutSkills.join(', ') || 'all nine agents carry skills');
/* ── Diagnostics: nothing half-loads in silence ──────────────────────────── */
const UNATTACHED = `---\nid: ghost-agent\nname: Ghost Agent\npages:\n - positions\nskills:\n - no-such-skill\n---\n\n# Ghost\n`;
const withUnattached = agentReg.readAgentRegistry([{ path: 'custom/ghost.md', raw: UNATTACHED }]);
record('an agent naming a missing skill is reported',
withUnattached.diagnostics.some((d) => d.kind === 'unattached' && d.agentId === 'ghost-agent'),
withUnattached.diagnostics.find((d) => d.kind === 'unattached')?.message ?? 'NO DIAGNOSTIC');
const BROKEN_AGENT = `---\nid: broken\n name: bad indent\n---\n# Broken\n`;
const withBrokenAgent = agentReg.readAgentRegistry([{ path: 'custom/broken.md', raw: BROKEN_AGENT }]);
record('an unreadable stored agent is reported, not silently dropped',
withBrokenAgent.diagnostics.some((d) => d.kind === 'unreadable'),
withBrokenAgent.diagnostics.find((d) => d.kind === 'unreadable')?.message ?? 'NO DIAGNOSTIC');
const SHADOW_AGENT = `---\nid: positions-agent\nname: My Positions Agent\npages:\n - positions\n---\n\n# Shadow\n`;
const withShadowAgent = agentReg.readAgentRegistry([{ path: 'custom/shadow.md', raw: SHADOW_AGENT }]);
record('a stored agent overriding a built-in is reported',
withShadowAgent.diagnostics.some((d) => d.kind === 'shadowed' && d.agentId === 'positions-agent'),
withShadowAgent.diagnostics.find((d) => d.kind === 'shadowed')?.message ?? 'NO DIAGNOSTIC');
/* A definition with one bad field keeps the rest AND says so. */
const PARTIAL_AGENT = `---\nid: partial-agent\nname: Partial Agent\npages:\n - positions\nreasoning: telepathy\n---\n\n# Partial\n`;
const withPartialAgent = agentReg.readAgentRegistry([{ path: 'custom/partial.md', raw: PARTIAL_AGENT }]);
const partialAgent = withPartialAgent.agents.find((a) => a.id === 'partial-agent');
record('an agent with one bad field still registers',
Boolean(partialAgent) && partialAgent.pages.includes('positions'));
record('...falls back to the documented default',
partialAgent?.reasoning === 'balanced', partialAgent?.reasoning);
record('...and the field it lost is reported',
withPartialAgent.diagnostics.some((d) => d.kind === 'incomplete' && d.agentId === 'partial-agent'),
withPartialAgent.diagnostics.find((d) => d.kind === 'incomplete')?.message ?? 'NO DIAGNOSTIC');
/* A cycle would make subagent resolution non-terminating. */
const SELF_SUB = `---\nid: loop-agent\nname: Loop Agent\npages:\n - positions\nsubagents:\n - loop-agent\n---\n\n# Loop\n`;
const looped = agentReg.parseAgent(SELF_SUB, { custom: true });
record('an agent cannot be its own subagent',
looped.subagents.length === 0 && looped.errors.length > 0,
looped.errors[0] || 'NOT REPORTED');
/* ── Validation: what the editor refuses ─────────────────────────────────── */
const REFUSALS = [
['an empty definition', ''],
['a malformed id', `---\nid: Not An Id\nname: Bad\npages:\n - positions\n---\n# x\n`],
['no name', `---\nid: no-name\npages:\n - positions\n---\n# x\n`],
['no pages', `---\nid: no-pages\nname: No Pages\n---\n# x\n`],
['an unsupported page', `---\nid: bad-page\nname: Bad Page\npages:\n - the-moon\n---\n# x\n`],
['an unsupported reasoning mode', `---\nid: bad-reason\nname: Bad Reason\npages:\n - positions\nreasoning: vibes\n---\n# x\n`],
['an unsupported permission role', `---\nid: bad-role\nname: Bad Role\npages:\n - positions\npermissions:\n people:\n - user: a@b.com\n role: emperor\n---\n# x\n`],
];
for (const [label, source] of REFUSALS) {
record(`validateAgentSource refuses ${label}`,
Boolean(agentReg.validateAgentSource(source)),
agentReg.validateAgentSource(source) || 'ACCEPTED');
}
/**
* An agent that omits `id:` derives one from its name.
*
* The same fallback `parseSkill` applies, and deliberately not a refusal: an
* explicit id is an address other definitions refer to, so writing one is
* encouraged, but leaving it out means "call it after its name" rather than
* "this file is broken".
*/
const DERIVED_ID = `---\nname: Derived Id Agent\npages:\n - positions\n---\n\n# D\n`;
record('an agent with no `id:` derives one from its name',
agentReg.parseAgent(DERIVED_ID, { custom: true }).id === 'derived-id-agent'
&& agentReg.validateAgentSource(DERIVED_ID) === null,
agentReg.parseAgent(DERIVED_ID, { custom: true }).id);
record('an agent with neither id nor name is refused',
Boolean(agentReg.validateAgentSource(`---\npages:\n - positions\n---\n\n# x\n`)));
record('validateAgentSource accepts a minimal agent',
agentReg.validateAgentSource(`---\nid: minimal\nname: Minimal\npages:\n - positions\n---\n\n# Minimal\n`) === null);
/**
* An agent carrying no skills is accepted.
*
* Deliberately not a refusal, unlike a skill that declares no capabilities.
* A skill with nothing to say cannot answer; an agent with no skills still has
* its page's own reader — which is exactly how Control Center, Hired History,
* Talent Pool and Activity answer today.
*/
record('validateAgentSource accepts an agent with no skills',
agentReg.validateAgentSource(`---\nid: skill-less\nname: Skill-less\npages:\n - control-center\n---\n\n# S\n`) === null);
/* ── Fields ⇄ definition, through the existing writer ────────────────────── */
const rootSource = root.markdown;
const patched = agentFields.applyAgentFields(rootSource, {
name: 'Renamed Agent',
permissions: {
owner: 'owner@krow.app',
access: 'specific',
people: [{ user: 'a@krow.app', role: 'editor' }, { user: 'b@krow.app', role: 'viewer' }],
},
knowledge: [{ id: 'policy', label: 'Overtime policy', kind: 'note', body: 'Beyond 20h needs sign-off.' }],
});
const reparsed = agentReg.parseAgent(patched, { custom: true });
/* The claim the whole editor rests on: `patchFrontmatter` already writes
nested block maps and `- key: value` sequences, so agents needed no second
writer. Asserted rather than assumed. */
record('a nested `permissions.people` list round-trips through the existing writer',
JSON.stringify(reparsed.permissions.people)
=== JSON.stringify([{ user: 'a@krow.app', role: 'editor' }, { user: 'b@krow.app', role: 'viewer' }]),
JSON.stringify(reparsed.permissions.people));
record('a nested `knowledge` entry round-trips too',
reparsed.knowledge.length === 1 && reparsed.knowledge[0].body === 'Beyond 20h needs sign-off.',
JSON.stringify(reparsed.knowledge));
record('patching one field leaves the body untouched',
reparsed.instructions === root.instructions);
record('patching one field leaves the others untouched',
JSON.stringify(reparsed.skills) === JSON.stringify(root.skills)
&& reparsed.subagents.length === root.subagents.length,
`${reparsed.skills.length} skills, ${reparsed.subagents.length} subagents`);
record('the patched definition still validates',
agentReg.validateAgentSource(patched) === null,
agentReg.validateAgentSource(patched) || 'ok');
record('fields read back out match what was written in',
agentFields.agentFieldsFromSource(patched).permissions.access === 'specific');
/* ── Storage round trip ──────────────────────────────────────────────────── */
const template = customAgents.agentTemplate({ id: 'my-agent', name: 'My Agent', pages: ['positions'] });
record('a fresh agent template validates',
agentReg.validateAgentSource(template) === null,
agentReg.validateAgentSource(template) || 'ok');
record('a fresh agent is a draft, never published',
agentReg.parseAgent(template, { custom: true }).status === 'draft',
agentReg.parseAgent(template, { custom: true }).status);
const storedAgent = customAgents.upsertCustomAgent([], template);
record('an authored agent stores as its own Markdown',
storedAgent.next.length === 1 && storedAgent.next[0].raw === template);
record('...and reads back with its id intact',
agentReg.parseAgent(storedAgent.next[0].raw, { custom: true }).id === 'my-agent');
const restored = customAgents.upsertCustomAgent(storedAgent.next, template);
record('re-saving an agent replaces its entry rather than duplicating it',
restored.next.length === 1, `${restored.next.length} stored`);
record('customAgentSource finds a stored definition',
customAgents.customAgentSource(storedAgent.next, 'my-agent') === template);
record('removeCustomAgent drops exactly one entry',
customAgents.removeCustomAgent(storedAgent.next, 'my-agent').length === 0);
/* An unparseable storedAgent entry must not take its neighbours with it. */
const mixed = [{ path: 'custom/bad.md', raw: BROKEN_AGENT }, ...storedAgent.next];
record('a broken stored agent does not remove the good ones',
customAgents.removeCustomAgent(mixed, 'nobody').length === 2);
/* ── Search ─────────────────────────────────────────────────────────────── */
record('an empty search returns every agent',
agentReg.searchAgents(agentReg.AGENTS, '').length === agentReg.AGENTS.length);
record('search matches on name',
agentReg.searchAgents(agentReg.AGENTS, 'analytics').some((a) => a.id === 'analytics-agent'));
record('search matches on what an agent is for',
agentReg.searchAgents(agentReg.AGENTS, 'audit trail').some((a) => a.id === 'activity-agent'));
record('search that matches nothing returns nothing',
agentReg.searchAgents(agentReg.AGENTS, 'zzzznope').length === 0);
/* ── Vocabulary is closed ───────────────────────────────────────────────── */
record('every agent names an icon the product has',
agentReg.AGENTS.every((a) => vocab.AGENT_ICONS.includes(a.icon)),
agentReg.AGENTS.map((a) => a.icon).join(', '));
record('every agent names a supported reasoning mode',
agentReg.AGENTS.every((a) => vocab.SUPPORTED_REASONING.includes(a.reasoning)));
record('every agent has a version of at least 1',
agentReg.AGENTS.every((a) => Number.isInteger(a.version) && a.version >= 1));
record('every agent states when to use it',
agentReg.AGENTS.every((a) => a.trigger.length > 0));
record('every agent states its instructions',
agentReg.AGENTS.every((a) => a.instructions.length > 0));
/* ── The parser is the skill parser ─────────────────────────────────────── */
/* A BOM, CRLF line endings, a blank line above the fence and trailing spaces
after it — the four things that used to take a skill definition down. An
agent gets the same tolerance for free, because it is the same function. */
const HOSTILE = `\r\n\r\n--- \r\nid: hostile-agent\r\nname: Hostile Agent\r\npages:\r\n - positions\r\n--- \r\n\r\n# Hostile\r\n\r\n## Instructions\r\n\r\nStill readable.\r\n`;
const hostile = agentReg.parseAgent(HOSTILE, { custom: true });
record('an agent survives a BOM, CRLF, a leading blank line and trailing spaces',
hostile.id === 'hostile-agent' && hostile.pages.includes('positions'),
`${hostile.id} / ${JSON.stringify(hostile.pages)}`);
record('...and its body still reads',
hostile.instructions.includes('Still readable.'),
hostile.instructions || 'LOST');
/* ── 16. Page boundary: runtime, knowledge and tools ──────────────────────
*
* The one property the whole design rests on: **an agent narrows a page and can
* never widen it.** Every layer added on top — data, knowledge, tools — has to
* inherit that, or selecting an agent becomes a way around the page boundary.
*
* It is asserted exhaustively rather than by example. Every agent is tried on
* every page, and the scoped result must be a subset of what the page offers
* with no agent at all. A subset cannot contain something the page did not have,
* so this is a proof rather than a spot-check.
*/
console.log('\n── Page boundary: runtime, knowledge and tools ──');
const runtime = await server.ssrLoadModule('/src/lib/agents/runtime.js');
/* ── Agent ownership of skill UI ──────────────────────────────────────────
*
* Ownership is opt-in: a skill an agent claims is that agent's, a skill nobody
* claims is unchanged. Found live — the panel's agent list was still being read
* from `preferences.customAgents` after authored agents had moved to the
* definitions store, so an attachment made in Agent Configure was invisible to
* the page and every agent saw every skill's UI.
*/
{
const A = { id: 'agent-a', skills: ['board', 'staffing-risk'] };
const B = { id: 'agent-b', skills: ['staffing-risk'] };
const permits = (id, agent, agents = [A, B]) => runtime.agentPermitsSkill(id, { agents, agent });
record('ownership: a claimed skill is permitted for the agent that claims it',
permits('board', A) === true, 'agent-a claims board');
record('ownership: a claimed skill is refused for an agent that does not',
permits('board', B) === false, 'agent-b does not claim board');
record('ownership: a skill no agent claims is unconstrained',
permits('unowned-skill', A) === true && permits('unowned-skill', B) === true,
'unowned stays visible to every agent');
record('ownership: a skill claimed by both is permitted for both',
permits('staffing-risk', A) === true && permits('staffing-risk', B) === true,
'shared claim');
record('ownership: with no agents loaded nothing is constrained', (() => {
const none = runtime.agentPermitsSkill('board', { agents: [], agent: null });
const undef = runtime.agentPermitsSkill('board', {});
return { pass: none === true && undef === true, detail: `${none} / ${undef}` };
})().pass);
record('ownership: an agent with no skills still sees unowned skills', (() => {
const empty = { id: 'agent-c', skills: [] };
return {
pass: permits('unowned-skill', empty) === true && permits('board', empty) === false,
detail: 'unowned yes, owned-by-another no',
};
})().pass);
record('ownership: the rule is the one the page actually uses', (() => {
const source = readFileSync(join(ROOT, resolveSourcePath('src/components/skills/SkillSurface.jsx')), 'utf8');
return {
pass: /agentPermitsSkill\(skill\.id, scope\)/.test(source)
&& /from '@\/lib\/agents\/runtime'/.test(source),
detail: 'useSkillSections filters through agentPermitsSkill',
};
})().pass);
record('ownership: the panel reads agents from the definitions store', (() => {
/* The live failure: this file read `preferences.customAgents`, which authored
agents had already moved out of, so no attachment was ever visible here. */
const source = readFileSync(join(ROOT, resolveSourcePath('src/components/ai-assistant/AgentContext.jsx')), 'utf8');
return {
pass: /useAgentDefinitions\(\)/.test(source)
&& /sourcesFrom\(/.test(source)
&& !/allAgents\(preferences\.customAgents/.test(source),
detail: 'AgentProvider builds its registry from agent-definitions',
};
})().pass);
}
const contexts = await server.ssrLoadModule('/src/components/ai-assistant/contexts.js');
const knowledge = await server.ssrLoadModule('/src/lib/agents/knowledge.js');
const tools = await server.ssrLoadModule('/src/lib/skills/tools.js');
const agentContext = await server.ssrLoadModule('/src/lib/agents/context.js');
const routingModule = await server.ssrLoadModule('/src/components/ai-assistant/routing.js');
const actions = await server.ssrLoadModule('/src/lib/skills/actions.js');
const ALL_AGENTS = agentReg.AGENTS;
const ALL_SKILLS = reg.SKILLS;
const PAGE_CONTEXTS = Object.keys(expectedBaseline.contexts);
/* ── The invariant, over every page × agent pair ─────────────────────────── */
const widened = [];
const scopedCounts = [];
for (const contextId of PAGE_CONTEXTS) {
const unscoped = reg.skillsForContext(contextId, [], []).map((s) => s.id);
for (const agent of ALL_AGENTS) {
const disabled = runtime.agentScopedDisabledWith(agent, ALL_AGENTS, ALL_SKILLS, []);
const scoped = reg.skillsForContext(contextId, disabled, []).map((s) => s.id);
const extra = scoped.filter((id) => !unscoped.includes(id));
if (extra.length) widened.push(`${contextId} + ${agent.id}: ${JSON.stringify(extra)}`);
scopedCounts.push(scoped.length);
}
}
record(`no agent widens any page (${PAGE_CONTEXTS.length} pages × ${ALL_AGENTS.length} agents)`,
widened.length === 0,
widened.slice(0, 3).join(' | ') || `${PAGE_CONTEXTS.length * ALL_AGENTS.length} combinations, every result a subset`);
/* With no agent at all, nothing is withheld — the pre-agent behaviour. */
record('no agent selected withholds nothing',
PAGE_CONTEXTS.every((contextId) =>
JSON.stringify(reg.skillsForContext(contextId, runtime.agentScopedDisabled(null, ALL_SKILLS, []), []).map((s) => s.id))
=== JSON.stringify(reg.skillsForContext(contextId, [], []).map((s) => s.id))));
record('`agentScopedDisabled(null, …)` returns its input untouched',
JSON.stringify(runtime.agentScopedDisabled(null, ALL_SKILLS, ['a', 'b'])) === JSON.stringify(['a', 'b']));
/* ── The three named cross-boundary cases ────────────────────────────────── */
const CROSS_CASES = [
['Positions page + Analytics Agent', 'admin.positions', 'analytics-agent'],
['Candidates page + Krow Workforce Agent', 'admin.candidatesList', 'krow-workforce-agent'],
['Analytics page + Positions Agent', 'admin.analytics', 'positions-agent'],
];
for (const [label, contextId, agentId] of CROSS_CASES) {
const agent = agentReg.getAgent(ALL_AGENTS, agentId);
const pageOnly = reg.skillsForContext(contextId, [], []).map((s) => s.id);
const disabled = runtime.agentScopedDisabledWith(agent, ALL_AGENTS, ALL_SKILLS, []);
const scoped = reg.skillsForContext(contextId, disabled, []).map((s) => s.id);
/* Data. */
record(`${label}: reads nothing the page does not already offer`,
scoped.every((id) => pageOnly.includes(id)),
`page offers ${JSON.stringify(pageOnly)}, agent sees ${JSON.stringify(scoped)}`);
/* Tools. */
const pageTools = tools.toolsForContext(contextId, [], []).map((t) => t.name);
const agentTools = tools.toolsForContext(contextId, disabled, []).map((t) => t.name);
record(`${label}: reaches no tool the page does not already offer`,
agentTools.every((name) => pageTools.includes(name)),
`page offers ${JSON.stringify(pageTools)}, agent reaches ${JSON.stringify(agentTools)}`);
/* Knowledge. */
const covers = runtime.agentCovers(agent, contextId);
const read = knowledge.retrieveKnowledge({
agent, contextId, question: 'What does the scope note say about pages?',
});
if (!covers) {
record(`${label}: retrieves no knowledge, because the agent is constrained here`,
read.available === false && read.passages.length === 0,
read.note || 'NO NOTE');
} else {
record(`${label}: knowledge stays inside the agent's own entries`,
read.passages.every((p) => (agent.knowledge || []).some((k) => k.id === p.documentId)),
`${read.passages.length} passage(s)`);
}
/* Routing. */
const intent = routingModule.resolveIntent({
question: 'What needs my attention?',
contextId,
disabledSkills: disabled,
agent,
agentCoversPage: covers,
agentSuggestion: runtime.defaultAgentForContext(ALL_AGENTS, contextId),
});
record(`${label}: ${covers ? 'answers from this page' : 'declines honestly instead of reaching'}`,
covers ? intent.kind !== 'constrained' : intent.kind === 'constrained',
`kind=${intent.kind}`);
}
/**
* A constrained agent must not answer *from the pages it does cover*.
*
* The subtlest way to break the boundary: Analytics Agent on Positions could
* plausibly answer an analytics question "because that is what it is for". It
* must not — the page is the boundary, and an agent is a lens on the page in
* front of the reader, never a route to a different one.
*/
const analyticsAgent = agentReg.getAgent(ALL_AGENTS, 'analytics-agent');
const leaked = routingModule.resolveIntent({
question: 'How much overtime are we running?',
contextId: 'admin.positions',
disabledSkills: runtime.agentScopedDisabledWith(analyticsAgent, ALL_AGENTS, ALL_SKILLS, []),
agent: analyticsAgent,
agentCoversPage: false,
agentSuggestion: agentReg.getAgent(ALL_AGENTS, 'positions-agent'),
});
record('a constrained agent does not answer from the pages it covers elsewhere',
leaked.kind === 'constrained' && !leaked.skill,
`kind=${leaked.kind}, skill=${leaked.skill?.id ?? 'none'}`);
record('...and says which agent belongs here instead',
JSON.stringify(leaked.doc).includes('Positions Agent'));
/* ── Tools ──────────────────────────────────────────────────────────────── */
record('every action a handler exists for is described',
tools.undescribedActions().length === 0,
tools.undescribedActions().join(', ') || `${tools.TOOL_NAMES.length} tools described`);
record('a tool that writes a record requires approval',
tools.toolRequiresApproval('create_position') === true);
record('a tool that only navigates does not',
['navigate_to_positions', 'navigate_to_analytics', 'open_related_page']
.every((name) => tools.toolRequiresApproval(name) === false));
record('every mutating tool requires approval',
tools.TOOLS.filter((t) => t.mutates).every((t) => t.requiresApproval),
tools.TOOLS.filter((t) => t.mutates).map((t) => t.name).join(', ') || 'none mutate');
record('every read-only tool is honest about mutating nothing',
tools.TOOLS.filter((t) => t.readOnly).every((t) => t.mutates === null));
/* A tool is reachable only through a skill on this page that declares it. */
const undeclaredTools = [];
for (const contextId of PAGE_CONTEXTS) {
const declared = new Set(
reg.skillsForContext(contextId, [], []).flatMap((s) => s.actions || [])
);
for (const tool of tools.toolsForContext(contextId, [], [])) {
if (!declared.has(tool.name)) undeclaredTools.push(`${contextId}: ${tool.name}`);
}
}
record('a tool is reachable only where a skill on that page declares it',
undeclaredTools.length === 0, undeclaredTools.join(', ') || 'every tool traced to a declaring skill');
/**
* The real action still refuses what a skill did not declare.
*
* `runAction` gated on `skill.actions` before any of this existed, and the tool
* layer describes actions rather than performing them — so the gate must be
* exactly where it was.
*/
const positionsSkill = reg.SKILLS.find((s) => s.id === 'staffing-risk');
record('runAction still refuses an action the skill did not declare',
actions.runAction('create_position', { skill: positionsSkill, draft: {}, status: 'draft' }) === null);
const creator = reg.SKILLS.find((s) => s.id === 'create-position');
record('...and still performs one it did',
actions.runAction('create_position', { skill: creator, draft: { title: 'X' }, status: 'draft' })?.type
=== 'create_position');
record('open_related_page resolves only addresses the product has',
actions.runAction('open_related_page', { skill: { actions: ['open_related_page'] }, page: 'positions' })?.route
=== '/admin/positions'
&& actions.runAction('open_related_page', { skill: { actions: ['open_related_page'] }, page: 'the-moon' }) === null);
/* ── Both conversations, end to end ─────────────────────────────────────── */
/*
* A whole conversation, driven the way the panel drives it.
*
* The flow engine is the one place a wrong answer produces no error at all: an
* unrecognised reply re-renders the summary, which is exactly what a CORRECT
* reply does on the turn before. So the only way to know the confirmation step
* works is to walk one to the end and check that a record came out.
*/
const flowEngine = await server.ssrLoadModule('/src/lib/skills/conversationFlow.js');
const ROLES = ['Bartender', 'Server', 'Line Cook'];
const WORKERS = [
{ id: 'wp-1', name: 'Asha Menon', email: 'asha@example.test' },
{ id: 'wp-2', name: 'Ravi Kumar', email: 'ravi@example.test' },
];
const COMPANIES = ['Fairmont San Jose', 'Taj Coromandel'];
/** Answer a flow's questions in order, and return the last turn. */
function walk(registry, skill, opening, answers, ctx) {
let turn = flowEngine.beginFlow({ registry, question: opening, skill, ctx });
for (const answer of answers) {
if (!turn.flow) break;
turn = flowEngine.advanceFlow({ registry, flow: turn.flow, answer, skill, ctx });
}
return turn;
}
const positionSkill = reg.SKILLS.find((s) => s.id === 'create-position');
const positionFlow = flowsModule.flowFor(positionSkill);
const positionCtx = { roles: ROLES, companies: COMPANIES };
const posted = walk(positionFlow, positionSkill,
'Create a bartender position in Chennai paying $30-$40/hr',
['Fairmont San Jose', 'No minimum', 'Skip', 'None', 'Publish Job Posting'],
positionCtx);
record('the position conversation reaches a record',
Boolean(posted.create),
posted.create ? `status=${posted.create.status}` : `stage=${posted.flow?.stage}, step=${posted.flow?.step}`);
record('...carrying every answer the conversation collected',
posted.create?.draft.company === 'Fairmont San Jose'
&& posted.create?.draft.title === 'Bartender'
&& posted.create?.draft.location === 'Chennai'
&& posted.create?.draft.pay_range_min === '30',
JSON.stringify(posted.create?.draft ?? {}));
record('...and the payload the API receives is unchanged by the split',
JSON.stringify(actions.runAction('create_position',
{ skill: positionSkill, draft: posted.create.draft, status: posted.create.status })?.data.status) === '"active"'
&& actions.runAction('create_position',
{ skill: positionSkill, draft: posted.create.draft, status: posted.create.status })?.data.pay_range_min === 30);
/*
* The confirmation bug, pinned.
*
* "Create position" committed and "create positions" — the plural the Positions
* page itself uses — silently did not: the anchored regex missed it, and the
* reader got the summary back with no clue what was wrong. All four phrasings
* are now the same answer.
*/
const atReview = walk(positionFlow, positionSkill,
'Create a bartender position in Chennai paying $30-$40/hr',
['Fairmont San Jose', 'No minimum', 'Skip', 'None'],
positionCtx);
for (const [phrase, shouldCommit] of [
['Create position', true], ['create positions', true], ['Create a position', true],
['create it', true], ['Publish', true], ['Save as draft', true],
['banana', false], ['no', false],
]) {
const turn = flowEngine.advanceFlow({
registry: positionFlow, flow: atReview.flow, answer: phrase, skill: positionSkill, ctx: positionCtx,
});
record(`confirmation: "${phrase}" ${shouldCommit ? 'commits' : 'does not commit'}`,
Boolean(turn.create) === shouldCommit,
`create=${Boolean(turn.create)}`);
}
record('...and "no" opens the change menu rather than refusing',
flowEngine.advanceFlow({
registry: positionFlow, flow: atReview.flow, answer: 'no', skill: positionSkill, ctx: positionCtx,
}).flow?.stage === 'change');
record('...while "Save as draft" commits the draft status, not the published one',
flowEngine.advanceFlow({
registry: positionFlow, flow: atReview.flow, answer: 'Save as draft', skill: positionSkill, ctx: positionCtx,
}).create?.status === 'draft');
/*
* A request that names no role must settle no title.
*
* "Create new position" used to arrive with the title already set to "New" — a
* value nobody typed, on the one field a position cannot be created without.
* The scaffolding is a PHRASE at least as often as it is a single word, and the
* first fix for this only handled the single word: "create another new position"
* still produced "Nother New", the regex having eaten the "a" of "another".
*
* Both halves are pinned below, because both failed silently — a fabricated
* title reads like an answer, so nothing about it looks wrong until somebody
* notices the position is called "One More".
*/
for (const question of [
'Create position', 'Create a position', 'create positions',
'Create new position', 'create a new position', 'create another new position',
'create a brand new position', 'create one more position', 'create the position',
'open a new role', 'add a new position', 'post a new job',
]) {
const draft = flowEngine.beginFlow({
registry: positionFlow, question, skill: positionSkill, ctx: positionCtx,
}).flow.draft;
record(`"${question}" settles no title`, !draft.title, JSON.stringify(draft));
}
/*
* ...and the fix must not rename a role that legitimately uses one of those
* words. The scaffolding list is stripped to DECIDE whether a phrase named
* anything, never to rewrite what it named — otherwise "second chef" becomes
* "Chef" and the cure is worse than the bug.
*/
for (const [question, want] of [
['create a bartender position', 'Bartender'],
['create a sous chef position', 'Sous Chef'],
['create a second chef position', 'Second Chef'],
['create an open kitchen lead position', 'Open Kitchen Lead'],
['create a fresh produce buyer position', 'Fresh Produce Buyer'],
['create an extra hands supervisor position', 'Extra Hands Supervisor'],
['position for a third cook', 'Third Cook'],
]) {
const draft = flowEngine.beginFlow({
registry: positionFlow, question, skill: positionSkill, ctx: positionCtx,
}).flow.draft;
record(`"${question}" keeps its own words`, draft.title === want, draft.title ?? 'null');
}
/* The employee role: the other conversation, over the same engine. */
const roleSkill = reg.SKILLS.find((s) => s.id === 'create-employee-role');
const roleFlow = flowsModule.flowFor(roleSkill);
const roleCtx = { roles: ROLES, workers: WORKERS };
const NEW_NAME = 'A New Person';
const NEW_EMAIL = 'a-new-person@example.test';
const recorded = walk(roleFlow, roleSkill, 'Create an employee role',
[NEW_NAME, NEW_EMAIL, 'Bartender', '3 years', 'Skip', 'None', '$25-$35/hr', 'Weekends', 'Skip',
'Create employee role'],
roleCtx);
record('the employee-role conversation reaches a record',
Boolean(recorded.create),
recorded.create ? `status=${recorded.create.status}` : `stage=${recorded.flow?.stage}, step=${recorded.flow?.step}`);
/**
* It creates a NEW person, and the name and email are both the caller's.
*
* This flow used to look the name up among the existing workers and record the
* role against the first match, which is wrong twice: a name selects nobody
* when several people share one, and a name nobody had was understood as
* nothing at all — the same question asked again, forever.
*/
record('...for a NEW person, from the name and email the caller gave',
recorded.create?.draft.worker_name === NEW_NAME
&& recorded.create?.draft.worker_email === NEW_EMAIL
&& recorded.create?.draft.role_category === 'Bartender',
JSON.stringify(recorded.create?.draft ?? {}));
record('...and no worker id is carried, because none is chosen',
!recorded.create?.draft.worker_profile_id);
/* The name is the FIRST question and it is not a lookup: a name that matches
nobody moves the conversation on rather than repeating itself. */
const afterName = (answer) => flowEngine.advanceFlow({
registry: roleFlow,
flow: flowEngine.beginFlow({ registry: roleFlow, question: 'Create an employee role', skill: roleSkill, ctx: roleCtx }).flow,
answer, skill: roleSkill, ctx: roleCtx,
});
record('a name nobody has is accepted, and the next question is the email',
afterName('Somebody Entirely New').flow.step === 'worker_email');
record('...and a name that DOES match an existing worker is still just a name',
(() => {
const out = afterName(WORKERS[0].name);
return out.flow.step === 'worker_email'
&& out.flow.draft.worker_name === WORKERS[0].name
&& !out.flow.draft.worker_email
&& !out.flow.draft.worker_profile_id;
})(), 'no existing worker is selected');
/**
* The email is asked for and never invented.
*
* Nothing derives an address from the name, from the operator or from anything
* an earlier conversation collected. An answer that is not an address is
* re-asked; the draft carries no email until the caller types one.
*/
for (const notAnEmail of ['I do not know', 'skip it', 'same as before', 'A New Person']) {
record(`"${notAnEmail}" is not an email, so it is asked for again`,
(() => {
const named = afterName(NEW_NAME);
const out = flowEngine.advanceFlow({
registry: roleFlow, flow: named.flow, answer: notAnEmail, skill: roleSkill, ctx: roleCtx,
});
return out.flow.step === 'worker_email' && !out.flow.draft.worker_email;
})());
}
record('...and nothing in the flow can produce an email on its own',
(() => {
const named = afterName(NEW_NAME).flow;
/* Every field, asked with the name settled and no email given: none of them
may put an address into the draft. */
return !named.worker_email;
})());
/* The `@` tokens resolve from data the caller already holds. */
const firstChips = (registry, skill, ctx) => flowEngine.beginFlow({
registry, question: registry.id === 'position' ? 'Create a position' : 'Create an employee role', skill, ctx,
}).followUp.map((c) => c.label);
record('@companies offers the clients this organization already staffs for',
COMPANIES.every((c) => firstChips(positionFlow, positionSkill, positionCtx).includes(c)),
firstChips(positionFlow, positionSkill, positionCtx).join(', '));
/* The first question offers no chips at all: a new person's name cannot be
suggested from the people already on file, and offering them would be the
lookup this flow no longer does. */
record('the name question suggests nobody',
firstChips(roleFlow, roleSkill, roleCtx).length === 0,
firstChips(roleFlow, roleSkill, roleCtx).join(', ') || 'no chips');
/*
* The chip the server suggests has to be a phrase a skill answers to.
*
* No page context declares `capabilities`, so every server suggestion is
* dispatched as its own TEXT — which means the catalogue's wording and the
* skills' triggers are one coupling with nothing else holding it together. A
* renamed chip would produce a suggestion that opens nothing, silently.
*/
for (const [chip, wantSkill] of [
['Create a company position', 'create-position'],
['Create an employee role', 'create-employee-role'],
]) {
const matched = reg.matchSkill(chip, 'admin.positions', [], []);
record(`the "${chip}" chip is answered by ${wantSkill}`,
matched?.id === wantSkill,
matched?.id ?? `no skill on Positions answers "${chip}"`);
}
/* And the wording actually distinguishes them: the two chips must not both
route to the same conversation, which is the whole reason "company" and
"employee" are in the text the reader clicks. */
record('...and the two create chips route to different conversations',
reg.matchSkill('Create a company position', 'admin.positions', [], [])?.flow
!== reg.matchSkill('Create an employee role', 'admin.positions', [], [])?.flow,
`${reg.matchSkill('Create a company position', 'admin.positions', [], [])?.flow} vs `
+ `${reg.matchSkill('Create an employee role', 'admin.positions', [], [])?.flow}`);
/* ── Knowledge ──────────────────────────────────────────────────────────── */
record('an agent with no knowledge says so rather than returning nothing',
(() => {
const bare = agentReg.getAgent(ALL_AGENTS, 'activity-agent');
const read = knowledge.retrieveKnowledge({ agent: bare, contextId: 'admin.activity', question: 'policy' });
return read.available === false && /no knowledge attached/i.test(read.note || '');
})());
record('no agent is active means no knowledge, stated honestly',
(() => {
const read = knowledge.retrieveKnowledge({ agent: null, question: 'anything' });
return read.available === false && read.passages.length === 0 && Boolean(read.note);
})());
const rootAgent = agentReg.getAgent(ALL_AGENTS, 'krow-workforce-agent');
const found = knowledge.retrieveKnowledge({
agent: rootAgent, contextId: 'admin.positions', question: 'What can this agent see across pages?',
});
record('an agent with knowledge retrieves from its own entries',
found.available === true && found.passages.length > 0,
`${found.passages.length} passage(s)`);
record('every passage names the document it came from',
found.passages.every((p) => p.documentId && p.chunkId && p.text));
record('a question the knowledge does not cover returns nothing, and says so',
(() => {
const miss = knowledge.retrieveKnowledge({
agent: rootAgent, contextId: 'admin.positions', question: 'zzzz quantum bicycles',
});
return miss.available === true && miss.passages.length === 0 && Boolean(miss.note);
})());
record('no knowledge document is invented',
ALL_AGENTS.every((a) => knowledge.knowledgeDocuments(a)
.every((d) => (a.knowledge || []).some((k) => k.id === d.id))),
`${ALL_AGENTS.reduce((n, a) => n + knowledge.knowledgeDocuments(a).length, 0)} declared documents in total`);
/* ── Question classification ────────────────────────────────────────────── */
record('a question about records is structured, never retrieval',
runtime.classifyQuestion({ question: 'Which employees worked more than 20 overtime hours?' }) === 'structured');
record('a question about a document is knowledge',
runtime.classifyQuestion({ question: 'What does our overtime policy say?' }) === 'knowledge');
record('a question needing both is combined',
runtime.classifyQuestion({ question: 'Which employees exceeded the overtime policy this month?' }) === 'combined');
record('an ambiguous question stays structured rather than guessing at retrieval',
runtime.classifyQuestion({ question: 'How is attendance?' }) === 'structured');
/* ── Coverage and defaults ──────────────────────────────────────────────── */
const NATIVE = {
'admin.controlCenter': 'control-center-agent',
'admin.positions': 'positions-agent',
'admin.candidatesList': 'candidates-agent',
'admin.hiredHistory': 'hired-history-agent',
'admin.talentPool': 'talent-pool-agent',
'admin.forge': 'krow-forge-agent',
'admin.analytics': 'analytics-agent',
'admin.activity': 'activity-agent',
};
for (const [contextId, expected] of Object.entries(NATIVE)) {
record(`${contextId.replace('admin.', '')} opens on its own agent`,
runtime.defaultAgentForContext(ALL_AGENTS, contextId)?.id === expected,
runtime.defaultAgentForContext(ALL_AGENTS, contextId)?.id || 'none');
}
record('every page has an agent to open with',
PAGE_CONTEXTS.every((contextId) => runtime.defaultAgentForContext(ALL_AGENTS, contextId)),
PAGE_CONTEXTS.filter((c) => !runtime.defaultAgentForContext(ALL_AGENTS, c)).join(', ') || 'all covered');
record('a requested agent is never silently swapped for another',
(() => {
const turn = runtime.resolveAgentForTurn(ALL_AGENTS, 'analytics-agent', 'admin.positions');
return turn.agent.id === 'analytics-agent' && turn.covers === false && turn.suggestion?.id === 'positions-agent';
})());
/* ── Subagents ──────────────────────────────────────────────────────────── */
/* `subagents` means DELEGATE TO, not borrow from — CLAUDE.md §3 and §6. These
checks pin that, because the opposite behaviour lived here for a long time
and reads as reasonable: a parent silently carried one level of its
subagents' skills, which is a second and incompatible meaning for the key
the backend uses to spawn a delegated run. */
record('an agent carries only its own skills, never a subagent\'s',
(() => {
const a = { id: 'a', skills: ['s1'], subagents: ['b'], status: 'published' };
const b = { id: 'b', skills: ['s2'], subagents: [], status: 'published' };
const carried = runtime.agentSkillIds(a, [a, b]);
return carried.length === 1 && carried[0] === 's1';
})(),
'reaching another agent is delegation, which the runtime does with its own budget and trajectory');
record('the shipped root agent carries exactly what it declares',
runtime.agentSkillIds(rootAgent, ALL_AGENTS).length === new Set(rootAgent.skills).size,
`${new Set(rootAgent.skills).size} declared`);
record('a subagent cycle cannot be walked, because nothing walks subagents here',
(() => {
const a = { id: 'a', skills: ['s1'], subagents: ['b'], status: 'published' };
const b = { id: 'b', skills: ['s2'], subagents: ['a'], status: 'published' };
return runtime.agentSkillIds(a, [a, b]).length === 1;
})(),
'cycles are refused at publish (definition.FindSubagentCycle) and bounded at run time by the depth cap');
/* ── Starters ───────────────────────────────────────────────────────────── */
record('an agent offers no starters on a page it does not cover',
runtime.agentStarters(analyticsAgent, 'admin.positions').length === 0);
record('an agent offers its starters on a page it does cover',
runtime.agentStarters(analyticsAgent, 'admin.analytics').length > 0,
runtime.agentStarters(analyticsAgent, 'admin.analytics').map((s) => s.label).join(' | '));
record('a starter names no capability, so it cannot address an unoffered skill',
ALL_AGENTS.every((a) => runtime.agentStarters(a, null).every((s) => s.capability === null)));
/* ── Context envelope ───────────────────────────────────────────────────── */
const envelope = agentContext.buildOwliverContext({
context: { id: 'admin.positions', page: 'Positions' },
pathname: '/admin/positions',
pageContext: { position: { id: 'job_chef' } },
});
record('the envelope resolves its address from the placement table',
envelope.route === '/admin/positions' && envelope.pageKey === 'positions',
`${envelope.pageKey} @ ${envelope.route}`);
record('a page that publishes only a record still yields a valid envelope',
envelope.position?.id === 'job_chef'
&& Array.isArray(envelope.selectedItems) && envelope.selectedItems.length === 0
&& envelope.period === null,
JSON.stringify({ selected: envelope.selectedItems.length, period: envelope.period }));
record('a page that publishes nothing at all still yields a valid envelope',
(() => {
const bare = agentContext.buildOwliverContext({ context: { id: 'admin.activity', page: 'Activity' } });
return bare.pageKey === 'activity' && bare.position === null && Object.keys(bare.metrics).length === 0;
})());
record('the stored form of the envelope carries no records',
(() => {
const stored = agentContext.storableContext(envelope);
return !('selectedItems' in stored) && !('metrics' in stored) && !('position' in stored)
&& stored.pageKey === 'positions';
})());
/* ── Nothing above changed the unscoped product ─────────────────────────── */
/**
* The regression assertion for the whole phase.
*
* `resolveIntent` gained four parameters. With none of them supplied it must
* behave exactly as it did before — same branch, same skill, same document
* shape — or every existing caller has quietly changed.
*/
const UNSCOPED_CASES = [
['admin.positions', 'Which positions need attention?'],
['admin.candidatesList', 'Who is waiting on a decision?'],
['admin.analytics', 'What is the hiring trend?'],
['admin.activity', 'What happened recently?'],
['admin.profile', 'What are my permissions?'],
];
const drifted = [];
for (const [contextId, question] of UNSCOPED_CASES) {
const out = routingModule.resolveIntent({ question, contextId });
const before = expectedBaseline.contexts[contextId]?.intents.find((i) => i.question === question);
if (out.kind === 'constrained') drifted.push(`${contextId}: became constrained without an agent`);
if (before && out.kind !== before.kind) drifted.push(`${contextId} "${question}": ${before.kind} → ${out.kind}`);
}
record('resolveIntent with no agent behaves exactly as before',
drifted.length === 0, drifted.join(' | ') || `${UNSCOPED_CASES.length} cases unchanged`);
/* ── 17. Agent switcher wiring ────────────────────────────────────────────
*
* The switcher is a React component and there is no component harness here, so
* what is checked is the logic behind it: which agent a page opens on, what the
* list offers, and — the part that matters — that none of it can move the page.
*
* The rendering itself is verified by driving the real application; see the
* Phase 6 report.
*/
console.log('\n── Agent switcher wiring ──');
const icons = await server.ssrLoadModule('/src/components/agents/icons.js');
/* Every icon an agent names resolves, or falls back to the avatar on purpose. */
record('every agent icon resolves to a component or to the avatar',
ALL_AGENTS.every((a) => a.icon === 'owliver' || icons.agentIconFor(a.icon)),
ALL_AGENTS.map((a) => `${a.icon}${icons.agentIconFor(a.icon) ? '' : '(avatar)'}`).join(', '));
record('the primary agent draws the Owliver avatar rather than a glyph',
icons.agentIconFor('owliver') === null);
record('an unknown icon falls back to the avatar rather than crashing',
icons.agentIconFor('not-an-icon') === null);
/* ── The list the switcher renders ──────────────────────────────────────── */
/**
* Agents that work here sort above those that do not.
*
* On a page where two of nine apply, the seven that cannot must not sit above
* them. Order is otherwise the registry's own, so the list does not reshuffle
* as the reader types.
*/
for (const contextId of ['admin.positions', 'admin.analytics', 'admin.activity']) {
const ordered = [...agentReg.searchAgents(ALL_AGENTS, '')]
.sort((a, b) => Number(runtime.agentCovers(b, contextId)) - Number(runtime.agentCovers(a, contextId)));
const firstConstrained = ordered.findIndex((a) => !runtime.agentCovers(a, contextId));
const lastCovering = ordered.map((a) => runtime.agentCovers(a, contextId)).lastIndexOf(true);
record(`${contextId.replace('admin.', '')}: agents that work here are listed first`,
firstConstrained === -1 || lastCovering < firstConstrained,
ordered.slice(0, 3).map((a) => `${a.name}${runtime.agentCovers(a, contextId) ? '' : ' (constrained)'}`).join(', '));
}
record('search reaches every agent by name',
ALL_AGENTS.every((a) => agentReg.searchAgents(ALL_AGENTS, a.name).some((m) => m.id === a.id)));
record('a constrained agent is still listed rather than hidden',
agentReg.searchAgents(ALL_AGENTS, 'Analytics').some((a) => a.id === 'analytics-agent'),
'listed on every page, marked where it does not apply');
/* ── Native agent per page ──────────────────────────────────────────────── */
/**
* A page opens on the agent written for it, and that state is behaviourally
* identical to no agent at all.
*
* The second half is what makes automatic selection safe: a page agent carries
* every skill its own page offers, so selecting it withholds nothing. If that
* ever stopped being true, opening a page would silently lose a capability.
*/
for (const [contextId, expected] of Object.entries(NATIVE)) {
const native = runtime.defaultAgentForContext(ALL_AGENTS, contextId);
const unscoped = reg.skillsForContext(contextId, [], []).map((s) => s.id);
const withNative = reg.skillsForContext(
contextId, runtime.agentScopedDisabledWith(native, ALL_AGENTS, ALL_SKILLS, []), []
).map((s) => s.id);
record(`${contextId.replace('admin.', '')}: opening on its native agent withholds nothing`,
JSON.stringify(unscoped) === JSON.stringify(withNative),
`${expected}: ${withNative.length}/${unscoped.length} skills`);
}
/* ── Switching agent cannot move the page ───────────────────────────────── */
/**
* The protected contract, at the UI seam.
*
* `AgentProvider` is handed the context id and only reads it. There is no
* setter, no navigation and no write-back, so selecting an agent cannot change
* which page the reader is on. Asserted structurally: the page's context id and
* the skills it offers are identical whichever agent is active.
*/
const movedPage = [];
for (const contextId of PAGE_CONTEXTS) {
const pageOnly = reg.skillsForContext(contextId, [], []).map((s) => s.id);
for (const candidate of ALL_AGENTS) {
const turn = runtime.resolveAgentForTurn(ALL_AGENTS, candidate.id, contextId);
/* The context the panel resolves is unchanged by the choice. */
if (turn.agent?.id !== candidate.id) movedPage.push(`${contextId}: ${candidate.id} was swapped`);
/* And the page still offers exactly what it offered. */
const stillOffers = reg.skillsForContext(contextId, [], []).map((s) => s.id);
if (JSON.stringify(stillOffers) !== JSON.stringify(pageOnly)) {
movedPage.push(`${contextId}: page changed under ${candidate.id}`);
}
}
}
record('choosing an agent never changes the page or what it offers',
movedPage.length === 0,
movedPage.slice(0, 3).join(' | ') || `${PAGE_CONTEXTS.length * ALL_AGENTS.length} selections, page unchanged in every one`);
/* ── The constrained state ──────────────────────────────────────────────── */
const constrainedPairs = [];
for (const contextId of PAGE_CONTEXTS) {
for (const candidate of ALL_AGENTS) {
if (runtime.agentCovers(candidate, contextId)) continue;
constrainedPairs.push([contextId, candidate]);
}
}
record('a constrained agent offers no starters',
constrainedPairs.every(([contextId, candidate]) => runtime.agentStarters(candidate, contextId).length === 0),
`${constrainedPairs.length} constrained pairs`);
/**
* A constrained agent does not answer — which is not the same as carrying no
* skills here.
*
* An earlier version of this check asserted the empty list and was wrong.
* Control Center Agent is constrained on Positions, yet it carries
* `staffing-risk`, and `staffing-risk` genuinely *is* a Positions skill. The
* list is non-empty and entirely within the page's boundary — the subset proof
* above already covers that.
*
* What actually protects the reader is that the constrained branch fires before
* any skill is consulted, so the agent declines rather than answering under a
* name that does not belong to this page. That is what is asserted here, over
* every constrained pair rather than a sample.
*/
const answeredWhileConstrained = [];
for (const [contextId, candidate] of constrainedPairs) {
const intent = routingModule.resolveIntent({
question: 'What needs my attention?',
contextId,
disabledSkills: runtime.agentScopedDisabledWith(candidate, ALL_AGENTS, ALL_SKILLS, []),
agent: candidate,
agentCoversPage: false,
agentSuggestion: runtime.defaultAgentForContext(ALL_AGENTS, contextId),
});
if (intent.kind !== 'constrained') {
answeredWhileConstrained.push(`${contextId} + ${candidate.id}: ${intent.kind}`);
}
}
record('a constrained agent declines rather than answering, on every page',
answeredWhileConstrained.length === 0,
answeredWhileConstrained.slice(0, 3).join(' | ') || `${constrainedPairs.length} constrained pairs all decline`);
/* And whatever it does carry is still inside the page's own boundary. */
record('a constrained agent still cannot exceed the page it is constrained on',
constrainedPairs.every(([contextId, candidate]) => {
const pageOnly = reg.skillsForContext(contextId, [], []).map((x) => x.id);
return reg.skillsForContext(contextId, runtime.agentScopedDisabledWith(candidate, ALL_AGENTS, ALL_SKILLS, []), [])
.every((x) => pageOnly.includes(x.id));
}));
record('every constrained pair has a native agent to point at instead',
constrainedPairs.every(([contextId]) => runtime.defaultAgentForContext(ALL_AGENTS, contextId)));
/* ── Starters merge without duplicating a suggestion ────────────────────── */
/**
* A starter worded like a skill's suggestion must yield one chip, not two.
*
* The panel de-duplicates on what a chip *resolves to* as well as its label, and
* an agent starter deliberately carries no capability — so it can never address
* a skill the page has not offered, and a duplicate wording drops out.
*/
const dupes = [];
for (const [contextId] of Object.entries(NATIVE)) {
const native = runtime.defaultAgentForContext(ALL_AGENTS, contextId);
const disabled = runtime.agentScopedDisabledWith(native, ALL_AGENTS, ALL_SKILLS, []);
const starters = runtime.agentStarters(native, contextId);
const suggestions = resolver.owliverSuggestions(contextId, disabled, [], {});
const labels = [...starters, ...suggestions].map((c) => String(c.label).trim().toLowerCase());
const unique = new Set(labels);
if (labels.length !== unique.size) {
/* Not a failure in itself — the panel drops the repeat — but it is worth
knowing which wordings collide. */
dupes.push(`${contextId}: ${labels.length - unique.size}`);
}
}
record('agent starters carry no capability, so they cannot address an unoffered skill',
ALL_AGENTS.every((a) => runtime.agentStarters(a, null).every((s) => s.capability === null)),
dupes.length ? `overlapping wordings de-duplicated on: ${dupes.join(', ')}` : 'no overlapping wordings');
/**
* Every destination the switcher offers must exist.
*
* This check exists because it did not, and a visual review found two footer
* actions navigating to routes the router had no entry for. Tests covered what
* the switcher *computed* and nothing about where it *sent* the reader, which
* is exactly the gap a 404 lives in.
*
* Read out of `App.jsx` rather than asserted against a written list, so a route
* that is renamed or removed fails here rather than in someone's browser.
*/
const appSource = readFileSync(join(ROOT, resolveSourcePath('src/App.jsx')), 'utf8');
/**
* The switcher, if there still is one.
*
* `AgentSwitcher.jsx` was removed when the panel took over agent selection, and
* these two checks read its source to assert properties *of that file*. A
* deleted file has no dead routes and no configure-specific branch, so the
* checks below are vacuously satisfied — but reading it unconditionally threw
* ENOENT and stopped the whole run here, taking every section after it with it.
* Absent is a state to report, not to crash on.
*/
const SWITCHER_PATH = join(ROOT, resolveSourcePath('src/components/ai-assistant/AgentSwitcher.jsx'));
const switcherSource = existsSync(SWITCHER_PATH) ? readFileSync(SWITCHER_PATH, 'utf8') : null;
/* Only live navigations count: a disabled control goes nowhere by design. */
const navigated = switcherSource
? [...switcherSource.matchAll(/navigate\('([^']+)'\)/g)].map((m) => m[1])
: [];
const routed = new Set(
[...appSource.matchAll(/<Route\s+path="([^"]+)"/g)]
.map((m) => m[1])
.filter((path) => path !== '*')
.map((path) => (path.startsWith('/') ? path : `/admin/${path}`))
);
const dead = navigated.filter((to) => !routed.has(to));
record('every route the agent switcher navigates to exists',
dead.length === 0,
!switcherSource ? 'no switcher component — nothing to navigate'
: dead.length ? `DEAD: ${dead.join(', ')}`
: `${navigated.length} destination(s): ${navigated.join(', ')}`);
/**
* Authoring is reachable, and its screen exists.
*
* This asked about the SWITCHER, which no longer exists. The panel header used
* to open a popover listing every agent, Browse all and Create agent; it was
* deliberately reduced to a label, because it was a second place to decide
* which agent answers — competing with the editor, and letting a reader put
* Owliver into a state nothing on the page explained. See AgentBadge.jsx.
*
* Creating an agent moved with it, to the Agents list, which is where
* AgentBadge's own note says this belongs. The property is unchanged and worth
* keeping — offered somewhere, and the route resolves — so it is asserted
* against where the control actually is.
*/
const agentsListSource = readFileSync(join(ROOT, resolveSourcePath('src/pages/admin/WorkspaceAgents.jsx')), 'utf8');
record('creating an agent is reachable, and its screen exists',
/navigate\('\/admin\/workspace\/agents\/new'\)/.test(agentsListSource)
&& routed.has('/admin/workspace/agents/new'),
'offered on the Agents list, and the route resolves');
record('every published agent offers at least one starter where it applies',
ALL_AGENTS.filter((a) => a.status === 'published')
.every((a) => a.pages.length === 0 || runtime.agentStarters(a, null).length > 0),
ALL_AGENTS.map((a) => `${a.id}:${a.starters.length}`).join(' '));
/* ── Removing an agent ───────────────────────────────────────────────────
*
* Removal is permanent, and the two things that make it safe are both
* structural rather than visible: WHICH agents offer it, and that archiving
* never quietly becomes it.
*
* The menu is duplicated between the card view and the table row — two blocks
* of the same JSX — so every property here is asserted against BOTH. A guard
* added to one and forgotten in the other is exactly the shape of bug that
* would ship a delete button on a built-in agent.
*
* `shipped` is the source of truth, from the ids this build bundles. Nothing
* here reads a display name: two agents may legitimately share one.
*/
const removeItems = agentsListSource.match(
/onAction\('remove', agent\)[\s\S]{0,400}?<\/DropdownMenuItem>/g) || [];
record('both agent menus offer removal, and neither offers it unconditionally',
removeItems.length === 2
&& (agentsListSource.match(/\{\(overridden \|\| !shipped\) && \(/g) || []).length === 2,
`${removeItems.length} remove item(s), each behind (overridden || !shipped)`);
record('a custom agent is offered Remove, and a built-in one is only offered a revert',
removeItems.every((block) => / Remove</.test(block) && /Revert to shipped/.test(block)
&& /shipped\s*\?/.test(block)),
'the label is chosen by `shipped`, not by name');
/* Archive is reversible; Remove is not. The menu must not make removal depend
on status, or an archived custom agent becomes unremovable — which would
silently turn Archive into the only exit and leave the row forever. */
record('removal does not depend on an agent status, so an archived custom agent stays removable',
removeItems.every((block) => !/status/.test(block)),
'the remove item reads `shipped`/`overridden` only');
/* Removal is REVERSIBLE, and the confirmation is where a reader learns that.
Copy that promised permanence would be a false statement about what the
button does — the agent is archived, not destroyed — so the wording is
asserted, including the absence of the two phrases that would be lies. */
record('removing a custom agent confirms first, in words that say it is reversible',
/'Remove agent\?'/.test(agentsListSource)
&& /This agent will be removed from the active Agents list\./.test(agentsListSource)
&& /can be restored later from Archived/.test(agentsListSource)
&& /'Remove Agent'/.test(agentsListSource),
'title, description and confirm label');
record('...and the confirmation never claims the removal is permanent',
!/cannot be undone/i.test(agentsListSource)
&& !/permanently (remove|delete)/i.test(agentsListSource),
'no irreversibility language anywhere on the screen');
/* The substance behind that copy: Remove on an authored agent runs ARCHIVE.
Only a shipped agent's own override is deleted, and deleting an override is
how "Revert to shipped" restores the product's definition — it destroys no
agent. If this resolution is ever removed, Remove becomes a hard delete
again and every promise above becomes false. */
record('Remove archives an authored agent rather than deleting it',
/const resolved = action === 'remove' && !isShipped\(agent\.id\) \? 'archive' : action;/
.test(agentsListSource),
'run() resolves remove -> archive for anything not shipped');
record('...and the default list stops showing what has been removed',
/useState\('active'\)/.test(agentsListSource)
&& /if \(status === 'active'\) return found\.filter\(\(a\) => a\.status !== 'archived'\);/
.test(agentsListSource),
'the default filter is active, which excludes archived');
record('...while Archived stays reachable as the place removal is undone',
/setStatus\('archived'\)/.test(agentsListSource)
&& /onAction\('restore', agent\)/.test(agentsListSource),
'the Archived tab and Restore as draft both survive');
/* The tab must be there BEFORE anything is in it. It was rendered behind
`archivedCount > 0`, so the one screen that says where a removed agent went
only appeared once a reader had already removed one and gone looking — and
the confirmation names "Archived" while no such tab is on screen. */
record('...and Archived is offered even when it is empty',
!/\{archivedCount > 0 && \(/.test(agentsListSource)
&& /Archived <span[^>]*>\(\{archivedCount\}\)/.test(agentsListSource),
'the tab is unconditional and shows its count');
record('...and reverting a built-in agent is not worded as a removal',
/'Revert to the shipped definition\?'/.test(agentsListSource)
&& /isShipped\(confirming\?\.agent\?\.id\) \? 'Confirm' : 'Remove Agent'/.test(agentsListSource),
'the shipped path keeps its own copy');
record('a removal that fails is reported rather than swallowed',
/if \(result\?\.error\) toast\.error\(result\.error\)/.test(agentsListSource),
'run() surfaces the error the mutation returned');
/* The gate itself, exercised rather than read: every id this build ships is
`shipped`, so none of them reaches the branch that renders Remove. This is
the same set `useAgents().isShipped` tests against — `AGENTS`, bundled from
the agent definitions under src/agents — so the check moves with the
product. */
const shippedAgentIds = new Set(ALL_AGENTS.map((a) => a.id));
record('every agent this build ships is classified as built-in',
ALL_AGENTS.every((a) => shippedAgentIds.has(a.id)),
`${shippedAgentIds.size} shipped id(s)`);
record('...and an authored agent is not, whatever its status',
['draft', 'published', 'archived'].every((status) => {
const id = `authored-${status}-agent`;
return !shippedAgentIds.has(id);
}),
'draft, published and archived authored ids are all removable');
/* ── Remove and restore, exercised end to end ────────────────────────────
*
* The claim the confirmation makes — "its configuration and skills will be
* preserved and it can be rmRestored later" — is a claim about these two
* functions, so it is tested against them rather than against the sentence.
*
* Both are pure over Markdown and the Markdown is the authoritative artefact,
* so "nothing was lost" is checkable exactly: the source may differ in the
* status line and in nothing else, and the parsed agent may differ in `status`
* and in nothing else. A patch that dropped a tool, renumbered a version or
* rewrote an id would fail here rather than in somebody's account.
*/
const rmLifecycle = await server.ssrLoadModule('/src/lib/agents/agentLifecycle.js');
const rmAuthored = [
'---',
'id: coverage-helper',
'name: Coverage Helper',
'description: Finds and offers cover for open shifts.',
'status: published',
'version: 3',
'pages:',
' - control-center',
' - positions',
'tools:',
' - workforce.coverage',
'skills:',
' - create-position',
'---',
'',
'## Instructions',
'Find the shifts nobody has taken and say who could take them.',
].join('\n');
const rmStatusless = (src) => src.replace(/^status:.*$/m, 'status: <redacted>');
/* `markdown` is excluded because it IS the source, status line and all — the
line above already asserts that source byte-for-byte, so comparing it again
here would only re-report the one difference both checks expect. Every other
parsed field, including tools, skills, pages, instructions and version, is
compared. */
const rmWithoutStatus = (agent) => {
const { status, markdown, ...rest } = agent;
return JSON.stringify(rest);
};
const rmRemoved = rmLifecycle.archiveAgent(rmAuthored);
const rmRestored = rmLifecycle.restoreAgent(rmRemoved);
const rmBefore = agentReg.parseAgent(rmAuthored, { custom: true });
const rmAfterRemove = agentReg.parseAgent(rmRemoved, { custom: true });
const rmAfterRestore = agentReg.parseAgent(rmRestored, { custom: true });
record('remove: an authored agent becomes archived rather than disappearing',
rmAfterRemove.status === 'archived',
`status ${rmBefore.status} -> ${rmAfterRemove.status}`);
record('remove: the definition keeps its identity',
rmAfterRemove.id === rmBefore.id && rmAfterRemove.id === 'coverage-helper',
rmAfterRemove.id);
record('remove: instructions, tools, skills, pages and version all survive',
rmStatusless(rmRemoved) === rmStatusless(rmAuthored)
&& rmWithoutStatus(rmAfterRemove) === rmWithoutStatus(rmBefore),
'the source differs in the status line and nothing else');
record('restore: the SAME agent comes back, as a draft',
rmAfterRestore.id === rmBefore.id && rmAfterRestore.status === 'draft',
`${rmAfterRestore.id} @ ${rmAfterRestore.status}`);
record('restore: nothing was lost on the round trip',
rmStatusless(rmRestored) === rmStatusless(rmAuthored)
&& rmWithoutStatus(rmAfterRestore) === rmWithoutStatus(rmBefore),
'archive -> restore is lossless apart from status');
/* Removing something already archived must not escalate into anything else:
it is still that same archived definition afterwards. */
const rmRemovedTwice = rmLifecycle.archiveAgent(rmRemoved);
record('remove: removing an already-archived agent leaves it archived and whole',
agentReg.parseAgent(rmRemovedTwice, { custom: true }).status === 'archived'
&& rmStatusless(rmRemovedTwice) === rmStatusless(rmAuthored),
'idempotent, and still non-destructive');
/* ── 18. Conversation records and insights ───────────────────────────────
*
* Conversations gained fields: which agent answered, where, what it used, and
* how it was rated. The risk in changing a stored shape is not that the new
* records are wrong — it is that the old ones quietly stop reading, and a
* reader's history disappears without anything saying so.
*
* So the migration is asserted first, and the figures built on top are asserted
* to describe only records that exist.
*/
console.log('\n── Conversation records and insights ──');
const insightsSelectors = await server.ssrLoadModule('/src/lib/agents/conversationInsights.js');
const historyModule = await server.ssrLoadModule('/src/components/ai-assistant/history.js');
/* `history.js` writes to localStorage, which does not exist under SSR. A
minimal in-memory stand-in lets the real module be exercised rather than a
reimplementation of it — the migration is the thing under test, and a mock of
it would reproduce none of the failures this section exists for. */
const historyStore = new Map();
globalThis.localStorage = {
getItem: (k) => (historyStore.has(k) ? historyStore.get(k) : null),
setItem: (k, v) => historyStore.set(k, String(v)),
removeItem: (k) => historyStore.delete(k),
clear: () => historyStore.clear(),
};
const HISTORY_KEY = 'krow_assistant:history';
const seedHistory = (records) => historyStore.set(HISTORY_KEY, JSON.stringify(records));
/* ── A conversation held before any of this existed ──────────────────────── */
const V1_RECORD = {
id: 'c_old',
contextId: 'admin.positions',
page: 'Positions',
title: 'Which positions need attention?',
turns: 2,
updatedAt: new Date().toISOString(),
messages: [
{ role: 'user', text: 'Which positions need attention?' },
{ role: 'assistant', blocks: [{ type: 'text', text: 'Three roles need attention.' }] },
],
};
seedHistory([V1_RECORD]);
const migrated = historyModule.readHistory();
record('a conversation stored before agents existed still reads',
migrated.length === 1 && migrated[0].id === 'c_old',
`${migrated.length} record(s)`);
record('...and its messages are returned byte-identical',
JSON.stringify(migrated[0].messages) === JSON.stringify(V1_RECORD.messages));
record('...with the new fields present and honestly empty',
migrated[0].schema === 2
&& migrated[0].agentId === null
&& migrated[0].feedback === null
&& Array.isArray(migrated[0].skillsUsed) && migrated[0].skillsUsed.length === 0,
JSON.stringify({
schema: migrated[0].schema, agentId: migrated[0].agentId, skills: migrated[0].skillsUsed,
}));
record('migration does not rewrite the stored copy',
JSON.parse(historyStore.get(HISTORY_KEY))[0].schema === undefined,
'read-time migration, so nothing can fail half-written');
/* ── A conversation held now ─────────────────────────────────────────────── */
historyStore.clear();
historyModule.saveConversation({
id: 'c_new',
contextId: 'admin.positions',
page: 'Positions',
agentId: 'positions-agent',
pageContext: { page: 'Positions', pageKey: 'positions', route: '/admin/positions', period: null },
skillsUsed: ['staffing-risk', 'staffing-risk'],
toolsUsed: ['create_position'],
knowledgeUsed: [],
messages: [
{ role: 'user', text: 'Which roles are at risk?' },
{ role: 'assistant', blocks: [{ type: 'text', text: 'Three.' }] },
],
});
const [savedConversation] = historyModule.readHistory();
record('a conversation records which agent answered',
savedConversation.agentId === 'positions-agent', savedConversation.agentId);
record('...what it used, deduplicated',
JSON.stringify(savedConversation.skillsUsed) === JSON.stringify(['staffing-risk'])
&& JSON.stringify(savedConversation.toolsUsed) === JSON.stringify(['create_position']),
`${JSON.stringify(savedConversation.skillsUsed)} / ${JSON.stringify(savedConversation.toolsUsed)}`);
/**
* The stored context is the *reduced* envelope.
*
* Selections and computed figures are records; writing them per turn would put
* the dataset into localStorage a message at a time. What a reviewer needs
* later is where the question was asked, not a copy of what was on screen.
*/
record('the stored page context carries no records',
!('selectedItems' in savedConversation.pageContext) && !('metrics' in savedConversation.pageContext)
&& !('position' in savedConversation.pageContext) && savedConversation.pageContext.pageKey === 'positions',
Object.keys(savedConversation.pageContext).join(', '));
/* ── Feedback ───────────────────────────────────────────────────────────── */
historyModule.recordFeedback('c_new', { rating: 'up' });
record('a conversation can be rated', historyModule.readHistory()[0].feedback?.rating === 'up');
historyModule.recordFeedback('c_new', { rating: 'down', note: 'Missed the chef role' });
const rerated = historyModule.readHistory();
record('re-rating corrects rather than appends',
rerated.length === 1 && rerated[0].feedback.rating === 'down' && rerated[0].feedback.note === 'Missed the chef role',
`${rerated.length} record(s), rating=${rerated[0].feedback.rating}`);
historyModule.recordFeedback('c_new', null);
record('clearing a rating leaves none behind, not a neutral one',
historyModule.readHistory()[0].feedback === null);
record('rating a conversation that does not exist changes nothing',
historyModule.recordFeedback('c_nope', { rating: 'up' }).length === 1);
/* A rating survives the thread growing. */
historyModule.recordFeedback('c_new', { rating: 'up' });
historyModule.saveConversation({
id: 'c_new',
contextId: 'admin.positions',
page: 'Positions',
agentId: 'positions-agent',
messages: [
{ role: 'user', text: 'Which roles are at risk?' },
{ role: 'assistant', blocks: [{ type: 'text', text: 'Three.' }] },
{ role: 'user', text: 'And the chef role?' },
],
});
record('a rating survives the conversation continuing',
historyModule.readHistory()[0].feedback?.rating === 'up'
&& historyModule.readHistory()[0].turns === 2,
`rating kept across ${historyModule.readHistory()[0].turns} turns`);
/* ── Insight selectors ──────────────────────────────────────────────────── */
const nowMs = Date.now();
const day = (n) => new Date(nowMs - n * 86400000).toISOString();
const FIXTURE = [
{ id: 'a1', agentId: 'positions-agent', contextId: 'admin.positions', page: 'Positions', turns: 3, skillsUsed: ['staffing-risk'], toolsUsed: [], feedback: { rating: 'up' }, updatedAt: day(0), messages: [{ role: 'user', text: 'x' }] },
{ id: 'a2', agentId: 'positions-agent', contextId: 'admin.positions', page: 'Positions', turns: 1, skillsUsed: ['staffing-risk', 'create-position'], toolsUsed: ['create_position'], feedback: { rating: 'down' }, updatedAt: day(1), messages: [{ role: 'user', text: 'x' }] },
{ id: 'a3', agentId: 'analytics-agent', contextId: 'admin.analytics', page: 'Analytics', turns: 2, skillsUsed: ['overtime-analysis'], toolsUsed: [], feedback: null, updatedAt: day(1), messages: [{ role: 'user', text: 'x' }] },
];
/**
* Nothing recorded is a different answer from nothing happening.
*
* A row of zeros reads as "the agent was asked and did nothing". The empty flag
* is what lets a view say "not asked yet" instead — which is the whole reason
* Insights can be honest before any conversation exists.
*/
const none = insightsSelectors.conversationStats([]);
record('no conversations reports empty rather than zeros',
none.empty === true && none.total === 0 && none.feedback.score === null,
`score=${none.feedback.score} (null, not 0)`);
const stats = insightsSelectors.conversationStats(FIXTURE);
record('conversation counts match the records', stats.total === 3 && stats.turns === 6,
`${stats.total} conversations, ${stats.turns} turns`);
record('pages counted are the distinct contexts', stats.pages === 2, `${stats.pages} pages`);
record('feedback counts every rating and every absence',
stats.feedback.up === 1 && stats.feedback.down === 1 && stats.feedback.unrated === 1
&& stats.feedback.score === 50,
JSON.stringify(stats.feedback));
record('skills are tallied across conversations, most used first',
JSON.stringify(stats.bySkill) === JSON.stringify([
{ id: 'staffing-risk', count: 2 }, { id: 'create-position', count: 1 }, { id: 'overtime-analysis', count: 1 },
]),
JSON.stringify(stats.bySkill));
record('tools are tallied too',
JSON.stringify(stats.byTool) === JSON.stringify([{ id: 'create_position', count: 1 }]));
record('conversations are grouped per agent',
JSON.stringify(stats.byAgent) === JSON.stringify([
{ id: 'positions-agent', count: 2 }, { id: 'analytics-agent', count: 1 },
]));
record('a day nothing was asked is not charted as a zero',
stats.byDay.length === stats.activeDays && stats.byDay.every((d) => d.count > 0),
`${stats.byDay.length} active day(s)`);
record('stats can be scoped to one agent',
insightsSelectors.conversationStats(FIXTURE, { agentId: 'analytics-agent' }).total === 1);
record('an agent with no conversations reports empty, not zero',
insightsSelectors.conversationStats(FIXTURE, { agentId: 'activity-agent' }).empty === true);
record('stats can be windowed by date',
insightsSelectors.conversationStats(FIXTURE, { since: day(0.5) }).total === 1,
`${insightsSelectors.conversationStats(FIXTURE, { since: day(0.5) }).total} in the last 12 hours`);
record('conversationsForAgent narrows without mutating the input',
insightsSelectors.conversationsForAgent(FIXTURE, 'positions-agent').length === 2
&& FIXTURE.length === 3);
record('unrated conversations are the review queue',
insightsSelectors.unratedConversations(FIXTURE).map((r) => r.id).join(',') === 'a3');
/**
* A review row carries no thread.
*
* A list renders forty of these and one is ever opened; including the messages
* would load every conversation to draw a table.
*/
const row = insightsSelectors.reviewRow(FIXTURE[0]);
record('a review row omits the thread itself',
!('messages' in row) && row.id === 'a1' && row.page === 'Positions',
Object.keys(row).join(', '));
/* ── Nothing here invents a figure ──────────────────────────────────────── */
const invented = [];
for (const key of ['total', 'turns', 'pages']) {
if (insightsSelectors.conversationStats([])[key] !== 0) invented.push(key);
}
for (const entry of [...stats.bySkill, ...stats.byTool, ...stats.byAgent]) {
const real = FIXTURE.some((r) => [...r.skillsUsed, ...r.toolsUsed, r.agentId].includes(entry.id));
if (!real) invented.push(entry.id);
}
record('every figure traces to a record that exists',
invented.length === 0, invented.join(', ') || 'nothing invented');
delete globalThis.localStorage;
/* ── 19. Agent management ─────────────────────────────────────────────────
*
* The management screens let someone who has never seen a Markdown file
* create, configure and publish an agent. What makes that safe is that they are
* not a second agent system: every screen writes fields, `agentPatch` turns
* those into frontmatter, and the existing parser reads them back.
*
* So what is checked here is the round trip — a form edit must survive being
* written and re-read — and the lifecycle rules that protect a published agent.
*/
console.log('\n── Agent management ──');
const lifecycle = await server.ssrLoadModule('/src/lib/agents/agentLifecycle.js');
const shippedSource = agentReg.getAgent(ALL_AGENTS, 'positions-agent').markdown;
/* ── Fields survive the round trip ──────────────────────────────────────── */
/**
* The property the whole management UI rests on.
*
* A form holds fields; storage holds Markdown. If a field could not survive
* being written and read back, configuring an agent would silently lose part of
* it — and the loss would only show up later, in an answer that did not happen.
*/
const EDITS = {
name: 'Renamed Positions Agent',
description: 'A different description.',
trigger: 'Use when roles are not filling.',
instructions: 'Answer about open roles only.\n\nAsk which role when none is open.',
icon: 'briefcase',
reasoning: 'deep',
webSearch: true,
pages: ['positions', 'control-center'],
skills: ['staffing-risk', 'create-position'],
subagents: ['analytics-agent'],
starters: [{ label: 'Which roles are at risk?', prompt: 'Which roles are at risk?' }],
knowledge: [{ id: 'policy', label: 'Fill policy', kind: 'note', body: 'A role open 30 days is escalated.', url: '' }],
permissions: { owner: 'demo@krow.app', access: 'specific', people: [{ user: 'a@krow.app', role: 'editor' }] },
};
const composed = agentFields.applyAgentFields(shippedSource, EDITS);
const readBack = agentFields.agentFieldsFromSource(composed);
const lost = [];
for (const [key, value] of Object.entries(EDITS)) {
if (JSON.stringify(readBack[key]) !== JSON.stringify(value)) {
lost.push(`${key}: wrote ${JSON.stringify(value)}, read ${JSON.stringify(readBack[key])}`);
}
}
record('every configurable field survives being written and read back',
lost.length === 0, lost.slice(0, 2).join(' | ') || `${Object.keys(EDITS).length} fields`);
record('the composed definition is valid',
agentReg.validateAgentSource(composed) === null,
agentReg.validateAgentSource(composed) || 'ok');
record('configuring an agent never has to touch Markdown',
/^---/.test(composed) && agentReg.parseAgent(composed, { custom: true }).name === EDITS.name,
'fields in, frontmatter out, parsed by the one parser');
/* Editing one field leaves the rest of the file alone, including its prose. */
const oneField = agentFields.applyAgentFields(shippedSource, { description: 'Just this.' });
const before8 = agentReg.parseAgent(shippedSource, { custom: true });
const after8 = agentReg.parseAgent(oneField, { custom: true });
record('editing one field leaves the others untouched',
after8.description === 'Just this.'
&& JSON.stringify(after8.skills) === JSON.stringify(before8.skills)
&& after8.instructions === before8.instructions,
`${after8.skills.length} skills and the instructions kept`);
/* ── Lifecycle ──────────────────────────────────────────────────────────── */
record('a duplicate is always a draft at v1',
(() => {
const copy = agentReg.parseAgent(
lifecycle.duplicateAgent(shippedSource, { existingIds: ALL_AGENTS.map((a) => a.id) }),
{ custom: true }
);
return copy.status === 'draft' && copy.version === 1 && copy.id !== 'positions-agent';
})(),
'a copy of a published agent must not enter the switcher unreviewed');
record('a duplicate keeps what the original carried',
(() => {
const copy = agentReg.parseAgent(
lifecycle.duplicateAgent(shippedSource, { existingIds: [] }), { custom: true }
);
return JSON.stringify(copy.skills) === JSON.stringify(before8.skills)
&& copy.instructions === before8.instructions;
})());
record('a duplicate never collides with an existing id',
(() => {
const taken = ALL_AGENTS.map((a) => a.id);
const first = agentReg.parseAgent(lifecycle.duplicateAgent(shippedSource, { existingIds: taken }), { custom: true });
const second = agentReg.parseAgent(
lifecycle.duplicateAgent(shippedSource, { existingIds: [...taken, first.id] }), { custom: true }
);
return first.id !== second.id;
})());
record('archiving takes an agent out of service without altering it',
(() => {
const archived = agentReg.parseAgent(lifecycle.archiveAgent(shippedSource), { custom: true });
return archived.status === 'archived'
&& JSON.stringify(archived.skills) === JSON.stringify(before8.skills);
})());
record('restoring brings it back as a draft, not straight back into service',
agentReg.parseAgent(lifecycle.restoreAgent(lifecycle.archiveAgent(shippedSource)), { custom: true })
.status === 'draft');
/**
* Publishing must never discard a version somebody else published.
*
* The failure it prevents is silent: a draft taken from v1 published over a v2
* looks exactly like the v2 change never having been made.
*/
record('publishing a draft moves it into service',
(() => {
const draft = lifecycle.restoreAgent(shippedSource);
const out = lifecycle.publishAgent(draft);
return !out.conflict && agentReg.parseAgent(out.source, { custom: true }).status === 'published';
})());
record('republishing a published agent moves its version on',
(() => {
const out = lifecycle.publishAgent(shippedSource);
return agentReg.parseAgent(out.source, { custom: true }).version === before8.version + 1;
})(),
'so "what is live" is always a specific version');
record('publishing over a newer version is refused, not silently applied',
(() => {
const out = lifecycle.publishAgent(lifecycle.restoreAgent(shippedSource), { publishedVersion: 5 });
return Boolean(out.conflict) && !out.source;
})(),
'a conflict the screen can explain, rather than a lost change');
/* ── An agent created from nothing ──────────────────────────────────────── */
/**
* The path a non-technical author actually takes: a blank template, filled in
* through the form, saved. It has to produce a definition the runtime accepts.
*/
const fresh = agentFields.applyAgentFields(customAgents.agentTemplate(), {
id: 'hr-helper',
name: 'HR Helper',
description: 'Answers hiring questions for the HR team.',
trigger: 'Use on Candidates for pipeline questions.',
instructions: 'Answer from candidate records on this page.',
icon: 'users',
reasoning: 'balanced',
pages: ['candidates'],
skills: ['candidate-analysis'],
starters: [{ label: 'How strong is the pool?', prompt: 'How strong is the pool?' }],
});
record('an agent created entirely through the form is valid',
agentReg.validateAgentSource(fresh) === null,
agentReg.validateAgentSource(fresh) || 'ok');
const freshAgent = agentReg.parseAgent(fresh, { custom: true });
record('...and starts as a draft rather than live',
freshAgent.status === 'draft', freshAgent.status);
record('...and registers alongside the shipped ones',
(() => {
const { agents, diagnostics } = agentReg.readAgentRegistry([{ path: 'custom/hr-helper.md', raw: fresh }]);
return agents.some((a) => a.id === 'hr-helper') && diagnostics.length === 0;
})(),
'no diagnostics, so nothing it declared was dropped');
record('...and is bounded by the page exactly like a shipped agent',
(() => {
const disabled = runtime.agentScopedDisabledWith(freshAgent, ALL_AGENTS, ALL_SKILLS, []);
const pageOnly = reg.skillsForContext('admin.candidatesList', [], []).map((s) => s.id);
const scoped = reg.skillsForContext('admin.candidatesList', disabled, []).map((s) => s.id);
/* And it reaches nothing at all on a page it does not cover. */
const elsewhere = reg.skillsForContext('admin.analytics', disabled, []).map((s) => s.id);
return scoped.every((x) => pageOnly.includes(x)) && elsewhere.length === 0;
})(),
'a user-created agent gets the same boundary, not a weaker one');
/* ── Editing a shipped agent overrides rather than mutates ───────────────── */
record('editing a shipped agent is reported as an override',
(() => {
const { diagnostics } = agentReg.readAgentRegistry([{ path: 'custom/positions-agent.md', raw: composed }]);
return diagnostics.some((d) => d.kind === 'shadowed' && d.agentId === 'positions-agent');
})(),
'the shipped definition is never altered on disk');
record('the shipped definition is still intact after an override',
agentReg.AGENTS.find((a) => a.id === 'positions-agent').name === 'Positions Agent');
/* ── The Add Skills modal reads the one registry ────────────────────────── */
/**
* Asserted against the registry rather than the component, because the failure
* worth preventing is architectural: a separate list for agents would drift
* from the one Owliver runs, and an agent would offer a skill the runtime does
* not have.
*/
const attachable = reg.skillsWithFacet(reg.allSkills([]), 'owliver').filter((s) => s.status === 'active');
record('every attachable skill comes from the shared registry',
attachable.every((s) => reg.SKILLS.some((r) => r.id === s.id)),
`${attachable.length} attachable`);
record('workforce training paths are not offered as agent skills',
attachable.every((s) => s.kind !== 'workforce'),
'a training path is something a person learns, not something an agent does');
record('every category offered by the picker matches at least one skill',
(() => {
const categories = [...new Set(attachable.map((s) => s.category).filter(Boolean))];
return categories.every((c) => attachable.some((s) => s.category === c));
})(),
[...new Set(attachable.map((s) => s.category).filter(Boolean))].join(', '));
/* ── Every management destination exists ────────────────────────────────── */
const managementRoutes = ['/admin/workspace/agents', '/admin/workspace/agents/new'];
const appRoutes = new Set(
[...readFileSync(join(ROOT, resolveSourcePath('src/App.jsx')), 'utf8').matchAll(/<Route\s+path="([^"]+)"/g)]
.map((m) => m[1])
.map((path) => (path.startsWith('/') ? path : `/admin/${path}`))
);
record('every agent management route is registered',
managementRoutes.every((r) => appRoutes.has(r)),
managementRoutes.filter((r) => !appRoutes.has(r)).join(', ') || managementRoutes.join(', '));
/**
* Every surface a skill can be attached to has to be somewhere a person can
* stand.
*
* `surfaces.js` is the closed vocabulary: a skill declares a page, the page
* carries a route, and Owliver's placement table keys off the same route. A
* surface whose route is not in the router is a page that can be authored
* against, targeted by a skill and composed into a node tree — and then 404s.
* That is exactly what happened to `candidates-analysis`, which was imported by
* `App.jsx` and never routed. One assertion closes the class.
*/
record('every surface route is registered in the router',
surfaces.SKILL_SURFACES.every((s) => !s.route || appRoutes.has(s.route)),
surfaces.SKILL_SURFACES.filter((s) => s.route && !appRoutes.has(s.route)).map((s) => s.id).join(', ')
|| `${surfaces.SKILL_SURFACES.filter((s) => s.route).length} routes reachable`);
record('the dynamic agent route is registered after the static one',
(() => {
const source = readFileSync(join(ROOT, resolveSourcePath('src/App.jsx')), 'utf8');
return source.indexOf('workspace/agents/new') < source.indexOf('workspace/agents/:id');
})(),
'so `agents/new` cannot be read as an agent whose id is "new"');
/* ── 20. Owliver on the agent configuration screen ────────────────────────
*
* Configure is a workspace page, not an operational one, and it is now a host
* for the *existing* Owliver rather than a second chat. Two things have to hold
* and they pull in opposite directions:
*
* - Owliver must actually mount there, at both addresses.
* - Standing there must not become standing on an operational page — most
* sharply when the agent being *edited* is an operational agent.
*
* The second is the one worth testing hardest: configuring the Analytics Agent
* must not put a reader on Analytics.
*/
console.log('\n── Owliver on Agent Configure ──');
const CONFIGURE_CONTEXT = 'admin.agentConfigure';
/* ── It mounts, at both addresses ────────────────────────────────────────── */
for (const route of ['/admin/workspace/agents/new', '/admin/workspace/agents/analytics-agent']) {
const resolved = placement.resolveAssistantContext('admin', route);
record(`Owliver mounts on \`${route}\``,
resolved?.id === CONFIGURE_CONTEXT, resolved?.id || 'no panel');
}
/* This used to assert the context carried a `respond` function and a list of
capabilities. Both were the local simulator's, and both went with it. The
property it was really guarding — that Agent Configure reuses a context from
the shared table rather than declaring a panel of its own — survives, and is
what is asserted now. */
record('the configure context is the one Owliver already uses, not a new panel',
Boolean(contexts.ASSISTANT_CONTEXTS[CONFIGURE_CONTEXT])
&& Boolean(contexts.ASSISTANT_CONTEXTS[CONFIGURE_CONTEXT]?.page)
&& Array.isArray(contexts.ASSISTANT_CONTEXTS[CONFIGURE_CONTEXT]?.topics),
'a context in the existing table, resolved by the existing placement');
/**
* The rest of the workspace hosts Owliver too — under its *own* context.
*
* These two checks used to assert the opposite: that the agents list and the
* other workspace routes carried no panel at all. That was right while Agent
* Configure was the only workspace host, and wrong as a general rule — it left
* Owliver dead on every configuration surface in the product, on the reasoning
* that a page with no specialist agent is a page with no assistant. It is not.
*
* What still matters, and is what these now assert, is that each one resolves
* to *its own* context rather than borrowing Agent Configure's: the page a
* reader is standing on is never something another page's context describes.
*/
const WORKSPACE_HOSTS = {
'/admin/settings': 'admin.settings',
'/admin/workspace': 'admin.workspace',
'/admin/workspace/agents': 'admin.workspaceAgents',
'/admin/workspace/skills': 'admin.workspaceSkills',
'/admin/workspace/skill-development': 'admin.skillDevelopment',
'/admin/workspace/skills/new': 'admin.skillConfigure',
'/admin/workspace/skills/owliver/new': 'admin.skillConfigure',
'/admin/workspace/skills/my-skill': 'admin.skillConfigure',
'/admin/workspace/skills/owliver/my-skill': 'admin.skillConfigure',
};
const misplaced = Object.entries(WORKSPACE_HOSTS)
.filter(([route, id]) => placement.resolveAssistantContext('admin', route)?.id !== id)
.map(([route, id]) => `${route}: expected ${id}, got ${placement.resolveAssistantContext('admin', route)?.id ?? 'no panel'}`);
record('every workspace surface hosts Owliver under its own context',
misplaced.length === 0,
misplaced.join(' | ') || `${Object.keys(WORKSPACE_HOSTS).length} routes`);
record('the agents list does not borrow the configure context',
placement.resolveAssistantContext('admin', '/admin/workspace/agents')?.id === 'admin.workspaceAgents');
/**
* The pattern must not swallow addresses beneath it.
*
* One segment after `agents/`, and no deeper. A nested route added later would
* otherwise silently inherit this panel.
*/
record('the dynamic pattern matches one segment only',
placement.resolveAssistantContext('admin', '/admin/workspace/agents/x/y') === null
&& placement.resolveAssistantContext('admin', '/admin/workspace/agents/x') !== null);
/* ── The eight operational pages are untouched ───────────────────────────── */
/**
* Exact matching still happens first, so none of the eight ever reaches the
* pattern table. Asserted rather than assumed, because "I added a fallback" is
* exactly the change that quietly re-routes something.
*/
const OPERATIONAL = {
'/admin': 'admin.controlCenter',
'/admin/positions': 'admin.positions',
'/admin/candidates': 'admin.candidatesList',
'/admin/hired': 'admin.hiredHistory',
'/admin/talent-pool': 'admin.talentPool',
'/admin/university': 'admin.forge',
'/admin/analytics': 'admin.analytics',
'/admin/activity': 'admin.activity',
};
const rerouted = Object.entries(OPERATIONAL)
.filter(([route, expectedId]) => placement.resolveAssistantContext('admin', route)?.id !== expectedId);
record('all eight operational pages resolve exactly as before',
rerouted.length === 0, rerouted.map(([r]) => r).join(', ') || '8 pages unchanged');
/* ── The critical boundary ──────────────────────────────────────────────── */
/**
* Editing the Analytics Agent does not put the reader on Analytics.
*
* The edited agent is a record being changed, not the page anyone is standing
* on. Two independent reasons this holds, both asserted: no skill declares the
* configure page, and the Analytics Agent does not cover it.
*/
record('no skill is available on the configure page at all',
reg.skillsForContext(CONFIGURE_CONTEXT, [], []).length === 0,
JSON.stringify(reg.skillsForContext(CONFIGURE_CONTEXT, [], []).map((s) => s.id)));
const analyticsAgentCfg = agentReg.getAgent(ALL_AGENTS, 'analytics-agent');
record('the Analytics Agent does not cover the configure page',
runtime.agentCovers(analyticsAgentCfg, CONFIGURE_CONTEXT) === false);
record('editing the Analytics Agent exposes no analytics skill',
reg.skillsForContext(
CONFIGURE_CONTEXT,
runtime.agentScopedDisabledWith(analyticsAgentCfg, ALL_AGENTS, ALL_SKILLS, []),
[]
).length === 0,
'the edited agent is metadata, not the current page');
/* The Analytics page keeps everything it had. A subset check, not equality:
the snapshot predates the analysis skills added since, and the baseline
section above already governs additions. What matters here is that mounting
Owliver on a workspace page took nothing away from an operational one. */
const analyticsNow = reg.skillsForContext('admin.analytics', [], []).map((s) => s.id);
const analyticsLost = expectedBaseline.contexts['admin.analytics'].skills
.filter((id) => !analyticsNow.includes(id));
record('...and the Analytics page kept every skill it had',
analyticsLost.length === 0,
analyticsLost.length ? `LOST ${JSON.stringify(analyticsLost)}` : `${analyticsNow.length} skills, none lost`);
/* Every agent, on the configure page: none reaches an operational skill. */
const leakedOnConfigure = ALL_AGENTS.filter((a) =>
reg.skillsForContext(
CONFIGURE_CONTEXT, runtime.agentScopedDisabledWith(a, ALL_AGENTS, ALL_SKILLS, []), []
).length > 0);
record('no agent reaches an operational skill from the configure page',
leakedOnConfigure.length === 0,
leakedOnConfigure.map((a) => a.id).join(', ') || `${ALL_AGENTS.length} agents, none`);
/* ── The page answers honestly ──────────────────────────────────────────── */
const configureContext = contexts.ASSISTANT_CONTEXTS[CONFIGURE_CONTEXT];
record('the configure page has a native agent to answer with',
runtime.defaultAgentForContext(ALL_AGENTS, CONFIGURE_CONTEXT)?.id === 'krow-workforce-agent',
runtime.defaultAgentForContext(ALL_AGENTS, CONFIGURE_CONTEXT)?.name || 'none');
record('it answers questions about agents and skills',
['What agents can I configure?', 'Where do skills come from?', 'What does reasoning do?']
.every((q) => routingModule.resolveIntent({ question: q, contextId: CONFIGURE_CONTEXT }).kind === 'answer'),
'agent-management questions are in scope');
/**
* A workforce question asked here is declined, not answered.
*
* This is the honest half of the boundary: the page has no operational records,
* so it must say so and point at the page that does — rather than answering
* from whatever the configuration screen happens to know.
*/
const workforceHere = ['How many candidates applied this week?', 'What is our attendance rate?']
.map((q) => routingModule.resolveIntent({ question: q, contextId: CONFIGURE_CONTEXT }));
record('a workforce question on the configure page is declined or routed away',
workforceHere.every((i) => i.kind === 'outOfScope' || i.kind === 'navigate'),
workforceHere.map((i) => i.kind).join(', '));
record('...and never answered from the configure page itself',
workforceHere.every((i) => i.kind !== 'answer' && !i.skill));
/* ── One Owliver, not two ───────────────────────────────────────────────── */
/**
* Asserted structurally: the configure screen must not import or define a chat.
* The panel it gets is the one the Admin shell already mounts.
*/
const detailSource = readFileSync(join(ROOT, resolveSourcePath('src/pages/admin/AgentDetail.jsx')), 'utf8');
const configureSource = readFileSync(join(ROOT, resolveSourcePath('src/components/agents/AgentConfigure.jsx')), 'utf8');
/* The property is right and the old pattern was too blunt. It matched any
occurrence of the substring, so `useAssistantPanel` — the hook for talking to
the ONE panel the shell mounts, and the opposite of defining a second one —
read as a violation, and so did the words `useConversation` inside a comment.
What actually constitutes a second chat is IMPORTING one or RENDERING one, so
that is what is matched now. */
const secondChat = [
/import\s+[^;]*\bKrowAssistant\b[^;]*from/,
/import\s+[^;]*\bcreateAssistantProvider\b[^;]*from/,
/import\s+[^;]*\buseConversation\b[^;]*from/,
/<KrowAssistant\b/,
/<AssistantPanel\b(?!Provider)/,
].filter((re) => re.test(detailSource + configureSource));
record('the configure screen defines no chat of its own',
secondChat.length === 0,
secondChat.length ? `defines one: ${secondChat[0]}` : 'uses the shell\'s panel, defines none');
record('the panel is mounted once, by the shell',
/* `<AssistantPanel` alone also matches `<AssistantPanelProvider`, which is a
different component and legitimately present. */
(readFileSync(join(ROOT, resolveSourcePath('src/layouts/AdminLayout.jsx')), 'utf8').match(/<AssistantPanel\b(?!Provider)/g) || []).length === 1);
/* The switcher is gone — see the note above. What replaced it is a label, and
the property worth keeping is that it stayed one: a header that can be
clicked is a second place to choose an agent. */
const badgeSource = readFileSync(join(ROOT, resolveSourcePath('src/components/ai-assistant/AgentBadge.jsx')), 'utf8');
record('the panel header names the agent and does not switch it',
!/<button|onClick=|navigate\(/.test(badgeSource),
'a label, not a control');
record('PageContext is untouched',
!/agentConfigure/.test(readFileSync(join(ROOT, resolveSourcePath('src/components/ai-assistant/PageContext.jsx')), 'utf8')));
/* ── The configure surface declares no placements ───────────────────────── */
/**
* Nothing renders skill cards on this screen, so the surface offers no
* placement. A `ui:` skill that tried to attach is refused at validation rather
* than validating and then drawing nothing.
*/
const configureSurface = surfaces.surfaceFor('workspace-agent-configure');
record('the configure surface exists and offers no placements',
Boolean(configureSurface) && configureSurface.placements.length === 0,
`${configureSurface?.placements.length ?? '—'} placements`);
record('no skill declares the configure page',
reg.SKILLS.every((s) => !s.pages.includes('workspace-agent-configure')),
'so there is nothing operational to reach here');
/* ── The configure workspace ─────────────────────────────────────────────
*
* The screen is an authoring surface and the only one in the console that is,
* so it carries a visual treatment the eight operational pages do not. What
* follows guards the two things that treatment must not cost.
*
* **Every control still exists.** A redesign that quietly drops a field looks
* exactly like a redesign that kept it — until someone cannot set an icon. So
* each closed vocabulary is asserted to be *rendered from its own table*, not
* merely present as a word: the icon picker over `AGENT_ICONS`, the page chips
* over `SUPPORTED_SKILL_PAGES`, reasoning over `REASONING_MODES`, and the
* knowledge kinds over `KNOWLEDGE_KINDS`. A hand-written subset would pass a
* grep and still be wrong the day a value is added.
*
* **It stays Krow.** The palette is the tokens, and nothing else.
*/
const canvasSource = readFileSync(join(ROOT, resolveSourcePath('src/components/agents/AgentCanvas.jsx')), 'utf8');
const agentUi = detailSource + configureSource + canvasSource;
/* One surface with sections inside it, not four cards side by side. */
record('the configure screen composes one workspace surface',
/<Workspace\b/.test(configureSource)
&& (configureSource.match(/<DocSection\b/g) || []).length === 4
&& !/<Surface\b/.test(configureSource),
'Workspace + 4 DocSection, no per-section card');
/* The rail is navigation over the document, so every id it offers must exist
as something the document actually renders. A leaf pointing at nothing is a
dead link the eye cannot see. */
const railSections = [...configureSource.matchAll(/\{\s*id:\s*'([a-z-]+)',\s*icon:/g)].map((m) => m[1]);
const railLeaves = [...configureSource.matchAll(/\{\s*id:\s*'([a-z-]+)',\s*label:/g)].map((m) => m[1]);
const railIds = [...railSections, ...railLeaves];
const missingTargets = railIds.filter((id) => !new RegExp(`id="${id}"`).test(configureSource));
record('every rail entry addresses something the document renders',
railSections.length === 4 && railLeaves.length === 12 && missingTargets.length === 0,
missingTargets.length
? `no target for ${missingTargets.join(', ')}`
: `${railSections.length} sections, ${railLeaves.length} leaves, all addressable`);
/* Each closed vocabulary is rendered from its own table. */
record('the icon picker offers every icon an agent may name',
/icons=\{AGENT_ICONS\}/.test(configureSource) && vocab.AGENT_ICONS.length === 10,
`${vocab.AGENT_ICONS.length} icons, from AGENT_ICONS`);
/* This asked for SUPPORTED_SKILL_PAGES — every surface, configuration screens
included. The editor deliberately offers the narrower DOMAIN_SURFACES: a
surface with no placements renders no skill sections, so an agent scoped to
one has nothing to answer from. Offering it would let somebody build an agent
that is silently mute.
The pages a definition ALREADY declares are unioned in, so an agent written
by hand against a configuration surface keeps it rather than losing it on the
next save. Both halves are asserted. */
record('the page chips offer every domain surface, and keep what a definition declares',
/\.\.\.DOMAIN_SURFACES, \.\.\.fields\.pages/.test(configureSource),
`${surfaces.DOMAIN_SURFACES.length} domain surfaces of `
+ `${surfaces.SUPPORTED_SKILL_PAGES.length} supported, plus whatever is declared`);
record('reasoning offers every mode',
/REASONING_MODES\.map/.test(configureSource),
vocab.REASONING_MODES.map((m) => m.label).join(' / '));
record('knowledge offers every kind',
/KNOWLEDGE_KINDS\.map/.test(configureSource),
vocab.KNOWLEDGE_KINDS.join(', '));
/* The controls that are not list-driven, one by one. */
const CONTROLS = {
name: /value=\{fields\.name\}/,
description: /value=\{fields\.description\}/,
'when to use': /value=\{fields\.trigger\}/,
instructions: /value=\{fields\.instructions\}/,
/* Skills are editable here again, but NOT the way this check used to look
for. The old controls wrote `fields.skills` directly, which made attaching
a skill a DRAFT — and the catalog persists one immediately, on purpose. Two
write paths for one field meant the two screens disagreed about when a
skill took effect.
So Configure now calls the caller's `onToggleSkill`, the same optimistic
path the catalog uses. The property worth guarding is that both controls
exist and both go through it. */
'add skill': /onValueChange=\{\(id\) => !fields\.skills\.includes\(id\) && onToggleSkill/,
'remove skill': /onRemove=\{onToggleSkill \? \(\) => onToggleSkill\(id\) : undefined\}/,
'add knowledge': /knowledge: \[\s*\n?\s*\.\.\.fields\.knowledge/,
'remove knowledge': /knowledge: fields\.knowledge\.filter/,
'add starter': /starters: \[\.\.\.fields\.starters/,
'remove starter': /starters: fields\.starters\.filter/,
'web search': /onCheckedChange=\{\(webSearch\) => set\(\{ webSearch \}\)\}/,
'add subagent': /subagents: \[\.\.\.fields\.subagents/,
'remove subagent': /subagents: fields\.subagents\.filter/,
};
const missingControls = Object.entries(CONTROLS)
.filter(([, re]) => !re.test(configureSource)).map(([k]) => k);
/* Skills are bounded by the pages above — "a page decides which skills exist
there; an agent chooses among them, and can narrow that list, never widen
it." The old controls offered a flat list, which is how an agent ends up
carrying a skill its pages do not provide. */
record('the skills offered are bounded by the pages chosen',
/getSkillsForPage\(page,/.test(configureSource)
&& /for \(const page of fields\.pages\)/.test(configureSource),
'derived from fields.pages, not a flat catalog');
/* An agent whose pages changed may still carry a skill they no longer offer.
Dropping it silently would edit the definition behind the author's back. */
record('a skill the pages no longer offer is flagged, not dropped',
/Not available on the pages above/.test(configureSource));
record('every field the editor had is still editable',
missingControls.length === 0,
missingControls.length ? `MISSING ${missingControls.join(', ')}` : `${Object.keys(CONTROLS).length} controls`);
/* Lifecycle state stays legible: version, status, and the customized marker. */
record('the header still states version, status and customization',
/v\{agent\.version\}/.test(detailSource)
&& /\{agent\.status\}/.test(detailSource)
&& /isOverridden\(id\) && <Badge variant="info">Customized/.test(detailSource)
&& /Publish update/.test(detailSource),
'v · status · Customized · Publish update');
record('save state is shown and Save is offered only when there is something to save',
/dirty \? 'Unsaved changes' : 'Saved'/.test(detailSource)
&& /onClick=\{persist\} disabled=\{!dirty\}/.test(detailSource));
/**
* Closed sections stay reachable by the rail, which means they stay mounted —
* and a mounted control nobody can see must not be in the tab order.
*/
record('collapsed content is inert rather than merely hidden',
/inert=\{open \? undefined : true\}/.test(canvasSource)
&& !/aria-hidden=\{open/.test(canvasSource),
'not tabbable while closed');
/**
* Motion is CSS transitions only, so the global `prefers-reduced-motion` rule
* in index.css governs it. A JavaScript animation would need its own guard and
* would eventually be written without one.
*/
record('the workspace animates in CSS, so reduced motion is inherited',
!/framer-motion/.test(configureSource + canvasSource)
&& /transition-\[grid-template-rows\]/.test(canvasSource),
'no JS animation on this screen');
/**
* The palette is the design system's. Not "no hex anywhere" — the check is
* that this screen introduced none of its own.
*/
const strayColour = [...agentUi.matchAll(/(?:bg|text|border|from|via|to|ring)-(?:\[#[0-9a-fA-F]{3,8}\]|purple|violet|fuchsia|pink|indigo|cyan|teal|lime|orange)-?\d*/g)]
.map((m) => m[0]);
record('the configure screen introduces no colour outside the Krow palette',
strayColour.length === 0,
strayColour.length ? `STRAY ${[...new Set(strayColour)].join(', ')}` : 'tokens only');
/* The redesign is scoped to this screen. Nothing else may import its parts. */
const canvasImporters = [
...readdirSync(join(ROOT, 'src/pages/admin')).map((f) => ['src/pages/admin', f]),
...readdirSync(join(ROOT, 'src/components/agents')).map((f) => ['src/components/agents', f]),
]
.filter(([, f]) => /\.[jt]sx?$/.test(f))
.filter(([, f]) => !/^(AgentConfigure|AgentDetail|AgentCanvas)\./.test(f))
.filter(([dir, f]) => /agents\/AgentCanvas|from '\.\/AgentCanvas'/
.test(readFileSync(join(ROOT, resolveSourcePath(`${dir}/${f}`)), 'utf8')))
.map(([dir, f]) => `${dir}/${f}`);
record('the workspace treatment is used by the configure screen alone',
canvasImporters.length === 0,
canvasImporters.join(', ') || 'no other page imports it');
/* ── 20b. Native agent pages vs agent-less pages ──────────────────────────
*
* The correction this section pins down, in one sentence: **a page with no
* agent of its own is not a page without Owliver.**
*
* Two runtime modes, and only the first existed as a deliberate design:
*
* 1. **Native.** The eight operational pages open on the agent written for
* them, with that page's skills and that page's records. Unchanged, and
* most of what follows exists to prove it stayed unchanged.
* 2. **Fallback.** Settings, the workspace surfaces, Agent Configure and
* Profile have no specialist and need none. They open on the general Krow
* Workforce Agent, and Owliver works there — chat, chips, history,
* everything — while still declining any question about records the page
* does not hold.
*
* The failure this replaces was reading "no native agent" as "constrained", and
* a constrained panel is a dead one: no starters, no answers, a decline to every
* question. That is the right behaviour for an agent the reader *chose* which
* does not cover the page, and the wrong behaviour for a page nobody wrote an
* agent for. Both are asserted below, because the whole correction is the
* distinction between them.
*/
console.log('\n── Native agent pages vs agent-less pages ──');
/** Pages with an agent of their own. `NATIVE` above is the same eight. */
const NATIVE_CONTEXTS = Object.keys(NATIVE);
/**
* Pages with none.
*
* Profile is on this list and always was — it had no specialist before any of
* this and resolved to the general agent, which is exactly the behaviour the
* six new surfaces now share. Listing it here is what proves the fallback is
* one rule rather than a special case for the pages added last.
*/
const AGENTLESS_CONTEXTS = [
'admin.settings',
'admin.workspace',
'admin.workspaceAgents',
'admin.workspaceSkills',
'admin.skillConfigure',
'admin.skillDevelopment',
'admin.agentConfigure',
'admin.profile',
];
const GENERAL = 'krow-workforce-agent';
/* ── 1–2. Resolution, both modes ────────────────────────────────────────── */
for (const contextId of NATIVE_CONTEXTS) {
const native = runtime.nativeAgentForContext(ALL_AGENTS, contextId);
const resolved = runtime.resolveDefaultAgent(ALL_AGENTS, contextId);
record(`${contextId.replace('admin.', '')}: resolves its own agent`,
native?.id === NATIVE[contextId] && resolved?.id === NATIVE[contextId],
resolved?.id || 'none');
}
for (const contextId of AGENTLESS_CONTEXTS) {
const page = contextId.replace('admin.', '');
record(`${page}: has no agent of its own, and resolves the general one`,
runtime.nativeAgentForContext(ALL_AGENTS, contextId) === null
&& runtime.resolveDefaultAgent(ALL_AGENTS, contextId)?.id === GENERAL,
runtime.resolveDefaultAgent(ALL_AGENTS, contextId)?.id || 'NONE');
}
record('the general agent covers every agent-less page',
AGENTLESS_CONTEXTS.every((c) => runtime.agentCovers(agentReg.getAgent(ALL_AGENTS, GENERAL), c)),
`${AGENTLESS_CONTEXTS.length} pages`);
/* Nothing above changed which agent the eight operational pages open on. */
record('the eight native pages still open on exactly the agents they did',
Object.entries(NATIVE).every(([c, id]) => runtime.defaultAgentForContext(ALL_AGENTS, c)?.id === id),
Object.entries(NATIVE).filter(([c, id]) => runtime.defaultAgentForContext(ALL_AGENTS, c)?.id !== id)
.map(([c]) => c).join(', ') || '8 pages unchanged');
/* ── 3–5. An agent-less page does not open constrained ──────────────────── */
/**
* The heart of it.
*
* With nothing chosen, the panel resolves an agent that covers the page — so
* `covers` is true, no constrained document is produced, and the reader gets an
* assistant rather than an apology.
*/
const openedConstrained = [];
for (const contextId of AGENTLESS_CONTEXTS) {
const resolved = runtime.resolveDefaultAgent(ALL_AGENTS, contextId);
const turn = runtime.resolveAgentForTurn(ALL_AGENTS, null, contextId);
const intent = routingModule.resolveIntent({
question: 'What can I do here?',
contextId,
agent: resolved,
agentCoversPage: runtime.agentCovers(resolved, contextId),
agentSuggestion: resolved,
});
if (!turn.covers || intent.kind === 'constrained') openedConstrained.push(contextId);
}
record('no agent-less page opens in the constrained state',
openedConstrained.length === 0,
openedConstrained.join(', ') || `${AGENTLESS_CONTEXTS.length} pages open with a working agent`);
record('Settings opens on the general agent, not constrained',
runtime.resolveDefaultAgent(ALL_AGENTS, 'admin.settings')?.id === GENERAL
&& runtime.resolveAgentForTurn(ALL_AGENTS, null, 'admin.settings').covers === true);
record('Agent Configure opens on the general agent, not constrained',
runtime.resolveDefaultAgent(ALL_AGENTS, 'admin.agentConfigure')?.id === GENERAL
&& runtime.resolveAgentForTurn(ALL_AGENTS, null, 'admin.agentConfigure').covers === true);
/* ── 6–7. The edited agent is metadata, never the runtime ───────────────── */
/**
* Configuring an agent does not become standing on the page it covers.
*
* Section 20 proves the *data* half — no skill is reachable there. This is the
* *identity* half: whichever agent is being edited, the agent answering on the
* configure page is the general one, because nothing about the record on the
* form is an input to agent resolution.
*/
for (const edited of ['analytics-agent', 'positions-agent', 'activity-agent']) {
record(`editing \`${edited}\` does not make it the runtime agent`,
runtime.resolveDefaultAgent(ALL_AGENTS, 'admin.agentConfigure')?.id === GENERAL
&& runtime.agentCovers(agentReg.getAgent(ALL_AGENTS, edited), 'admin.agentConfigure') === false,
'the edited agent is configuration, not the active runtime identity');
}
/* ── 8–13. Stale selection ──────────────────────────────────────────────── */
/**
* A selection made on one page must not decide another.
*
* `resolveSelection` is a pure function of (agents, selection, context), so the
* whole rule is provable here rather than through a React tree. Three outcomes,
* and every case below is one of them: applied because it covers, applied
* because this is where it was chosen, or retired.
*/
const LEAKS = [
['positions-agent', 'admin.positions', 'admin.settings'],
['analytics-agent', 'admin.analytics', 'admin.settings'],
['activity-agent', 'admin.activity', 'admin.settings'],
['positions-agent', 'admin.positions', 'admin.agentConfigure'],
['control-center-agent', 'admin.controlCenter', 'admin.workspaceSkills'],
['talent-pool-agent', 'admin.talentPool', 'admin.skillDevelopment'],
];
const leakedSelections = [];
for (const [id, chosenOn, arrivingAt] of LEAKS) {
const applied = runtime.resolveSelection(ALL_AGENTS, { id, contextId: chosenOn }, arrivingAt);
const answering = runtime.resolveAgentForTurn(ALL_AGENTS, applied.id, arrivingAt);
if (applied.id !== null || !applied.retire || answering.agent?.id !== GENERAL || !answering.covers) {
leakedSelections.push(`${id} (${chosenOn}) → ${arrivingAt}: ${answering.agent?.id}`);
}
}
record('a selection made on another page never follows onto an agent-less page',
leakedSelections.length === 0,
leakedSelections.join(' | ') || `${LEAKS.length} navigations, every one resolved to the general agent`);
record('a retired selection is retired, not merely ignored',
runtime.resolveSelection(ALL_AGENTS, { id: 'positions-agent', contextId: 'admin.positions' }, 'admin.settings').retire === true,
'so returning to that page later does not resurrect it');
/* Arriving on a page the selection *does* cover keeps it — the choice is only
dropped where it could not answer. */
record('a selection that covers the page it arrives on is kept',
(() => {
const applied = runtime.resolveSelection(
ALL_AGENTS, { id: 'analytics-agent', contextId: 'admin.settings' }, 'admin.analytics'
);
return applied.id === 'analytics-agent' && applied.covers === true && applied.retire === false;
})(),
'Settings → Analytics keeps a deliberate choice');
record('Agent Configure → Analytics resolves Analytics normally',
(() => {
const carried = runtime.resolveSelection(ALL_AGENTS, { id: null, contextId: 'admin.agentConfigure' }, 'admin.analytics');
return carried.id === null
&& runtime.resolveDefaultAgent(ALL_AGENTS, 'admin.analytics')?.id === 'analytics-agent';
})());
record('Settings → Analytics resolves Analytics normally',
runtime.resolveDefaultAgent(ALL_AGENTS, 'admin.analytics')?.id === 'analytics-agent');
/* An account-level default is a choice made nowhere, and is read by the same
rule: it applies where it covers, and never constrains a page it does not. */
record('an account default that does not cover a page does not constrain it',
runtime.resolveSelection(ALL_AGENTS, 'analytics-agent', 'admin.settings').id === null
&& runtime.resolveSelection(ALL_AGENTS, 'analytics-agent', 'admin.analytics').id === 'analytics-agent');
/* A stored id for an agent that no longer exists resolves to nothing rather
than leaving the panel pointed at a ghost. */
record('a selection naming an agent that no longer exists is retired',
runtime.resolveSelection(ALL_AGENTS, { id: 'deleted-agent', contextId: 'admin.settings' }, 'admin.settings').retire === true);
/* ── 18. An explicit incompatible choice still constrains ───────────────── */
/**
* The other half, and the reason "stale" had to be defined rather than just
* cleared: choosing a specialist *here*, on a page it does not cover, is a
* deliberate act. It is honoured, shown as constrained, and it declines — the
* honest answer, and the same one every constrained pair gives above.
*/
const explicit = runtime.resolveSelection(
ALL_AGENTS, { id: 'analytics-agent', contextId: 'admin.settings' }, 'admin.settings'
);
const explicitTurn = runtime.resolveAgentForTurn(ALL_AGENTS, explicit.id, 'admin.settings');
record('choosing an incompatible agent on this page is honoured, and constrained',
explicit.id === 'analytics-agent' && explicit.retire === false
&& explicitTurn.agent?.id === 'analytics-agent' && explicitTurn.covers === false,
'the reader chose it here, so it is not swapped out from under them');
record('...and it declines rather than answering',
routingModule.resolveIntent({
question: 'What can I configure here?',
contextId: 'admin.settings',
agent: explicitTurn.agent,
agentCoversPage: false,
agentSuggestion: runtime.resolveDefaultAgent(ALL_AGENTS, 'admin.settings'),
}).kind === 'constrained');
record('...and the way out of it is named on a page with no specialist',
Boolean(runtime.resolveDefaultAgent(ALL_AGENTS, 'admin.settings')),
'the general agent is what a constrained answer points at');
/* ── 14–17. Owliver actually works there ────────────────────────────────── */
/* Every one of these pages mounts the panel. A resolution rule is worth nothing
if there is no panel to resolve for. */
record('every agent-less context is a real placement with a panel',
AGENTLESS_CONTEXTS.every((id) => Object.values(placement.PLACEMENT_ROUTES).includes(id)
|| Object.values(placement.PLACEMENT_PATTERN_ROUTES).includes(id)),
AGENTLESS_CONTEXTS.filter((id) => !Object.values(placement.PLACEMENT_ROUTES).includes(id)).join(', ') || 'all mounted');
/* Each one names itself, so the header reads the page the reader is on. */
record('every agent-less page states its own name, key and route',
AGENTLESS_CONTEXTS.every((id) => {
const context = contexts.ASSISTANT_CONTEXTS[id];
const pageKey = reg.pageKeyForContext(id);
return Boolean(context?.page) && Boolean(pageKey) && Boolean(reg.routeForPageKey(pageKey));
}),
AGENTLESS_CONTEXTS.map((id) => `${contexts.ASSISTANT_CONTEXTS[id].page}/${reg.pageKeyForContext(id)}`).join(', '));
/**
* The chips are real.
*
* Every suggestion offered on these pages is answered by the page it is offered
* on — a chip is a promise, and one that declines is worse than no chip. The
* count is asserted too: suggestions disappearing on an agent-less page was one
* of the reported symptoms.
*/
const emptyChips = [];
const brokenChips = [];
for (const contextId of AGENTLESS_CONTEXTS) {
const prompts = dynamic.buildPrompts(contextId, factSheet, null) || [];
if (!prompts.length) emptyChips.push(contextId);
for (const prompt of prompts) {
const intent = routingModule.resolveIntent({ question: prompt.prompt, contextId });
if (intent.kind !== 'answer') brokenChips.push(`${contextId}: "${prompt.label}" → ${intent.kind}`);
}
}
record('every agent-less page offers suggestions',
emptyChips.length === 0,
emptyChips.join(', ') || AGENTLESS_CONTEXTS
.map((c) => `${c.replace('admin.', '')}:${(dynamic.buildPrompts(c, factSheet, null) || []).length}`).join(' '));
record('every suggestion an agent-less page offers is one it can answer',
brokenChips.length === 0,
brokenChips.slice(0, 3).join(' | ') || 'every chip resolves to an answer');
/* And the landing screen is furnished — a title, something to type into. */
record('every agent-less page has a landing screen rather than an empty panel',
AGENTLESS_CONTEXTS.every((contextId) => {
const intro = dynamic.buildIntro(contextId, factSheet, 'Test');
return Boolean(intro.title) && Boolean(intro.description) && intro.placeholders.length > 0;
}));
/**
* No fake skills.
*
* The lazy way to make a configuration page answer is to invent a skill for it.
* That would show up in exactly two places, and both are checked: the page would
* carry skills, and its chips would resolve to one. Neither is true — the
* answers come from the page responder, which is the mechanism every context in
* the table already used.
*/
record('no agent-less page carries any skill',
AGENTLESS_CONTEXTS.every((c) => reg.skillsForContext(c, [], []).length === 0),
AGENTLESS_CONTEXTS.filter((c) => reg.skillsForContext(c, [], []).length).join(', ') || 'no skills, on any of them');
/* A chip may name one of the page's own capabilities — Profile's always have —
and that is a context responder, not a skill. What must not exist is a skill
behind any of them. */
record('no chip on an agent-less page is backed by a skill',
AGENTLESS_CONTEXTS.every((contextId) =>
(dynamic.buildPrompts(contextId, factSheet, null) || []).every((p) =>
!reg.matchSkill(p.prompt, contextId, [], []))),
'the fallback is architectural, not a skill layer');
record('no agent reaches a skill from an agent-less page',
AGENTLESS_CONTEXTS.every((contextId) => ALL_AGENTS.every((a) =>
reg.skillsForContext(contextId, runtime.agentScopedDisabledWith(a, ALL_AGENTS, ALL_SKILLS, []), []).length === 0)),
`${AGENTLESS_CONTEXTS.length} pages × ${ALL_AGENTS.length} agents`);
/**
* Honest about what it cannot answer.
*
* An operational question asked on a configuration screen must never be
* answered from that screen. It may route to the page that holds the records —
* which is the useful answer — or decline. What it may not do is produce a
* figure.
*/
const OPERATIONAL_QUESTIONS = [
'What are the open positions today?',
'What is our attendance rate?',
'Which candidates need review?',
'How many people did we hire last month?',
];
const fabricated = [];
for (const contextId of AGENTLESS_CONTEXTS) {
for (const question of OPERATIONAL_QUESTIONS) {
const intent = routingModule.resolveIntent({ question, contextId });
if (intent.kind === 'answer' || intent.skill) fabricated.push(`${contextId}: "${question}" → ${intent.kind}`);
}
}
record('an operational question on an agent-less page is declined or routed, never answered',
fabricated.length === 0,
fabricated.slice(0, 3).join(' | ')
|| `${AGENTLESS_CONTEXTS.length} pages × ${OPERATIONAL_QUESTIONS.length} questions`);
/* The decline names what the page *can* do, so it is an offer rather than a
dead end. */
/* The offer used to be a bulleted list of capability labels — the simulator's
canned readings, deleted with it. It is now a sentence naming the page's
subjects, which is a better offer: a label named a report, a subject names
something somebody can ask about in their own words. The check asserts the
PROPERTY (a decline says what the page does cover) rather than the block
type, so the next change of shape does not break it for no reason. */
record('the decline offers what the page can answer instead',
AGENTLESS_CONTEXTS.every((contextId) => {
const intent = routingModule.resolveIntent({ question: 'What is our attendance rate?', contextId });
if (intent.kind !== 'outOfScope') return true;
const said = (intent.doc?.blocks || [])
.map((b) => b.text || (b.items || []).join(' ')).join(' ');
return said.includes('This page covers') || /\b(covers|ask about)\b/.test(said);
}));
/* ── The switcher still works there ─────────────────────────────────────── */
/**
* An agent-less page is not a page with no switcher.
*
* The list is the whole registry, ordered so the agents that work here come
* first — which on these pages is the general agent, because it is the one that
* covers them.
*/
record('the switcher lists every agent on an agent-less page',
AGENTLESS_CONTEXTS.every((contextId) =>
agentReg.searchAgents(ALL_AGENTS, '').length === ALL_AGENTS.length),
`${ALL_AGENTS.length} agents, on every page`);
record('the general agent sorts to the top of the list on an agent-less page',
AGENTLESS_CONTEXTS.every((contextId) => {
const ordered = [...agentReg.searchAgents(ALL_AGENTS, '')]
.sort((a, b) => Number(runtime.agentCovers(b, contextId)) - Number(runtime.agentCovers(a, contextId)));
return ordered[0]?.id === GENERAL;
}),
'the one that can answer here is offered first');
record('choosing an agent on an agent-less page still cannot move the page',
AGENTLESS_CONTEXTS.every((contextId) => ALL_AGENTS.every((candidate) =>
runtime.resolveAgentForTurn(ALL_AGENTS, candidate.id, contextId).agent?.id === candidate.id)));
/* ── 21. Owliver behaviour baseline ───────────────────────────────────────
*
* The Agent layer is additive, which is a claim rather than a guarantee: every
* page's skills, suggestions, prompts and intent routing must survive it
* unchanged. That is far more than fits in a reviewer's head, and all of it
* fails silently — a page quietly answering with one skill fewer looks exactly
* like a page that never had it.
*
* So today's behaviour was recorded before any of it was built
* (`scripts/__baseline__/owliver-baseline.json`) and is compared here, per
* context and per question. `captureBaseline` is the same function that wrote
* the file, so there is one definition of what is measured.
*
* A failure here is a regression in the existing product, not a stale
* expectation. The file is regenerated deliberately, never to turn a check
* green.
*/
console.log('\n── Owliver behaviour baseline ──');
if (!existsSync(BASELINE_PATH)) {
record('baseline snapshot exists', false, 'run `node scripts/owliver-baseline.mjs --write`');
} else {
const expected = JSON.parse(readFileSync(BASELINE_PATH, 'utf8'));
const actual = await captureBaseline(server);
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
record('baseline schema matches', expected.schema === actual.schema,
`expected ${expected.schema}, got ${actual.schema}`);
/**
* Additive is allowed; losing something is not.
*
* The snapshot records the product as it was before the agent layer existed,
* and later phases legitimately add skills — that is what they are for. So a
* strict equality here would fail on every intended change and force the
* snapshot to be regenerated, which is exactly how a baseline stops catching
* anything.
*
* The contract is therefore a *subset*: every skill that existed then must
* still exist now. A skill disappearing is a regression and still fails.
* What must not move at all — routes, page keys, suggested prompts and intent
* routing — is asserted strictly below, and those are the checks that catch a
* new definition quietly taking over an existing question.
*/
const lostSkills = expected.skillIds.filter((id) => !actual.skillIds.includes(id));
const gainedSkills = actual.skillIds.filter((id) => !expected.skillIds.includes(id));
record('every skill that existed before the agent layer still registers',
lostSkills.length === 0,
lostSkills.length
? `LOST ${JSON.stringify(lostSkills)}`
: `${expected.skillIds.length} kept, ${gainedSkills.length} added since`);
record('registry still reports no diagnostics', actual.diagnostics.length === 0,
actual.diagnostics.map((d) => d.message).join(' | ') || 'none');
/**
* Route → context: every address that resolved before must resolve the same
* way now.
*
* Additive, for the same reason page skills are. A later phase can mount
* Owliver somewhere new — the agent configuration screen is the first — and a
* strict equality would fail on that intended addition and force the snapshot
* to be regenerated, which is how a baseline stops catching anything.
*
* What must never happen is an *existing* route resolving somewhere else, or
* ceasing to resolve at all: that is one of the eight operational pages
* quietly changing which assistant it carries. Both are failures here.
*/
const movedRoutes = Object.entries(expected.routes)
.filter(([route, contextId]) => actual.routes[route] !== contextId)
.map(([route, contextId]) => `${route}: ${contextId} → ${actual.routes[route] ?? 'nothing'}`);
const addedRoutes = Object.keys(actual.routes).filter((route) => !(route in expected.routes));
record('every route that resolved before resolves the same way',
movedRoutes.length === 0,
movedRoutes.join(' | ')
|| `${Object.keys(expected.routes).length} unchanged${addedRoutes.length ? `, added ${JSON.stringify(addedRoutes)}` : ''}`);
/* Per context, so a failure names the page it broke rather than reporting
that "something" moved. */
for (const contextId of Object.keys(expected.contexts)) {
const want = expected.contexts[contextId];
const got = actual.contexts[contextId];
const page = contextId.replace(/^admin\./, '');
if (!got) {
record(`${page}: context still exists`, false, 'context missing from the registry');
continue;
}
record(`${page}: page key and route unchanged`,
want.pageKey === got.pageKey && want.route === got.route,
`${got.pageKey} @ ${got.route}`);
/* The page boundary itself. A page may gain skills as later phases add
them; it may never lose one, because that is a capability the page had
and silently stopped offering. */
const lost = want.skills.filter((id) => !got.skills.includes(id));
const gained = got.skills.filter((id) => !want.skills.includes(id));
record(`${page}: keeps every skill it had`, lost.length === 0,
lost.length ? `LOST ${JSON.stringify(lost)}`
: gained.length ? `${want.skills.length} kept, gained ${JSON.stringify(gained)}`
: `${got.skills.length} skill(s), unchanged`);
const lostSuggestions = want.suggestions.filter((label) => !got.suggestions.includes(label));
record(`${page}: keeps every suggestion it offered`, lostSuggestions.length === 0,
lostSuggestions.length ? `LOST ${JSON.stringify(lostSuggestions)}`
: `${want.suggestions.length} kept, ${got.suggestions.length - want.suggestions.length} added`);
record(`${page}: suggested prompts unchanged`, same(want.prompts, got.prompts),
same(want.prompts, got.prompts)
? `${got.prompts.length} prompt(s)`
: `expected ${JSON.stringify(want.prompts)}, got ${JSON.stringify(got.prompts)}`);
/* Intent routing, question by question — skill matching, the branch taken,
and the shape of the answer. */
const drifted = want.intents.filter((w, i) => !same(w, got.intents[i]));
record(`${page}: intent routing unchanged`, drifted.length === 0,
drifted.length === 0
? `${want.intents.length} question(s)`
: drifted.map((d) => `"${d.question}"`).join(', '));
}
}
/* ── The agent provider ───────────────────────────────────────────────────────
*
* The provider that calls the real backend. Checked here rather than left to a
* browser because the failure modes are all shape failures — a run that came
* back Completed and rendered nothing, a proposed write rendered as prose with
* no button — and none of them is visible without asserting on the blocks.
*
* `fetch` is stubbed. The point is the mapping from a run result to blocks, not
* the network; the network is tested on the Go side, against a real model.
*/
console.log('\n── Agent provider ──');
{
const providerMod = await server.ssrLoadModule('/src/components/ai-assistant/provider.js');
const drain = async (stream) => {
let last = [];
for await (const snapshot of stream) last = snapshot;
return last;
};
const withFetch = async (impl, fn) => {
const original = globalThis.fetch;
globalThis.fetch = impl;
try { return await fn(); } finally { globalThis.fetch = original; }
};
const jsonResponse = (body, status = 200) => ({
ok: status >= 200 && status < 300,
status,
json: async () => body,
});
const provider = providerMod.createAgentProvider({ baseUrl: '/api/v1' });
/* A completed run renders its answer. */
{
const blocks = await withFetch(
async () => jsonResponse({
runId: 'run_1', termination: 'Completed',
output: 'Twelve events, mostly logins.', usage: {},
}),
() => drain(provider.stream({ question: 'what happened?', agent: { id: 'activity-agent' } }))
);
record('agent provider: a completed run renders its answer',
blocks.some((b) => b.type === 'text' && b.text.includes('Twelve events')),
`${blocks.length} block(s)`);
record('agent provider: a completed run adds no system note',
!blocks.some((b) => b.type === 'note'));
}
/* A proposed write renders as a decision, not as prose. */
{
const blocks = await withFetch(
async () => jsonResponse({
runId: 'run_2', termination: 'ConfirmationPending',
message: 'The agent has proposed a change and is waiting for you to approve it.',
confirmations: [{
token: 'cnf_abc', tool: 'assign_worker',
title: 'Assign Maya Chen to Bar Supervisor',
summary: 'Maya Chen will be scheduled to work Friday evening.',
details: [{ label: 'Worker', value: 'Maya Chen' }],
warnings: ['Maya Chen already has 1 assignment over this period.'],
}],
usage: {},
}),
() => drain(provider.stream({ question: 'cover Friday', agent: { id: 'coverage-agent' } }))
);
const confirmation = blocks.find((b) => b.type === 'confirmation');
record('agent provider: a proposed write renders as a confirmation block',
Boolean(confirmation), confirmation ? confirmation.title : `${blocks.length} block(s)`);
record('agent provider: the confirmation carries its token',
confirmation?.token === 'cnf_abc');
record('agent provider: the confirmation carries its warnings',
confirmation?.warnings?.length === 1);
}
/* A token, once approved, travels on the next request — and nothing else about
the caller does. The browser cannot name a principal. */
{
let sent = null;
await withFetch(
async (_url, init) => { sent = JSON.parse(init.body); return jsonResponse({ termination: 'Completed', output: 'Assigned.', usage: {} }); },
() => drain(provider.stream({
question: 'cover Friday', agent: { id: 'coverage-agent' }, confirmation: 'cnf_abc',
}))
);
record('agent provider: an approval sends its token', sent?.confirmation === 'cnf_abc');
record('agent provider: the body names no principal',
sent && !('identity' in sent) && !('userId' in sent) && !('orgId' in sent),
Object.keys(sent || {}).join(', '));
}
/* Failures say what to do about them, and a 404 does not distinguish
"no such agent" from "not yours". */
{
for (const [status, expect] of [[401, 'Sign in again'], [404, 'not available'], [429, 'Try again']]) {
const blocks = await withFetch(
async () => jsonResponse({ error: { message: 'x' } }, status),
() => drain(provider.stream({ question: 'hi', agent: { id: 'a' } }))
);
const note = blocks.find((b) => b.type === 'note');
record(`agent provider: ${status} explains what to do`,
Boolean(note?.text?.includes(expect)), note?.text || 'no note');
}
}
/* No agent, no run. The panel resolves an agent before calling; reaching the
provider without one is a routing bug and should say so. */
{
let called = false;
const blocks = await withFetch(
async () => { called = true; return jsonResponse({}); },
() => drain(provider.stream({ question: 'hi', agent: null }))
);
record('agent provider: no agent means no request', !called);
record('agent provider: no agent says so', blocks.some((b) => b.type === 'note'));
}
}
/* ── Preferring the agent ─────────────────────────────────────────────────────
*
* The rule that decides whether a question is answered by the browser or by the
* agent. Checked here because getting it wrong in either direction is invisible
* from the outside: too eager and a form flow breaks; too shy and the panel
* keeps answering from templates while a real agent sits behind it.
*/
console.log('\n── Preferring the agent ──');
{
const routing = await server.ssrLoadModule('/src/components/ai-assistant/routing.js');
const { preferAgent } = routing;
const defers = (intent) => preferAgent(intent, { modelBacked: true }).kind === 'answer';
const keeps = (intent) => preferAgent(intent, { modelBacked: true }) === intent;
/* Text answers go to the agent — it reads the same rows and can be followed up. */
for (const kind of ['answer-doc', 'skill', 'workforce', 'outOfScope']) {
record(`prefer-agent: ${kind} defers to the agent`, defers({ kind }));
}
/* Anything that DOES something keeps its path. The agent has no tool for
these, so deferring would lose capability rather than gimmickry. */
const acts = [
['flow + create', { kind: 'flow', create: {} }],
['draft + perform', { kind: 'draft', perform: {} }],
['workforce + assign', { kind: 'workforce', assign: {} }],
['workforce + headcount', { kind: 'workforce', headcount: {} }],
['workforce + interview', { kind: 'workforce', interview: {} }],
['skill + action', { kind: 'skill', action: {} }],
];
for (const [label, intent] of acts) {
record(`prefer-agent: ${label} keeps its own path`, keeps(intent));
}
/* Deliberate product behaviour, left alone. */
record('prefer-agent: navigate is left alone', keeps({ kind: 'navigate', destination: {} }));
record('prefer-agent: constrained is left alone', keeps({ kind: 'constrained', agent: {} }));
/* With no agent behind the panel there is nothing better to defer TO, and
deferring would turn every template answer into a worse one. */
const inert = ['answer-doc', 'skill', 'workforce', 'outOfScope']
.every((kind) => preferAgent({ kind }, { modelBacked: false }).kind === kind);
record('prefer-agent: inert without a live agent', inert,
'the panel behaves exactly as it shipped');
}
/* ── Version pinning ──────────────────────────────────────────────────────────
*
* §3: running conversations pin the version they started with. The frontend's
* half is small — send back the version the first answer carried — and the
* consequence of getting it wrong is invisible: an edit published mid-thread
* would silently change which agent is answering, and the reader would see the
* change as the agent being inconsistent.
*/
console.log('\n── Version pinning ──');
{
const providerMod = await server.ssrLoadModule('/src/components/ai-assistant/provider.js');
const provider = providerMod.createAgentProvider({ baseUrl: '/api/v1' });
const drain = async (stream) => { let last = []; for await (const s of stream) last = s; return last; };
const withFetch = async (impl, fn) => {
const orig = globalThis.fetch;
globalThis.fetch = impl;
try { return await fn(); } finally { globalThis.fetch = orig; }
};
const json = (body) => ({
ok: true, status: 200,
headers: { get: () => 'application/json' },
json: async () => body,
});
let sent = null;
const capture = async (_u, init) => { sent = JSON.parse(init.body); return json({ termination: 'Completed', output: 'ok', usage: {} }); };
await withFetch(capture, () => drain(provider.stream({
question: 'q', agent: { id: 'a' }, agentVersion: 3,
})));
record('pinning: a pinned version travels with the question', sent.agentVersion === 3,
`agentVersion=${sent.agentVersion}`);
await withFetch(capture, () => drain(provider.stream({ question: 'q', agent: { id: 'a' } })));
record('pinning: an unpinned turn sends none, and gets whatever is current',
sent.agentVersion === undefined, `agentVersion=${sent.agentVersion}`);
/* The one that matters: an approval carries the version it was approved
under, so the write happens against the agent the person was shown. */
await withFetch(capture, () => drain(provider.stream({
question: 'q', agent: { id: 'a' }, confirmation: 'cnf_x', agentVersion: 2,
})));
record('pinning: an approval carries both its token and its version',
sent.confirmation === 'cnf_x' && sent.agentVersion === 2,
JSON.stringify(sent));
}
/* ── A deployment with no agent ───────────────────────────────────────────────
*
* The local simulator used to be the fallback. It is gone, so an unconfigured
* deployment now has NOTHING to answer with — and the whole risk of deleting it
* is that this state becomes silent. A panel that accepts a question and
* produces nothing is the worst of the possible failures and the hardest to
* diagnose from the outside.
*/
console.log('\n── No agent configured ──');
{
const { createUnconfiguredProvider } = await server.ssrLoadModule('/src/components/ai-assistant/provider.js');
const p = createUnconfiguredProvider();
let blocks = [];
for await (const snap of p.stream({ question: 'what needs my attention?' })) blocks = snap;
record('unconfigured: a question still gets an answer', blocks.length > 0,
`${blocks.length} block(s)`);
record('unconfigured: the answer says WHY it cannot answer',
blocks.some((b) => /not configured/i.test(b.text || '')),
blocks[0]?.text?.slice(0, 80) || 'nothing said');
record('unconfigured: it names what to set',
blocks.some((b) => /VITE_AGENT_API/.test(b.text || '')),
'an operator can act on it');
record('unconfigured: it is not silent', p.id === 'unconfigured');
}
/* ── Streaming ────────────────────────────────────────────────────────────────
*
* The property worth guarding is not "deltas arrive" — it is that a client
* reading only the LAST snapshot ends up where it would have without streaming.
* If that ever stops being true, streaming has quietly become a second, worse
* answering path.
*/
/**
* Follow-ups after an answer are follow-ups, not the landing screen redrawn.
*
* The suggestions themselves are the server's; what is asserted here is the
* only decision the panel makes about them — that a conversation is never
* offered a question it has already asked or already been offered. Without it a
* page holding six intents returns its top three after every answer, including
* the one just pressed.
*/
console.log('\n── Follow-ups ──');
{
const { nextSteps } = await server.ssrLoadModule('/src/components/ai-assistant/useAssistant.js');
const PAGE = [
{ label: 'How healthy is the platform right now?', prompt: 'How healthy is the platform right now?' },
{ label: 'What needs attention right now?', prompt: 'What needs attention right now?' },
{ label: 'What should I do next?', prompt: 'What should I do next?' },
];
/* A query-scoped answer is narrower, and led by the thing just asked — which
is exactly why it can come back empty once that one is excluded. */
/* A query-scoped answer is led by the thing just asked and carries some
neighbours with it — which is what makes exclusion meaningful rather than
total. */
const refresh = async ({ query = '' } = {}) => (
query ? (/next|should/i.test(query) ? [PAGE[2], PAGE[1], PAGE[0]] : PAGE.slice(0, 2)) : PAGE
);
const said = (r) => (r || []).map((c) => c.prompt);
record('the question just asked is never offered back',
await (async () => {
const out = said(await nextSteps({ question: 'What should I do next?', history: [], refresh }));
return !out.includes(PAGE[2].prompt) && out.length > 0;
})());
record('...nor is a chip this thread has already offered',
said(await nextSteps({
question: 'How healthy is the platform right now?',
history: [
{ role: 'user', text: 'What should I do next?' },
{ role: 'assistant', followUp: [PAGE[0], PAGE[1]] },
],
refresh,
})).length === 0);
record('...and case and punctuation are not differences',
JSON.stringify(said(await nextSteps({
/* No capital, no question mark — still the question already asked. */
question: 'what should i do next',
history: [{ role: 'user', text: 'What needs attention right now?' }],
refresh,
}))) === JSON.stringify([PAGE[0].prompt]));
/* The correction a live run forced: a question the catalogue does not cover
must not fall through to the page's own ranking. "Summarize hiring
activity" matched nothing, nothing was excluded, and the reader got the
three standing highlights under an unrelated answer. */
record('a question the catalogue does not cover offers nothing, not the page ranking',
(await nextSteps({
question: 'Summarize hiring activity', history: [],
refresh: async ({ query } = {}) => (query ? [] : PAGE),
})) === undefined);
record('...and the page ranking is never reached as a fallback',
(await nextSteps({
question: 'What should I do next?', history: [],
refresh: async ({ query } = {}) => (query ? [PAGE[2]] : PAGE),
})) === undefined);
record('when the catalogue is spent the row is left empty, not repeated',
(await nextSteps({
question: 'x',
history: [{ role: 'assistant', followUp: PAGE }],
refresh: async () => PAGE,
})) === undefined);
record('a response that repeats itself is collapsed',
said(await nextSteps({
question: 'x', history: [],
refresh: async () => [PAGE[0], PAGE[0], PAGE[1]],
})).length === 2);
}
console.log('\n── Streaming ──');
{
const providerMod = await server.ssrLoadModule('/src/components/ai-assistant/provider.js');
const provider = providerMod.createAgentProvider({ baseUrl: '/api/v1' });
const sse = (frames) => ({
ok: true,
status: 200,
headers: { get: (k) => (k === 'Content-Type' ? 'text/event-stream' : null) },
body: {
getReader() {
const chunks = frames.map((f) => new TextEncoder().encode(`data: ${JSON.stringify(f)}\n\n`));
let i = 0;
return {
read: async () => (i < chunks.length ? { done: false, value: chunks[i++] } : { done: true }),
cancel: () => {},
};
},
},
});
const withFetch = async (impl, fn) => {
const orig = globalThis.fetch;
globalThis.fetch = impl;
try { return await fn(); } finally { globalThis.fetch = orig; }
};
const collect = async (stream) => {
const snaps = [];
for await (const s of stream) snaps.push(s);
return snaps;
};
/* Text arrives progressively, and the final run replaces it. */
{
const snaps = await withFetch(
async () => sse([
{ delta: '## Roles at risk\n\n' },
{ delta: 'Three roles have applicants but ' },
{ delta: 'none strong.' },
{ run: { termination: 'Completed', output: '## Roles at risk\n\nThree roles have applicants but none strong.', usage: {} } },
]),
() => collect(provider.stream({ question: 'risk?', agent: { id: 'positions-agent' } }))
);
record('streaming: the answer arrives in more than one snapshot', snaps.length > 1,
`${snaps.length} snapshots`);
record('streaming: text renders before the run finishes',
snaps[0].some((b) => b.type === 'heading' || b.type === 'text'),
JSON.stringify(snaps[0]).slice(0, 70));
/* The one that matters. */
const last = snaps[snaps.length - 1];
record('streaming: the last snapshot is what a non-streaming client would get',
last.some((b) => b.type === 'heading' && /Roles at risk/.test(b.text))
&& last.some((b) => b.type === 'text' && /none strong/.test(b.text)),
JSON.stringify(last).slice(0, 90));
}
/* A proposed write survives the stream — it rides on the final run event, not
on any delta, so a client that only read deltas would miss the decision. */
{
const snaps = await withFetch(
async () => sse([
{ delta: "I'll put someone forward." },
{ run: {
termination: 'ConfirmationPending',
output: "I'll put someone forward.",
confirmations: [{ token: 'cnf_x', title: 'Assign Maya Chen', summary: 'She will be scheduled.' }],
usage: {},
} },
]),
() => collect(provider.stream({ question: 'cover friday', agent: { id: 'positions-agent' } }))
);
const last = snaps[snaps.length - 1];
record('streaming: a proposed write survives to the final snapshot',
last.some((b) => b.type === 'confirmation' && b.token === 'cnf_x'));
}
/* A stream that dies mid-answer keeps what arrived. An empty panel would
throw away text the reader was already reading. */
{
const snaps = await withFetch(
async () => sse([{ delta: 'Half an answ' }]),
() => collect(provider.stream({ question: 'x', agent: { id: 'a' } }))
);
const last = snaps[snaps.length - 1];
record('streaming: a dropped connection keeps what arrived',
last.some((b) => /Half an answ/.test(b.text || '')),
JSON.stringify(last).slice(0, 60));
}
/* A server that answers JSON instead of a stream still works. */
{
const snaps = await withFetch(
async () => ({
ok: true, status: 200,
headers: { get: () => 'application/json' },
json: async () => ({ termination: 'Completed', output: 'Plain answer.', usage: {} }),
}),
() => collect(provider.stream({ question: 'x', agent: { id: 'a' } }))
);
record('streaming: a non-streaming server still answers',
snaps.at(-1).some((b) => /Plain answer/.test(b.text || '')));
}
}
/* ── An assumed attendance does not manufacture a score ──────────────────── */
/**
* `attendance_score` defaults to 100 so an unrated worker shows a full bar
* instead of an accusatory 0. It carries 30% of reliability and 12% of the KROW
* score, so used as a scoring input it lifted a profile with no evidence at all
* from 0 to 12 — out of "Not yet scored" and into the low band, where every
* ranking read it as a poor worker rather than an unassessed one. And this is
* live: recalcProfilePatch runs on course completion, skill assessments and
* identity creation, so a seeded 0 did not stay 0.
*/
const scoreModule = await server.ssrLoadModule('/src/lib/krowScore.js');
const noEvidence = seedModule.seedData.WorkerProfile.filter((p) => !(p.shifts_completed > 0));
record('the seed still contains profiles with no attendance record', noEvidence.length > 0,
`${noEvidence.length} of ${seedModule.seedData.WorkerProfile.length}`);
const manufactured = noEvidence.filter((p) => scoreModule.computeKrowScore(p).krow_score > 0);
record('a profile with no evidence scores 0, not a number made of an assumption',
manufactured.length === 0,
manufactured.map((p) => `${p.full_name}=${scoreModule.computeKrowScore(p).krow_score}`).join(', ') || 'all zero');
record('...and its breakdown reports attendance as absent, not as zero percent',
noEvidence.every((p) => scoreModule.computeKrowScore(p).breakdown.attendance === null));
/* Recalculating a seeded profile must change nothing.
krowScore.js calls itself "formula-based, auto-recalculated", but every one of
the nine seeded profiles disagreed with the formula that claims to compute it
— hand-authored narrative values on one side, an engine on the other, never
reconciled. That is not cosmetic: recalcProfilePatch runs on course
completion, skill assessments and identity creation, so a worker's score
jumped the moment they touched anything (Priya 68 → 78, a whole band). The
seed now stores the engine's own output, and this keeps the two in step. */
const scoreDrift = seedModule.seedData.WorkerProfile
.map((p) => ({ p, r: scoreModule.recalcProfilePatch(p) }))
.filter(({ p, r }) =>
r.krow_score !== p.krow_score ||
r.reliability_score !== p.reliability_score ||
r.profile_completion !== p.profile_completion);
record('recalculating a seeded profile changes nothing', scoreDrift.length === 0,
scoreDrift.map(({ p, r }) =>
`${p.full_name}: ${p.krow_score}/${p.reliability_score}/${p.profile_completion}` +
` -> ${r.krow_score}/${r.reliability_score}/${r.profile_completion}`).join('; ') || 'all 9 in step');
/* A completion percentage cannot exceed 100. It could: seven optional fields
incremented the numerator and the denominator counted five, so the most
complete profile computed 107% complete. */
const overComplete = seedModule.seedData.WorkerProfile
.filter((p) => scoreModule.computeProfileCompletion(p) > 100);
record('profile completion never exceeds 100%', overComplete.length === 0,
overComplete.map((p) => `${p.full_name}=${scoreModule.computeProfileCompletion(p)}`).join(', ') || 'all within range');
/* And the breakdown is percentages a person reads, not raw binary floats. */
const unrounded = seedModule.seedData.WorkerProfile.flatMap((p) =>
Object.entries(scoreModule.computeKrowScore(p).breakdown)
.filter(([, v]) => v !== null && !Number.isInteger(v))
.map(([k, v]) => `${p.full_name}.${k}=${v}`));
record('every breakdown figure is a whole percentage', unrounded.length === 0,
unrounded.join(', ') || 'all whole');
/* The guard must not silence a worker who has actually turned up. */
const withRecord = seedModule.seedData.WorkerProfile.filter((p) => p.shifts_completed > 0);
record('a worker with a shift record still scores on attendance',
withRecord.length > 0 &&
withRecord.every((p) => scoreModule.computeKrowScore(p).breakdown.attendance > 0),
`${withRecord.length} profiles with shifts`);
/* Real evidence still earns a score — the guard withholds, it does not block. */
const firstCourse = seedModule.seedData.Course[0];
const earned = scoreModule.recalcProfilePatch({
...noEvidence[0], completed_courses: [firstCourse.id], earned_badges: [],
xp: (noEvidence[0].xp || 0) + (firstCourse.xp || 0),
});
record('completing a real course does earn a score', earned.krow_score > 0,
`${noEvidence[0].full_name} → ${earned.krow_score}`);
/* ── Every application status can be placed on the funnel ────────────────── */
/**
* `atOrBeyond` ranks a status by its index in STAGE_ORDER, so a status missing
* from that list ranks -1 and falls out of *every* bucket including `applied`.
* That is what happened to `rejected` and `assigned`: a position's funnel simply
* lost them, and a role whose candidates had all been assigned to shifts read as
* unfilled. Nothing in the app held the full status list to check against.
*/
const records = await server.ssrLoadModule('/src/lib/hiringRecords.js');
const unplaceable = records.APPLICATION_STATUSES.filter((st) => records.rankOf(st) < 0);
record('every application status can be placed on the funnel',
unplaceable.length === 0, unplaceable.join(', ') || 'all placed');
record('a rejected candidate counts as screened',
records.rankOf('rejected') >= records.STAGE_ORDER.indexOf('ai_screened'));
record('...but is never counted as shortlisted, a stage rejection overwrites',
records.rankOf('rejected') < records.STAGE_ORDER.indexOf('shortlisted'));
record('an assigned candidate counts as hired',
records.HIRED_STATUSES.includes('assigned') &&
records.rankOf('assigned') >= records.STAGE_ORDER.indexOf('hired'));
/* ── Final selection, and seats ──────────────────────────────────────────
*
* 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, which is 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.
*
* That distinction is the whole point: `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 who
* have merely been booked in.
*/
const fsApps = [
{ id: 'a-applied', status: 'applied' },
{ id: 'a-booked', status: 'interview' }, // arranged, never sat
{ id: 'a-done', status: 'interview' }, // sat it
{ id: 'a-hired', status: 'hired' },
{ id: 'a-assigned', status: 'assigned' },
{ id: 'a-rejected', status: 'rejected' },
];
const fsInterviews = [
{ id: 'i1', application_id: 'a-done' },
{ id: 'i2', application_id: 'a-hired' },
{ id: 'i3', application_id: 'a-rejected' },
];
const queue = records.finalSelection(fsApps, fsInterviews).map((a) => a.id);
record('final selection holds only the interviewed and undecided',
queue.length === 1 && queue[0] === 'a-done', queue.join(', ') || 'empty');
record('...so an interview merely arranged does not qualify',
!records.isFinalSelection(fsApps[1], fsInterviews),
'status interview with no interview row');
record('...and a decided application never reappears in the queue',
['a-hired', 'a-assigned', 'a-rejected']
.every((id) => !queue.includes(id)),
'hired, assigned and rejected all excluded');
record('...while the interview record, not interview_id, is what is read',
!records.isFinalSelection({ id: 'x', status: 'interview', interview_id: 'dangling' }, []),
'a soft reference alone proves nothing');
/* Seats are counted from the employment records. A `filled` column would be a
second source of truth, and nothing could say which one was right. */
const seatPosting = { id: 'p1', headcount: 2 };
const seatStaff = [
{ id: 's1', job_posting_id: 'p1', status: 'active' },
{ id: 's2', job_posting_id: 'p1', status: 'onboarding' },
{ id: 's3', job_posting_id: 'p1', status: 'inactive' }, // left; seat reopens
{ id: 's4', job_posting_id: 'p2', status: 'active' }, // another position
];
record('seats filled are counted from the staff records',
records.filledFor('p1', seatStaff) === 2, `${records.filledFor('p1', seatStaff)} of 2`);
record('...someone who has left frees their seat',
records.remainingFor({ id: 'p1', headcount: 3 }, seatStaff) === 1,
'inactive staff do not hold a seat');
record('...a full position reports no remaining seats, never a negative number',
records.isFullyStaffed(seatPosting, seatStaff)
&& records.remainingFor({ id: 'p1', headcount: 1 }, seatStaff) === 0,
'over-hiring floors at zero');
/* ── The Candidates page IS the final-selection queue ─────────────────────
*
* The derivation above is only worth having if the page that decides hires
* actually opens on it. Candidates used to list every application the org had
* ever taken — all seven statuses at once — which made it a second Talent Pool
* rather than a queue of decisions waiting.
*
* Two rules these assertions hold in place, both of them safety rules rather
* than presentation ones:
*
* The evidence is the interview ROW. `interview_id` is a soft reference the
* schema deliberately leaves unconstrained, and it dangles in real data.
*
* The score never gates. `ai_score`, `verdict` and `hire_recommendation`
* assist a recruiter; a human makes the selection. A candidate who scored
* zero and sat the interview is still a decision somebody owes them.
*/
record('a candidate who scored zero still reaches the queue',
records.isFinalSelection({ id: 'a-zero', status: 'interview', ai_score: 0 },
[{ id: 'i9', application_id: 'a-zero' }]),
'screening is not a gate on the decision');
{
/* Comments are stripped first: these assert what the page DOES, and a
comment explaining the bug that was fixed must not read as the bug. */
const decomment = (src) => src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
const candidatesSrc = decomment(readFileSync(join(ROOT, resolveSourcePath('src/pages/admin/Candidates.jsx')), 'utf8'));
const nodesSrc = decomment(readFileSync(join(ROOT, resolveSourcePath('src/pages/admin/candidates/nodes.jsx')), 'utf8'));
/* Read from source rather than imported: the page pulls in the interview
modal, which touches `window` at module scope, and standing up a DOM to
read one string constant would be a worse test than reading the string. */
const defaultStage = candidatesSrc.match(/export const DEFAULT_STAGE = '([a-z_]+)'/)?.[1];
record('the candidate page opens on the final-selection queue',
defaultStage === 'final' && /useState\(DEFAULT_STAGE\)/.test(candidatesSrc),
String(defaultStage));
/* The interview rows were fetched and then thrown away — `useInterviews();`
on its own line, its result unbound — which is why the queue could not be
derived on the one page that needed it. */
record('the candidate page keeps the interview rows it fetches',
/(const|let)\s*\{[^}]*\}\s*=\s*useInterviews\(\)/.test(candidatesSrc),
'useInterviews() is bound, not discarded');
/* The filter compared `a.stage`. Applications carry `status`; no application
has ever had a `stage` property, so every stage selection emptied the
page. */
record('the stage filter reads a field applications actually carry',
!/\ba\.stage\b/.test(candidatesSrc) && /matchesStage\(/.test(candidatesSrc),
'status, not an undefined stage');
/* Narrowing the default view is only safe if nothing becomes unreachable.
`shortlisted` and `assigned` were absent from these options, so the rows
holding them could not be found by any selection. */
const offered = [...nodesSrc.matchAll(/\{ value: '([a-z_]+)', label: '[^']+' \}/g)]
.map((m) => m[1]);
const unreachable = records.APPLICATION_STATUSES.filter((st) => !offered.includes(st));
record('every application status is still reachable from the stage filter',
unreachable.length === 0, unreachable.join(', ') || 'all seven offered');
record('...and the queue itself is offered alongside them',
offered.includes('final') && offered.includes('all'),
'final selection and all stages');
}
/* ── Human interviews leave a trace, and only completion counts ───────────
*
* A human interview used to persist nothing at all: the modal collected a
* type, a date, a time and notes, announced "Interview scheduled!", and dropped
* every one of them. So the final-selection queue could only ever hold people
* an AI had interviewed, and anyone seen by a person was invisible to it.
*
* What changed is where each fact lives, and the split is the safety property:
*
* booking → a `user_activity` row. Advances nobody.
* completion→ an `ai_interviews` row. The evidence, unchanged since Step 6.
*
* `isFinalSelection` is not touched by any of this and is not expected to be.
* These assertions exist to prove that a booking cannot masquerade as an
* interview, which is the one way this work could put a candidate in front of a
* hiring decision that nobody has actually met.
*/
const human = await server.ssrLoadModule('/src/lib/humanInterviews.js');
{
const app = { id: 'a-human', status: 'interview', job_posting_id: 'p1', applicant_name: 'A' };
const at = (n) => new Date(Date.UTC(2026, 0, n)).toISOString();
const bookedEvent = {
event_type: human.SCHEDULED, application_id: 'a-human', created_date: at(1),
metadata: { interview_type: 'video', scheduled_date: '2026-01-09', scheduled_time: '10:00', notes: 'prep' },
};
const completedRow = { id: 'i-human', application_id: 'a-human' };
/* Booked, and nothing else. */
record('a booked human interview is recorded',
human.humanInterviewState(app, [bookedEvent], []) === 'scheduled');
record('...and the booking keeps what was entered',
(() => {
const d = human.scheduleDetails(human.scheduledInterview(app, [bookedEvent]));
return d.type === 'video' && d.date === '2026-01-09' && d.time === '10:00' && d.notes === 'prep';
})(), 'type, date, time and notes survive');
record('...but booking alone is NOT final selection',
!records.isFinalSelection(app, []),
'no interview row, no decision to make');
/* Held. */
record('a completed human interview is recorded',
human.humanInterviewState(app, [bookedEvent], [completedRow]) === 'completed');
record('...and a completed human interview DOES reach final selection',
records.isFinalSelection(app, [completedRow]));
record('...even with no score, because a human interview measures none',
records.isFinalSelection({ ...app, ai_score: 0 }, [completedRow]),
'absence of an AI score is not absence of an interview');
/* Not held. Both outcomes are events, and neither is an interview. */
for (const [label, type] of [['cancelled', human.CANCELLED], ['not attended', human.NO_SHOW]]) {
const events = [bookedEvent, { event_type: type, application_id: 'a-human', created_date: at(2) }];
record(`an interview ${label} leaves no completion record`,
human.humanInterviewState(app, events, []) === (type === human.CANCELLED ? 'cancelled' : 'no_show'));
record(`...so ${label} never reaches final selection`,
!records.isFinalSelection(app, []));
}
/* A decided application is out regardless of how it was interviewed. */
for (const status of ['hired', 'assigned', 'rejected']) {
record(`a ${status} application leaves the queue even with a human interview`,
!records.isFinalSelection({ ...app, status }, [completedRow]));
}
/* The Step 6 guard, restated against a human booking: a booking may well set
interview_id one day, and it must still prove nothing on its own. */
record('a dangling interview_id still does not qualify a human interview',
!records.isFinalSelection({ ...app, interview_id: 'nope' }, []),
'the row is the evidence, not the reference');
/* An event claiming completion with no row behind it. The log is not the
record, and treating it as one would reintroduce exactly the soft-reference
problem interview_id already demonstrates. */
record('a completion EVENT without an interview row is not completion',
human.humanInterviewState(app,
[{ event_type: human.COMPLETED, application_id: 'a-human', created_date: at(3) }], []) === 'none',
'user_activity is a log, not the evidence');
}
/* ── Nothing about AI is invented for a human interview ───────────────────
*
* The completed-interview record lives in `ai_interviews`, which is currently
* reused as the durable completion record for both flows. Reusing the table is
* a deliberate decision; filling in its AI columns with plausible numbers no
* machine produced would not be. A fabricated score or verdict would be read by
* analytics, by the candidate profile and by a recruiter as a measurement.
*
* `overall_interview_score` matters twice over: the server copies it onto the
* application only when the request mentions it, so sending a 0 would overwrite
* a real screening score with one this interview never took.
*/
{
const hooksSrc = readFileSync(join(ROOT, resolveSourcePath('src/lib/krowHooks.js')), 'utf8');
const complete = hooksSrc.slice(
hooksSrc.indexOf('export function useCompleteHumanInterview'),
hooksSrc.indexOf('export function useRecordInterviewNotHeld')
);
const schedule = hooksSrc.slice(
hooksSrc.indexOf('export function useScheduleHumanInterview'),
hooksSrc.indexOf('export function useCompleteHumanInterview')
);
record('the human completion path exists at all', complete.length > 0 && schedule.length > 0);
const fabricated = ['overall_interview_score', 'integrity_score', 'ai_flags', 'category_scores',
'hire_recommendation', 'messages', 'ai_score']
.filter((f) => new RegExp(`^\\s*${f}\\s*:`, 'm').test(complete));
record('a human interview fabricates no AI data',
fabricated.length === 0, fabricated.join(', ') || 'no AI column is written');
record('...and the interviewer\'s own assessment is only sent when given',
/if \(assessment\) record\.verdict = assessment;/.test(complete),
'verdict is human-entered or left to the schema');
record('...while the facts it does write are the ones it knows',
/application_id: application\.id/.test(complete)
&& /job_posting_id: application\.job_posting_id/.test(complete));
/* The rule the whole step turns on. */
record('scheduling never writes an interview record',
!/AIInterview|useCreateInterview|createInterview/.test(schedule),
'a booking touches user_activity only');
record('...and completion goes through the existing interview client',
/createInterview\.mutateAsync/.test(complete),
'no second interview API');
/* Not held writes an event and nothing else — no row, and no invented
application status to go with it. */
const notHeld = hooksSrc.slice(hooksSrc.indexOf('export function useRecordInterviewNotHeld'));
record('a cancelled or unattended interview writes no record and no new status',
!/AIInterview|createInterview/.test(notHeld) && !/JobApplication\.update/.test(notHeld),
'an event, and nothing else');
}
/* The AI flow is untouched: it still writes its own record at completion, and
still does it through the same client this now shares. */
{
const aiSrc = readFileSync(join(ROOT, resolveSourcePath('src/components/krow/AIInterviewModal.jsx')), 'utf8');
record('the AI interview still records itself on completion',
/const finishInterview = useCallback/.test(aiSrc)
&& /createInterview\.mutateAsync\(\{/.test(aiSrc)
&& /messages: finalMessages/.test(aiSrc),
'unchanged by the human path');
}
/* The modal is no longer theatre. */
{
const modalSrc = readFileSync(join(ROOT, resolveSourcePath('src/components/krow/ScheduleInterviewModal.jsx')), 'utf8');
record('scheduling an interview persists it',
/useScheduleHumanInterview/.test(modalSrc) && /schedule\.mutateAsync/.test(modalSrc));
record('...the props its six call sites pass are unchanged',
/\{ open, onClose, application, position \}/.test(modalSrc),
'open, onClose, application — position optional');
record('...and a failed save is reported rather than celebrated',
/setError\(/.test(modalSrc) && !/setScheduled\(true\)/.test(modalSrc),
'the old version always claimed success');
}
/* Every path that files an application on somebody's behalf must carry the
link back to the talent-pool record. The column is nullable, so omitting it
saves cleanly and fails silently: the application belongs to an email address
instead of to a person, and the hire it becomes cannot be traced back to the
profile it came from. Both call sites are asserted because they were written
at different times and only one of them is on the position page. */
const positionDetailSource = readFileSync(join(ROOT, resolveSourcePath('src/pages/PositionDetail.jsx')), 'utf8');
const hooksSource = readFileSync(join(ROOT, resolveSourcePath('src/lib/krowHooks.js')), 'utf8');
record('admitting talent to a position links the application to the profile',
/worker_profile_id: profile\.id/.test(positionDetailSource),
'PositionDetail.admitTalent');
record('...and so does assigning a worker straight from the pool',
/worker_profile_id: worker\.profile\?\.id/.test(hooksSource),
'useAssignWorkers filing an application');
/* ── One person, many positions ──────────────────────────────────────────
*
* The two profile routes answer different questions and must stay separate.
* `/admin/talent/:id` is the PERSON — a `worker_profiles` row, and every
* position they are up for. `/admin/candidates/:id` is that person FOR ONE
* POSITION — a `job_applications` row, with its score, interview and decision.
*
* One screen doing both is how a product ends up with a candidate record per
* vacancy, which is exactly what `worker_profiles` exists to prevent.
*/
record('the person and the application have separate routes',
routed.has('/admin/talent/:id') && routed.has('/admin/candidates/:id'),
[...routed].filter((r) => r.includes('talent') || r.includes('candidates')).join(', '));
/* Which applications are this person's. The foreign key is the answer; email is
the fallback for rows written before the key was being set, and it has to be
there or every application already in the database detaches from its person. */
const whoProfile = { id: 'wp-1', email: 'Maria@Example.com' };
const whoApps = [
{ id: 'by-key', worker_profile_id: 'wp-1', email: 'nothing@else.com' },
{ id: 'by-email', worker_profile_id: null, email: 'maria@example.com' },
{ id: 'by-email-case', email: 'MARIA@EXAMPLE.COM' },
{ id: 'somebody-else', worker_profile_id: 'wp-2', email: 'other@example.com' },
];
const whoIds = records.applicationsForProfile(whoApps, whoProfile).map((a) => a.id);
record('an application finds its person by foreign key',
whoIds.includes('by-key'), 'worker_profile_id');
record('...and falls back to email, case-insensitively, for older rows',
whoIds.includes('by-email') && whoIds.includes('by-email-case'),
'citext identity, as worker_profiles already asserts');
record('...without collecting somebody else',
!whoIds.includes('somebody-else') && whoIds.length === 3, whoIds.join(', '));
record('...and asks for nobody when there is no profile',
records.applicationsForProfile(whoApps, null).length === 0, 'empty, not everything');
/* The reported symptom: a talent pool you could read and not use. */
const talentPoolSource = readFileSync(join(ROOT, resolveSourcePath('src/pages/admin/talent-pool/nodes.jsx')), 'utf8');
record('a talent pool row opens that person',
/onRowClick=\{\(p\) => navigate\(`\/admin\/talent\/\$\{p\.id\}`\)\}/.test(talentPoolSource),
'DataTable wires the handler only when it is given one');
record('...and no action pretends to open something it does not',
!/toast\.info\(`Opening/.test(talentPoolSource),
'the "view profile" toast is gone');
record('...and a person can be put in front of a vacancy from the pool',
/setConsidering\(p\)/.test(talentPoolSource)
&& /AddToPositionModal/.test(talentPoolSource),
'the step that was missing between pool and position');
/* "Screened and waiting on a decision" includes the shortlisted. This counted
`ai_screened` alone, which read as correct only while nothing was ever
shortlisted — the first shortlisted candidate dropped out of the count in
silence. The baseline records the prompt; this records the rule. */
const insightsFacts = insightsModule.buildFacts({
postings: SEED.JobPosting,
interviews: [],
staff: [],
profiles: [],
activity: [],
courses: [],
profile: null,
user: seedModule.DEMO_USER,
today: new Date('2026-08-20T09:00:00.000Z'),
applications: [
{ id: 's1', status: 'shortlisted', ai_score: 91, job_posting_id: 'p', applicant_name: 'Shortlisted Strong' },
{ id: 's2', status: 'ai_screened', ai_score: 85, job_posting_id: 'p', applicant_name: 'Screened Strong' },
{ id: 's3', status: 'shortlisted', ai_score: 40, job_posting_id: 'p', applicant_name: 'Shortlisted Weak' },
],
});
record('a shortlisted strong candidate counts as waiting on a decision',
insightsFacts.stalled.length === 2,
insightsFacts.stalled.map((a) => a.applicant_name).join(', ') || 'none');
/* The backend fixture is generated from this seed, not maintained beside it.
`seed/fixtures/seed.json` is what the Go seeder loads and `src/api/seed.js` is
what a person edits. They were two hand-maintained copies of the same data,
which fails quietly: the demo and the API would answer the same question with
different numbers, and the first symptom would be a page disagreeing with an
agent. This asserts the committed fixture is exactly what this seed implies —
`npm run seed:fixture` regenerates it. */
const builtFixture = await buildFixture(server);
if (!existsSync(FIXTURE_PATH)) {
record('the backend fixture is in step with this seed', false,
'seed.json missing — run `npm run seed:fixture`');
} else {
const onDisk = readFileSync(FIXTURE_PATH, 'utf8');
record('the backend fixture is in step with this seed', onDisk === builtFixture,
onDisk === builtFixture
? `${JSON.parse(builtFixture).entities.JobApplication.length} applications, byte-identical`
: 'stale — run `npm run seed:fixture`');
}
/* ShiftRecord stays out of the fixture on purpose: its dates anchor to the day
the seeder runs, so the Go side generates it rather than reading a snapshot
that would go stale. */
record('...and leaves the date-anchored shift records to the Go seeder',
!('ShiftRecord' in JSON.parse(builtFixture).entities) && seedModule.seedData.ShiftRecord.length > 0,
`${seedModule.seedData.ShiftRecord.length} shift records held in seed.js only`);
/* ── The registry does not report a shipped agent as an override of itself ── */
/**
* Authored agents now come from `/api/v1/agent-definitions`, and that registry
* holds both kinds: the shipped set, published from the same files this build
* ships, and anything a person authored. Passing the first kind through as an
* authored source made every shipped agent report that it "replaces the
* built-in agent of the same id" — nine warnings on the one screen that exists
* to say what is wrong, about a deployment that was working correctly.
*
* The rows below are the shape the live registry actually returns.
*/
const storeModule = await server.ssrLoadModule('/src/lib/agents/agentStore.js');
const agentsModule = await server.ssrLoadModule('/src/lib/agents/registry.js');
const shippedIds = new Set(agentsModule.AGENTS.map((a) => a.id));
const registryRows = [
...agentsModule.AGENTS.map((a) => ({
definition_id: a.id, visibility: 'organization', markdown: a.markdown,
})),
{
definition_id: 'authored-example', visibility: 'personal',
markdown: '---\nid: authored-example\nname: Authored Example\nstatus: published\n'
+ 'version: 1\npages:\n - control-center\n---\n\n# Authored Example\n',
},
];
const built = agentsModule.readAgentRegistry(
storeModule.sourcesFrom(registryRows, shippedIds), { customSkills: [] });
const falseShadows = built.diagnostics.filter((d) => d.kind === 'shadowed');
record('a shipped agent published to the registry is not reported as overriding itself',
falseShadows.length === 0,
falseShadows.map((d) => d.agentId).join(', ') || 'no false overrides');
record('...and an authored agent still appears beside the shipped ones',
built.agents.some((a) => a.id === 'authored-example')
&& built.agents.length === agentsModule.AGENTS.length + 1,
`${built.agents.length} agents from ${registryRows.length} rows`);
/* A genuine personal override of a shipped id is still reported — the
diagnostic is useful, it was only being raised for the wrong rows. */
const overridden = agentsModule.readAgentRegistry(
storeModule.sourcesFrom([{
definition_id: agentsModule.AGENTS[0].id, visibility: 'personal',
markdown: agentsModule.AGENTS[0].markdown,
}], shippedIds), { customSkills: [] });
record('a personal override of a shipped agent is still reported as one',
overridden.diagnostics.some((d) => d.kind === 'shadowed'),
overridden.diagnostics.map((d) => d.kind).join(', ') || 'none');
/* The seed must exercise every status, or none of the above can catch anything. */
const seededStatuses = new Set(seedModule.seedData.JobApplication.map((a) => a.status));
const neverSeeded = records.APPLICATION_STATUSES.filter((st) => !seededStatuses.has(st));
record('the seed exercises every application status',
neverSeeded.length === 0,
neverSeeded.length ? `never produced: ${neverSeeded.join(', ')}` : `all ${seededStatuses.size}`);
record('...and at least one assignment exists, so `assigned` means something',
seedModule.seedData.Assignment.length > 0,
`${seedModule.seedData.Assignment.length} assignment(s)`);
record('...while most positions still have none, so the empty case stays covered',
seedModule.seedData.Assignment.length < seedModule.seedData.JobPosting.length,
`${seedModule.seedData.Assignment.length} of ${seedModule.seedData.JobPosting.length} positions`);
/* The accounting identity: a funnel that loses people is not a funnel. */
const positionsModule = await server.ssrLoadModule('/src/lib/admin/positionInsights.js');
const everyStage = [
{ id: '1', job_posting_id: 'p', status: 'applied', ai_score: 0 },
{ id: '2', job_posting_id: 'p', status: 'ai_screened', ai_score: 75 },
{ id: '3', job_posting_id: 'p', status: 'rejected', ai_score: 40 },
{ id: '4', job_posting_id: 'p', status: 'assigned', ai_score: 88 },
];
const posStats = positionsModule.buildPosition(
{ id: 'p', title: 'Bartender', status: 'active' }, everyStage).stats;
record('a position accounts for every applicant it has',
posStats.unscreened + posStats.screened === posStats.applied,
`${posStats.unscreened} unscreened + ${posStats.screened} screened of ${posStats.applied}`);
record('a rejected applicant is screened but not shortlisted',
posStats.screened === 3 && posStats.shortlisted === 1,
`screened ${posStats.screened}, shortlisted ${posStats.shortlisted}`);
record('an assigned applicant still counts as hired on the position',
posStats.hired === 1, `hired ${posStats.hired}`);
const funnel = records.buildFunnel(everyStage);
record('the funnel never grows as it narrows',
funnel.stages.every((st, i) => i === 0 || st.count <= funnel.stages[i - 1].count),
funnel.stages.map((st) => `${st.label}=${st.count}`).join(' '));
/* The list above is the app's claim about the schema. This is the schema. */
const SCHEMA_SQL = join(ROOT, '..', 'krow-backend', 'migrations', '000001_initial_schema.up.sql');
if (existsSync(SCHEMA_SQL)) {
const declared = readFileSync(SCHEMA_SQL, 'utf8')
.match(/CREATE TYPE application_status AS ENUM \(([^)]*)\)/)?.[1]
?.match(/'([a-z_]+)'/g)?.map((q) => q.slice(1, -1)) ?? [];
record('the app knows every status the schema defines',
declared.length > 0 &&
declared.every((st) => records.APPLICATION_STATUSES.includes(st)) &&
records.APPLICATION_STATUSES.every((st) => declared.includes(st)),
`schema: ${declared.join(', ')}`);
} else {
record('the app knows every status the schema defines', true,
'krow-backend not present beside this repo — cross-check skipped, not verified');
}
/* ── The dashboard cards obey the same rule as the resolvers ─────────────── */
/**
* `talent.pool` above proves the resolver does not average away the unscored.
* The dashboard cards compute their own figures and were never covered, and
* three of them broke the rule: two averaged an unscored hire in as a zero, and
* one labelled its AI-scored count "screened" — a word the rest of the product
* uses for the stage (status <> 'applied'), so that card and the unscreened
* count on the same dashboard did not add up to the pool.
*
* Rendered rather than reimplemented: the figure that matters is the one a
* person reads off the card.
*/
const renderCard = async (path, props) =>
renderToStaticMarkup(React.createElement((await server.ssrLoadModule(path)).default, props));
const twoHiresOneScored = [
{ id: 'h1', status: 'hired', ai_score: 80, created_date: '2026-01-01', updated_date: '2026-01-02' },
{ id: 'h2', status: 'hired', ai_score: 0, created_date: '2026-01-01', updated_date: '2026-01-02' },
];
const hiringCard = await renderCard('/src/components/krow/HiringAnalytics.jsx',
{ applications: twoHiresOneScored, interviews: [], staff: [] });
const shownAvg = hiringCard.match(/>([\d—]+)<\/div><div class="text-\[10px\][^"]*"[^>]*>AVG SCORE/)?.[1];
record('hiring analytics averages only the hires that carry a score',
shownAvg === '80', `card shows ${shownAvg ?? '(not found)'}, want 80 (not 40)`);
const noneScored = twoHiresOneScored.map((a) => ({ ...a, ai_score: 0 }));
const hiringNone = await renderCard('/src/components/krow/HiringAnalytics.jsx',
{ applications: noneScored, interviews: [], staff: [] });
record('...and shows an em dash rather than a confident 0 when none is scored',
/—<\/div><div class="text-\[10px\][^"]*"[^>]*>AVG SCORE/.test(hiringNone));
const impact = await renderCard('/src/components/krow/ImpactMetrics.jsx',
{ applications: twoHiresOneScored, interviews: [], staff: [] });
record('quality of hire averages only the scored hires',
impact.includes('80/100'), impact.includes('40/100') ? 'shows 40/100' : 'no 40/100');
record('...and its caption counts the hires the figure is made of',
/1 scored hire/.test(impact), /2 hired candidates/.test(impact) ? 'still says 2 hired candidates' : '');
const dist = await renderCard('/src/components/krow/ScoreDistribution.jsx',
{ applications: twoHiresOneScored });
record('the score card says "scored", not "screened"',
/1 candidate scored/.test(dist.replace(/\s+/g, ' ')) && !/candidates? screened/.test(dist),
dist.replace(/\s+/g, ' ').match(/\d+ candidates? \w+/)?.[0] ?? '(caption not found)');
let hadWindow = true;
/* Runs LAST, deliberately. Loading the block renderers pulls in the design
system, which needs a `window` to evaluate — and framer-motion, loaded in the
same graph, decides once at import time whether it is in a browser and caches
the answer. Put this section earlier and the shim below makes every motion
component rendered after it throw. */
/**
* Citation markup never reaches a reader — asserted on the rendered DOM.
*
* ROOT CAUSE, so the tests below read as something rather than as a list:
* `knowledge/context.go` instructs the model "Each <source> carries an id: cite
* it when you use what it says", and `knowledge_search` repeats it — without
* ever saying in WHAT FORM. The ids are `knowledge_chunks.id`, which are UUIDs.
* So the model invents a citation syntax, differs on it between runs, and the
* panel — which has no citation surface — renders whatever it invented.
*
* Everything here is asserted by SHAPE. Nothing matches a particular id.
*/
console.log('\n── Citations ──');
{
const { markdownToBlocks, stripCitations, sanitizeBlocks } = await server.ssrLoadModule('/src/components/ai-assistant/provider.js');
/* The block renderers reach `@/components/ds`, which reads `window` when the
module is evaluated. That is a pre-existing SSR limitation of the design
system and not what is under test here — the alternative to this shim is
asserting on block objects instead of on markup, which is exactly the
weaker test this section exists to avoid. */
hadWindow = 'window' in globalThis;
if (!hadWindow) {
globalThis.window = {
matchMedia: () => ({ matches: false, addEventListener() {}, removeEventListener() {} }),
addEventListener() {}, removeEventListener() {},
};
}
const { ResponseDocument } = await server.ssrLoadModule('/src/components/ai-assistant/ResponseBlocks.jsx');
/* Anything that would be an internal reference artefact on screen. Applied to
rendered HTML, not to intermediate values. */
const ARTEFACT = new RegExp([
'<cite', '&lt;cite', '</?citation', '/cite', // tag forms
'\\]\\(#\\)', '\\[[0-9a-f]{4,}', // link and bracket forms
'\\((?:id|ids|ref|refs|reference|references|source|sources)\\b', // the prose form
'`[0-9a-f]{4,}-', // a backticked id, however it got there
'\\(\\s*\\)', '\\(\\s*,', '``', // what a bad lift leaves behind
].join('|'), 'i');
/* An in-app link renders through the router, so the renderer needs one — the
same context the panel has when it is mounted in the app. */
const { MemoryRouter } = await server.ssrLoadModule('/node_modules/react-router-dom/dist/index.mjs')
.catch(async () => import('react-router-dom'));
/** The REAL render path: the block renderers the panel mounts. */
const dom = (blocks) => renderToStaticMarkup(
React.createElement(MemoryRouter, null,
React.createElement(ResponseDocument, { blocks: sanitizeBlocks(blocks) }))
);
/* A. – E. Every format seen, through the parser and into the DOM. */
const answer = [
'<cite id="2470e231-18e8-4c7e-a7a3-24972fabf548">',
'The screening backlog has 10 applications waiting.',
'</cite>',
'',
'## <cite id="b-2470e231">Next steps</cite>',
'',
'- Review the backlog today ([337042b3](#))',
'- Coverage is 97% across 113 shifts',
'',
'| Stage | Count |',
'|---|---|',
'| Applied ([2b94bc43](#)) | 4 |',
].join('\n');
const html = dom(markdownToBlocks(answer));
record('the rendered DOM carries no citation artefact', !ARTEFACT.test(html),
(html.match(ARTEFACT) || ['clean'])[0]);
record('...while the sentences survive intact',
html.includes('The screening backlog has 10 applications waiting.')
&& html.includes('Next steps') && html.includes('Review the backlog today'));
record('...and the business numbers in them are untouched',
html.includes('10 applications') && html.includes('97%') && html.includes('113 shifts')
&& html.includes('Applied') && html.includes('4'));
/**
* The acceptance criterion, on the rendered DOM, both ways round.
*
* The same UUID has to vanish in one sentence and survive in the other, which
* is the only test that proves the rule is about the wrapper rather than
* about the id. A sanitiser that passed the first half alone could simply be
* deleting every UUID it sees — and would take a worker's record id off their
* own profile.
*/
{
const cited = dom(markdownToBlocks(
`According to the staffing policy (id \`${'f34e8ef0-9a01-5921-9baf-641f168e2f05'}\`) workers must complete the required check.`
));
record('the reported UUID is absent from the DOM when it is a citation',
!cited.includes('f34e8ef0-9a01-5921-9baf-641f168e2f05') && !ARTEFACT.test(cited),
cited.replace(/<[^>]+>/g, '').trim());
record('...and the sentence around it is intact and unbroken',
cited.includes('According to the staffing policy workers must complete the required check.'));
const business = dom(markdownToBlocks(
`Worker ID: ${'f34e8ef0-9a01-5921-9baf-641f168e2f05'} — 10 applications, 97% coverage.`
));
record('...while the SAME UUID stays in the DOM when it is worker data',
business.includes('f34e8ef0-9a01-5921-9baf-641f168e2f05'),
business.replace(/<[^>]+>/g, '').trim());
}
/**
* A real link renders as a link; a citation-shaped one never gets the chance.
*
* The two live at different layers on purpose. `provider.js` decides what is
* an internal reference, by the wrapper's shape, before a block exists.
* `ResponseBlocks` decides how a surviving link looks. Neither knows about
* the other, which is why fixing one has never had to touch the other.
*/
{
const linked = dom(markdownToBlocks(
'See [staffing policy](#staffing) and [Positions](/admin/positions), or [the docs](https://example.test/x).'
));
record('an anchor link renders as an anchor',
linked.includes('href="#staffing"') && linked.includes('>staffing policy<'), linked.includes('href="#staffing"'));
record('an in-app link renders through the router',
linked.includes('href="/admin/positions"') && linked.includes('>Positions<'));
record('an external link opens away from the app',
linked.includes('href="https://example.test/x"') && linked.includes('rel="noreferrer noopener"'));
record('...and no link markup is left as text',
!/\[staffing policy\]|\(#staffing\)|\[Positions\]/.test(linked));
/* The security half. Model-written text is untrusted (I7): a link it can
write must not be a link it can execute. */
for (const href of ['javascript:alert(1)', 'data:text/html,<script>', 'vbscript:x', '//evil.test/x']) {
const out = dom(markdownToBlocks(`Click [here](${href}) now.`));
record(`an unsafe href is inert: ${href.slice(0, 22)}`,
!/<a\s/.test(out), /<a\s/.test(out) ? 'RENDERED AN ANCHOR' : 'stayed plain text');
}
/* And the distinction the brief turns on. */
const cited = dom(markdownToBlocks('Kept [337042b3](#) here, and [staffing policy](#staffing) too.'));
record('a citation-shaped link is removed, a real one beside it is not',
!cited.includes('337042b3') && cited.includes('href="#staffing"'),
cited.replace(/<[^>]+>/g, '').trim());
/* A heading and a table cell are plain strings, so a link there keeps its
label and loses its target rather than showing its source. */
const inCell = dom(markdownToBlocks('| Stage | Link |\n|---|---|\n| Applied | [staffing policy](#staffing) |'));
record('a link inside a table cell reads as its label',
inCell.includes('staffing policy') && !inCell.includes('](#staffing)'));
}
/* Every format discovered, in one table. The point of a table rather than a
test each is that a new rule has to be run against ALL of them — the way
this suite grew was one format at a time, and each addition was a chance to
break an earlier one. */
const U1 = 'f34e8ef0-9a01-5921-9baf-641f168e2f05';
const U2 = '2470e231-18e8-4c7e-a7a3-24972fabf548';
for (const [name, input, want] of [
['1 html cite', `<cite id="${U2}">text</cite>`, 'text'],
['2 html citation', `<citation id="${U2}">text</citation>`, 'text'],
['3 unmatched closer', 'text</cite>', 'text'],
['4 markdown citation', 'Kept [337042b3](#) here.', 'Kept here.'],
['5 citation group', 'Kept ([337042b3](#), [2b94bc43](#)).', 'Kept.'],
['6 bare uuid in brackets', 'Kept [337042b3-e7a5-4146-a972-d3370748eebf] here.', 'Kept here.'],
['7 uuid group in brackets', `Kept [${U1}, ${U2}] here.`, 'Kept here.'],
['8 backtick id wrapper', `Kept (id \`${U1}\`) here.`, 'Kept here.'],
['9 several backtick ids', `Kept (id \`${U1}\`, \`${U2}\`) here.`, 'Kept here.'],
/* The exact sentence reported, and the exact expected output. */
[' the reported sentence',
`According to the staffing policy (id \`${U1}\`) workers must complete the required check.`,
'According to the staffing policy workers must complete the required check.'],
[' A at the end', `Staffing policy applies (id \`${U1}\`).`, 'Staffing policy applies.'],
/* As reported, with the closing backtick missing — a model that opens a
code span and forgets to close it must not defeat this. */
[' B in the middle', `Staffing (id \`${U1}) policy applies.`, 'Staffing policy applies.'],
[' C two separate wrappers',
`Staffing policy (id \`${U1}\`) and right-to-work policy (id \`${U2}\`) apply.`,
'Staffing policy and right-to-work policy apply.'],
[' D two ids in one wrapper', `Policy (id \`${U1}\`, \`${U2}\`).`, 'Policy.'],
[' the older link sentence',
'an SIA licence is required before a first shift ([337042b3](#), [2b94bc43](#)).',
'an SIA licence is required before a first shift.'],
[' a full uuid link', `Kept [${U2}](#).`, 'Kept.'],
[' markdown inside a cite', `<cite id="${U1}">**Bold** kept.</cite>`, '**Bold** kept.'],
[' reference: spelling', `Kept (reference \`${U1}\`) here.`, 'Kept here.'],
[' source: spelling', `Kept (source \`${U1}\`) here.`, 'Kept here.'],
[' with a colon', `Kept (id: \`${U1}\`) here.`, 'Kept here.'],
[' without backticks', `Kept (id ${U1}) here.`, 'Kept here.'],
]) {
record(`stripped: ${name}`, stripCitations(input) === want, JSON.stringify(stripCitations(input)));
}
/* F. / G. Streaming. `readRunStream` re-parses the WHOLE answer on every
delta, so any split point is a state the reader can see — which makes the
only honest test every split point, not a chosen one. */
for (const [name, full] of [
['a link group', 'A licence is required before a first shift ([337042b3](#), [2b94bc43](#)). Act today.'],
['a cite tag', 'Screened 10 of 24 <cite id="2470e231-18e8-4c7e">this week</cite>. Act today.'],
['two separate groups', 'Coverage is 97% ([2b94bc43](#)) and 113 shifts ran ([337042b3](#)).'],
['a backtick wrapper', `Policy (id \`${U1}\`) applies. Act today.`],
['two backtick wrappers', `Staffing (id \`${U1}\`) and right-to-work (id \`${U2}\`) apply.`],
['a bracketed uuid', `Policy [${U1}] applies today.`],
]) {
let leaked = null;
for (let i = 1; i <= full.length && !leaked; i += 1) {
const frame = dom(markdownToBlocks(full.slice(0, i), { partial: true }));
if (ARTEFACT.test(frame)) leaked = `at ${i}: ${(frame.match(ARTEFACT) || [])[0]}`;
}
record(`no frame of a streamed answer leaks (${name})`, !leaked, leaked || `${full.length} frames clean`);
}
record("F the brief's partial renders nothing of itself",
markdownToBlocks('A licence is required ([3370', { partial: true }).map((b) => b.text).join(' ') === 'A licence is required');
/* And the mirror of it: a FINISHED answer has no frontier, so a bracket that
merely looks like the start of an id is left where the model put it. */
for (const [name, input] of [
['a footnote marker ending the answer', 'See [staffing policy](#staffing) and note [12]'],
['a trailing open bracket', 'The formula is (a + b'],
]) {
record(`a completed answer keeps: ${name}`, stripCitations(input) === input, JSON.stringify(stripCitations(input)));
}
/* What must survive — and the important half is the SAME UUID as a business
value. The rule has to be about the wrapper, never about the id, or a
worker's real record id disappears off their own profile. */
for (const [name, input] of [
['the same uuid as worker data', `Worker ID: ${U1}`],
[' ...as application data', `Application ID is ${U1}.`],
[' ...in an ordinary sentence', `The chunk ${U1} was reindexed.`],
[' ...in a table cell', `| Worker | ${U1} |`],
[' a bare reference number', 'Reference number 337042b3 is recorded in the worker profile.'],
[' a parenthetical that is not a reference', `Omar Haddad (hired 2026-01-04) starts Monday.`],
['H business numbers', '10 applications, 97% coverage and 113 shifts.'],
[' more business data', '89 match, 45 days open, £10 and $10 on 2026-09-02 — 3 uncovered.'],
['I an anchor link', 'See [staffing policy](#staffing).'],
[' an application link', 'See [Positions](/admin/positions).'],
[' a readable label on #', 'See [Read more](#).'],
[' an inequality', 'Throughput 5 < 10 and 20 > 3.'],
[' a code tag', 'Use <code>x</code> here.'],
[' a word starting with cit', 'A <city>place</city>.'],
[' bold, italic and code', '**Bold**, _italic_ and `code` survive.'],
/* Four hex characters, and a year — the reason the bare-bracket rule wants
eight, or a dash, before it will act. */
[' a year in brackets', 'Revenue in [2026] rose.'],
[' a footnote marker', 'See note [12] below.'],
[' a markdown task box', '- [ ] Screen the backlog'],
]) {
record(`untouched: ${name}`, stripCitations(input) === input, JSON.stringify(stripCitations(input)));
}
/* The paths that never see the markdown parser: a bounded run's message, a
transport failure, and a proposed write. Each was its own way through. */
record('a note block is sanitised too',
!ARTEFACT.test(dom([{ type: 'note', text: 'Stopped early ([337042b3](#)).' }])));
record('a confirmation is sanitised and KEEPS its token',
(() => {
const [out] = sanitizeBlocks([{
type: 'confirmation', token: 'tok_337042b3',
title: 'Move ([2b94bc43](#))', summary: 'Do it ([337042b3](#))',
warnings: ['Risky ([337042b3](#))'],
}]);
return out.token === 'tok_337042b3' && out.title === 'Move'
&& out.summary === 'Do it' && out.warnings[0] === 'Risky';
})());
record('...and an action keeps the route it points at',
(() => {
const [out] = sanitizeBlocks([{
type: 'actions',
items: [{ label: 'Open ([337042b3](#))', route: '/admin/positions#337042b3' }],
}]);
return out.items[0].label === 'Open' && out.items[0].route === '/admin/positions#337042b3';
})());
}
/**
* When the composer offers anything at all.
*
* The rule used to be "there is something in the box", so suggestions appeared
* from the second character typed — at the exact moment the reader had already
* decided what to ask, and off two characters that cannot say what they meant.
* It is now "the box is focused and empty", which is the whole state table
* below.
*/
console.log('\n── Composer suggestions ──');
{
const { shouldShowSuggestions } = await server.ssrLoadModule('/src/components/ai-assistant/KrowAssistant.jsx');
const { actionSuggestions } = await server.ssrLoadModule('/src/lib/skills/tools.js');
const base = { focused: true, busy: false, view: 'chat', count: 3 };
const show = (over) => shouldShowSuggestions({ ...base, ...over });
record('on load, before the composer is touched, nothing is offered', !show({ focused: false }));
record('an empty composer that gains focus offers what it has', show({}));
record('focus leaving hides them', !show({ focused: false }));
record('nothing is offered while an answer is arriving', !show({ busy: true }));
record('nothing is offered in History', !show({ view: 'history' }));
record('with nothing to offer, no panel at all', !show({ count: 0 }));
/**
* Typed action intent.
*
* The phrases are the SKILLS' own — a definition's `prompt:` — resolved
* through `skillsForContext`, so the page filter and the agent's scoping are
* already applied. Nothing here is a list of action names.
*/
const onPositions = reg.skillsForContext(positions);
const said = (typed) => actionSuggestions(typed, onPositions).map((c) => c.prompt);
record('typing one character names no action', said('C').length === 0);
record('...and two still names none', said('Cr').length === 0, JSON.stringify(said('Cr')));
record('"Create" surfaces the actions this page can perform',
said('Create').length > 0, JSON.stringify(said('Create')));
record('...and they come from the skills, not from a list here',
said('Create').every((p) => onPositions.some((sk) => sk.prompt === p)), JSON.stringify(said('Create')));
record('"Create position" still resolves, article and all',
said('Create position').length > 0 && said('create a position').length > 0,
JSON.stringify(said('Create position')));
record('arbitrary text names no action', said('abc').length === 0 && said('he').length === 0);
record('a reading question is never autocompleted',
said('which positions').length === 0 && said('show me').length === 0);
record('only skills that declare an action are offered',
actionSuggestions('create', [{ prompt: 'Create a thing', actions: [] }]).length === 0);
record('a switched-off skill is not offered',
reg.skillsForContext(positions, ['create-position']).every((sk) => sk.id !== 'create-position')
&& said('Create').length >= 0);
record('duplicates are collapsed',
actionSuggestions('create', [
{ prompt: 'Create a position', actions: ['create_position'] },
{ prompt: 'Create a position', actions: ['create_position'] },
]).length === 1);
}
{
/* Put the environment back. A half-built `window` reads as a browser to
framer-motion, which decides once at import time whether it is in a browser
and caches the answer — so this whole region runs last and cleans up after
itself. */
if (!hadWindow) delete globalThis.window;
}
/**
* Certifications follow the role, and the role's evidence is the org's own.
*
* ARCHITECTURE, because the tests only make sense against it: the schema has NO
* role-to-certification relationship — `role_categories` and `certifications`
* are both bare `(id, org_id, name)` lists with nothing joining them. Relevance
* is therefore observed rather than looked up, from the one place the two
* already appear together: `job_postings.role_category` beside
* `job_postings.certifications_required`.
*
* Every fixture below is built from that shape rather than from real role or
* certification names, so the assertions are about the RULE and would survive
* this organization renaming everything it has.
*/
console.log('\n── Role-aware certifications ──');
{
const { certificationsForRole } = await server.ssrLoadModule('/src/lib/positionModel.js');
const flows = await server.ssrLoadModule('/src/lib/skills/flows/index.js');
/* Two roles with different requirements, one with none, named positionally so
nothing here depends on what this deployment happens to call them. */
const [ROLE_A, ROLE_B, ROLE_NONE] = ['role-alpha', 'role-beta', 'role-gamma'];
const [C1, C2, C3] = ['cert-one', 'cert-two', 'cert-three'];
const POSTINGS = [
{ role_category: ROLE_A, certifications_required: [C1, C2] },
{ role_category: ROLE_A, certifications_required: [C1] },
{ role_category: ROLE_B, certifications_required: [C3] },
{ role_category: ROLE_NONE, certifications_required: [] },
];
const set = (xs) => JSON.stringify([...(xs || [])].sort());
record('1. a role with relevant certifications offers exactly those',
set(certificationsForRole(ROLE_A, POSTINGS)) === set([C1, C2]),
JSON.stringify(certificationsForRole(ROLE_A, POSTINGS)));
record('2. ...deduplicated across every posting for that role',
certificationsForRole(ROLE_A, POSTINGS).length === 2);
record('3. a different role offers a different list, not a global one',
set(certificationsForRole(ROLE_B, POSTINGS)) === set([C3])
&& !certificationsForRole(ROLE_B, POSTINGS).includes(C1));
record('4. a role with none offers none — never a fallback list',
set(certificationsForRole(ROLE_NONE, POSTINGS)) === set([]));
record('5. a role this organization has never posted offers none',
set(certificationsForRole('role-never-posted', POSTINGS)) === set([]));
record('6. matching ignores case and surrounding space',
set(certificationsForRole(` ${ROLE_A.toUpperCase()} `, POSTINGS)) === set([C1, C2]));
/* The distinction the brief asks for explicitly. */
record('7. "not loaded" is null and "none relevant" is empty — not the same',
certificationsForRole(ROLE_A, null) === null
&& Array.isArray(certificationsForRole(ROLE_NONE, POSTINGS)));
/* Resolved through the flows, which is how the conversation reaches it. */
for (const [flowName, draftFor] of [
['employee-role', (role) => ({ role_category: role })],
['position', (role) => ({ role_category: role })],
]) {
const flow = flows.FLOWS[flowName];
const offered = (role) => flow.resolve('@certifications', { postings: POSTINGS }, draftFor(role));
record(`8. ${flowName}: suggestions follow the selected role`,
set(offered(ROLE_A)) === set([C1, C2]) && set(offered(ROLE_B)) === set([C3]));
record(`9. ${flowName}: changing the role recalculates, leaving nothing stale`,
(() => {
const first = offered(ROLE_A);
const second = offered(ROLE_B);
return first.every((c) => !second.includes(c)) && second.length > 0;
})());
record(`10. ${flowName}: a role with no requirements offers no chips at all`,
offered(ROLE_NONE).length === 0);
record(`11. ${flowName}: unloaded postings offer nothing rather than a global list`,
flow.resolve('@certifications', { postings: null }, draftFor(ROLE_A)).length === 0);
}
/* One source of truth: both conversational flows answer identically for the
same role and the same evidence. */
record('12. both flows agree, because both read the same helper',
set(flows.FLOWS['employee-role'].resolve('@certifications', { postings: POSTINGS }, { role_category: ROLE_A }))
=== set(flows.FLOWS.position.resolve('@certifications', { postings: POSTINGS }, { role_category: ROLE_A })));
/**
* Every optional question is actually REACHABLE.
*
* The conversation seeded its draft from the record's write-time defaults, so
* `certifications: []`, `notes: ''` and `experience_years: 0` all counted as
* answers and five of the eight questions were skipped — the certification
* step among them, which is why none of the rules above could be reached in
* the product. A conversation has to tell "missing" from "answered".
*/
{
const role = flows.FLOWS['employee-role'];
const fresh = role.prefill('create an employee role', { roles: [] });
const unreachable = Object.entries(role.fields)
.filter(([, field]) => field.settled(fresh))
.map(([name]) => name);
record('14. a new employee-role conversation has nothing settled in advance',
unreachable.length === 0, unreachable.join(', ') || 'every question is asked');
}
/* And it is derived, not listed: a certification this deployment invents
shows up without any code here changing. */
record('13. a certification new to the data is offered without a code change',
(() => {
const invented = 'cert-invented-by-the-tenant';
const extended = [...POSTINGS, { role_category: ROLE_B, certifications_required: [invented] }];
return certificationsForRole(ROLE_B, extended).includes(invented);
})());
}
/**
* Two sources of people, kept apart.
*
* A CANDIDATE is somebody in the hiring pipeline — `job_applications`. The
* TALENT POOL is the workforce — `worker_profiles`. They are different sets and
* a question about one is never answered from the other, however well somebody
* in the other set scores.
*
* The separation is structural rather than filtered: `poolFor` iterates
* applications and nothing else, and uses profiles only as an email lookup to
* enrich a candidate it has already found. A worker who has never applied
* cannot appear in it, so there is nothing to filter out. That property is
* asserted below rather than assumed.
*/
console.log('\n── Candidates vs Talent Pool ──');
{
const wf = await server.ssrLoadModule('/src/lib/skills/workforceFlow.js');
const { poolFor } = await server.ssrLoadModule('/src/lib/workforce.js');
/* Fixtures built from the shape of the records, not from anyone's name. */
const position = { id: 'pos-1', title: 'Test Role', role_category: 'role-alpha' };
const applicants = [
{ id: 'app-1', email: 'pipeline-one@example.test', applicant_name: 'Pipeline One', job_posting_id: 'pos-1', created_date: '2026-01-01', status: 'interview' },
{ id: 'app-2', email: 'pipeline-two@example.test', applicant_name: 'Pipeline Two', job_posting_id: 'pos-1', created_date: '2026-01-02', status: 'applied' },
];
/* A worker who has NEVER applied, and one who has. The first must never
appear; the second appears because of the application, not the profile. */
const profiles = [
{ id: 'wp-1', email: 'workforce-only@example.test', full_name: 'Workforce Only', krow_score: 99 },
{ id: 'wp-2', email: 'pipeline-one@example.test', full_name: 'Pipeline One', krow_score: 50 },
];
const pool = poolFor(position, { applications: applicants, profiles, assignments: [], courses: [], staff: [] });
const emails = pool.map((r) => String(r.email || '').toLowerCase());
record('candidate matching returns only people in the hiring pipeline',
emails.length === 2 && emails.every((e) => applicants.some((a) => a.email === e)),
JSON.stringify(emails));
record('...so a workforce-only person never appears, whatever they score',
!emails.includes('workforce-only@example.test'));
record('...and no candidate is invented from a profile',
pool.length === applicants.length, `${pool.length} rows from ${applicants.length} applications`);
record('with no applications there are no candidates — no fallback fills the gap',
poolFor(position, { applications: [], profiles, assignments: [], courses: [], staff: [] }).length === 0);
/**
* Intent decides the source, and this is where it was going wrong.
*
* A bare `show ` test was checked second and swallowed every collection
* query, so "show employees who match this role" — a workforce question —
* resolved to a candidate lookup. Meanwhile the matching intent was narrow
* enough that most natural phrasings reached no intent at all.
*/
const intent = (q) => wf.matchWorkforceIntent(q.toLowerCase());
for (const q of [
'Who is a match for this role?',
'Show candidates matching this role',
'Find the best candidate for this position',
'Show strong candidates for this role',
'Find strong candidates',
'Show candidates matching a role',
]) {
record(`pipeline question resolves to candidate matching: "${q}"`, intent(q) === 'matches', String(intent(q)));
}
for (const q of ['Which candidates should I interview?', 'Who should I interview?']) {
record(`interview question stays in the pipeline: "${q}"`, intent(q) === 'interview_ready', String(intent(q)));
}
/* A workforce question reaches NO pipeline intent, so the Talent Pool
responder answers it from worker_profiles. */
for (const q of [
'Find someone in the Talent Pool',
'Show employees who match this role',
'Find available employees for this position',
'Show me the workers for this role',
]) {
record(`workforce question never reaches a pipeline intent: "${q}"`, intent(q) === null, String(intent(q)));
}
/* A question naming BOTH belongs to the pipeline — it says "candidate". */
record('a question naming both is answered from the pipeline',
intent('Which candidates are available?') === 'availability');
/* And a person-detail request still opens a record. */
for (const [q, want] of [
['Show Some Person', 'candidate_detail'],
["Show Some Person's details", 'candidate_detail'],
['Tell me about Some Person', 'candidate_detail'],
['Open the full profile for Some Person', 'open_profile'],
['Assign the best person', 'assign'],
['Who applied today?', 'applied_today'],
['Which position needs people first?', 'priority'],
]) {
record(`unchanged: "${q}" → ${want}`, intent(q) === want, String(intent(q)));
}
}
/* ── UI node system: contract, registry, operations, validation ────────────
Phase 1 of the runtime UI customization work. Nothing here renders — these
assert the engine's behaviour, which is what every later phase and the agent
itself will depend on.
The single most important assertion in this section is the last one: that the
engine contains no branch on a node type. Everything else can be re-derived
by reading the code; that one decays silently the first time somebody adds a
convenient special case. */
{
console.log('\n── UI node system ──');
const nodeMod = await server.ssrLoadModule('/src/lib/ui/node.js');
const registryMod = await server.ssrLoadModule('/src/lib/ui/registry.js');
const opsMod = await server.ssrLoadModule('/src/lib/ui/operations.js');
const validateMod = await server.ssrLoadModule('/src/lib/ui/validate.js');
const inspectMod = await server.ssrLoadModule('/src/lib/ui/inspect.js');
const patchMod = await server.ssrLoadModule('/src/lib/ui/patch.js');
const { NodeTypeRegistry } = registryMod;
const { makeNode, findNode } = nodeMod;
const { applyOperation, applyOperations } = opsMod;
const { validateTree } = validateMod;
/* A component stand-in. The registry stores a reference and never calls it
here, so a function is enough and keeps this file free of React. */
const Stub = () => null;
/**
* A registry built per case.
*
* Deliberately NOT the application's. These types are named for their shape
* rather than for anything in KROW, which is the point: if the engine needed
* to know a real component to work, none of this would pass.
*/
const freshRegistry = () => {
const r = new NodeTypeRegistry();
r.register({
type: 'section', label: 'Section', component: Stub, container: true,
propSchema: { title: { type: 'string' } },
constraints: { maxColumns: 4 },
});
r.register({
type: 'panel', label: 'Panel', component: Stub, container: true,
accepts: ['metric'],
});
r.register({
type: 'metric', label: 'Metric', component: Stub,
dataShapes: ['stats'], dataRequired: true,
propSchema: {
title: { type: 'string' },
tone: { enum: ['neutral', 'positive', 'negative'] },
rank: { type: 'number', min: 1, max: 5 },
},
});
r.register({
type: 'grid', label: 'Grid', component: Stub,
dataShapes: ['table', 'stats'],
});
r.register({
type: 'rule', label: 'Rule', component: Stub, capabilities: ['remove', 'move'],
});
r.register({
type: 'banner', label: 'Banner', component: Stub, roles: ['admin'],
});
/* Updatable, but reads no data — which is what separates "this component
will not take a binding" from "this component will not be changed at
all". `rule` above is the other half of that pair. */
r.register({ type: 'divider', label: 'Divider', component: Stub });
return r;
};
/* `candidates.activity` needs no page context and supports several shapes,
which is why the fixtures bind to it. Read from the vocabulary rather than
assumed — if it ever stops supporting `stats`, these fail loudly. */
const surfaces = await server.ssrLoadModule('/src/lib/skills/surfaces.js');
const SOURCE = 'candidates.activity';
record(
'fixture source exists in the closed vocabulary',
surfaces.SUPPORTED_DATA_SOURCES.includes(SOURCE),
SOURCE
);
record(
'fixture source supports the fixture shapes',
surfaces.sourceSupportsShape(SOURCE, 'stats') && surfaces.sourceSupportsShape(SOURCE, 'table'),
(surfaces.dataSourceFor(SOURCE)?.shapes || []).join(', ')
);
const tree = () => [
makeNode({
id: 'top', type: 'section', props: { title: 'Overview' }, origin: 'builtin',
children: [
makeNode({ id: 'one', type: 'metric', origin: 'builtin', data: { source: SOURCE } }),
makeNode({ id: 'two', type: 'metric', origin: 'builtin', data: { source: SOURCE } }),
],
}),
makeNode({ id: 'bottom', type: 'section', origin: 'builtin', children: [] }),
];
const reg = freshRegistry();
const ctx = { registry: reg };
const run = (t, op, extra = {}) => applyOperation(t, op, { ...ctx, ...extra });
/* ── Registration ──────────────────────────────────────────────────────── */
record('a fresh registry lists what was registered',
freshRegistry().list().join(',') === 'section,panel,metric,grid,rule,banner,divider');
record('a duplicate type is refused at registration', (() => {
try { const r = freshRegistry(); r.register({ type: 'metric', component: Stub }); return false; }
catch (e) { return /already registered/.test(e.message); }
})());
record('a type with no component is refused', (() => {
try { new NodeTypeRegistry().register({ type: 'x' }); return false; }
catch (e) { return /needs a `component`/.test(e.message); }
})());
record('an unknown capability is refused at registration', (() => {
try {
new NodeTypeRegistry().register({ type: 'x', component: Stub, capabilities: ['teleport'] });
return false;
} catch (e) { return /unknown capability/.test(e.message); }
})());
record('a non-container claiming `add` is refused', (() => {
try {
new NodeTypeRegistry().register({ type: 'x', component: Stub, capabilities: ['add'] });
return false;
} catch (e) { return /not a container/.test(e.message); }
})());
/* ── The baseline ──────────────────────────────────────────────────────── */
record('a well-formed tree validates', validateTree(tree(), ctx).ok);
/* ── add ───────────────────────────────────────────────────────────────── */
{
const r = run(tree(), {
op: 'add', parent: 'bottom',
node: { id: 'three', type: 'metric', data: { source: SOURCE } },
});
record('add: places a node in the named container',
r.ok && findNode(r.tree, 'three') !== null && findNode(r.tree, 'bottom').children.length === 1);
record('add: a new node defaults to origin `user`',
r.ok && findNode(r.tree, 'three').origin === 'user');
record('add: does not disturb the rest of the tree',
r.ok && findNode(r.tree, 'top').children.length === 2);
}
record('add: honours an index', (() => {
const r = run(tree(), {
op: 'add', parent: 'top', index: 0,
node: { id: 'first', type: 'metric', data: { source: SOURCE } },
});
return r.ok && findNode(r.tree, 'top').children[0].id === 'first';
})());
record('add: a duplicate id is refused', (() => {
const r = run(tree(), { op: 'add', parent: 'bottom', node: { id: 'one', type: 'metric', data: { source: SOURCE } } });
return !r.ok && /already exists/.test(r.problems[0].message);
})());
record('add: an unregistered type is refused by name', (() => {
const r = run(tree(), { op: 'add', parent: 'bottom', node: { id: 'x', type: 'hologram' } });
return !r.ok && /Unsupported UI type: hologram/.test(r.problems[0].message);
})());
record('add: a container that does not accept the type is refused', (() => {
const t = run(tree(), { op: 'add', parent: null, node: { id: 'p', type: 'panel' } }).tree;
const r = run(t, { op: 'add', parent: 'p', node: { id: 'g', type: 'grid', data: { source: SOURCE } } });
return !r.ok && /cannot sit inside|cannot hold/.test(r.problems[0].message);
})());
record('add: a non-container parent is refused', (() => {
const r = run(tree(), { op: 'add', parent: 'one', node: { id: 'x', type: 'metric', data: { source: SOURCE } } });
return !r.ok && /cannot hold other nodes/.test(r.problems[0].message);
})());
/* ── update ────────────────────────────────────────────────────────────── */
record('update: merges props and leaves the others alone', (() => {
const r = run(tree(), { op: 'update', target: 'top', props: { title: 'Renamed' } });
return r.ok && findNode(r.tree, 'top').props.title === 'Renamed';
})());
record('update: an explicit null unsets a prop', (() => {
const seeded = run(tree(), { op: 'update', target: 'one', props: { tone: 'positive' } }).tree;
const r = run(seeded, { op: 'update', target: 'one', props: { tone: null } });
return r.ok && !('tone' in findNode(r.tree, 'one').props);
})());
record('update: an unknown prop is refused, naming what is accepted', (() => {
const r = run(tree(), { op: 'update', target: 'one', props: { colour: 'red' } });
return !r.ok && /has no property `colour`/.test(r.problems[0].message)
&& /title, tone, rank/.test(r.problems[0].message);
})());
record('update: a value outside an enum is refused', (() => {
const r = run(tree(), { op: 'update', target: 'one', props: { tone: 'chartreuse' } });
return !r.ok && /must be one of: neutral, positive, negative/.test(r.problems[0].message);
})());
record('update: a number out of range is refused', (() => {
const r = run(tree(), { op: 'update', target: 'one', props: { rank: 9 } });
return !r.ok && /must be at most 5/.test(r.problems[0].message);
})());
record('update: a prop of the wrong kind is refused', (() => {
const r = run(tree(), { op: 'update', target: 'one', props: { title: 42 } });
return !r.ok && /must be a string/.test(r.problems[0].message);
})());
record('update: layout columns within the type constraint are accepted', (() => {
const r = run(tree(), { op: 'update', target: 'top', layout: { columns: 2 } });
return r.ok && findNode(r.tree, 'top').layout.columns === 2;
})());
record('update: layout columns beyond the type constraint are refused', (() => {
const r = run(tree(), { op: 'update', target: 'top', layout: { columns: 5 } });
return !r.ok && /supports 1–4 columns/.test(r.problems[0].message);
})());
record('update: a column count outside the grid is refused', (() => {
const r = run(tree(), { op: 'update', target: 'top', layout: { columns: 40 } });
return !r.ok && /between 1 and 12/.test(r.problems[0].message);
})());
record('update: an unknown gap is refused', (() => {
const r = run(tree(), { op: 'update', target: 'top', layout: { gap: 'enormous' } });
return !r.ok && /`gap` must be one of/.test(r.problems[0].message);
})());
record('update: a type that declines `update` refuses it', (() => {
const seeded = run(tree(), { op: 'add', parent: 'bottom', node: { id: 'r', type: 'rule' } }).tree;
const r = run(seeded, { op: 'update', target: 'r', props: {} });
return !r.ok && /cannot be changed/.test(r.problems[0].message);
})());
record('update: a locked node refuses it', (() => {
const t = [makeNode({ id: 'fixed', type: 'section', locked: true, origin: 'builtin' })];
const r = run(t, { op: 'update', target: 'fixed', props: { title: 'no' } });
return !r.ok && /fixed here/.test(r.problems[0].message);
})());
/* ── data binding ──────────────────────────────────────────────────────── */
record('data: an unknown source is refused by name', (() => {
const r = run(tree(), { op: 'update', target: 'one', data: { source: 'invented.metric' } });
return !r.ok && /Unsupported data source: invented\.metric/.test(r.problems[0].message);
})());
record('data: a source that cannot fill the type’s shape is refused', (() => {
/* A source whose shapes exclude `stats`, found from the vocabulary rather
than named here, so this stays true as the vocabulary changes. */
const incompatible = surfaces.DATA_SOURCES.find((s) => !s.shapes.includes('stats'));
if (!incompatible) return true;
const r = run(tree(), { op: 'update', target: 'one', data: { source: incompatible.id } });
return !r.ok && /cannot be shown as `metric`/.test(r.problems[0].message);
})());
record('data: a type that reads no data refuses a binding', (() => {
const seeded = run(tree(), { op: 'add', parent: 'bottom', node: { id: 'd1', type: 'divider' } }).tree;
const r = run(seeded, { op: 'update', target: 'd1', data: { source: SOURCE } });
return !r.ok && /does not read data/.test(r.problems[0].message);
})());
record('data: a required binding cannot be dropped', (() => {
const r = run(tree(), { op: 'update', target: 'one', data: null });
return !r.ok && /needs a data source/.test(r.problems[0].message);
})());
record('data: an unknown period is refused', (() => {
const r = run(tree(), {
op: 'update', target: 'one', data: { source: SOURCE, params: { periods: ['epoch'] } },
});
return !r.ok && /Unsupported period: epoch/.test(r.problems[0].message);
})());
record('data: a limit out of range is refused', (() => {
const r = run(tree(), {
op: 'update', target: 'one', data: { source: SOURCE, params: { limit: 500 } },
});
return !r.ok && /between 1 and 50/.test(r.problems[0].message);
})());
/* ── remove ────────────────────────────────────────────────────────────── */
record('remove: takes out a node the user added', (() => {
const seeded = run(tree(), {
op: 'add', parent: 'bottom', node: { id: 'mine', type: 'metric', data: { source: SOURCE } },
}).tree;
const r = run(seeded, { op: 'remove', target: 'mine' });
return r.ok && findNode(r.tree, 'mine') === null;
})());
record('remove: a built-in is refused and told to hide instead', (() => {
const r = run(tree(), { op: 'remove', target: 'one' });
return !r.ok && /part of the page/.test(r.problems[0].message)
&& /Hide it instead/.test(r.problems[0].message);
})());
record('remove: an unknown target is refused by id', (() => {
const r = run(tree(), { op: 'remove', target: 'nowhere' });
return !r.ok && /No UI node with the id `nowhere`/.test(r.problems[0].message);
})());
/* ── move ──────────────────────────────────────────────────────────────── */
record('move: relocates into another container', (() => {
const r = run(tree(), { op: 'move', target: 'one', parent: 'bottom', index: 0 });
return r.ok
&& findNode(r.tree, 'bottom').children.map((n) => n.id).join() === 'one'
&& findNode(r.tree, 'top').children.map((n) => n.id).join() === 'two';
})());
record('move: within a parent, the index counts after removal', (() => {
const r = run(tree(), { op: 'move', target: 'one', parent: 'top', index: 1 });
return r.ok && findNode(r.tree, 'top').children.map((n) => n.id).join() === 'two,one';
})());
record('move: to the root', (() => {
const r = run(tree(), { op: 'move', target: 'one', parent: null, index: 0 });
return r.ok && r.tree[0].id === 'one' && findNode(r.tree, 'top').children.length === 1;
})());
record('move: a node cannot be moved inside itself', (() => {
const r = run(tree(), { op: 'move', target: 'top', parent: 'top' });
return !r.ok && /inside itself/.test(r.problems[0].message);
})());
record('move: into a container that does not accept the type is refused', (() => {
const t = run(tree(), { op: 'add', parent: null, node: { id: 'p2', type: 'panel' } }).tree;
const seeded = run(t, { op: 'add', parent: null, node: { id: 'g2', type: 'grid', data: { source: SOURCE } } }).tree;
const r = run(seeded, { op: 'move', target: 'g2', parent: 'p2' });
return !r.ok;
})());
/* ── replace ───────────────────────────────────────────────────────────── */
record('replace: swaps the type and keeps id, place and binding', (() => {
const r = run(tree(), { op: 'replace', target: 'one', type: 'grid' });
const after = findNode(r.tree, 'one');
return r.ok && after.type === 'grid' && after.data.source === SOURCE
&& findNode(r.tree, 'top').children[0].id === 'one';
})());
record('replace: drops the old type’s props rather than carrying them over', (() => {
const seeded = run(tree(), { op: 'update', target: 'one', props: { tone: 'positive' } }).tree;
const r = run(seeded, { op: 'replace', target: 'one', type: 'grid' });
return r.ok && Object.keys(findNode(r.tree, 'one').props).length === 0;
})());
record('replace: an unregistered type is refused', (() => {
const r = run(tree(), { op: 'replace', target: 'one', type: 'sculpture' });
return !r.ok && /Unsupported UI type: sculpture/.test(r.problems[0].message);
})());
record('replace: a target whose binding the new type cannot draw is refused', (() => {
const incompatible = surfaces.DATA_SOURCES.find(
(s) => s.shapes.includes('stats') && !s.shapes.includes('table')
);
if (!incompatible) return true;
const t = [makeNode({ id: 'm', type: 'metric', origin: 'user', data: { source: incompatible.id } })];
const r = run(t, { op: 'replace', target: 'm', type: 'grid' });
/* `grid` draws table and stats; if the source only fills stats it still
works, so this asserts only that an impossible pairing is caught. */
return r.ok || /cannot be shown as/.test(r.problems[0].message);
})());
record('replace: a container with children cannot become a leaf', (() => {
const r = run(tree(), { op: 'replace', target: 'top', type: 'grid' });
return !r.ok && /cannot hold the nodes already inside/.test(r.problems[0].message);
})());
/* ── hide ──────────────────────────────────────────────────────────────── */
record('hide: marks a built-in hidden without removing it', (() => {
const r = run(tree(), { op: 'hide', target: 'one' });
return r.ok && findNode(r.tree, 'one').hidden === true;
})());
record('hide: is reversible', (() => {
const hidden = run(tree(), { op: 'hide', target: 'one' }).tree;
const r = run(hidden, { op: 'hide', target: 'one', hidden: false });
return r.ok && findNode(r.tree, 'one').hidden === false;
})());
record('hide: a type that declines it refuses it', (() => {
const seeded = run(tree(), { op: 'add', parent: 'bottom', node: { id: 'r3', type: 'rule' } }).tree;
const r = run(seeded, { op: 'hide', target: 'r3' });
return !r.ok && /cannot be hidden/.test(r.problems[0].message);
})());
/* ── reorder ───────────────────────────────────────────────────────────── */
record('reorder: rearranges a container’s children', (() => {
const r = run(tree(), { op: 'reorder', parent: 'top', order: ['two', 'one'] });
return r.ok && findNode(r.tree, 'top').children.map((n) => n.id).join() === 'two,one';
})());
record('reorder: the root is addressable with a null parent', (() => {
const r = run(tree(), { op: 'reorder', parent: null, order: ['bottom', 'top'] });
return r.ok && r.tree.map((n) => n.id).join() === 'bottom,top';
})());
record('reorder: a partial order keeps the unnamed nodes after it', (() => {
const seeded = run(tree(), {
op: 'add', parent: 'top', node: { id: 'three', type: 'metric', data: { source: SOURCE } },
}).tree;
const r = run(seeded, { op: 'reorder', parent: 'top', order: ['three'] });
return r.ok && findNode(r.tree, 'top').children.map((n) => n.id).join() === 'three,one,two';
})());
record('reorder: an id from another container is refused', (() => {
const r = run(tree(), { op: 'reorder', parent: 'top', order: ['bottom'] });
return !r.ok && /is not inside `top`/.test(r.problems[0].message);
})());
record('reorder: a repeated id is refused', (() => {
const r = run(tree(), { op: 'reorder', parent: 'top', order: ['one', 'one'] });
return !r.ok && /same node twice/.test(r.problems[0].message);
})());
/* ── Permission ────────────────────────────────────────────────────────── */
record('permission: a role-restricted type is refused for another role', (() => {
const r = run(tree(), {
op: 'add', parent: 'bottom', node: { id: 'b', type: 'banner' },
}, { role: 'talent' });
return !r.ok && /do not have access/.test(r.problems[0].message);
})());
record('permission: the same node is allowed for the role that may have it', (() => {
const r = run(tree(), {
op: 'add', parent: 'bottom', node: { id: 'b', type: 'banner' },
}, { role: 'admin' });
return r.ok;
})());
/* ── Engine behaviour ──────────────────────────────────────────────────── */
record('an unknown operation is refused by name', (() => {
const r = run(tree(), { op: 'summon', target: 'one' });
return !r.ok && /Unknown operation: summon/.test(r.problems[0].message);
})());
record('a refused operation returns the tree untouched', (() => {
const before = tree();
const r = applyOperation(before, { op: 'remove', target: 'one' }, ctx);
return !r.ok && r.tree === before;
})());
record('a batch is all-or-nothing', (() => {
const before = tree();
const r = applyOperations(before, [
{ op: 'hide', target: 'one' },
{ op: 'remove', target: 'two' },
], ctx);
return !r.ok && r.tree === before && r.problems[0].opIndex === 1;
})());
record('a successful batch applies in order', (() => {
const r = applyOperations(tree(), [
{ op: 'hide', target: 'one' },
{ op: 'reorder', parent: 'top', order: ['two', 'one'] },
], ctx);
return r.ok && findNode(r.tree, 'one').hidden && findNode(r.tree, 'top').children[0].id === 'two';
})());
record('a duplicate id anywhere in the tree is caught', (() => {
const broken = [
makeNode({ id: 'dup', type: 'section', children: [makeNode({ id: 'dup', type: 'section' })] }),
];
const { ok, problems } = validateTree(broken, ctx);
return !ok && problems.some((p) => /Two nodes share the id `dup`/.test(p.message));
})());
record('validation reports every problem, not just the first', (() => {
const broken = [
makeNode({ id: 'a', type: 'nope' }),
makeNode({ id: 'b', type: 'alsonope' }),
];
return validateTree(broken, ctx).problems.length >= 2;
})());
/* ── Inspect ───────────────────────────────────────────────────────────── */
record('inspect: describes every node with its capabilities', (() => {
const seen = inspectMod.inspectTree(tree(), ctx);
const one = seen.find((n) => n.id === 'one');
return seen.length === 4 && one.type === 'metric' && one.capabilities.includes('hide')
&& one.parent === 'top' && one.data.source === SOURCE;
})());
record('inspect: reports the editable properties a type declares', (() => {
const one = inspectMod.inspectTree(tree(), ctx).find((n) => n.id === 'one');
return one.editable.map((f) => f.key).sort().join() === 'rank,title,tone'
&& one.editable.find((f) => f.key === 'tone').options.includes('positive');
})());
record('inspect: an outline reads as a structure', (() => {
const lines = inspectMod.outlineTree(tree(), ctx);
return lines.length === 4 && lines[0].includes('Overview') && lines[1].startsWith(' ');
})());
record('resolveTarget: an id said verbatim wins', (() => {
const hits = inspectMod.resolveTarget(tree(), 'one', ctx);
return hits.length > 0 && hits[0].id === 'one';
})());
record('resolveTarget: matches on a node’s visible title', (() => {
const hits = inspectMod.resolveTarget(tree(), 'the overview section', ctx);
return hits.length > 0 && hits[0].id === 'top';
})());
record('resolveTarget: returns nothing rather than guessing', (() => {
return inspectMod.resolveTarget(tree(), 'the quarterly revenue waterfall', ctx).length === 0;
})());
record('resolveTarget: keeps ties as candidates instead of picking one', (() => {
const hits = inspectMod.resolveTarget(tree(), 'metric', ctx);
return hits.length === 2;
})());
record('addableTypes: offers only what the container accepts', (() => {
const inPanel = inspectMod.addableTypes('panel', ctx).map((t) => t.type);
return inPanel.join() === 'metric';
})());
record('addableTypes: hides a type the role may not use', (() => {
const asTalent = inspectMod.addableTypes('section', { ...ctx, role: 'talent' }).map((t) => t.type);
return !asTalent.includes('banner');
})());
/* ── Patch: stored operations, not stored trees ────────────────────────── */
record('patch: applying a stored op list reproduces the change', (() => {
const patch = patchMod.pushOp(patchMod.emptyPatch('demo'), { op: 'hide', target: 'one' });
const { tree: next } = patchMod.applyPatch(tree(), patch, ctx);
return findNode(next, 'one').hidden === true;
})());
record('patch: undo drops the last operation', (() => {
let patch = patchMod.emptyPatch('demo');
patch = patchMod.pushOp(patch, { op: 'hide', target: 'one' });
patch = patchMod.pushOp(patch, { op: 'hide', target: 'two' });
const undone = patchMod.popOp(patch);
const { tree: next } = patchMod.applyPatch(tree(), undone, ctx);
return undone.ops.length === 1 && findNode(next, 'one').hidden && !findNode(next, 'two').hidden;
})());
record('patch: reset returns the page the application ships', (() => {
let patch = patchMod.pushOp(patchMod.emptyPatch('demo'), { op: 'hide', target: 'one' });
patch = patchMod.clearOps(patch);
const { tree: next } = patchMod.applyPatch(tree(), patch, ctx);
return !findNode(next, 'one').hidden;
})());
record('patch: an op naming a node that no longer exists is skipped, not fatal', (() => {
const patch = {
schema: patchMod.PATCH_SCHEMA,
page: 'demo',
ops: [{ op: 'hide', target: 'departed' }, { op: 'hide', target: 'two' }],
};
const { tree: next, skipped } = patchMod.applyPatch(tree(), patch, ctx);
return skipped.length === 1 && findNode(next, 'two').hidden === true;
})());
record('patch: an unreadable operation is dropped on load', (() => {
const { patch, dropped } = patchMod.normalizePatch({
schema: patchMod.PATCH_SCHEMA,
ops: [{ op: 'hide', target: 'one' }, { op: 'detonate' }, 'nonsense'],
}, 'demo');
return patch.ops.length === 1 && dropped === 2;
})());
record('patch: a stored op carries only the fields its operation reads', (() => {
/* `component` is the field that would matter if this boundary leaked: a
stored blob is the one place a value the engine never validated could
arrive from. It must not survive the read. */
const op = patchMod.normalizeOp({ op: 'hide', target: 'one', component: '<script>', extra: 1 });
return Object.keys(op).sort().join() === 'op,target';
})());
record('patch: a patch from an unknown schema version is not half-read', (() => {
const { patch, dropped } = patchMod.normalizePatch({ schema: 999, ops: [{ op: 'hide', target: 'one' }] }, 'demo');
return patch.ops.length === 0 && dropped === 1;
})());
record('patch: an empty page patch is not stored', (() => {
const { layouts } = patchMod.normalizeLayouts({
demo: { schema: patchMod.PATCH_SCHEMA, ops: [] },
other: { schema: patchMod.PATCH_SCHEMA, ops: [{ op: 'hide', target: 'one' }] },
});
return !('demo' in layouts) && 'other' in layouts;
})());
/* ── Extensibility: a new type needs no engine change ──────────────────── */
record('a type registered at run time works with no change to the engine', (() => {
const r2 = freshRegistry();
r2.register({
type: 'sparkline', label: 'Sparkline', component: Stub,
dataShapes: ['stats'], propSchema: { title: { type: 'string' } },
});
const c2 = { registry: r2 };
const added = applyOperation(tree(), {
op: 'add', parent: 'bottom', node: { id: 'sp', type: 'sparkline', data: { source: SOURCE } },
}, c2);
if (!added.ok) return false;
const moved = applyOperation(added.tree, { op: 'move', target: 'sp', parent: 'top', index: 0 }, c2);
const swapped = applyOperation(moved.tree, { op: 'replace', target: 'sp', type: 'grid' }, c2);
const seen = inspectMod.inspectTree(swapped.tree, c2).find((n) => n.id === 'sp');
return moved.ok && swapped.ok && seen.type === 'grid';
})());
/* ── The rule that decays silently ─────────────────────────────────────── */
const typeBranches = (() => {
/* Every UI type name the application knows, plus the page keys — none of
them may appear in the engine. Read from the vocabularies rather than
listed here, so a type added later is covered without editing this. */
const banned = [
...surfaces.SUPPORTED_SECTION_TYPES,
...surfaces.SUPPORTED_SKILL_PAGES,
/* Every type the application has registered. Empty until the node types
land, and self-tightening after that: a type registered tomorrow is
banned from the engine tomorrow, without this list being edited. */
...registryMod.nodeRegistry.list(),
];
const engine = ['node.js', 'registry.js', 'operations.js', 'validate.js', 'patch.js', 'inspect.js']
.map((f) => readFileSync(join(ROOT, resolveSourcePath(`src/lib/ui/${f}`)), 'utf8'));
const offences = [];
engine.forEach((source, i) => {
/* Comments explain the design and legitimately name things; code must
not. Strip block and line comments before looking. */
const code = source
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/^\s*\/\/.*$/gm, '');
for (const word of banned) {
if (new RegExp(`['"\`]${word}['"\`]`).test(code)) {
offences.push(`${['node', 'registry', 'operations', 'validate', 'patch', 'inspect'][i]}.js: ${word}`);
}
}
});
return offences;
})();
record('the engine contains no branch on a node type', typeBranches.length === 0, typeBranches.join(', '));
/* ── The existing skill vocabulary is untouched ────────────────────────── */
record('the UI engine did not change the skill section vocabulary',
surfaces.SUPPORTED_SECTION_TYPES.join(',') === 'card,stats,list,timeline,flow,table,progress,insight,weights');
}
/* ── UI composition, renderer and the first migrated page ──────────────────
Phase 2. The engine from the previous section now draws something, and one
page composes through it. The assertions that matter most are the last two:
that the renderer adds no markup of its own, and that the migrated page
renders what it always rendered. */
{
console.log('\n── UI composition and rendering ──');
const compositionMod = await server.ssrLoadModule('/src/lib/ui/composition.js');
const registryMod2 = await server.ssrLoadModule('/src/lib/ui/registry.js');
const nodeMod2 = await server.ssrLoadModule('/src/lib/ui/node.js');
const patchMod2 = await server.ssrLoadModule('/src/lib/ui/patch.js');
const validateMod2 = await server.ssrLoadModule('/src/lib/ui/validate.js');
const opsMod2 = await server.ssrLoadModule('/src/lib/ui/operations.js');
/* The page's own module registers its types and its composition as a side
effect of being imported — the same thing the page does when it mounts. */
await server.ssrLoadModule('/src/components/ui-tree/nodeTypes.jsx');
await server.ssrLoadModule('/src/pages/admin/activity/nodes.jsx');
await server.ssrLoadModule('/src/pages/admin/hired-history/nodes.jsx');
await server.ssrLoadModule('/src/pages/admin/positions/nodes.js');
/* Loaded so the scope assertions have a second real page to compare against,
and so the two slots a page mounts can be told apart by name. */
await server.ssrLoadModule('/src/pages/admin/candidates-analysis/nodes.jsx');
const appRegistry = registryMod2.nodeRegistry;
/* ── The composition ───────────────────────────────────────────────────── */
record('the migrated page registered a composition',
compositionMod.hasComposition('activity'));
record('the migrated pages compose through the tree', (() => {
const pages = compositionMod.composedPages();
return {
pass: ['activity', 'hired-history', 'positions'].every((p) => pages.includes(p)),
detail: pages.join(', '),
};
})().pass);
const activityTree = compositionMod.composePage('activity').tree;
record('the composition is the page in reading order',
activityTree.map((n) => n.id).join(',')
=== 'activity-extensions-top,activity-summary,activity-privileged-notice,timeline,audit,activity-extensions-bottom',
activityTree.map((n) => n.id).join(', '));
record('node ids are continuous with the ids the DOM already carried',
activityTree.some((n) => n.id === 'timeline') && activityTree.some((n) => n.id === 'audit'));
record('every composed node names a registered type',
activityTree.every((n) => appRegistry.has(n.type)),
activityTree.filter((n) => !appRegistry.has(n.type)).map((n) => n.type).join(', '));
record('the composition validates against the application registry', (() => {
const { ok, problems } = validateMod2.validateTree(activityTree, { registry: appRegistry });
return { ok, detail: problems.map((p) => p.message).join('; ') };
})().ok);
record('every composed node is a built-in',
activityTree.every((n) => n.origin === 'builtin'));
record('a page section can be moved and hidden, and nothing else', (() => {
const section = appRegistry.get('activity-audit-log');
return section.capabilities.join(',') === 'move,hide';
})());
record('a built-in section cannot be removed', (() => {
const r = opsMod2.applyOperation(activityTree, { op: 'remove', target: 'audit' }, { registry: appRegistry });
return !r.ok;
})());
record('the skill surface slot is a container that moves, hides and reorders', (() => {
const surface = appRegistry.get('skill-surface');
return surface.container === true && surface.capabilities.join(',') === 'move,hide,reorder';
})());
record('a slot accepts only the nine reading types', (() => {
const surface = appRegistry.get('skill-surface');
return surface.accepts.join(',') === surfaces.SUPPORTED_SECTION_TYPES.join(',');
})());
record('a composition is unaffected by a patch for another page', (() => {
const patch = patchMod2.pushOp(patchMod2.emptyPatch('somewhere-else'), { op: 'hide', target: 'audit' });
const { tree } = compositionMod.composePage('activity', { patch: { ...patch, page: 'activity', ops: [] } });
return tree.every((n) => !n.hidden);
})());
record('a saved patch is folded into what the page composes', (() => {
const patch = patchMod2.pushOp(patchMod2.emptyPatch('activity'), { op: 'hide', target: 'audit' });
const { tree } = compositionMod.composePage('activity', { patch, registry: appRegistry });
return nodeMod2.findNode(tree, 'audit').hidden === true;
})());
record('a preview is composed on top of what was saved', (() => {
const patch = patchMod2.pushOp(patchMod2.emptyPatch('activity'), { op: 'hide', target: 'audit' });
const preview = patchMod2.pushOp(patchMod2.emptyPatch('activity'), {
op: 'reorder', parent: null, order: ['timeline', 'activity-summary'],
});
const { tree } = compositionMod.composePage('activity', { patch, preview, registry: appRegistry });
return nodeMod2.findNode(tree, 'audit').hidden === true && tree[0].id === 'timeline';
})());
record('composing does not mutate the registered composition', (() => {
const patch = patchMod2.pushOp(patchMod2.emptyPatch('activity'), { op: 'hide', target: 'audit' });
compositionMod.composePage('activity', { patch, registry: appRegistry });
const fresh = compositionMod.composePage('activity').tree;
return fresh.every((n) => !n.hidden);
})());
record('a page that never registered composes to nothing',
compositionMod.composePage('a-page-that-does-not-exist').tree.length === 0);
/* ── The renderer ──────────────────────────────────────────────────────── */
const hadWindow2 = 'window' in globalThis;
if (!hadWindow2) {
globalThis.window = {
matchMedia: () => ({ matches: false, addEventListener() {}, removeEventListener() {} }),
addEventListener() {}, removeEventListener() {},
};
}
const { UiTreeRenderer } = await server.ssrLoadModule('/src/components/ui-tree/UiTreeRenderer.jsx');
const { MemoryRouter: Router2 } = await import('react-router-dom');
const draw = (nodes, context = {}) => renderToStaticMarkup(
React.createElement(Router2, null,
React.createElement(UiTreeRenderer, { nodes, context, registry: appRegistry }))
);
/* A registry of plain components, so what the renderer emits is visible with
nothing else in the way. */
const plain = new registryMod2.NodeTypeRegistry();
plain.register({ type: 'leaf', component: ({ attrs = {} }) => React.createElement('p', attrs, 'leaf') });
plain.register({ type: 'box', component: ({ attrs = {}, children }) => React.createElement('section', attrs, children), container: true });
plain.register({ type: 'bare', component: () => React.createElement('span', null, 'bare'), wrap: true });
const drawPlain = (nodes) => renderToStaticMarkup(
React.createElement(UiTreeRenderer, { nodes, registry: plain })
);
record('the renderer adds no element of its own', (() => {
const html = drawPlain([nodeMod2.makeNode({ id: 'a', type: 'leaf' }), nodeMod2.makeNode({ id: 'b', type: 'leaf' })]);
const stripped = html.replace(/ data-ui-(?:node|type)="[^"]*"/g, '');
return stripped === '<p>leaf</p><p>leaf</p>';
})());
record('a node carries its identity into the DOM', (() => {
const html = drawPlain([nodeMod2.makeNode({ id: 'only', type: 'leaf' })]);
return html.includes('data-ui-node="only"') && html.includes('data-ui-type="leaf"');
})());
record('a type that cannot forward props gets a wrapper carrying only identity', (() => {
const html = drawPlain([nodeMod2.makeNode({ id: 'w', type: 'bare' })]);
return html === '<div data-ui-node="w" data-ui-type="bare"><span>bare</span></div>';
})());
record('a hidden node renders nothing at all', (() => {
const html = drawPlain([nodeMod2.makeNode({ id: 'a', type: 'leaf', hidden: true })]);
return html === '';
})());
record('a node of an unknown type renders nothing rather than throwing', (() => {
const html = drawPlain([nodeMod2.makeNode({ id: 'ghost', type: 'departed-type' })]);
return html === '';
})());
record('a container draws its children inside itself', (() => {
const html = drawPlain([nodeMod2.makeNode({
id: 'outer', type: 'box', children: [nodeMod2.makeNode({ id: 'inner', type: 'leaf' })],
})]);
return /<section[^>]*data-ui-node="outer"[^>]*><p[^>]*data-ui-node="inner"/.test(html);
})());
record('a container with no layout adds no grid', (() => {
const html = drawPlain([nodeMod2.makeNode({
id: 'outer', type: 'box', children: [nodeMod2.makeNode({ id: 'inner', type: 'leaf' })],
})]);
return !html.includes('class=');
})());
record('a container asking for columns renders a grid', (() => {
const html = drawPlain([nodeMod2.makeNode({
id: 'outer', type: 'box', layout: { columns: 2 },
children: [nodeMod2.makeNode({ id: 'inner', type: 'leaf' })],
})]);
return /class="grid grid-cols-1 sm:grid-cols-2 gap-4"/.test(html);
})());
record('reordering the tree reorders the markup', (() => {
const nodes = [nodeMod2.makeNode({ id: 'a', type: 'leaf' }), nodeMod2.makeNode({ id: 'b', type: 'leaf' })];
const moved = opsMod2.applyOperation(nodes, { op: 'reorder', parent: null, order: ['b', 'a'] }, { registry: plain });
const html = drawPlain(moved.tree);
return html.indexOf('data-ui-node="b"') < html.indexOf('data-ui-node="a"');
})());
record('the renderer never receives a component name from configuration', (() => {
/* The only path from a node to a component is `entry.component`, and an
entry is only created by a `register` call that was handed a reference.
A node naming a type nobody registered draws nothing — asserted above —
so there is no string that can become code. */
const source = readFileSync(join(ROOT, resolveSourcePath('src/components/ui-tree/UiTreeRenderer.jsx')), 'utf8')
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/^\s*\/\/.*$/gm, '');
return !/\bimport\s*\(/.test(source)
&& !/\beval\s*\(/.test(source)
&& !/new\s+Function/.test(source)
&& !/dangerouslySetInnerHTML/.test(source);
})());
/* ── The migrated page renders what it always rendered ─────────────────── */
{
const { QueryClient, QueryClientProvider } = await import('@tanstack/react-query');
const Page = (await server.ssrLoadModule('/src/pages/admin/Activity.jsx')).default;
const client = new QueryClient({ defaultOptions: { queries: { retry: false, enabled: false } } });
/* Mounted the way the app mounts it: the editing session lives in the
layout, above both the page and the panel. */
const { UiEditingProvider } = await server.ssrLoadModule('/src/components/ui-tree/UiEditingProvider.jsx');
let now = renderToStaticMarkup(
React.createElement(Router2, { initialEntries: ['/admin/activity'] },
React.createElement(QueryClientProvider, { client },
React.createElement(UiEditingProvider, { page: 'activity' },
React.createElement(Page))))
);
const before = readFileSync(join(ROOT, 'scripts/__baseline__/activity-page.pre-migration.html'), 'utf8');
/**
* Remove the layout controls before comparing.
*
* Phase 3 adds one genuinely new thing to the page — the affordance that
* lets a person change it — and that is a deliberate addition, not a
* regression in what the page already drew. Everything below the controls
* must still be exactly what it was, so the control subtree is lifted out
* by balanced tag counting and the rest is compared untouched.
*/
const stripControls = (html) => {
const at = html.indexOf('<div data-ui-controls');
if (at < 0) return html;
let depth = 0;
let i = at;
while (i < html.length) {
if (html.startsWith('<div', i)) { depth += 1; i = html.indexOf('>', i) + 1; continue; }
if (html.startsWith('</div>', i)) {
depth -= 1; i += 6;
if (depth === 0) break;
continue;
}
i += 1;
}
return html.slice(0, at) + html.slice(i);
};
record('the layout controls are present and separable',
now.includes('<div data-ui-controls') && stripControls(now).indexOf('data-ui-controls') === -1);
now = stripControls(now);
/**
* Two renderings are the same page when they paint the same styled boxes,
* in the same order, around the same words.
*
* Compared this way rather than byte-for-byte because Phase 2 legitimately
* adds two things and must be free to add exactly those: the `data-ui-*`
* identity attributes, and — for one node whose component destructures its
* props — a wrapper element with no class and no content of its own. Both
* are invisible. Anything else, including a single changed utility class or
* a reordered section, moves the signature and fails here.
*/
const classes = (html) => (html.match(/class="[^"]*"/g) || []).join('\n');
const words = (html) => html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
record('the migrated page paints the same styled elements, in the same order',
classes(now) === classes(before),
classes(now) === classes(before) ? '' : 'class sequence differs');
record('the migrated page shows the same words',
words(now) === words(before),
words(now) === words(before) ? '' : 'text differs');
/**
* The exact delta, pinned two ways.
*
* A prefix/suffix diff cannot isolate this change — the wrapper's opening
* and closing tags are ~3kB apart and everything between them looks
* inserted — so the delta is characterised instead: the same tags in the
* same numbers except for one extra `div` pair, and that pair carrying
* nothing but the node's identity. Together with the class-sequence and
* word assertions above, that leaves no room for a visible change.
*/
const tagNames = (html) => (html.match(/<\/?[a-z][a-z0-9-]*/gi) || []).map((t) => t.toLowerCase());
record('exactly one element was added, and it is a div', (() => {
const tallies = (list) => list.reduce((acc, t) => ({ ...acc, [t]: (acc[t] || 0) + 1 }), {});
const b = tallies(tagNames(before));
const n = tallies(tagNames(now));
const names = [...new Set([...Object.keys(b), ...Object.keys(n)])];
const changed = names.filter((t) => (b[t] || 0) !== (n[t] || 0));
return {
pass: changed.join(',') === '<div,</div'
&& n['<div'] - b['<div'] === 1 && n['</div'] - b['</div'] === 1,
detail: changed.join(', '),
};
})().pass);
record('the added element paints nothing — identity attributes only',
now.includes('<div data-ui-node="activity-summary" data-ui-type="activity-summary">'));
record('the migrated page carries stable node identity in the DOM', (() => {
const ids = [...now.matchAll(/data-ui-node="([^"]+)"/g)].map((m) => m[1]);
return ids.includes('timeline') && ids.includes('audit') && ids.includes('activity-summary');
})());
record('the page still renders its heading and both sections',
now.includes('Operational timeline') && now.includes('Audit log') && now.includes('Total events'));
}
if (!hadWindow2) delete globalThis.window;
}
/* ── Preview, Apply, and surviving a reload ────────────────────────────────
Phase 3. The loop that makes this a runtime feature: a change is proposed,
shown, and either kept in the person's own account or thrown away — with the
application's source untouched either way. */
{
console.log('\n── UI persistence ──');
const patchMod3 = await server.ssrLoadModule('/src/lib/ui/patch.js');
const compositionMod3 = await server.ssrLoadModule('/src/lib/ui/composition.js');
const registryMod3 = await server.ssrLoadModule('/src/lib/ui/registry.js');
const nodeMod3 = await server.ssrLoadModule('/src/lib/ui/node.js');
await server.ssrLoadModule('/src/components/ui-tree/nodeTypes.jsx');
await server.ssrLoadModule('/src/pages/admin/activity/nodes.jsx');
const reg3 = registryMod3.nodeRegistry;
const ctx3 = { registry: reg3 };
const PAGE = 'activity';
/** What a person's change looks like once they have made it. */
const hideAudit = { op: 'hide', target: 'audit', hidden: true };
const previewPatch = patchMod3.pushOp(patchMod3.emptyPatch(PAGE), hideAudit);
/* ── 1. Proposed, and visible, without being stored ────────────────────── */
record('a proposed change shows in the composed page', (() => {
const { tree } = compositionMod3.composePage(PAGE, { preview: previewPatch, ...ctx3 });
return nodeMod3.findNode(tree, 'audit').hidden === true;
})());
record('a preview writes nothing to the layout store', (() => {
/* The store is only ever changed by `mergeLayouts`. A preview never reaches
it, which is why previewing takes no argument from it and cannot. */
const stored = patchMod3.mergeLayouts({}, PAGE, patchMod3.emptyPatch(PAGE));
return Object.keys(stored).length === 0;
})());
record('discarding leaves the store exactly as it was', (() => {
const saved = { [PAGE]: patchMod3.pushOp(patchMod3.emptyPatch(PAGE), { op: 'hide', target: 'timeline' }) };
/* Discard is dropping the preview; the saved map is not a party to it. */
const after = patchMod3.mergeLayouts(saved, PAGE, saved[PAGE]);
return JSON.stringify(after) === JSON.stringify(saved);
})());
/* ── 2. Applying stores operations, and only operations ────────────────── */
const applied = patchMod3.mergeLayouts({}, PAGE, previewPatch);
record('applying stores the patch under its own page key',
Object.keys(applied).join() === PAGE, Object.keys(applied).join(', '));
record('the stored payload is a schema, a page and an operation list',
Object.keys(applied[PAGE]).sort().join() === 'ops,page,schema,updatedAt',
Object.keys(applied[PAGE]).sort().join(', '));
record('a stored operation carries no markup, no component and no Markdown', (() => {
const json = JSON.stringify(applied);
return !/</.test(json) && !/component|className|jsx|markdown|---/i.test(json);
})());
record('the stored operation is exactly what was proposed',
JSON.stringify(applied[PAGE].ops) === JSON.stringify([hideAudit]));
record('applying writes only the uiLayouts key, so no other preference moves', (() => {
/* `useUiLayouts.save` sends `{ uiLayouts }` and nothing else; the endpoint
shallow-merges, so `customSkills` and the rest survive untouched. */
const source = readFileSync(join(ROOT, resolveSourcePath('src/lib/krowHooks.js')), 'utf8');
const fn = source.slice(source.indexOf('export function useUiLayouts'), source.indexOf('export function useJobPostings'));
const writes = [...fn.matchAll(/mutateAsync\(\{([^}]*)\}/g)].map((m) => m[1].trim());
return writes.length === 1 && writes[0].startsWith('uiLayouts:');
})());
/* ── 3. A cold reload rebuilds the same page ───────────────────────────── */
record('a stored patch survives the round trip through JSON', (() => {
const overWire = JSON.parse(JSON.stringify(applied));
const { layouts } = patchMod3.normalizeLayouts(overWire);
return JSON.stringify(layouts[PAGE].ops) === JSON.stringify([hideAudit]);
})());
record('a cold reload reconstructs the identical effective tree', (() => {
/* What the browser had before the reload. */
const live = compositionMod3.composePage(PAGE, { preview: previewPatch, ...ctx3 }).tree;
/* What it rebuilds from storage alone, with no preview and no memory. */
const overWire = JSON.parse(JSON.stringify(applied));
const { layouts } = patchMod3.normalizeLayouts(overWire);
const rebuilt = compositionMod3.composePage(PAGE, { patch: layouts[PAGE], ...ctx3 }).tree;
return JSON.stringify(rebuilt) === JSON.stringify(live);
})());
record('a person with no stored layout gets the page as it ships', (() => {
const { layouts } = patchMod3.normalizeLayouts(undefined);
const tree = compositionMod3.composePage(PAGE, { patch: layouts[PAGE], ...ctx3 }).tree;
const shipped = compositionMod3.composePage(PAGE, ctx3).tree;
return JSON.stringify(tree) === JSON.stringify(shipped);
})());
record('one account’s layout is not readable from another’s preferences', (() => {
/* The store is a value inside one user's preferences row, addressed by the
session. Two accounts are two objects; there is no shared key and nothing
here reads a page's layout from anywhere but the caller's own. */
const mine = patchMod3.mergeLayouts({}, PAGE, previewPatch);
const theirs = patchMod3.normalizeLayouts(undefined).layouts;
const theirTree = compositionMod3.composePage(PAGE, { patch: theirs[PAGE], ...ctx3 }).tree;
return Object.keys(theirs).length === 0
&& nodeMod3.findNode(theirTree, 'audit').hidden === false
&& nodeMod3.findNode(compositionMod3.composePage(PAGE, { patch: mine[PAGE], ...ctx3 }).tree, 'audit').hidden === true;
})());
/* ── 4. Undo and reset ─────────────────────────────────────────────────── */
record('undo drops the most recent change and keeps the rest', (() => {
let patch = patchMod3.emptyPatch(PAGE);
patch = patchMod3.pushOp(patch, { op: 'hide', target: 'timeline' });
patch = patchMod3.pushOp(patch, hideAudit);
const undone = patchMod3.popOp(patch);
const { tree } = compositionMod3.composePage(PAGE, { patch: undone, ...ctx3 });
return undone.ops.length === 1
&& nodeMod3.findNode(tree, 'timeline').hidden === true
&& nodeMod3.findNode(tree, 'audit').hidden === false;
})());
record('reset restores the page the application ships', (() => {
const patch = patchMod3.clearOps(previewPatch);
const { tree } = compositionMod3.composePage(PAGE, { patch, ...ctx3 });
return JSON.stringify(tree) === JSON.stringify(compositionMod3.composePage(PAGE, ctx3).tree);
})());
record('reset removes the page’s key rather than storing an empty patch', (() => {
const store = patchMod3.mergeLayouts(applied, PAGE, patchMod3.clearOps(previewPatch));
return !(PAGE in store);
})());
/* ── 5. A bad patch cannot cost a good one ─────────────────────────────── */
record('a stale operation is skipped and the valid ones still apply', (() => {
const patch = {
schema: patchMod3.PATCH_SCHEMA,
page: PAGE,
ops: [
{ op: 'hide', target: 'a-section-a-release-removed' },
hideAudit,
],
};
const { tree, skipped } = compositionMod3.composePage(PAGE, { patch, ...ctx3 });
return skipped.length === 1 && nodeMod3.findNode(tree, 'audit').hidden === true;
})());
record('one page’s unreadable patch does not touch another page’s', (() => {
const store = {
[PAGE]: previewPatch,
'some-other-page': { schema: 999, ops: [{ op: 'hide', target: 'whatever' }] },
};
const { layouts, dropped } = patchMod3.normalizeLayouts(store);
return dropped === 1 && layouts[PAGE].ops.length === 1 && !('some-other-page' in layouts);
})());
record('saving one page preserves every other page’s layout', (() => {
const store = patchMod3.mergeLayouts({}, 'positions', patchMod3.pushOp(patchMod3.emptyPatch('positions'), { op: 'hide', target: 'x' }));
const after = patchMod3.mergeLayouts(store, PAGE, previewPatch);
return 'positions' in after && PAGE in after;
})());
record('an operation that would be refused is never stored', (() => {
/* `propose` validates before it previews, so a refused change never becomes
a preview and therefore never reaches an apply. */
const provider = readFileSync(join(ROOT, resolveSourcePath('src/components/ui-tree/UiEditingProvider.jsx')), 'utf8');
const proposeBody = provider.slice(provider.indexOf('const propose'), provider.indexOf('/** Throw the experiment away'));
return /applyOperation\(/.test(proposeBody)
&& /if \(!result\.ok\)/.test(proposeBody)
&& proposeBody.indexOf('if (!result.ok)') < proposeBody.indexOf('setPreview');
})());
/* ── 6. Nothing in the source is written ───────────────────────────────── */
record('no runtime path writes a file, a skill or a definition', (() => {
const files = [
'src/lib/ui/patch.js', 'src/lib/ui/composition.js', 'src/lib/ui/operations.js',
'src/components/ui-tree/UiEditingProvider.jsx',
'src/components/ui-editor/UiEditor.jsx', 'src/components/ui-editor/NodeInspector.jsx',
'src/components/ui-editor/NodePicker.jsx', 'src/components/ui-editor/TreePanel.jsx',
];
const offences = [];
for (const f of files) {
const code = readFileSync(join(ROOT, resolveSourcePath(f)), 'utf8')
.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
for (const bad of ['writeFile', 'customSkills', 'skillDefinition', 'skill-definitions', '.md']) {
if (code.includes(bad)) offences.push(`${f}: ${bad}`);
}
}
return { pass: offences.length === 0, detail: offences.join(', ') };
})().pass);
record('the shipped Markdown skills are unchanged and still parse', (() => {
/* The registry reads the real `.md` files off disk through Vite. Zero
diagnostics means every one of them still parses exactly as before — the
strongest statement available that the layout work did not touch them. */
const { skills, diagnostics } = reg.readSkillRegistry([]);
return { pass: skills.length > 0 && diagnostics.length === 0, detail: `${skills.length} skills, ${diagnostics.length} diagnostics` };
})().pass);
record('the layout store is a different key from the skill store',
!JSON.stringify(applied).includes('customSkills'));
}
/* ── Owliver edits the page ────────────────────────────────────────────────
Phase 4. Natural language reaches the same eight operations everything else
uses. The assertions that matter most are the ones about what Owliver will
NOT do: guess a target, invent a type, invent a data source, or apply
anything in the turn it proposed it. */
{
console.log('\n── Owliver UI editing ──');
const intentMod = await server.ssrLoadModule('/src/lib/ui/intent.js');
const uiEditMod = await server.ssrLoadModule('/src/components/ai-assistant/uiEdit.js');
const routingMod = await server.ssrLoadModule('/src/components/ai-assistant/routing.js');
const compositionMod4 = await server.ssrLoadModule('/src/lib/ui/composition.js');
const registryMod4 = await server.ssrLoadModule('/src/lib/ui/registry.js');
const opsMod4 = await server.ssrLoadModule('/src/lib/ui/operations.js');
const nodeMod4 = await server.ssrLoadModule('/src/lib/ui/node.js');
await server.ssrLoadModule('/src/components/ui-tree/nodeTypes.jsx');
await server.ssrLoadModule('/src/pages/admin/activity/nodes.jsx');
const reg4 = registryMod4.nodeRegistry;
const base = compositionMod4.composePage('activity').tree;
/* A page with a reading on it, so "change this to a table" has something to
act on. Built through the engine, not hand-written, so it is a tree the
product could really produce. */
const withChart = opsMod4.applyOperation(base, {
op: 'add', parent: null,
node: { id: 'flow-1', type: 'flow', data: { source: 'candidates.activity' }, props: { title: 'Hiring activity' } },
}, { registry: reg4 });
record('a reading can be placed on the page for these cases', withChart.ok,
withChart.ok ? '' : withChart.problems.map((p) => p.message).join('; '));
const tree4 = withChart.tree;
const ask = (q, opts = {}) => intentMod.matchUiEdit(q, { tree: tree4, registry: reg4, ...opts });
const reply = (q, opts = {}) => uiEditMod.resolveUiEdit({
question: q,
ui: { available: true, tree: tree4, registry: reg4, previewing: false, role: null, ...opts },
});
/* ── The four requests ─────────────────────────────────────────────────── */
record('"Hide the audit section." → hide the audit node', (() => {
const m = ask('Hide the audit section.');
return m?.kind === 'plan' && m.op.op === 'hide' && m.op.target === 'audit' && m.op.hidden === true;
})());
record('"Move timeline above the notice." → a reorder putting it first', (() => {
const m = ask('Move timeline above the notice.');
if (m?.kind !== 'plan' || m.op.op !== 'reorder') return { pass: false, detail: JSON.stringify(m) };
const order = m.op.order;
return {
pass: order.indexOf('timeline') < order.indexOf('activity-privileged-notice'),
detail: order.join(', '),
};
})().pass);
record('the move is a real change, applied through the engine', (() => {
const m = ask('Move timeline above the notice.');
const r = opsMod4.applyOperation(tree4, m.op, { registry: reg4 });
const ids = r.tree.map((n) => n.id);
return r.ok && ids.indexOf('timeline') < ids.indexOf('activity-privileged-notice');
})());
record('"Change the hiring activity to a table." → replace with table', (() => {
const m = ask('Change the hiring activity to a table.');
return m?.kind === 'plan' && m.op.op === 'replace' && m.op.target === 'flow-1' && m.op.type === 'table';
})());
record('the replacement is accepted by the engine', (() => {
const m = ask('Change the hiring activity to a table.');
const r = opsMod4.applyOperation(tree4, m.op, { registry: reg4 });
return r.ok && nodeMod4.findNode(r.tree, 'flow-1').type === 'table';
})());
record('"Add a card." asks what it should show rather than inventing one', (() => {
const m = ask('Add a card.');
return m?.kind === 'needs-source' && m.type.type === 'card' && m.options.length > 0;
})());
record('the sources offered are all real, from the closed vocabulary', (() => {
const m = ask('Add a card.');
return m.options.every((id) => surfaces.SUPPORTED_DATA_SOURCES.includes(id));
})());
record('"Add a card showing …" names a source and produces a plan', (() => {
const m = ask('Add a card showing candidate activity');
return m?.kind === 'plan' && m.op.op === 'add'
&& m.op.node.type === 'card'
&& surfaces.SUPPORTED_DATA_SOURCES.includes(m.op.node.data.source);
})());
/* ── What it refuses ───────────────────────────────────────────────────── */
record('an unknown target is reported, not guessed', (() => {
const m = ask('Hide the quarterly revenue section');
return m?.kind === 'unknown';
})());
record('a request naming nothing on the page is not a layout edit at all',
ask('Hide the quarterly revenue waterfall') === null);
record('an ambiguous target asks which one', (() => {
/* Two skill-surface slots sit on this page and share a label, so a phrase
naming only the label fits both. */
const m = ask('Hide the skill sections');
return { pass: m?.kind === 'ambiguous' && m.candidates.length >= 2, detail: m?.kind };
})().pass);
record('the ambiguous reply names the candidates and offers them as chips', (() => {
const r = reply('Hide the skill sections');
return r?.kind === 'ui-answer'
&& /which did you mean/i.test(JSON.stringify(r.doc))
&& r.followUp.length >= 2;
})());
record('an unregistered UI type is refused, and the real ones are named', (() => {
const r = reply('Change the hiring activity to a hologram');
/* `hologram` names no type, so the request cannot resolve to one. */
return r === null || (r.kind === 'ui-answer' && !/hologram/i.test(JSON.stringify(r.doc)));
})());
record('a type this reading cannot become is refused with the reason', (() => {
/* `weights` is a registered type; a source that has no weights in it cannot
be drawn as one, and the refusal says so rather than failing later. */
const m = ask('Change the hiring activity to weights');
return { pass: m?.kind === 'refused', detail: m?.message || m?.kind };
})().pass);
record('a built-in section cannot be turned into something else', (() => {
const m = ask('Change the audit log to a table');
return m?.kind === 'refused' && /cannot be changed into/.test(m.message);
})());
record('a section that declines hiding says so', (() => {
/* Every Activity section allows hiding, so this asserts the mechanism on a
type that does not: capability is read from the registry, not assumed. */
const r = new registryMod4.NodeTypeRegistry();
r.register({ type: 'fixed-thing', label: 'Fixed thing', component: () => null, capabilities: ['move'] });
const t = [nodeMod4.makeNode({ id: 'fixed-thing-1', type: 'fixed-thing', props: { title: 'Header' } })];
const m = intentMod.matchUiEdit('hide the header', { tree: t, registry: r });
return m?.kind === 'refused' && /cannot be hidden/.test(m.message);
})());
record('a role without access to a type cannot add it', (() => {
const r = new registryMod4.NodeTypeRegistry();
r.register({ type: 'banner', label: 'Banner', component: () => null, roles: ['admin'] });
const m = intentMod.matchUiEdit('add a banner', { tree: [], registry: r, role: 'talent' });
return m?.kind === 'refused' && /do not have access/.test(m.message);
})());
record('an invalid property never reaches a plan', (() => {
/* There is no phrasing that sets an arbitrary property: the only one the
language layer can set is a column count, and it is bounded twice — by
the grid, and by the type's own constraints. */
const m = ask('Make the hiring activity 40 columns');
if (m?.kind !== 'plan') return true;
const r = opsMod4.applyOperation(tree4, m.op, { registry: reg4 });
return !r.ok && /between 1 and 12/.test(r.problems[0].message);
})());
record('a valid column count is planned and accepted', (() => {
const m = ask('Make the hiring activity two columns');
return m?.kind === 'plan' && m.op.op === 'update' && m.op.layout.columns === 2;
})());
record('a built-in section refuses a change it never declared', (() => {
/* Activity's own sections declare `move` and `hide` and nothing else, so a
property change is refused by capability rather than by validation. */
const m = ask('Make the audit log two columns');
return m?.kind === 'refused' && /cannot be changed/.test(m.message);
})());
/* ── Preview, then a separate Apply ────────────────────────────────────── */
record('a plan comes back as a preview, never as an applied change', (() => {
const r = reply('Hide the audit section.');
return r.kind === 'ui-preview'
&& /preview/i.test(JSON.stringify(r.doc))
&& r.followUp.map((c) => c.label).join() === 'Apply,Discard';
})());
/**
* These two asserted that the panel's own chip text returned nothing when
* nothing was previewed — which is how the phrase reached the model, and how
* a person clicking "Discard" twice was told by an agent scoped to open roles
* that layout changes were outside its scope. The corrected contract: the
* request is still claimed, and still changes nothing. What must keep
* returning null is `apply` as an ordinary word, asserted just below.
*/
record('"apply" with nothing previewed changes nothing, and is not handed on', (() => {
const r = reply('Apply the layout change');
return r?.kind === 'ui-answer' && !r.op && /nothing to apply/i.test(JSON.stringify(r.doc));
})());
record('"apply" is recognised only while a preview is on screen', (() => {
const r = reply('Apply the layout change', { previewing: true });
return r?.kind === 'ui-apply';
})());
record('"discard" behaves the same way, and acts only during a preview', (() => {
const idle = reply('Discard the layout change');
return idle?.kind === 'ui-answer' && !idle.op
&& /nothing to discard/i.test(JSON.stringify(idle.doc))
&& reply('Discard the layout change', { previewing: true })?.kind === 'ui-discard';
})());
record('asking what is on the page is a reading, not a change', (() => {
const r = reply('What is on this page?');
return r?.kind === 'ui-answer' && /timeline/i.test(JSON.stringify(r.doc));
})());
/* ── Owliver never authors ─────────────────────────────────────────────── */
record('no plan carries markup, a component or Markdown', (() => {
const asks = [
'Hide the audit section.', 'Move timeline above the notice.',
'Change the hiring activity to a table.', 'Add a card showing candidate activity',
'Make the hiring activity two columns',
];
const json = JSON.stringify(asks.map((q) => ask(q)?.op).filter(Boolean));
return !/</.test(json) && !/component|className|jsx|markdown|dangerously/i.test(json);
})());
record('every produced operation is one of the eight the engine knows', (() => {
const asks = [
'Hide the audit section.', 'Move timeline above the notice.',
'Change the hiring activity to a table.', 'Add a card showing candidate activity',
'Make the hiring activity two columns',
];
const kinds = asks.map((q) => ask(q)?.op?.op).filter(Boolean);
return { pass: kinds.every((k) => opsMod4.OPERATIONS.includes(k)) && kinds.length === 5, detail: kinds.join(', ') };
})().pass);
record('the language layer names no page and no component', (() => {
const code = readFileSync(join(ROOT, resolveSourcePath('src/lib/ui/intent.js')), 'utf8')
.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
const banned = [...surfaces.SUPPORTED_SKILL_PAGES, ...reg4.list()];
const hits = banned.filter((w) => new RegExp(`['"\`]${w}['"\`]`).test(code));
return { pass: hits.length === 0, detail: hits.join(', ') };
})().pass);
/* ── Everything Owliver already did, unchanged ─────────────────────────── */
const uiBag = { available: true, tree: tree4, registry: reg4, previewing: false, role: null };
record('an ordinary question on a composed page is not taken as a layout edit', (() => {
const ordinary = [
'Which candidates need attention?',
'Show me recent activity',
'Who applied today?',
'What is the hiring trend?',
'Create a position',
'Create an employee role',
'Which positions are in draft?',
'Tell me about Some Person',
];
const taken = ordinary.filter((q) => uiEditMod.resolveUiEdit({ question: q, ui: uiBag }) !== null);
return { pass: taken.length === 0, detail: taken.join(' | ') };
})().pass);
record('with no layout session, routing behaves exactly as before', (() => {
const withUi = routingMod.resolveIntent({ question: 'Who applied today?', contextId: 'admin.activity', ui: uiBag });
const without = routingMod.resolveIntent({ question: 'Who applied today?', contextId: 'admin.activity' });
return withUi.kind === without.kind;
})());
record('a page that composes no tree never reaches the UI matcher',
uiEditMod.resolveUiEdit({ question: 'Hide the audit section.', ui: { available: false, tree: [] } }) === null);
/**
* Apply and discard with nothing being previewed.
*
* Found in a browser, not here: clicking the panel's own "Discard the layout
* change" chip a second time fell through to the model, and an agent scoped
* to open roles answered that layout changes were not in its scope. A request
* about the interface must never be answered by something that does not know
* the interface exists.
*
* The two halves pull against each other and both are asserted: the panel's
* own words are always claimed, and "apply" as an ordinary English word is
* never claimed. These support the live browser flow in
* `scripts/browser-flows.js`; they do not stand in for it.
*/
record('apply with nothing previewed is answered here, not by the model', (() => {
const m = intentMod.matchUiEdit('Apply the layout change', { tree: tree4, registry: reg4, previewing: false });
return { pass: m?.kind === 'nothing-previewed' && m.op === 'apply', detail: JSON.stringify(m) };
})().pass);
record('discard with nothing previewed is answered here, not by the model', (() => {
const m = intentMod.matchUiEdit('Discard the layout change', { tree: tree4, registry: reg4, previewing: false });
return { pass: m?.kind === 'nothing-previewed' && m.op === 'discard', detail: JSON.stringify(m) };
})().pass);
record('and it says so in words rather than acting', (() => {
const a = uiEditMod.resolveUiEdit({ question: 'Apply the layout change', ui: uiBag });
const d = uiEditMod.resolveUiEdit({ question: 'Discard the layout change', ui: uiBag });
const words = JSON.stringify([a, d]);
return {
pass: a?.kind === 'ui-answer' && d?.kind === 'ui-answer'
&& !a.op && !d.op
&& /nothing to apply/i.test(words) && /nothing to discard/i.test(words),
detail: words.slice(0, 200),
};
})().pass);
record('an ordinary "apply" is still not a layout request', (() => {
const ordinary = [
'How do I apply for this position?',
'Can this candidate apply for two roles?',
'Apply the vetting weights filter',
'Which candidates applied today?',
'Discard this draft position',
];
const taken = ordinary.filter((q) => uiEditMod.resolveUiEdit({ question: q, ui: uiBag }) !== null);
return { pass: taken.length === 0, detail: taken.join(' | ') };
})().pass);
record('while previewing, apply and discard still act', (() => {
const previewing = { ...uiBag, previewing: true };
const a = uiEditMod.resolveUiEdit({ question: 'Apply the layout change', ui: previewing });
const d = uiEditMod.resolveUiEdit({ question: 'Discard the layout change', ui: previewing });
return { pass: a?.kind === 'ui-apply' && d?.kind === 'ui-discard', detail: `${a?.kind} / ${d?.kind}` };
})().pass);
record('routing returns the layout intent on a composed page', (() => {
const intent = routingMod.resolveIntent({
question: 'Hide the audit section.', contextId: 'admin.activity', ui: uiBag,
});
return intent.kind === 'ui-preview' && intent.op.target === 'audit';
})());
record('the Board skill vocabulary is untouched by the language layer',
surfaces.SUPPORTED_SECTION_TYPES.every((t) => reg4.has(t)),
'all nine reading types are registered node types');
}
/* ── Built-in UI and skill UI in one tree ──────────────────────────────────
Phase 5A. The coverage prerequisites: a skill's section is a node, Hired
History composes, and Positions' page-level slots do. The point of the
section is the pair of assertions at the end — one operation engine moving a
built-in and a skill section with the same call. */
{
console.log('\n── Skill sections as nodes ──');
const compMod = await server.ssrLoadModule('/src/lib/ui/composition.js');
const regMod5 = await server.ssrLoadModule('/src/lib/ui/registry.js');
const opsMod5 = await server.ssrLoadModule('/src/lib/ui/operations.js');
const nodeMod5 = await server.ssrLoadModule('/src/lib/ui/node.js');
const skillNodesMod = await server.ssrLoadModule('/src/lib/ui/skillNodes.js');
const inspectMod5 = await server.ssrLoadModule('/src/lib/ui/inspect.js');
const intentMod5 = await server.ssrLoadModule('/src/lib/ui/intent.js');
const uiConfigMod = await server.ssrLoadModule('/src/lib/skills/uiConfig.js');
const validateMod5 = await server.ssrLoadModule('/src/lib/ui/validate.js');
const patchMod5 = await server.ssrLoadModule('/src/lib/ui/patch.js');
const { MemoryRouter: Router5 } = await import('react-router-dom');
await server.ssrLoadModule('/src/components/ui-tree/nodeTypes.jsx');
await server.ssrLoadModule('/src/pages/admin/activity/nodes.jsx');
await server.ssrLoadModule('/src/pages/admin/hired-history/nodes.jsx');
await server.ssrLoadModule('/src/pages/admin/positions/nodes.js');
const reg5 = regMod5.nodeRegistry;
const ctx5 = { registry: reg5 };
/**
* The Board skill exactly as the account stores it — Markdown in, node out.
*
* Parsed by the real parser rather than hand-built, so this breaks if the
* adapter and the skill format ever stop agreeing.
*/
const BOARD_MD = [
'---',
'id: board',
'name: Board',
'description: Helps Owliver understand, analyze, and act on the current task board.',
'pages:',
' - positions',
'status: active',
'ui:',
' type: card',
' placement: after-position-list-summary',
' title: Board',
' source: candidates.activity',
' periods:',
' - today',
' - last-7-days',
' - previous-month',
'---',
'',
'# Board',
].join('\n');
const board = reg.parseSkill(BOARD_MD, 'custom/board.md');
record('the Board skill parses and declares a section on Positions', (() => {
const sections = uiConfigMod.sectionsForPage(board, 'positions');
return { pass: sections.length === 1 && sections[0].type === 'card', detail: JSON.stringify(sections[0] || null) };
})().pass);
const boardSections = uiConfigMod.sectionsForPage(board, 'positions')
.map((section) => ({ skill: board, section }));
const boardByPlacement = skillNodesMod.skillNodesByPlacement(boardSections);
record('a skill section becomes a node with skill origin and stable identity', (() => {
const node = boardByPlacement['after-position-list-summary'][0];
return node.id === 'skill-board-board' && node.origin === 'skill' && node.type === 'card';
})());
record('the node carries the section’s real data binding, not a copy of data', (() => {
const node = boardByPlacement['after-position-list-summary'][0];
return node.data.source === 'candidates.activity'
&& JSON.stringify(node.data.params.periods) === JSON.stringify(['today', 'last-7-days', 'previous-month']);
})());
record('the node keeps the skill’s title and attributes the skill', (() => {
const node = boardByPlacement['after-position-list-summary'][0];
return node.props.title === 'Board' && node.props.attribution === 'Board';
})());
record('an option the source does not read is dropped rather than losing the node', (() => {
/* `hires.performance` declares no options; a definition asking for periods
against it must still produce a renderable node. */
const node = skillNodesMod.skillSectionNode(
{ id: 'x', name: 'X' },
{ id: 's', type: 'stats', source: 'hires.performance', periods: ['today'], title: 'X' }
);
return !node.data.params.periods && node.data.source === 'hires.performance';
})());
/* ── The Board card on the Positions tree ──────────────────────────────── */
const positionsTree = compMod.composePage('positions', { skillNodes: boardByPlacement, ...ctx5 }).tree;
record('the Board card is a child of the page-level slot', (() => {
const slot = positionsTree.find((n) => n.id === 'positions-extensions-summary');
return slot?.children?.length === 1 && slot.children[0].id === 'skill-board-board';
})());
record('the composed Positions tree validates', (() => {
const { ok, problems } = validateMod5.validateTree(positionsTree, ctx5);
return { ok, detail: problems.map((p) => p.message).join('; ') };
})().ok);
record('the Board card is addressable by name', (() => {
const hits = inspectMod5.resolveTarget(positionsTree, 'the board card', { registry: reg5 });
return hits.length > 0 && hits[0].id === 'skill-board-board';
})());
record('the Board card can be hidden through the ordinary engine', (() => {
const r = opsMod5.applyOperation(positionsTree, { op: 'hide', target: 'skill-board-board' }, ctx5);
return r.ok && nodeMod5.findNode(r.tree, 'skill-board-board').hidden === true;
})());
record('Owliver reaches the Board card in words', (() => {
const m = intentMod5.matchUiEdit('hide the board card', { tree: positionsTree, registry: reg5 });
return m?.kind === 'plan' && m.op.op === 'hide' && m.op.target === 'skill-board-board';
})());
record('a skill node cannot be deleted, only hidden', (() => {
const r = opsMod5.applyOperation(positionsTree, { op: 'remove', target: 'skill-board-board' }, ctx5);
return !r.ok && /part of the page/.test(r.problems[0].message);
})());
record('hiding a Board card produces an operation, never a Markdown edit', (() => {
const r = opsMod5.applyOperation(positionsTree, { op: 'hide', target: 'skill-board-board' }, ctx5);
const json = JSON.stringify(r.diff);
return r.ok && !/---|ui:|placement|markdown/i.test(json);
})());
record('the skill definition is untouched by the operation', (() => {
/* The same Markdown, parsed again after the tree was mutated, must give the
same section. Nothing in the node path writes back. */
const again = reg.parseSkill(BOARD_MD, 'custom/board.md');
return JSON.stringify(uiConfigMod.sectionsForPage(again, 'positions'))
=== JSON.stringify(uiConfigMod.sectionsForPage(board, 'positions'));
})());
/* ── Hired History ─────────────────────────────────────────────────────── */
const hiredTree = compMod.composePage('hired-history').tree;
record('Hired History composes in reading order',
hiredTree.map((n) => n.id).join(',')
=== 'hired-extensions-top,hired-filters,hired-summary,chronology,records',
hiredTree.map((n) => n.id).join(', '));
record('the Recent Hiring Timeline is addressable as `chronology`',
Boolean(nodeMod5.findNode(hiredTree, 'chronology')));
record('the composed Hired History tree validates', (() => {
const { ok, problems } = validateMod5.validateTree(hiredTree, ctx5);
return { ok, detail: problems.map((p) => p.message).join('; ') };
})().ok);
record('the chronology node can be hidden', (() => {
const r = opsMod5.applyOperation(hiredTree, { op: 'hide', target: 'chronology' }, ctx5);
return r.ok && nodeMod5.findNode(r.tree, 'chronology').hidden === true;
})());
record('the chronology node can be moved above the summary', (() => {
const m = intentMod5.matchUiEdit('move the recent hiring timeline above the hire summary', {
tree: hiredTree, registry: reg5,
});
if (m?.kind !== 'plan') return { pass: false, detail: JSON.stringify(m) };
const r = opsMod5.applyOperation(hiredTree, m.op, ctx5);
const ids = r.tree.map((n) => n.id);
return { pass: r.ok && ids.indexOf('chronology') < ids.indexOf('hired-summary'), detail: ids.join(', ') };
})().pass);
record('Owliver reaches the timeline by the words on screen', (() => {
const m = intentMod5.matchUiEdit('hide the recent hiring timeline', { tree: hiredTree, registry: reg5 });
return m?.kind === 'plan' && m.op.target === 'chronology';
})());
/* ── One engine, both kinds of node ────────────────────────────────────── */
record('a built-in and a skill node move with the identical operation', (() => {
/* The same op shape, the same call, two different origins. If the engine
had learned the difference anywhere, one of these would need its own
path — and this is the assertion that would fail. */
const builtin = opsMod5.applyOperation(hiredTree, { op: 'hide', target: 'records' }, ctx5);
const skill = opsMod5.applyOperation(positionsTree, { op: 'hide', target: 'skill-board-board' }, ctx5);
return builtin.ok && skill.ok
&& builtin.diff.op === skill.diff.op
&& nodeMod5.findNode(builtin.tree, 'records').origin === 'builtin'
&& nodeMod5.findNode(skill.tree, 'skill-board-board').origin === 'skill';
})());
record('a saved patch survives across both origins', (() => {
const patch = {
schema: patchMod5.PATCH_SCHEMA, page: 'positions',
ops: [{ op: 'hide', target: 'skill-board-board' }],
};
const { tree } = compMod.composePage('positions', { patch, skillNodes: boardByPlacement, ...ctx5 });
return nodeMod5.findNode(tree, 'skill-board-board').hidden === true;
})());
record('a patch naming a card whose skill was switched off is skipped, not fatal', (() => {
const patch = {
schema: patchMod5.PATCH_SCHEMA, page: 'positions',
ops: [{ op: 'hide', target: 'skill-board-board' }],
};
/* No skill nodes: the definition is disabled, so the card is not there. */
const { tree, skipped } = compMod.composePage('positions', { patch, ...ctx5 });
return skipped.length === 1 && tree.length === 2;
})());
record('a page composes identically whether or not skills are supplied', (() => {
const without = compMod.composePage('hired-history', ctx5).tree;
const withNone = compMod.composePage('hired-history', { skillNodes: {}, ...ctx5 }).tree;
return JSON.stringify(without) === JSON.stringify(withNone);
})());
/* ── Rendering parity, against the captured baselines ──────────────────── */
{
const { QueryClient, QueryClientProvider } = await import('@tanstack/react-query');
const { UiEditingProvider } = await server.ssrLoadModule('/src/components/ui-tree/UiEditingProvider.jsx');
const draw = async (modulePath, page, route) => {
const Page = (await server.ssrLoadModule(modulePath)).default;
const client = new QueryClient({ defaultOptions: { queries: { retry: false, enabled: false } } });
return renderToStaticMarkup(
React.createElement(Router5, { initialEntries: [route] },
React.createElement(QueryClientProvider, { client },
React.createElement(UiEditingProvider, { page },
React.createElement(Page))))
);
};
const stripControls = (html) => {
const at = html.indexOf('<div data-ui-controls');
if (at < 0) return html;
let depth = 0;
let i = at;
while (i < html.length) {
if (html.startsWith('<div', i)) { depth += 1; i = html.indexOf('>', i) + 1; continue; }
if (html.startsWith('</div>', i)) { depth -= 1; i += 6; if (depth === 0) break; continue; }
i += 1;
}
return html.slice(0, at) + html.slice(i);
};
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');
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);
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');
})());
const positionsNow = await draw('/src/pages/admin/Positions.jsx', 'positions', '/admin/positions');
const positionsBefore = readFileSync(join(ROOT, 'scripts/__baseline__/positions.pre-migration.html'), 'utf8');
/**
* Positions, with whatever this run's account has authored.
*
* The suite creates Board skills earlier on, so by the time this renders,
* Positions legitimately carries one — which is the thing Phase 5A exists
* to make possible. So the assertion is not "nothing changed" but the two
* things that matter: the card is there **as an addressable node**, and
* the page's own markup around it is exactly what it was.
*/
record('a Board skill card renders on Positions as an addressable node', (() => {
const ids = [...positionsNow.matchAll(/data-ui-node="(skill-[^"]+)"/g)].map((m) => m[1]);
return { pass: ids.length > 0, detail: ids.join(', ') || 'no skill node rendered' };
})().pass, [...positionsNow.matchAll(/data-ui-node="(skill-[^"]+)"/g)].map((m) => m[1]).join(', '));
record('Positions’ own markup is unchanged apart from the skill slot', (() => {
/* Lift out each slot subtree — a `space-y-4` wrapper holding skill nodes —
by balanced tag counting, and what is left must be the baseline. */
let html = stripControls(positionsNow);
for (let guard = 0; guard < 4; guard += 1) {
/* The slot wrapper carries `space-y-4` plus whatever margin the page's
composition declared, so match on the pair rather than an exact class. */
const at = html.search(/<div class="space-y-4[^"]*"><section data-ui-node="skill-/);
if (at < 0) break;
let depth = 0;
let i = at;
while (i < html.length) {
if (html.startsWith('<div', i)) { depth += 1; i = html.indexOf('>', i) + 1; continue; }
if (html.startsWith('</div>', i)) { depth -= 1; i += 6; if (depth === 0) break; continue; }
i += 1;
}
html = html.slice(0, at) + html.slice(i);
}
return { pass: html === positionsBefore, detail: html === positionsBefore ? '' : `len ${positionsBefore.length}->${html.length}` };
})().pass);
}
}
/* ── Every page-level composition ─────────────────────────────────────────
Phase 5B. Five more pages in the tree, and the spacing vocabulary that let
the Positions slots keep the margins they had before they became nodes. */
{
console.log('\n── Page compositions ──');
const compMod5b = await server.ssrLoadModule('/src/lib/ui/composition.js');
const regMod5b = await server.ssrLoadModule('/src/lib/ui/registry.js');
const opsMod5b = await server.ssrLoadModule('/src/lib/ui/operations.js');
const nodeMod5b = await server.ssrLoadModule('/src/lib/ui/node.js');
const validateMod5b = await server.ssrLoadModule('/src/lib/ui/validate.js');
const intentMod5b = await server.ssrLoadModule('/src/lib/ui/intent.js');
const skillNodesMod5b = await server.ssrLoadModule('/src/lib/ui/skillNodes.js');
const uiCfg5b = await server.ssrLoadModule('/src/lib/skills/uiConfig.js');
await server.ssrLoadModule('/src/components/ui-tree/nodeTypes.jsx');
for (const m of ['activity', 'hired-history', 'candidates', 'talent-pool', 'analytics',
'candidates-analysis', 'control-center']) {
await server.ssrLoadModule(`/src/pages/admin/${m}/nodes.jsx`).catch(() => null);
}
await server.ssrLoadModule('/src/pages/admin/positions/nodes.js');
const reg5b = regMod5b.nodeRegistry;
const ctx5b = { registry: reg5b };
/** Every page that should now compose, and the nodes it must carry. */
const EXPECTED = {
activity: ['activity-summary', 'activity-privileged-notice', 'timeline', 'audit'],
'hired-history': ['hired-filters', 'hired-summary', 'chronology', 'records'],
candidates: ['candidates-toolbar', 'candidates-list'],
'talent-pool': ['segments', 'directory'],
analytics: ['performance', 'funnel', 'trend', 'departments', 'positions', 'efficiency', 'insights'],
'candidates-analysis': ['supply', 'distribution', 'risk', 'skills', 'fit', 'recommend', 'screening'],
'control-center': ['cc-snapshot', 'cc-activity', 'cc-pipeline', 'cc-positions', 'cc-actions', 'cc-lists', 'cc-recent'],
positions: ['positions-extensions-summary', 'positions-extensions-list'],
};
record('every migrated page registers a composition', (() => {
const pages = compMod5b.composedPages();
const missing = Object.keys(EXPECTED).filter((p) => !pages.includes(p));
return { pass: missing.length === 0, detail: missing.join(', ') || pages.join(', ') };
})().pass, compMod5b.composedPages().join(', '));
for (const [page, ids] of Object.entries(EXPECTED)) {
const tree = compMod5b.composePage(page, ctx5b).tree;
record(`${page}: composes the expected nodes`, (() => {
const have = tree.map((n) => n.id);
const missing = ids.filter((id) => !have.includes(id));
return { pass: missing.length === 0, detail: missing.length ? `missing ${missing.join(', ')}` : have.join(', ') };
})().pass);
record(`${page}: the tree validates`, (() => {
const { ok, problems } = validateMod5b.validateTree(tree, ctx5b);
return { ok, detail: problems.map((p) => p.message).join('; ') };
})().ok);
record(`${page}: every node is a built-in that moves and hides`, (() => {
const bad = tree.filter((n) => {
const entry = reg5b.get(n.type);
return !entry || !entry.capabilities.includes('hide') || !entry.capabilities.includes('move');
});
return { pass: bad.length === 0, detail: bad.map((n) => n.id).join(', ') };
})().pass);
/* One node per page, hidden and moved through the engine — the same two
calls, whatever the page happens to contain. */
const target = ids[0];
record(`${page}: \`${target}\` hides through the ordinary engine`, (() => {
const r = opsMod5b.applyOperation(tree, { op: 'hide', target }, ctx5b);
return r.ok && nodeMod5b.findNode(r.tree, target).hidden === true;
})());
record(`${page}: the page reorders through the ordinary engine`, (() => {
const order = [...tree.map((n) => n.id)].reverse();
const r = opsMod5b.applyOperation(tree, { op: 'reorder', parent: null, order }, ctx5b);
return r.ok && r.tree.map((n) => n.id).join() === order.join();
})());
}
/* ── The spacing vocabulary that replaced the lost margins ─────────────── */
record('a slot can declare the margin it always had', (() => {
const tree = compMod5b.composePage('positions', ctx5b).tree;
const summary = tree.find((n) => n.id === 'positions-extensions-summary');
const list = tree.find((n) => n.id === 'positions-extensions-list');
return summary.layout.spacingAfter === 'md' && list.layout.spacingBefore === 'lg';
})());
record('spacing is a closed vocabulary, not a class name', (() => {
/* Asserted against the validator rather than through an operation: the slot
type declares only move, hide and reorder, so an `update` is refused on
capability before the layout is ever read. The rule is what matters. */
const node = nodeMod5b.makeNode({
id: 'x', type: 'skill-surface', props: { page: 'positions', placement: 'after-position-list' },
layout: { spacingAfter: 'mb-96' },
});
const problems = validateMod5b.validateNode(node, ctx5b);
return { pass: problems.some((p) => /spacingAfter` must be one of/.test(p.message)),
detail: problems.map((p) => p.message).join('; ') };
})().pass);
record('a legal spacing step is accepted', (() => {
const node = nodeMod5b.makeNode({
id: 'x', type: 'skill-surface', props: { page: 'positions', placement: 'after-position-list' },
layout: { spacingAfter: 'lg', spacingBefore: 'sm' },
});
return validateMod5b.validateNode(node, ctx5b).length === 0;
})());
record('no page composition carries a class name', (() => {
/* Configuration must never be able to say "add this CSS". Every node in
every composed page, checked for anything that looks like a class. */
const offences = [];
for (const page of compMod5b.composedPages()) {
for (const node of nodeMod5b.walk(compMod5b.composePage(page, ctx5b).tree)) {
const json = JSON.stringify({ props: node.props, layout: node.layout });
if (/class|className|\b(?:mt|mb|px|py|bg|text)-/.test(json)) offences.push(`${page}/${node.id}`);
}
}
return { pass: offences.length === 0, detail: offences.join(', ') };
})().pass);
/* ── Built-in and skill nodes, one mechanism, on a migrated page ───────── */
{
const BOARD_MD = [
'---', 'id: hh-board', 'name: Hiring board', 'description: A reading for Hired History.',
'pages:', ' - hired-history', 'status: active',
'ui:', ' type: stats', ' placement: after-header', ' title: Hiring board',
' source: hires.performance', '---', '', '# Hiring board',
].join('\n');
const skill = reg.parseSkill(BOARD_MD, 'custom/hh-board.md');
const sections = uiCfg5b.sectionsForPage(skill, 'hired-history').map((section) => ({ skill, section }));
const byPlacement = skillNodesMod5b.skillNodesByPlacement(sections);
const tree = compMod5b.composePage('hired-history', { skillNodes: byPlacement, ...ctx5b }).tree;
record('a skill section joins a migrated page as a child node', (() => {
const slot = tree.find((n) => n.id === 'hired-extensions-top');
return { pass: slot?.children?.length === 1 && slot.children[0].origin === 'skill', detail: JSON.stringify(slot?.children?.map((c) => c.id)) };
})().pass);
record('one operation moves a built-in and a skill node on the same page', (() => {
/* The identical call shape, twice, against one tree. */
const builtin = opsMod5b.applyOperation(tree, { op: 'hide', target: 'chronology' }, ctx5b);
const skillNode = tree.find((n) => n.id === 'hired-extensions-top').children[0];
const skilled = opsMod5b.applyOperation(tree, { op: 'hide', target: skillNode.id }, ctx5b);
return builtin.ok && skilled.ok
&& builtin.diff.op === skilled.diff.op
&& nodeMod5b.findNode(builtin.tree, 'chronology').origin === 'builtin'
&& nodeMod5b.findNode(skilled.tree, skillNode.id).origin === 'skill';
})());
record('Owliver reaches both by name on the same page', (() => {
const a = intentMod5b.matchUiEdit('hide the recent hiring timeline', { tree, registry: reg5b });
const b = intentMod5b.matchUiEdit('hide the hiring board', { tree, registry: reg5b });
return { pass: a?.kind === 'plan' && b?.kind === 'plan' && a.op.target !== b.op.target,
detail: `${a?.op?.target} / ${b?.op?.target}` };
})().pass);
}
/* ── Rendering parity for every migrated page ──────────────────────────── */
{
const { QueryClient, QueryClientProvider } = await import('@tanstack/react-query');
const { UiEditingProvider } = await server.ssrLoadModule('/src/components/ui-tree/UiEditingProvider.jsx');
const { MemoryRouter: R } = await import('react-router-dom');
const stripControls = (html) => {
const at = html.indexOf('<div data-ui-controls');
if (at < 0) return html;
let depth = 0;
let i = at;
while (i < html.length) {
if (html.startsWith('<div', i)) { depth += 1; i = html.indexOf('>', i) + 1; continue; }
if (html.startsWith('</div>', i)) { depth -= 1; i += 6; if (depth === 0) break; continue; }
i += 1;
}
return html.slice(0, at) + html.slice(i);
};
const classes = (h) => (h.match(/class="[^"]*"/g) || []).join('\n');
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'],
];
for (const [page, modulePath, route, baseline] of PAGES) {
const Page = (await server.ssrLoadModule(modulePath)).default;
const client = new QueryClient({ defaultOptions: { queries: { retry: false, enabled: false } } });
const now = stripControls(renderToStaticMarkup(
React.createElement(R, { initialEntries: [route] },
React.createElement(QueryClientProvider, { client },
React.createElement(UiEditingProvider, { page },
React.createElement(Page))))
));
const before = readFileSync(join(ROOT, `scripts/__baseline__/${baseline}.pre-migration.html`), 'utf8');
/**
* A migration may INSERT a wrapper class — grouping three siblings under
* one node has to carry their spacing inward — but it must never change
* or drop one. Two pointers over the sequences: every baseline entry has
* to appear, in order; anything extra is reported.
*/
record(`${page}: paints the same styled elements, in the same order`, (() => {
const a = (now.match(/class="[^"]*"/g) || []);
const b = (before.match(/class="[^"]*"/g) || []);
const inserted = [];
let i = 0;
for (const want of b) {
while (i < a.length && a[i] !== want) { inserted.push(a[i]); i += 1; }
if (i >= a.length) return { pass: false, detail: `missing ${want}` };
i += 1;
}
inserted.push(...a.slice(i));
return { pass: true, detail: inserted.length ? `inserted ${inserted.join(' ')}` : 'identical' };
})().pass, (() => {
const a = (now.match(/class="[^"]*"/g) || []);
const b = (before.match(/class="[^"]*"/g) || []);
return a.length === b.length ? '' : `${a.length - b.length} wrapper class(es) inserted`;
})());
record(`${page}: shows the same words`, words(now) === words(before));
record(`${page}: carries node identity in the DOM`, (() => {
const ids = [...now.matchAll(/data-ui-node="([^"]+)"/g)].map((m) => m[1]);
return { pass: ids.length > 0, detail: ids.join(', ') };
})().pass);
}
}
}
/* ── A hidden node is still a node ─────────────────────────────────────────
The regression: hiding a section made it unreachable, because the only word
that would bring it back — "show" — was excluded from the layout verbs to
keep it from swallowing "show me the candidates". The tree decides now. */
{
console.log('\n── Hidden nodes stay addressable ──');
const compH = await server.ssrLoadModule('/src/lib/ui/composition.js');
const regH = await server.ssrLoadModule('/src/lib/ui/registry.js');
const opsH = await server.ssrLoadModule('/src/lib/ui/operations.js');
const nodeH = await server.ssrLoadModule('/src/lib/ui/node.js');
const inspectH = await server.ssrLoadModule('/src/lib/ui/inspect.js');
const intentH = await server.ssrLoadModule('/src/lib/ui/intent.js');
const patchH = await server.ssrLoadModule('/src/lib/ui/patch.js');
const skillH = await server.ssrLoadModule('/src/lib/ui/skillNodes.js');
const uiCfgH = await server.ssrLoadModule('/src/lib/skills/uiConfig.js');
await server.ssrLoadModule('/src/components/ui-tree/nodeTypes.jsx');
await server.ssrLoadModule('/src/pages/admin/hired-history/nodes.jsx');
const nreg = regH.nodeRegistry;
const ctx = { registry: nreg };
const PAGE = 'hired-history';
const ask = (q, tree) => intentH.matchUiEdit(q, { tree, registry: nreg });
const visible = compH.composePage(PAGE, ctx).tree;
/* ── hide a visible node → hidden, still inspectable ───────────────────── */
const hidden = opsH.applyOperation(visible, { op: 'hide', target: 'chronology' }, ctx).tree;
record('hiding a node leaves it in the tree', Boolean(nodeH.findNode(hidden, 'chronology')));
record('a hidden node is still in the inspection inventory', (() => {
const seen = inspectH.inspectTree(hidden, ctx).find((n) => n.id === 'chronology');
return seen && seen.hidden === true;
})());
record('a hidden node is still listed in the outline, marked hidden', (() => {
const lines = inspectH.outlineTree(hidden, ctx);
return lines.some((l) => l.includes('chronology') && /hidden/.test(l));
})());
record('a hidden node still resolves by name', (() => {
const hits = inspectH.resolveTarget(hidden, 'the recent hiring timeline', ctx);
return hits.length > 0 && hits[0].id === 'chronology';
})());
/* ── hide an already-hidden node → still the UI flow ───────────────────── */
record('hiding an already-hidden node stays in the UI flow', (() => {
const m = ask('Hide the recent hiring timeline', hidden);
return { pass: m !== null, detail: m?.kind };
})().pass);
record('…and says it is already hidden rather than repeating the change', (() => {
const m = ask('Hide the recent hiring timeline', hidden);
return m?.kind === 'refused' && /already hidden/.test(m.message);
})());
record('no redundant operation is produced', (() => {
const m = ask('Hide the recent hiring timeline', hidden);
return !m.op;
})());
/* ── show a hidden node ────────────────────────────────────────────────── */
record('"show" resolves a hidden node and plans to reveal it', (() => {
const m = ask('Show the recent hiring timeline', hidden);
return { pass: m?.kind === 'plan' && m.op.op === 'hide' && m.op.target === 'chronology' && m.op.hidden === false,
detail: JSON.stringify(m?.op || m?.kind) };
})().pass);
record('"unhide" and "bring back" reach it too', (() => {
const a = ask('Unhide the recent hiring timeline', hidden);
const b = ask('Bring back the recent hiring timeline', hidden);
return a?.kind === 'plan' && b?.kind === 'plan' && a.op.hidden === false && b.op.hidden === false;
})());
record('the reveal applies through the ordinary engine', (() => {
const m = ask('Show the recent hiring timeline', hidden);
const r = opsH.applyOperation(hidden, m.op, ctx);
return r.ok && nodeH.findNode(r.tree, 'chronology').hidden === false;
})());
record('showing an already-visible node says so', (() => {
const m = ask('Unhide the recent hiring timeline', visible);
return m?.kind === 'refused' && /already showing/.test(m.message);
})());
/* ── the branch that must NOT be taken ─────────────────────────────────── */
record('"show" over a page with nothing hidden is not a layout request',
ask('Show the recent hiring timeline', visible) === null);
record('ordinary "show me" questions are untouched, hidden node or not', (() => {
const asks = ['Show me the recent hires', 'Show me the candidates', 'Show the hiring trend'];
const taken = asks.filter((q) => ask(q, hidden) !== null);
return { pass: taken.length === 0, detail: taken.join(' | ') };
})().pass);
record('a normal data question after a hide still reaches Owliver', (() => {
/* The whole point of resolving `show` against the tree: this must stay null
so routing hands it to the reader that answers it. */
return ask('Who was hired most recently?', hidden) === null
&& ask('What is the average time to hire?', hidden) === null;
})());
/* ── across a cold reload ──────────────────────────────────────────────── */
record('hide → reload → show works from stored operations alone', (() => {
/* What the browser does on a fresh load: compose from the saved patch. */
const saved = patchH.pushOp(patchH.emptyPatch(PAGE), { op: 'hide', target: 'chronology' });
const overWire = JSON.parse(JSON.stringify({ [PAGE]: saved }));
const { layouts } = patchH.normalizeLayouts(overWire);
const reloaded = compH.composePage(PAGE, { patch: layouts[PAGE], ...ctx }).tree;
const m = ask('Show the recent hiring timeline', reloaded);
if (m?.kind !== 'plan') return { pass: false, detail: JSON.stringify(m?.kind) };
const r = opsH.applyOperation(reloaded, m.op, ctx);
return { pass: r.ok && nodeH.findNode(r.tree, 'chronology').hidden === false, detail: '' };
})().pass);
record('show → apply → reload leaves it visible', (() => {
const saved = patchH.pushOp(patchH.emptyPatch(PAGE), { op: 'hide', target: 'chronology' });
const shown = patchH.pushOp(saved, { op: 'hide', target: 'chronology', hidden: false });
const { layouts } = patchH.normalizeLayouts(JSON.parse(JSON.stringify({ [PAGE]: shown })));
const tree = compH.composePage(PAGE, { patch: layouts[PAGE], ...ctx }).tree;
return nodeH.findNode(tree, 'chronology').hidden === false;
})());
/* ── a hidden SKILL node ───────────────────────────────────────────────── */
{
const MD = ['---', 'id: hh-hidden', 'name: Hiring board', 'description: A reading.',
'pages:', ' - hired-history', 'status: active', 'ui:', ' type: stats',
' placement: after-header', ' title: Hiring board', ' source: hires.performance',
'---', '', '# Hiring board'].join('\n');
const sk = reg.parseSkill(MD, 'custom/hh-hidden.md');
const sections = uiCfgH.sectionsForPage(sk, PAGE).map((section) => ({ skill: sk, section }));
const withSkill = compH.composePage(PAGE, { skillNodes: skillH.skillNodesByPlacement(sections), ...ctx }).tree;
const skillId = withSkill.find((n) => n.id === 'hired-extensions-top').children[0].id;
const skillHidden = opsH.applyOperation(withSkill, { op: 'hide', target: skillId }, ctx).tree;
record('a hidden skill node stays in the inventory', (() => {
const seen = inspectH.inspectTree(skillHidden, ctx).find((n) => n.id === skillId);
return seen && seen.hidden === true && seen.origin === 'skill';
})());
record('a hidden skill node can be shown again by name', (() => {
const m = ask('Show the hiring board', skillHidden);
if (m?.kind !== 'plan') return { pass: false, detail: JSON.stringify(m?.kind) };
const r = opsH.applyOperation(skillHidden, m.op, ctx);
return { pass: r.ok && nodeH.findNode(r.tree, skillId).hidden === false, detail: '' };
})().pass);
record('hiding an already-hidden skill node stays in the UI flow', (() => {
const m = ask('Hide the hiring board', skillHidden);
return m?.kind === 'refused' && /already hidden/.test(m.message);
})());
}
/* ── ambiguity among hidden nodes still asks ───────────────────────────── */
record('an ambiguous hidden target asks which one', (() => {
/* Two slots share the label "Skill sections"; hide both, then name them. */
const withSlots = compH.composePage('activity', ctx).tree;
let t = opsH.applyOperation(withSlots, { op: 'hide', target: 'activity-extensions-top' }, ctx).tree;
t = opsH.applyOperation(t, { op: 'hide', target: 'activity-extensions-bottom' }, ctx).tree;
const m = intentH.matchUiEdit('Show the skill sections', { tree: t, registry: nreg });
return { pass: m?.kind === 'ambiguous' && m.candidates.length === 2, detail: m?.kind };
})().pass);
}
/* ── The visual editor ─────────────────────────────────────────────────────
Phase 5C. The editor is a client of the same engine, and the assertion that
matters is the equivalence one: the operation its Hide button stands for and
the operation Owliver builds from "hide the recent hiring timeline" are the
same object. */
{
console.log('\n── Visual editor ──');
const compE = await server.ssrLoadModule('/src/lib/ui/composition.js');
const regE = await server.ssrLoadModule('/src/lib/ui/registry.js');
const opsE = await server.ssrLoadModule('/src/lib/ui/operations.js');
const nodeE = await server.ssrLoadModule('/src/lib/ui/node.js');
const inspectE = await server.ssrLoadModule('/src/lib/ui/inspect.js');
const intentE = await server.ssrLoadModule('/src/lib/ui/intent.js');
const patchE = await server.ssrLoadModule('/src/lib/ui/patch.js');
const skillE = await server.ssrLoadModule('/src/lib/ui/skillNodes.js');
const uiCfgE = await server.ssrLoadModule('/src/lib/skills/uiConfig.js');
const editorOps = await server.ssrLoadModule('/src/components/ui-editor/ops.js');
const validateE = await server.ssrLoadModule('/src/lib/ui/validate.js');
await server.ssrLoadModule('/src/components/ui-tree/nodeTypes.jsx');
await server.ssrLoadModule('/src/pages/admin/hired-history/nodes.jsx');
await server.ssrLoadModule('/src/pages/admin/positions/nodes.js');
const nreg = regE.nodeRegistry;
const ectx = { registry: nreg };
const hiredTree = compE.composePage('hired-history', ectx).tree;
const caTree = compE.composePage('candidates-analysis', ectx).tree;
const boundaryE = await server.ssrLoadModule('/src/components/ui-tree/UiNodeBoundary.jsx');
const rendererE = await server.ssrLoadModule('/src/components/ui-tree/UiTreeRenderer.jsx');
const seen = (tree) => inspectE.inspectTree(tree, ectx);
const pick = (tree, id) => seen(tree).find((n) => n.id === id);
/* ── THE equivalence test ──────────────────────────────────────────────── */
record('Owliver and the editor build the identical hide operation', (() => {
const fromOwliver = intentE.matchUiEdit('Hide the recent hiring timeline', {
tree: hiredTree, registry: nreg,
})?.op;
const fromEditor = editorOps.hideOp(pick(hiredTree, 'chronology'));
return {
pass: JSON.stringify(fromOwliver) === JSON.stringify(fromEditor),
detail: `${JSON.stringify(fromOwliver)} vs ${JSON.stringify(fromEditor)}`,
};
})().pass, JSON.stringify(editorOps.hideOp(pick(hiredTree, 'chronology'))));
record('both hide operations persist through one mechanism', (() => {
/* Applied, saved and replayed the same way — there is only one path. */
const op = editorOps.hideOp(pick(hiredTree, 'chronology'));
const saved = patchE.mergeLayouts({}, 'hired-history',
patchE.pushOp(patchE.emptyPatch('hired-history'), op));
const { layouts } = patchE.normalizeLayouts(JSON.parse(JSON.stringify(saved)));
const tree = compE.composePage('hired-history', { patch: layouts['hired-history'], ...ectx }).tree;
return nodeE.findNode(tree, 'chronology').hidden === true;
})());
record('Owliver and the editor build the identical reorder operation', (() => {
const fromOwliver = intentE.matchUiEdit('Move the recent hiring timeline above the hire summary', {
tree: hiredTree, registry: nreg,
})?.op;
const roots = seen(hiredTree).filter((n) => n.parent === null);
const target = pick(hiredTree, 'chronology');
const summaryAt = roots.findIndex((n) => n.id === 'hired-summary');
const fromEditor = editorOps.reorderOp(target, roots, summaryAt);
return {
pass: JSON.stringify(fromOwliver?.order) === JSON.stringify(fromEditor?.order)
&& fromOwliver.op === fromEditor.op && fromOwliver.parent === fromEditor.parent,
detail: `${JSON.stringify(fromOwliver?.order)} vs ${JSON.stringify(fromEditor?.order)}`,
};
})().pass);
/* ── One engine, both origins ──────────────────────────────────────────── */
const BOARD = ['---', 'id: eq-board', 'name: Hiring board', 'description: A reading.',
'pages:', ' - hired-history', 'status: active', 'ui:', ' type: stats',
' placement: after-header', ' title: Hiring board', ' source: hires.performance',
'---', '', '# Hiring board'].join('\n');
const boardSkill = reg.parseSkill(BOARD, 'custom/eq-board.md');
const boardNodes = skillE.skillNodesByPlacement(
uiCfgE.sectionsForPage(boardSkill, 'hired-history').map((section) => ({ skill: boardSkill, section }))
);
const mixed = compE.composePage('hired-history', { skillNodes: boardNodes, ...ectx }).tree;
const skillNodeId = mixed.find((n) => n.id === 'hired-extensions-top').children[0].id;
for (const [what, id] of [['built-in', 'chronology'], ['skill', skillNodeId]]) {
record(`${what} node → hide, through the editor's operation`, (() => {
const op = editorOps.hideOp(pick(mixed, id));
const r = opsE.applyOperation(mixed, op, ectx);
return r.ok && nodeE.findNode(r.tree, id).hidden === true;
})());
record(`${what} node → reorder, through the editor's operation`, (() => {
const node = pick(mixed, id);
const siblings = seen(mixed).filter((n) => n.parent === node.parent);
/* An only child has nowhere to move; that is not a failure, and the
shape has to match what the caller reads. */
if (siblings.length < 2) return { pass: true, detail: 'only child' };
const op = editorOps.nudgeOp(node, siblings, node.index === 0 ? 1 : -1);
const r = opsE.applyOperation(mixed, op, ectx);
return { pass: r.ok, detail: r.ok ? '' : r.problems.map((p) => p.message).join('; ') };
})().pass);
}
/* ── Every editor operation, through the one engine ────────────────────── */
record('editor: add', (() => {
const op = editorOps.addOp(hiredTree, null, 'card', 'hires.performance');
const r = opsE.applyOperation(hiredTree, op, ectx);
return r.ok && nodeE.findNode(r.tree, op.node.id).data.source === 'hires.performance';
})());
record('editor: update a declared property', (() => {
const added = opsE.applyOperation(hiredTree,
editorOps.addOp(hiredTree, null, 'card', 'hires.performance'), ectx).tree;
const node = pick(added, 'card-1');
const r = opsE.applyOperation(added, editorOps.propOp(node, 'title', 'Renamed'), ectx);
return r.ok && nodeE.findNode(r.tree, 'card-1').props.title === 'Renamed';
})());
record('editor: update layout from the closed vocabulary', (() => {
const added = opsE.applyOperation(hiredTree,
editorOps.addOp(hiredTree, null, 'card', 'hires.performance'), ectx).tree;
const r = opsE.applyOperation(added, editorOps.layoutOp(pick(added, 'card-1'), 'gap', 'lg'), ectx);
return r.ok && nodeE.findNode(r.tree, 'card-1').layout.gap === 'lg';
})());
record('editor: replace', (() => {
const added = opsE.applyOperation(hiredTree,
editorOps.addOp(hiredTree, null, 'card', 'hires.performance'), ectx).tree;
const r = opsE.applyOperation(added, editorOps.replaceOp(pick(added, 'card-1'), 'table'), ectx);
return { pass: r.ok && nodeE.findNode(r.tree, 'card-1').type === 'table',
detail: r.ok ? '' : r.problems.map((p) => p.message).join('; ') };
})().pass);
record('editor: remove a node the person added', (() => {
const added = opsE.applyOperation(hiredTree,
editorOps.addOp(hiredTree, null, 'card', 'hires.performance'), ectx).tree;
const r = opsE.applyOperation(added, editorOps.removeOp(pick(added, 'card-1')), ectx);
return r.ok && nodeE.findNode(r.tree, 'card-1') === null;
})());
record('editor: show a hidden node', (() => {
const hidden = opsE.applyOperation(hiredTree, { op: 'hide', target: 'chronology' }, ectx).tree;
const op = editorOps.hideOp(pick(hidden, 'chronology'));
const r = opsE.applyOperation(hidden, op, ectx);
return op.hidden === false && r.ok && nodeE.findNode(r.tree, 'chronology').hidden === false;
})());
/* ── What the editor refuses to compose ────────────────────────────────── */
record('editor: a built-in cannot be removed', (() => {
/* Refused on capability — a page section never declares `remove` — before
the origin rule is even reached. Either refusal is correct; what matters
is that the editor cannot compose it. */
const r = opsE.applyOperation(hiredTree, editorOps.removeOp(pick(hiredTree, 'chronology')), ectx);
return { pass: !r.ok, detail: r.ok ? 'ACCEPTED' : r.problems[0].message };
})().pass);
record('editor: an invalid property is refused by the engine', (() => {
const r = opsE.applyOperation(hiredTree, editorOps.propOp(pick(hiredTree, 'chronology'), 'colour', 'red'), ectx);
return !r.ok;
})());
record('editor: an invalid layout value is refused', (() => {
const added = opsE.applyOperation(hiredTree,
editorOps.addOp(hiredTree, null, 'card', 'hires.performance'), ectx).tree;
const r = opsE.applyOperation(added, editorOps.layoutOp(pick(added, 'card-1'), 'gap', 'enormous'), ectx);
return !r.ok && /`gap` must be one of/.test(r.problems[0].message);
})());
record('editor: an unregistered type cannot be added', (() => {
const r = opsE.applyOperation(hiredTree, editorOps.addOp(hiredTree, null, 'hologram'), ectx);
return !r.ok && /Unsupported UI type/.test(r.problems[0].message);
})());
record('editor: an invalid placement is refused', (() => {
/* A page section inside a skill slot: the slot accepts only readings. */
const r = opsE.applyOperation(mixed, {
op: 'add', parent: 'hired-extensions-top',
node: { id: 'x', type: 'hired-records' },
}, ectx);
return !r.ok;
})());
record('editor: an unauthorized type is not offered and not accepted', (() => {
const r = new regE.NodeTypeRegistry();
r.register({ type: 'banner', label: 'Banner', component: () => null, roles: ['admin'] });
r.register({ type: 'box', label: 'Box', component: () => null, container: true });
const offered = inspectE.addableTypes(null, { registry: r, role: 'talent' }).map((t) => t.type);
const applied = opsE.applyOperation([], editorOps.addOp([], null, 'banner'),
{ registry: r, role: 'talent' });
return !offered.includes('banner') && !applied.ok;
})());
/* ── The two things that must be structurally impossible ───────────────── */
record('no editor control can carry a class name or a style', (() => {
/* Every property the editor can set comes from a `propSchema`. If no
registered type declares a styling property, the editor cannot offer one
— so this checks the schemas, not the components. */
const offenders = [];
for (const entry of nreg.all()) {
for (const key of Object.keys(entry.propSchema)) {
if (/class|style|css|html|jsx|markup/i.test(key)) offenders.push(`${entry.type}.${key}`);
}
}
return { pass: offenders.length === 0, detail: offenders.join(', ') };
})().pass);
record('a class name sent as a property is refused', (() => {
/* On a node that DOES accept updates, so the refusal is the schema's and
not merely the capability's. */
const added = opsE.applyOperation(hiredTree,
editorOps.addOp(hiredTree, null, 'card', 'hires.performance'), ectx).tree;
const r = opsE.applyOperation(added,
{ op: 'update', target: 'card-1', props: { className: 'bg-red-500' } }, ectx);
return { pass: !r.ok && /has no property `className`/.test(r.problems[0].message),
detail: r.ok ? 'ACCEPTED' : r.problems[0].message };
})().pass);
record('the editor never evaluates a string as code', (() => {
const files = ['UiEditor.jsx', 'TreePanel.jsx', 'NodeInspector.jsx', 'NodePicker.jsx', 'ops.js']
.map((f) => readFileSync(join(ROOT, resolveSourcePath(`src/components/ui-editor/${f}`)), 'utf8'))
.join('\n')
.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
return !/\beval\s*\(/.test(files) && !/new\s+Function/.test(files)
&& !/dangerouslySetInnerHTML/.test(files) && !/\bimport\s*\(/.test(files);
})());
record('the editor contains no page name and no component branch', (() => {
const files = ['UiEditor.jsx', 'TreePanel.jsx', 'NodeInspector.jsx', 'NodePicker.jsx', 'ops.js']
.map((f) => readFileSync(join(ROOT, resolveSourcePath(`src/components/ui-editor/${f}`)), 'utf8'))
.join('\n')
.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
const banned = [...surfaces.SUPPORTED_SKILL_PAGES, ...nreg.list()];
const hits = banned.filter((w) => new RegExp(`['"\`]${w}['"\`]`).test(files));
return { pass: hits.length === 0, detail: hits.join(', ') };
})().pass);
/* ── The panels render what the registry says ──────────────────────────── */
{
const hadWindow = 'window' in globalThis;
if (!hadWindow) {
globalThis.window = {
matchMedia: () => ({ matches: false, addEventListener() {}, removeEventListener() {} }),
addEventListener() {}, removeEventListener() {},
};
}
const { TreePanel } = await server.ssrLoadModule('/src/components/ui-editor/TreePanel.jsx');
const { NodeInspector } = await server.ssrLoadModule('/src/components/ui-editor/NodeInspector.jsx');
const { NodePicker } = await server.ssrLoadModule('/src/components/ui-editor/NodePicker.jsx');
const draw = (el) => renderToStaticMarkup(el);
/**
* One reading node with a given layout, rendered through the real renderer.
*
* A reading resolves its data through the app's query hooks, so it needs a
* client the way it has one in the browser. Queries are disabled rather
* than mocked: the section then draws its empty state, which is all a test
* about *classes* needs.
*/
const { QueryClient, QueryClientProvider } = await import('@tanstack/react-query');
const drawReading = (layout) => draw(React.createElement(
QueryClientProvider,
{ client: new QueryClient({ defaultOptions: { queries: { retry: false, enabled: false } } }) },
React.createElement(rendererE.UiTreeRenderer, {
nodes: [nodeE.makeNode({
id: 'layout-probe', type: 'card', origin: 'user',
data: { source: 'candidates.activity' }, ...(layout ? { layout } : {}),
})],
registry: nreg,
})
));
const classesOf = (html) => (html.match(/class="[^"]*"/) || ['(none)'])[0];
record('tree panel lists every node, built-in and skill', (() => {
const html = draw(React.createElement(TreePanel, {
tree: mixed, selectedId: null, onSelect: () => {}, registry: nreg,
}));
return html.includes('Recent hiring timeline') && html.includes('Hiring board') && html.includes('Skill');
})());
record('tree panel marks a hidden node and still lists it', (() => {
const hidden = opsE.applyOperation(mixed, { op: 'hide', target: 'chronology' }, ectx).tree;
const html = draw(React.createElement(TreePanel, {
tree: hidden, selectedId: null, onSelect: () => {}, registry: nreg,
}));
return html.includes('Recent hiring timeline') && html.includes('aria-label="Hidden"');
})());
record('tree panel marks the selected node', (() => {
const html = draw(React.createElement(TreePanel, {
tree: mixed, selectedId: 'chronology', onSelect: () => {}, registry: nreg,
}));
return /aria-current="true"/.test(html);
})());
record('property panel offers exactly the declared properties', (() => {
const added = opsE.applyOperation(hiredTree,
editorOps.addOp(hiredTree, null, 'card', 'hires.performance'), ectx).tree;
const node = pick(added, 'card-1');
const html = draw(React.createElement(NodeInspector, {
node, siblings: seen(added).filter((n) => n.parent === null), onOperate: () => {}, registry: nreg,
}));
const declared = Object.values(nreg.get('card').propSchema).map((r) => r.label);
/* Only that every declared property is offered. Whether a *styling*
property could exist at all is asserted against the schemas above —
checking the markup for "class" would only find Tailwind. */
const missing = declared.filter((label) => !html.includes(label));
return { pass: missing.length === 0, detail: missing.join(', ') };
})().pass);
record('property panel offers only the capabilities the type declares', (() => {
/* A built-in page section declares move and hide, so no Remove button. */
const html = draw(React.createElement(NodeInspector, {
node: pick(hiredTree, 'chronology'),
siblings: seen(hiredTree).filter((n) => n.parent === null),
onOperate: () => {}, registry: nreg,
}));
return html.includes('Hide') && !html.includes('Remove');
})());
record('property panel offers Show for a hidden node', (() => {
const hidden = opsE.applyOperation(hiredTree, { op: 'hide', target: 'chronology' }, ectx).tree;
const html = draw(React.createElement(NodeInspector, {
node: pick(hidden, 'chronology'),
siblings: seen(hidden).filter((n) => n.parent === null),
onOperate: () => {}, registry: nreg,
}));
return html.includes('Show') && !html.includes('>Hide<');
})());
record('node picker offers only what the location accepts', (() => {
const slot = pick(mixed, 'hired-extensions-top');
const html = draw(React.createElement(NodePicker, {
tree: mixed, parent: slot, onAdd: () => {}, registry: nreg,
}));
/* A slot takes readings, never a page section. */
return html.includes('Card') && !html.includes('Recent hiring timeline');
})());
/* ── Scope: what a page may be offered ───────────────────────────────────
*
* Every one of these was found in a browser, on Candidates Analysis, in one
* sitting. The picker offered 43 types on a page that composes seven — every
* private section of every other page, plus the slot it renders into — and
* adding one of them threw `undefined.length` inside a section reading a
* render context that page does not publish, which unmounted the whole
* application to a white screen.
*
* The registry had no way to say where a type belongs or that a type is not
* something a person adds. It has both now, and these hold it to them.
*/
record('a page is offered only its own sections and the generic readings', (() => {
const offered = inspectE.addableTypes(null, { registry: nreg, page: 'candidates-analysis' })
.map((t) => t.type);
const foreign = offered.filter((t) => {
const entry = nreg.get(t);
return entry.page && entry.page !== 'candidates-analysis';
});
return { pass: foreign.length === 0, detail: foreign.join(', ') || `${offered.length} offered` };
})().pass);
record('another page\'s section is not offered here', (() => {
const offered = inspectE.addableTypes(null, { registry: nreg, page: 'candidates-analysis' })
.map((t) => t.type);
return { pass: !offered.includes('hired-chronology'), detail: offered.join(', ') };
})().pass);
record('nor on the page it belongs to — a page composes its own sections', (() => {
/* It is the page's, not a person's: `move` and `hide`, never `add`. The
earlier reading of this asserted it *was* offered on its own page,
which is how a node could be created and then never deleted. */
const offered = inspectE.addableTypes(null, { registry: nreg, page: 'hired-history' })
.map((t) => t.type);
return { pass: !offered.includes('hired-chronology'), detail: offered.join(', ') };
})().pass);
record('everything offered anywhere can also be removed', (() => {
const pages = ['activity', 'analytics', 'candidates-analysis', 'hired-history', 'talent-pool'];
const stuck = [];
for (const page of pages) {
for (const option of inspectE.addableTypes(null, { registry: nreg, page })) {
if (!nreg.allows(option.type, 'remove')) stuck.push(`${page}:${option.type}`);
}
}
return { pass: stuck.length === 0, detail: stuck.join(', ') || 'no dead ends' };
})().pass);
record('a registration that can be added but not removed is refused at boot', (() => {
const reg = new regE.NodeTypeRegistry();
try {
reg.register({
type: 'stuck-node',
label: 'Stuck',
component: () => null,
capabilities: ['move', 'hide'],
addable: true,
});
return { pass: false, detail: 'accepted a node nobody could delete' };
} catch (error) {
return { pass: /must be able to remove/.test(error.message), detail: error.message };
}
})().pass);
record('the generic readings are offered on every page', (() => {
const pages = ['activity', 'analytics', 'candidates-analysis', 'hired-history', 'talent-pool'];
const missing = pages.filter((page) => {
const offered = inspectE.addableTypes(null, { registry: nreg, page }).map((t) => t.type);
return !surfaces.SUPPORTED_SECTION_TYPES.every((type) => offered.includes(type));
});
return { pass: missing.length === 0, detail: missing.join(', ') || pages.join(', ') };
})().pass);
record('a slot is a container and still not something a person adds', (() => {
const entry = nreg.get('skill-surface');
const offered = inspectE.addableTypes(null, { registry: nreg, page: 'candidates-analysis' })
.map((t) => t.type);
return {
pass: entry.container === true && entry.addable === false && !offered.includes('skill-surface'),
detail: `container=${entry.container} addable=${entry.addable}`,
};
})().pass);
record('two slots on one page are told apart by name', (() => {
const named = seen(caTree).filter((n) => n.type === 'skill-surface').map((n) => n.title);
return {
pass: named.length === 2 && named[0] !== named[1] && named.every(Boolean),
detail: named.join(' | '),
};
})().pass);
/**
* The boundary.
*
* React does not run error boundaries during server rendering, so a
* throwing component here takes the whole check script down rather than
* being caught — which is exactly why this is proven in a browser, in
* `scripts/browser-flows.js`, and only held to its contract here.
*/
record('every node is rendered inside a boundary', (() => {
const source = readFileSync(join(ROOT, resolveSourcePath('src/components/ui-tree/UiTreeRenderer.jsx')), 'utf8');
return {
pass: /<UiNodeBoundary node=\{node\}>/.test(source)
&& /import \{ UiNodeBoundary \}/.test(source),
detail: 'UiNode wraps its component in UiNodeBoundary',
};
})().pass);
record('a failed node renders in place, named, and still addressable', (() => {
const boundary = new boundaryE.UiNodeBoundary({ node: { id: 'bad', type: 'bar-chart', props: {} } });
boundary.state = { failed: true, forNode: 'bad' };
const html = draw(boundary.render());
return {
pass: html.includes('data-ui-node="bad"')
&& html.includes('data-ui-failed="true"')
&& /could not be shown/.test(html),
detail: html.slice(0, 160),
};
})().pass);
record('the boundary latches on a failure and clears when the node changes', (() => {
const B = boundaryE.UiNodeBoundary;
const failed = B.getDerivedStateFromError();
const sameNode = B.getDerivedStateFromProps({ node: { id: 'bad' } }, { failed: true, forNode: 'bad' });
const otherNode = B.getDerivedStateFromProps({ node: { id: 'other' } }, { failed: true, forNode: 'bad' });
return {
pass: failed.failed === true && sameNode === null && otherNode.failed === false,
detail: JSON.stringify({ failed, sameNode, otherNode }),
};
})().pass);
/* ── Layout actually renders ──────────────────────────────────────────
*
* The gap this closes: `ReadingNode` took `layout` as a prop and dropped
* it, so a layout operation validated, changed the tree, persisted — and
* the browser looked identical. A layout value that cannot be seen is
* worse than one that is refused, because nothing reports it.
*
* The acceptance criterion here is the same as it is in the browser: the
* *markup* has to change.
*/
record('layout: spacing before and after render as classes', (() => {
const html = drawReading({ spacingBefore: 'lg', spacingAfter: 'sm' });
return { pass: /\bmt-6\b/.test(html) && /\bmb-2\b/.test(html), detail: classesOf(html) };
})().pass);
record('layout: span renders as a column span', (() => {
const html = drawReading({ span: 6 });
return { pass: /\bcol-span-6\b/.test(html), detail: classesOf(html) };
})().pass);
record('layout: align renders as self-alignment', (() => {
const html = drawReading({ align: 'center' });
return { pass: /\bself-center\b/.test(html), detail: classesOf(html) };
})().pass);
record('layout: every vocabulary value maps to a real class', (() => {
const missing = [];
for (const value of nodeE.SPACING_VALUES) {
if (value === 'none') continue;
if (!rendererE.layoutClasses({ spacingBefore: value })) missing.push(`spacingBefore:${value}`);
if (!rendererE.layoutClasses({ spacingAfter: value })) missing.push(`spacingAfter:${value}`);
}
for (const value of nodeE.ALIGN_VALUES) {
if (!rendererE.layoutClasses({ align: value })) missing.push(`align:${value}`);
}
for (let n = nodeE.MIN_COLUMNS; n <= nodeE.MAX_COLUMNS; n += 1) {
if (!rendererE.layoutClasses({ span: n })) missing.push(`span:${n}`);
}
return { pass: missing.length === 0, detail: missing.join(', ') || 'all mapped' };
})().pass);
record('layout: a node that asks for nothing renders exactly as before', (() => {
/* The migration baselines depend on this: no layout, no extra class. */
const before = drawReading(null);
const empty = drawReading({});
return {
pass: before === empty && rendererE.layoutClasses(null) === undefined
&& rendererE.layoutClasses({}) === undefined,
detail: classesOf(before),
};
})().pass);
record('layout: an arbitrary value produces no class at all', (() => {
/* The closed half. Configuration can only ever name a vocabulary value;
anything else maps to nothing rather than to itself, so there is no
path from a stored patch to arbitrary CSS. */
const hostile = rendererE.layoutClasses({
span: 'p-99', align: 'absolute inset-0', spacingBefore: '"><script>', spacingAfter: 'mt-[999px]',
});
const html = drawReading({ align: 'evil-class' });
return {
pass: hostile === undefined && !/evil-class/.test(html),
detail: String(hostile),
};
})().pass);
record('layout: a validated operation is what reaches the DOM', (() => {
/* End to end through the engine rather than by constructing a node: the
op is validated, applied, and the resulting tree is rendered. */
const tree = [nodeE.makeNode({ id: 'r1', type: 'card', origin: 'user', data: { source: 'candidates.activity' } })];
const result = opsE.applyOperation(tree, { op: 'update', target: 'r1', layout: { spacingBefore: 'lg' } }, ectx);
const html = draw(React.createElement(
QueryClientProvider,
{ client: new QueryClient({ defaultOptions: { queries: { retry: false, enabled: false } } }) },
React.createElement(rendererE.UiTreeRenderer, { nodes: result.tree, registry: nreg })
));
return { pass: result.ok && /\bmt-6\b/.test(html), detail: `${result.ok} · ${classesOf(html)}` };
})().pass);
record('layout: an invalid value is refused before it can render', (() => {
const tree = [nodeE.makeNode({ id: 'r2', type: 'card', origin: 'user', data: { source: 'candidates.activity' } })];
const r = opsE.applyOperation(tree, { op: 'update', target: 'r2', layout: { spacingBefore: 'enormous' } }, ectx);
return { pass: !r.ok && /spacingBefore/.test(JSON.stringify(r.problems)), detail: JSON.stringify(r.problems) };
})().pass);
/* ── Variant and density ──────────────────────────────────────────────
*
* The whole point is that a value a person picks changes what they see.
* A setting that validates, persists and renders identically is the bug
* this is written to prevent, so the markup is what is asserted.
*/
const drawLook = (presentation) => draw(React.createElement(
QueryClientProvider,
{ client: new QueryClient({ defaultOptions: { queries: { retry: false, enabled: false } } }) },
React.createElement(rendererE.UiTreeRenderer, {
nodes: [nodeE.makeNode({
id: 'look-probe', type: 'card', origin: 'user',
data: { source: 'candidates.activity' }, ...(presentation ? { presentation } : {}),
})],
registry: nreg,
})
));
record('presentation: the vocabulary is closed and centralised', (() => {
const okVariants = JSON.stringify(nodeE.VARIANT_VALUES) === JSON.stringify(['default', 'subtle', 'emphasis']);
const okDensity = JSON.stringify(nodeE.DENSITY_VALUES) === JSON.stringify(['comfortable', 'compact']);
return { pass: okVariants && okDensity && nodeE.PRESENTATION_KEYS.join() === 'variant,density',
detail: `${nodeE.VARIANT_VALUES.join('/')} · ${nodeE.DENSITY_VALUES.join('/')}` };
})().pass);
record('presentation: a reading type declares support for both', (() => {
const entry = nreg.get('card');
return { pass: entry.variants.length === 3 && entry.densities.length === 2,
detail: `${entry.variants.join(',')} · ${entry.densities.join(',')}` };
})().pass);
record('presentation: a type that draws no panel declares none', (() => {
const entry = nreg.get('skill-surface');
return { pass: entry.variants.length === 0 && entry.densities.length === 0, detail: 'slot supports neither' };
})().pass);
record('presentation: density visibly changes the markup', (() => {
const comfortable = drawLook({ density: 'comfortable' });
const compact = drawLook({ density: 'compact' });
return { pass: /\bp-5\b/.test(comfortable) && /\bp-3\b/.test(compact) && comfortable !== compact,
detail: classesOf(compact) };
})().pass);
record('presentation: variant visibly changes the markup', (() => {
const plain = drawLook({ variant: 'default' });
const emphasis = drawLook({ variant: 'emphasis' });
const subtle = drawLook({ variant: 'subtle' });
return { pass: /ring-krow-blue/.test(emphasis) && /bg-surface-subtle/.test(subtle)
&& plain !== emphasis && plain !== subtle, detail: classesOf(emphasis) };
})().pass);
record('presentation: a node that asks for nothing renders as it always did', (() => {
const bare = drawLook(null);
return { pass: bare === drawLook({ variant: 'default', density: 'comfortable' })
&& /\bp-5\b/.test(bare) && /bg-surface\b/.test(bare), detail: classesOf(bare) };
})().pass);
record('presentation: a valid update is applied through the one engine', (() => {
const tree = [nodeE.makeNode({ id: 'p1', type: 'card', origin: 'user', data: { source: 'candidates.activity' } })];
const r = opsE.applyOperation(tree, { op: 'update', target: 'p1', presentation: { density: 'compact' } }, ectx);
return { pass: r.ok && nodeE.findNode(r.tree, 'p1').presentation.density === 'compact',
detail: JSON.stringify(r.problems) };
})().pass);
record('presentation: setting one value keeps the other', (() => {
const tree = [nodeE.makeNode({ id: 'p2', type: 'card', origin: 'user',
data: { source: 'candidates.activity' }, presentation: { variant: 'emphasis' } })];
const r = opsE.applyOperation(tree, { op: 'update', target: 'p2', presentation: { density: 'compact' } }, ectx);
const now = nodeE.findNode(r.tree, 'p2').presentation;
return { pass: r.ok && now.variant === 'emphasis' && now.density === 'compact', detail: JSON.stringify(now) };
})().pass);
for (const [label, presentation] of [
['an unknown variant', { variant: 'neon' }],
['an unknown density', { density: 'squished' }],
['a class name', { variant: 'p-0 bg-red-500' }],
]) {
record(`presentation: ${label} is refused`, (() => {
const tree = [nodeE.makeNode({ id: 'p3', type: 'card', origin: 'user', data: { source: 'candidates.activity' } })];
const r = opsE.applyOperation(tree, { op: 'update', target: 'p3', presentation }, ectx);
return { pass: !r.ok, detail: JSON.stringify(r.problems).slice(0, 120) };
})().pass);
}
record('presentation: an unknown presentation key cannot take effect', (() => {
/* Dropped at the contract rather than refused at the validator — the same
rule `layout` and `props` follow. Stronger than a refusal: the key
never exists to be read, so nothing downstream can grow a dependency on
it, and no stored patch can smuggle one in. */
const tree = [nodeE.makeNode({ id: 'p4', type: 'card', origin: 'user', data: { source: 'candidates.activity' } })];
const r = opsE.applyOperation(tree, { op: 'update', target: 'p4', presentation: { elevation: 'xl' } }, ectx);
const stored = r.ok ? nodeE.findNode(r.tree, 'p4').presentation : null;
/* Compared against the bare render rather than grepped for a substring:
`rounded-2xl` contains "xl", and a test that reads a class name out of
another class name proves nothing. */
return {
pass: stored && !('elevation' in stored) && drawLook({ elevation: 'xl' }) === drawLook(null),
detail: JSON.stringify(stored),
};
})().pass);
record('presentation: a type that supports none refuses a valid value', (() => {
const tree = compE.composePage('candidates-analysis', ectx).tree;
const slot = tree.find((n) => n.type === 'skill-surface');
const r = opsE.applyOperation(tree, { op: 'update', target: slot.id, presentation: { density: 'compact' } }, ectx);
return { pass: !r.ok, detail: JSON.stringify(r.problems).slice(0, 140) };
})().pass);
record('presentation: a registration cannot invent a value', (() => {
const reg = new regE.NodeTypeRegistry();
try {
reg.register({ type: 'bad-look', label: 'Bad', component: () => null, variants: ['neon'] });
return { pass: false, detail: 'accepted an invented variant' };
} catch (error) {
return { pass: /unknown variant/.test(error.message), detail: error.message };
}
})().pass);
record('presentation: the editor emits the operation Owliver emits', (() => {
/* A reading node, because a page's own sections declare only move and
hide — the equivalence being tested is between two clients, not a claim
that every node is editable. */
const tree = [nodeE.makeNode({
id: 'supply-card', type: 'card', origin: 'user',
props: { title: 'Supply summary' }, data: { source: 'candidates.activity' },
})];
const node = inspectE.inspectTree(tree, ectx)[0];
const fromEditor = editorOps.presentationOp(node, 'density', 'compact');
const fromOwliver = intentE.matchUiEdit('make the Supply summary compact',
{ tree, registry: nreg, page: 'candidates-analysis' });
return {
pass: fromEditor.op === 'update' && fromEditor.presentation.density === 'compact'
&& fromOwliver?.kind === 'plan' && fromOwliver.op.op === 'update'
&& fromOwliver.op.target === node.id && fromOwliver.op.presentation.density === 'compact',
detail: `${JSON.stringify(fromEditor)} vs ${JSON.stringify(fromOwliver?.op)}`,
};
})().pass);
record('presentation: a stored patch round-trips the value', (() => {
const { patch } = patchE.normalizePatch({ schema: 1, page: 'x', ops: [
{ op: 'update', target: 'p1', presentation: { density: 'compact', variant: 'emphasis' } },
] }, 'x');
const op = patch.ops[0];
return { pass: op?.presentation?.density === 'compact'
&& op?.presentation?.variant === 'emphasis', detail: JSON.stringify(op) };
})().pass);
record('presentation: ordinary data questions are not presentation requests', (() => {
const ordinary = [
'What is on this page?', 'Show recent hires', 'How many candidates applied?',
'Which positions need attention?', 'Compare the top candidates',
];
const taken = ordinary.filter((q) => {
const m = intentE.matchUiEdit(q, { tree: caTree, registry: nreg, page: 'candidates-analysis' });
return m?.kind === 'plan' && m.op?.presentation;
});
return { pass: taken.length === 0, detail: taken.join(' | ') || 'none taken' };
})().pass);
/* ── Generic replace ──────────────────────────────────────────────────
*
* A node turned into a different kind of node is still the same node: same
* id, same reading, same place. What changes is what draws it.
*/
const cardNode = (extra = {}) => nodeE.makeNode({
id: 'perf', type: 'card', origin: 'user',
props: { title: 'Hiring performance' },
data: { source: 'hires.performance' },
layout: { spacingBefore: 'lg' },
presentation: { variant: 'emphasis', density: 'compact' },
...extra,
});
const replaceWith = (type, node = cardNode()) =>
opsE.applyOperation([node], { op: 'replace', target: 'perf', type }, ectx);
record('replace: card becomes table', (() => {
const r = replaceWith('table');
return { pass: r.ok && nodeE.findNode(r.tree, 'perf').type === 'table',
detail: JSON.stringify(r.problems) };
})().pass);
record('replace: the node keeps its id', (() => {
const r = replaceWith('table');
return { pass: r.ok && r.tree.length === 1 && r.tree[0].id === 'perf', detail: r.tree[0]?.id };
})().pass);
record('replace: the reading is carried, never repointed', (() => {
const r = replaceWith('table');
return { pass: r.ok && nodeE.findNode(r.tree, 'perf').data.source === 'hires.performance',
detail: JSON.stringify(nodeE.findNode(r.tree, 'perf').data) };
})().pass);
record('replace: layout and presentation survive where the new type accepts them', (() => {
const after = replaceWith('table').tree[0];
return {
pass: after.layout.spacingBefore === 'lg'
&& after.presentation.variant === 'emphasis' && after.presentation.density === 'compact',
detail: JSON.stringify({ layout: after.layout, presentation: after.presentation }),
};
})().pass);
record('replace: a property both types declare is kept', (() => {
const after = replaceWith('table').tree[0];
return { pass: after.props.title === 'Hiring performance', detail: JSON.stringify(after.props) };
})().pass);
record('replace: a property the new type never declared is dropped', (() => {
const node = cardNode({ props: { title: 'Kept', notAThing: 'dropped' } });
const after = replaceWith('table', node).tree[0];
return { pass: after.props.title === 'Kept' && !('notAThing' in after.props),
detail: JSON.stringify(after.props) };
})().pass);
record('replace: hidden and origin are preserved', (() => {
const after = replaceWith('table', cardNode({ hidden: true })).tree[0];
return { pass: after.hidden === true && after.origin === 'user', detail: `${after.hidden}/${after.origin}` };
})().pass);
record('replace: a type that cannot draw the reading is refused by name', (() => {
/* `hires.performance` has no list in it. */
const r = replaceWith('list');
return { pass: !r.ok && /cannot show/i.test(JSON.stringify(r.problems)),
detail: JSON.stringify(r.problems).slice(0, 140) };
})().pass);
record('replace: a refused replacement mutates nothing', (() => {
const before = [cardNode()];
const r = opsE.applyOperation(before, { op: 'replace', target: 'perf', type: 'list' }, ectx);
return { pass: !r.ok && r.tree === before && r.tree[0].type === 'card', detail: r.tree[0].type };
})().pass);
record('replace: an unregistered type is refused', (() => {
/* A name nobody registers, and nothing here should ever register. */
const r = replaceWith('sculpture');
return { pass: !r.ok && /Unsupported UI type/i.test(JSON.stringify(r.problems)),
detail: JSON.stringify(r.problems).slice(0, 120) };
})().pass);
record('replace: a built-in page section cannot be replaced', (() => {
const tree = compE.composePage('candidates-analysis', ectx).tree;
const section = tree.find((n) => n.type && n.type.startsWith('ca-'));
const r = opsE.applyOperation(tree, { op: 'replace', target: section.id, type: 'table' }, ectx);
return { pass: !r.ok, detail: JSON.stringify(r.problems).slice(0, 140) };
})().pass);
record('replace: the offering is page-scoped', (() => {
const shapes = surfaces.dataSourceFor('hires.performance').shapes;
const here = nreg.replacements('card', { shapes, page: 'analytics' });
const foreign = here.filter((t) => {
const entry = nreg.get(t);
return entry.page && entry.page !== 'analytics';
});
return { pass: foreign.length === 0 && here.includes('table'),
detail: here.join(', ') };
})().pass);
record('replace: the offering excludes types nobody may add', (() => {
const offered = nreg.replacements('card', { shapes: null, page: 'analytics' });
return { pass: !offered.includes('skill-surface'), detail: offered.join(', ') || 'none' };
})().pass);
record('replace: the editor emits the operation Owliver emits', (() => {
const tree = [cardNode()];
const node = inspectE.inspectTree(tree, ectx)[0];
const fromEditor = editorOps.replaceOp(node, 'table');
const fromOwliver = intentE.matchUiEdit('change the Hiring performance card to a table',
{ tree, registry: nreg, page: 'analytics' });
return {
pass: JSON.stringify(fromEditor) === JSON.stringify(fromOwliver?.op),
detail: `${JSON.stringify(fromEditor)} vs ${JSON.stringify(fromOwliver?.op)}`,
};
})().pass);
record('replace: a stored patch round-trips the operation', (() => {
const { patch } = patchE.normalizePatch({ schema: 1, page: 'analytics', ops: [
{ op: 'replace', target: 'perf', type: 'table' },
] }, 'analytics');
return { pass: patch.ops[0]?.op === 'replace' && patch.ops[0]?.type === 'table',
detail: JSON.stringify(patch.ops[0]) };
})().pass);
record('replace: ordinary data questions do not become replacements', (() => {
const tree = [cardNode()];
const ordinary = ['What is on this page?', 'Show recent hires', 'How many candidates applied?',
'Compare the top candidates', 'Which positions need attention?'];
const taken = ordinary.filter((q) => {
const m = intentE.matchUiEdit(q, { tree, registry: nreg, page: 'analytics' });
return m?.kind === 'plan' && m.op?.op === 'replace';
});
return { pass: taken.length === 0, detail: taken.join(' | ') || 'none taken' };
})().pass);
/* ── Visualizations ───────────────────────────────────────────────────
*
* Five node types that are deliberately *not* skill section types, drawing
* the payload a `flow` reading already resolves to. The tests that matter
* are about compatibility, and there are now two kinds: whether a component
* *can* draw a reading, and whether doing so would be true.
*/
const VIZ = ['bar-chart', 'line-chart', 'area-chart', 'pie-chart', 'donut-chart'];
/* What a reading means, asked the same way every consumer asks it. */
const seriesMod = await server.ssrLoadModule('/src/lib/ui/series.js');
const seriesNode = (type, source, extra = {}) => nodeE.makeNode({
id: 'viz', type, origin: 'user', props: { title: 'Hiring performance' },
data: { source }, layout: { spacingBefore: 'lg' },
presentation: { variant: 'emphasis', density: 'compact' }, ...extra,
});
/**
* A reading whose meaning this visualization can honestly draw.
*
* There is no single source that fits all five, and that is the point: a
* line may draw a reading that is ordered, a pie may draw one whose points
* are parts of a whole, and no reading is both. Picked from what the source
* itself declares rather than assumed.
*/
const READING_OF_KIND = {
periodic: 'candidates.activity',
cumulative: 'position.pipeline',
parts: 'activity.breakdown',
};
const readingFor = (type) => READING_OF_KIND[nreg.get(type).seriesKinds[0]];
for (const type of VIZ) {
record(`${type}: is a registered node type with a real component`, (() => {
const entry = nreg.get(type);
return { pass: Boolean(entry) && typeof entry.component === 'function' && entry.dataRequired,
detail: entry ? `shapes ${entry.dataShapes.join(',')}` : 'missing' };
})().pass);
record(`${type}: declares the series shapes and nothing else`, (() => {
const entry = nreg.get(type);
return { pass: JSON.stringify([...entry.dataShapes].sort()) === JSON.stringify(['flow', 'stats']),
detail: entry.dataShapes.join(', ') };
})().pass);
record(`${type}: declares which series meanings it can honestly draw`, (() => {
const entry = nreg.get(type);
const known = entry.seriesKinds.every((k) => nodeE.SERIES_KINDS.includes(k));
return { pass: entry.seriesKinds.length > 0 && known, detail: entry.seriesKinds.join(', ') };
})().pass);
record(`${type}: supports the presentation vocabulary it honours`, (() => {
const entry = nreg.get(type);
return { pass: entry.variants.length === 3 && entry.densities.length === 2,
detail: `${entry.variants.length}/${entry.densities.length}` };
})().pass);
record(`${type}: a card on a series reading can become one`, (() => {
const source = readingFor(type);
const r = opsE.applyOperation([seriesNode('card', source)],
{ op: 'replace', target: 'viz', type }, ectx);
const after = r.ok ? nodeE.findNode(r.tree, 'viz') : null;
return { pass: r.ok && after.type === type && after.id === 'viz'
&& after.data.source === source, detail: JSON.stringify(r.problems) };
})().pass);
/* Gap 4: a reading whose meaning was never declared is refused, not
guessed at. `hires.performance` draws `flow` and `stats` — structurally
every chart could take it — and says nothing about what its figures
mean, which used to be read as consent. */
record(`${type}: a reading of unknown meaning is refused, not guessed`, (() => {
const before = [seriesNode('card', 'hires.performance')];
const r = opsE.applyOperation(before, { op: 'replace', target: 'viz', type }, ectx);
const said = JSON.stringify(r.problems);
return { pass: !r.ok && r.tree === before && /does not say what its figures mean/i.test(said),
detail: said.slice(0, 170) };
})().pass);
record(`${type}: replacement keeps layout, presentation and title`, (() => {
const after = opsE.applyOperation([seriesNode('card', readingFor(type))],
{ op: 'replace', target: 'viz', type }, ectx).tree[0];
return { pass: after.layout.spacingBefore === 'lg' && after.presentation.variant === 'emphasis'
&& after.presentation.density === 'compact' && after.props.title === 'Hiring performance',
detail: JSON.stringify({ l: after.layout, p: after.presentation, t: after.props.title }) };
})().pass);
record(`${type}: a reading with no series is refused by name`, (() => {
/* `position.requirements` declares list, table and card — no series in
it at all. One of only two such sources, which is itself the honest
compatibility picture: nearly every reading carries a series. */
const r = opsE.applyOperation([seriesNode('list', 'position.requirements')],
{ op: 'replace', target: 'viz', type }, ectx);
return { pass: !r.ok && /cannot show/i.test(JSON.stringify(r.problems)),
detail: JSON.stringify(r.problems).slice(0, 130) };
})().pass);
record(`${type}: a refused replacement mutates nothing`, (() => {
const before = [seriesNode('list', 'position.requirements')];
const r = opsE.applyOperation(before, { op: 'replace', target: 'viz', type }, ectx);
return { pass: !r.ok && r.tree === before && r.tree[0].type === 'list', detail: r.tree[0].type };
})().pass);
record(`${type}: the editor offers it only where the reading fits`, (() => {
const source = readingFor(type);
const fits = nreg.replacements('card', {
shapes: surfaces.dataSourceFor(source).shapes, page: 'analytics',
seriesKind: seriesMod.kindOfBinding({ source }), bound: true,
});
const doesNot = nreg.replacements('list', {
shapes: surfaces.dataSourceFor('position.requirements').shapes, page: 'analytics',
seriesKind: seriesMod.kindOfBinding({ source: 'position.requirements' }), bound: true,
});
/* And the one gap 4 is about: structurally able, meaning undeclared. */
const unknown = nreg.replacements('card', {
shapes: surfaces.dataSourceFor('hires.performance').shapes, page: 'analytics',
seriesKind: seriesMod.kindOfBinding({ source: 'hires.performance' }), bound: true,
});
return { pass: fits.includes(type) && !doesNot.includes(type) && !unknown.includes(type),
detail: `fits: ${fits.join(',')} | not: ${doesNot.join(',')} | unknown: ${unknown.join(',') || 'none'}` };
})().pass);
record(`${type}: Owliver plans the same operation the editor emits`, (() => {
const tree = [seriesNode('card', readingFor(type))];
const node = inspectE.inspectTree(tree, ectx)[0];
const fromEditor = editorOps.replaceOp(node, type);
const label = nreg.get(type).label.toLowerCase();
const fromOwliver = intentE.matchUiEdit(
`change the Hiring performance to a ${label}`, { tree, registry: nreg, page: 'analytics' });
return { pass: JSON.stringify(fromEditor) === JSON.stringify(fromOwliver?.op),
detail: `${JSON.stringify(fromEditor)} vs ${JSON.stringify(fromOwliver?.op)}` };
})().pass);
}
record('visualizations: none of them is a skill section type', (() => {
/* The MD vocabulary is untouched, so the Go parser and its oracle are too. */
const inMd = surfaces.SUPPORTED_SECTION_TYPES.filter((t) => VIZ.includes(t));
return { pass: inMd.length === 0, detail: surfaces.SUPPORTED_SECTION_TYPES.join(',') };
})().pass);
record('visualizations: ordinary data questions do not become visualisations', (() => {
const tree = [seriesNode('card', 'hires.performance')];
const ordinary = ['What is on this page?', 'Show recent hires', 'How many candidates applied?',
'What is the hiring trend?', 'Compare the top candidates'];
const taken = ordinary.filter((q) => {
const m = intentE.matchUiEdit(q, { tree, registry: nreg, page: 'analytics' });
return m?.kind === 'plan' && VIZ.includes(m.op?.type);
});
return { pass: taken.length === 0, detail: taken.join(' | ') || 'none taken' };
})().pass);
/* ── Series registry and meaning ──────────────────────────────────────
*
* The half `dataShapes` could not express. A `flow` is, in the section
* vocabulary's own words, "a sequence of stages *or* periods" — so these
* check that the difference is now declared, enforced, and explained.
*/
{
const seriesE = await server.ssrLoadModule('/src/lib/ui/series.js');
record('series: the kind vocabulary is closed', () => true);
record('series: the kind vocabulary is closed',
JSON.stringify(nodeE.SERIES_KINDS) === JSON.stringify(['periodic', 'cumulative', 'parts']),
nodeE.SERIES_KINDS.join(', '));
record('series: Control Center publishes its hiring activity', (() => {
const entry = seriesE.seriesFor('control-center.hiring-activity');
return { pass: Boolean(entry) && entry.kind === 'periodic' && entry.measures.length === 4,
detail: entry ? `${entry.kind} · ${entry.measures.map((m) => m.key).join(',')}` : 'missing' };
})().pass);
record('series: a registration with an unknown kind is refused', (() => {
try {
seriesE.registerSeries({ id: 'x.y', kind: 'vibes', measures: [{ key: 'a', label: 'A' }], read: () => [] });
return false;
} catch (e) { return /unknown kind/i.test(e.message); }
})());
record('series: a registration with no measure is refused', (() => {
try {
seriesE.registerSeries({ id: 'x.z', kind: 'parts', measures: [], read: () => [] });
return false;
} catch (e) { return /at least one measure/i.test(e.message); }
})());
record('series: a registration with no read is refused', (() => {
try {
seriesE.registerSeries({ id: 'x.w', kind: 'parts', measures: [{ key: 'a', label: 'A' }] });
return false;
} catch (e) { return /needs a `read/i.test(e.message); }
})());
record('series: a reading bound to periods is periodic', (() => {
const kind = seriesE.kindOfBinding({ source: 'candidates.activity', params: { periods: ['today'] } });
return { pass: kind === 'periodic', detail: String(kind) };
})().pass);
record('series: a binding that cannot say vetoes nothing', (() => {
const kind = seriesE.kindOfBinding({ source: 'hires.performance', params: {} });
return { pass: kind === null, detail: String(kind) };
})().pass);
record('series: a page series resolves the page\u2019s own rows, unchanged', (() => {
const rows = [{ label: '9/1', applications: 3, screened: 2, interviews: 1, hires: 0 }];
const out = seriesE.readSeries({ series: 'control-center.hiring-activity' },
{ context: { activitySeries: rows } });
return { pass: out.rows.length === 1 && out.rows[0].applications === 3
&& out.rows[0].label === '9/1' && out.measures.length === 4,
detail: JSON.stringify(out.rows) };
})().pass);
record('series: nothing is invented when the page published nothing', (() => {
const out = seriesE.readSeries({ series: 'control-center.hiring-activity' }, { context: {} });
return { pass: out.rows.length === 0, detail: JSON.stringify(out.rows) };
})().pass);
}
/* ── Built-in chart → another visualization ───────────────────────────
*
* The whole point, end to end on the real page composition: the section
* Control Center ships is bound to a series it publishes, and that is the
* only reason it can be drawn another way.
*/
{
const seriesE = await server.ssrLoadModule('/src/lib/ui/series.js');
await server.ssrLoadModule('/src/pages/admin/control-center/nodes.jsx');
const ccTree = () => compE.composePage('control-center', ectx).tree;
const activity = () => ccTree().find((n) => n.id === 'cc-activity');
record('built-in: Hiring activity is bound to the page\u2019s own series', (() => {
const node = activity();
return { pass: node?.data?.series === 'control-center.hiring-activity',
detail: JSON.stringify(node?.data) };
})().pass);
{
const verdict = validateE.validateTree(ccTree(), ectx);
record('built-in: it composes and validates as it ships',
verdict.ok === true, JSON.stringify(verdict.problems).slice(0, 260));
}
for (const type of ['bar-chart', 'line-chart', 'area-chart']) {
record(`built-in: Hiring activity can become a ${type}`, (() => {
const r = opsE.applyOperation(ccTree(), { op: 'replace', target: 'cc-activity', type }, ectx);
const after = r.ok ? nodeE.findNode(r.tree, 'cc-activity') : null;
return { pass: r.ok && after.type === type
&& after.id === 'cc-activity'
&& after.data.series === 'control-center.hiring-activity'
&& after.origin === 'builtin',
detail: JSON.stringify(r.problems).slice(0, 160) };
})().pass);
}
for (const type of ['pie-chart', 'donut-chart']) {
record(`built-in: Hiring activity refuses ${type}, and says why`, (() => {
const r = opsE.applyOperation(ccTree(), { op: 'replace', target: 'cc-activity', type }, ectx);
const said = JSON.stringify(r.problems);
return { pass: !r.ok && /ordered in time/i.test(said), detail: said.slice(0, 170) };
})().pass);
}
record('built-in: a refused visualization mutates nothing', (() => {
const before = ccTree();
const r = opsE.applyOperation(before, { op: 'replace', target: 'cc-activity', type: 'pie-chart' }, ectx);
return { pass: !r.ok && r.tree === before, detail: String(r.ok) };
})().pass);
record('built-in: the offering is the registry\u2019s, and excludes the untrue ones', (() => {
const node = activity();
const offered = nreg.replacements(node.type, {
page: 'control-center',
seriesKind: seriesE.kindOfBinding(node.data),
seriesBound: true,
bound: true,
});
return { pass: offered.includes('bar-chart') && offered.includes('line-chart')
&& offered.includes('area-chart') && !offered.includes('pie-chart')
&& !offered.includes('donut-chart'),
detail: offered.join(', ') };
})().pass);
record('built-in: Owliver and the editor emit the same operation', (() => {
const tree = ccTree();
const node = inspectE.inspectTree(tree, { ...ectx, page: 'control-center' })
.find((n) => n.id === 'cc-activity');
const fromEditor = editorOps.replaceOp(node, 'bar-chart');
const fromOwliver = intentE.matchUiEdit('Change Hiring activity to a bar chart',
{ tree, registry: nreg, page: 'control-center' });
return { pass: JSON.stringify(fromEditor) === JSON.stringify(fromOwliver?.op),
detail: `${JSON.stringify(fromEditor)} vs ${JSON.stringify(fromOwliver?.op)}` };
})().pass);
record('built-in: a bar chart can be changed back to a line chart', (() => {
const barred = opsE.applyOperation(ccTree(),
{ op: 'replace', target: 'cc-activity', type: 'bar-chart' }, ectx).tree;
const r = opsE.applyOperation(barred, { op: 'replace', target: 'cc-activity', type: 'line-chart' }, ectx);
const after = r.ok ? nodeE.findNode(r.tree, 'cc-activity') : null;
return { pass: r.ok && after.type === 'line-chart'
&& after.data.series === 'control-center.hiring-activity',
detail: JSON.stringify(r.problems).slice(0, 140) };
})().pass);
record('built-in: a stored patch replays the change from cold', (() => {
const patch = { ops: [{ op: 'replace', target: 'cc-activity', type: 'bar-chart' }] };
const round = JSON.parse(JSON.stringify(patch));
const out = compE.composePage('control-center', { patch: round, ...ectx });
const after = out.tree.find((n) => n.id === 'cc-activity');
return { pass: after?.type === 'bar-chart' && out.skipped.length === 0,
detail: `${after?.type} · skipped ${out.skipped.length}` };
})().pass);
record('built-in: a stored patch that became untrue is refused on load', (() => {
const patch = { ops: [{ op: 'replace', target: 'cc-activity', type: 'pie-chart' }] };
const out = compE.composePage('control-center', { patch, ...ectx });
const after = out.tree.find((n) => n.id === 'cc-activity');
return { pass: after?.type === 'cc-activity' && out.skipped.length === 1,
detail: `${after?.type} · skipped ${out.skipped.length}` };
})().pass);
}
/* ── Continuity, controls, and knowing what a reading means ───────────
*
* The four gaps the live verification found, each as the smallest test
* that would have caught it.
*/
{
const seriesE = await server.ssrLoadModule('/src/lib/ui/series.js');
const ccTree = () => compE.composePage('control-center', ectx).tree;
const barred = () => opsE.applyOperation(ccTree(),
{ op: 'replace', target: 'cc-activity', type: 'bar-chart' }, ectx).tree;
const ask = (q, opts = {}) => intentE.matchUiEdit(q,
{ tree: ccTree(), registry: nreg, page: 'control-center', ...opts });
/* ── A. "Change it back to a line chart." ─────────────────────────── */
record('continuity: a replaced node keeps the name of what it reads', (() => {
const node = inspectE.inspectTree(barred(), ectx).find((n) => n.id === 'cc-activity');
return { pass: node?.type === 'bar-chart' && node?.title === 'Hiring activity',
detail: `${node?.type} · ${node?.title}` };
})().pass);
record('continuity: it is still addressable by that name after replacement', (() => {
const m = intentE.matchUiEdit('Change Hiring activity to a line chart',
{ tree: barred(), registry: nreg, page: 'control-center' });
return { pass: m?.kind === 'plan' && m.op.target === 'cc-activity' && m.op.type === 'line-chart',
detail: `${m?.kind}: ${JSON.stringify(m?.op)}` };
})().pass);
record('continuity: "change it back to a line chart" resolves to what was changed', (() => {
const m = intentE.matchUiEdit('Change it back to a line chart',
{ tree: barred(), registry: nreg, page: 'control-center', focus: 'cc-activity' });
return { pass: m?.kind === 'plan' && m.op.op === 'replace'
&& m.op.target === 'cc-activity' && m.op.type === 'line-chart',
detail: `${m?.kind}: ${JSON.stringify(m?.op)}` };
})().pass);
record('continuity: the same sentence with no subject is still unknown', (() => {
const m = intentE.matchUiEdit('Change it back to a line chart',
{ tree: barred(), registry: nreg, page: 'control-center' });
return { pass: m?.kind === 'unknown', detail: String(m?.kind) };
})().pass);
record('continuity: a request that names something else is never redirected', (() => {
/* Focus is a last resort, not a preference: the words still win. */
const m = intentE.matchUiEdit('Hide the pipeline intelligence',
{ tree: ccTree(), registry: nreg, page: 'control-center', focus: 'cc-activity' });
return { pass: m?.kind === 'plan' && m.op.target === 'cc-pipeline',
detail: JSON.stringify(m?.op) };
})().pass);
record('continuity: a focus that has left the page resolves to nothing', (() => {
const m = intentE.matchUiEdit('Change it back to a line chart',
{ tree: barred(), registry: nreg, page: 'control-center', focus: 'no-such-node' });
return { pass: m?.kind === 'unknown', detail: String(m?.kind) };
})().pass);
record('continuity: a phrase with no pronoun does not reach for the focus', (() => {
const m = intentE.matchUiEdit('Change the audit log to a table',
{ tree: ccTree(), registry: nreg, page: 'control-center', focus: 'cc-activity' });
return { pass: m?.kind !== 'plan' || m.op.target !== 'cc-activity', detail: String(m?.kind) };
})().pass);
record('continuity: the data is untouched across bar → it → line', (() => {
const rows = [{ label: '9/1', applications: 4, screened: 2, interviews: 1, hires: 1 }];
const read = (tree) => seriesE.readSeries(tree.find((n) => n.id === 'cc-activity').data,
{ context: { activitySeries: rows } });
const before = read(ccTree());
const asBar = read(barred());
const m = intentE.matchUiEdit('Change it back to a line chart',
{ tree: barred(), registry: nreg, page: 'control-center', focus: 'cc-activity' });
const asLine = read(opsE.applyOperation(barred(), m.op, ectx).tree);
const same = JSON.stringify(before.rows) === JSON.stringify(asBar.rows)
&& JSON.stringify(asBar.rows) === JSON.stringify(asLine.rows);
return { pass: same && before.rows.length === 1 && before.rows[0].applications === 4,
detail: JSON.stringify(asLine.rows) };
})().pass);
/* ── B. The control survives the replacement ──────────────────────── */
record('control: the reading publishes the range, not the component', (() => {
const entry = seriesE.seriesFor('control-center.hiring-activity');
const values = (entry?.control?.options || []).map((o) => o.value);
return { pass: JSON.stringify(values) === JSON.stringify(['7d', '30d', '90d']),
detail: values.join(', ') || 'none' };
})().pass);
for (const type of ['bar-chart', 'line-chart', 'area-chart']) {
record(`control: 7D/30D/90D is still there after becoming a ${type}`, (() => {
const tree = opsE.applyOperation(ccTree(),
{ op: 'replace', target: 'cc-activity', type }, ectx).tree;
const node = tree.find((n) => n.id === 'cc-activity');
const control = seriesE.controlOfBinding(node.data, { range: '30d', setRange: () => {} });
return { pass: Boolean(control) && control.value === '30d'
&& control.options.length === 3 && control.label.length > 0,
detail: control ? `${control.label} · ${control.options.map((o) => o.label).join('/')}` : 'lost' };
})().pass);
}
record('control: choosing a range calls the page back, and nothing else', (() => {
const seen = [];
const control = seriesE.controlOfBinding({ series: 'control-center.hiring-activity' },
{ range: '7d', setRange: (v) => seen.push(v) });
control.set('90d');
return { pass: seen.length === 1 && seen[0] === '90d', detail: seen.join(',') };
})().pass);
record('control: none is invented when the page publishes none', (() => {
const control = seriesE.controlOfBinding({ series: 'control-center.hiring-activity' }, {});
return { pass: control === null, detail: JSON.stringify(control) };
})().pass);
record('control: a reading that declares none has none', (() => {
const control = seriesE.controlOfBinding({ source: 'candidates.activity' }, { range: '7d' });
return { pass: control === null, detail: JSON.stringify(control) };
})().pass);
record('control: a control with no option is refused at registration', (() => {
try {
seriesE.registerSeries({ id: 'x.control', kind: 'parts', measures: [{ key: 'a', label: 'A' }],
read: () => [], control: { id: 'r', options: [], read: () => null, write: () => {} } });
return false;
} catch (e) { return /at least one option/i.test(e.message); }
})());
record('control: a control with no writer is refused at registration', (() => {
try {
seriesE.registerSeries({ id: 'x.control2', kind: 'parts', measures: [{ key: 'a', label: 'A' }],
read: () => [], control: { id: 'r', options: [{ value: 'a', label: 'A' }], read: () => null } });
return false;
} catch (e) { return /needs a `read` and a `write`/i.test(e.message); }
})());
record('replace: identity, binding, origin and visibility all survive', (() => {
const before = ccTree().find((n) => n.id === 'cc-activity');
const after = barred().find((n) => n.id === 'cc-activity');
return { pass: after.id === before.id && after.origin === before.origin
&& Boolean(after.hidden) === Boolean(before.hidden)
&& JSON.stringify(after.data) === JSON.stringify(before.data),
detail: JSON.stringify({ id: after.id, origin: after.origin, data: after.data }) };
})().pass);
/* ── C. Asking for alternatives, in the words people use ──────────── */
for (const phrase of [
"I don't like this design. Show me other options.",
'Can you give me another visualization?',
'What else can I use?',
'Give me some alternatives.',
'I want a different chart.',
]) {
record(`alternatives: "${phrase.slice(0, 40)}…" is answered without a model`, (() => {
/* `matchUiEdit` is a pure function of the tree and the registry: there
is no provider to reach and nothing to await, so an answer here is
an answer with zero model calls by construction. */
const m = ask(phrase, { focus: 'cc-activity' });
const types = (m?.options || []).map((o) => o.type);
return { pass: m?.kind === 'options' && types.length > 0
&& types.every((t) => Boolean(nreg.get(t))),
detail: `${m?.kind}: ${types.join(', ') || '—'}` };
})().pass);
}
record('alternatives: the answer is the registry\u2019s, and excludes the untrue', (() => {
const m = ask('Show me other options.', { focus: 'cc-activity' });
const types = (m?.options || []).map((o) => o.type);
return { pass: types.includes('bar-chart') && types.includes('area-chart')
&& !types.includes('pie-chart') && !types.includes('donut-chart'),
detail: types.join(', ') };
})().pass);
record('alternatives: every one offered is one replace would accept', (() => {
const m = ask('I want a different chart.', { focus: 'cc-activity' });
const bad = (m?.options || []).filter((o) => (
!opsE.applyOperation(ccTree(), { op: 'replace', target: 'cc-activity', type: o.type }, ectx).ok
));
return { pass: (m?.options || []).length > 0 && bad.length === 0,
detail: bad.map((o) => o.type).join(', ') || 'none' };
})().pass);
record('alternatives: an ordinary question is still left alone', (() => {
const left = [
'What are my options for this position?',
'What other candidates applied?',
'Give me an alternative candidate',
'Show me another worker for Friday',
'Is there a different applicant for this shift?',
].filter((q) => ask(q, { focus: 'cc-activity' }) !== null);
return { pass: left.length === 0, detail: left.join(' | ') || 'none taken' };
})().pass);
/* ── D. Meaning that was never established ────────────────────────── */
record('meaning: a reading that declares one is believed', (() => {
const declared = ['position.activity', 'candidates.activity', 'position.pipeline',
'candidates.pipeline', 'activity.breakdown']
.map((id) => [id, seriesE.kindOfBinding({ source: id })]);
const bad = declared.filter(([, kind]) => !nodeE.SERIES_KINDS.includes(kind));
return { pass: bad.length === 0, detail: declared.map(([i, k]) => `${i}=${k}`).join(' ') };
})().pass);
record('meaning: every declared kind is one of the closed three', (() => {
const bad = surfaces.DATA_SOURCES
.filter((source) => source.series != null)
.filter((source) => !nodeE.SERIES_KINDS.includes(source.series));
return { pass: bad.length === 0, detail: bad.map((s) => `${s.id}=${s.series}`).join(', ') || 'all known' };
})().pass);
record('meaning: a reading that declares none is refused rather than guessed', (() => {
const node = nodeE.makeNode({ id: 'viz', type: 'card', origin: 'user',
data: { source: 'hires.performance' } });
const m = intentE.matchUiEdit('Change the Hiring performance card to a pie chart',
{ tree: [node], registry: nreg, page: 'analytics' });
return { pass: m?.kind === 'refused' && /does not say what its figures mean/i.test(m.message),
detail: `${m?.kind}: ${m?.message}` };
})().pass);
record('meaning: nor is it offered in the first place', (() => {
const node = nodeE.makeNode({ id: 'viz', type: 'card', origin: 'user',
data: { source: 'hires.performance' } });
const m = intentE.matchUiEdit('Show me other options for the card',
{ tree: [node], registry: nreg, page: 'analytics', focus: 'viz' });
const types = (m?.options || []).map((o) => o.type);
const charts = types.filter((t) => nreg.get(t).seriesKinds.length > 0);
return { pass: charts.length === 0, detail: types.join(', ') || 'nothing offered' };
})().pass);
record('meaning: a stored patch that never established one is refused on load', (() => {
const tree = [nodeE.makeNode({ id: 'viz', type: 'bar-chart', origin: 'user',
data: { source: 'hires.performance' } })];
const verdict = validateE.validateTree(tree, ectx);
return { pass: !verdict.ok, detail: JSON.stringify(verdict.problems).slice(0, 170) };
})().pass);
record('meaning: components that claim no meaning are untouched by the rule', (() => {
/* The Card → Table path, which says nothing about what its rows mean
and must keep working exactly as it did. */
const node = nodeE.makeNode({ id: 'viz', type: 'card', origin: 'user',
props: { title: 'Recent hires' }, data: { source: 'hires.recent' } });
const r = opsE.applyOperation([node], { op: 'replace', target: 'viz', type: 'table' }, ectx);
const after = r.ok ? nodeE.findNode(r.tree, 'viz') : null;
return { pass: r.ok && after.type === 'table' && after.data.source === 'hires.recent'
&& after.props.title === 'Recent hires',
detail: JSON.stringify(r.problems).slice(0, 150) };
})().pass);
}
/* ── Asking for the alternatives ──────────────────────────────────────
*
* The request that most invites a model to invent something. Every answer
* here is the registry's, and it is the same list the replace path checks
* a single name against.
*/
{
const ccTree = () => compE.composePage('control-center', ectx).tree;
const ask = (q) => intentE.matchUiEdit(q, { tree: ccTree(), registry: nreg, page: 'control-center' });
for (const phrase of [
'Show me other options for the Hiring activity',
'I do not like this design, show me other designs for Hiring activity',
'What else could the Hiring activity be',
'Give me alternatives for the Hiring activity',
]) {
record(`options: "${phrase.slice(0, 44)}…" is answered from the registry`, (() => {
const m = ask(phrase);
const types = (m?.options || []).map((o) => o.type);
return { pass: m?.kind === 'options' && types.length > 0
&& types.every((t) => Boolean(nreg.get(t)))
&& !types.includes('pie-chart'),
detail: `${m?.kind}: ${types.join(', ')}` };
})().pass);
}
record('options: at most four are offered', (() => {
const m = ask('Show me other options for the Hiring activity');
return { pass: (m?.options || []).length <= 4, detail: String(m?.options?.length) };
})().pass);
record('options: every option is one replace would accept', (() => {
const m = ask('Show me other options for the Hiring activity');
const bad = (m?.options || []).filter((o) => (
!opsE.applyOperation(ccTree(), { op: 'replace', target: 'cc-activity', type: o.type }, ectx).ok
));
return { pass: bad.length === 0, detail: bad.map((o) => o.type).join(', ') || 'none' };
})().pass);
record('options: an ordinary question about options is left alone', (() => {
const left = ['What are my options for this position?', 'Show me the candidates',
'What other candidates applied?', 'Give me an alternative candidate']
.filter((q) => ask(q) !== null);
return { pass: left.length === 0, detail: left.join(' | ') || 'none taken' };
})().pass);
record('options: a refusal offers the alternatives', (() => {
const m = ask('Change Hiring activity to a pie chart');
const labels = (m?.alternatives || []).map((a) => a.type);
return { pass: m?.kind === 'refused' && /ordered in time/i.test(m.message)
&& labels.includes('bar-chart') && !labels.includes('pie-chart'),
detail: `${m?.message} → ${labels.join(', ')}` };
})().pass);
}
/* ── Safety ───────────────────────────────────────────────────────────
*
* Nothing a person can type produces markup, a class name or a component.
*/
{
const ccTree = () => compE.composePage('control-center', ectx).tree;
const ask = (q) => intentE.matchUiEdit(q, { tree: ccTree(), registry: nreg, page: 'control-center' });
record('safety: an invented visualization type is refused, not improvised', (() => {
const m = ask('Turn the Hiring activity into a neon 3D holographic chart');
const planned = m?.kind === 'plan';
return { pass: !planned, detail: `${m?.kind}` };
})().pass);
record('safety: an unregistered type never reaches the tree', (() => {
const r = opsE.applyOperation(ccTree(),
{ op: 'replace', target: 'cc-activity', type: 'holograph' }, ectx);
return { pass: !r.ok && /Unsupported UI type/i.test(JSON.stringify(r.problems)),
detail: JSON.stringify(r.problems).slice(0, 120) };
})().pass);
record('safety: a request for custom CSS produces no operation', (() => {
const bad = ['Give this card some custom CSS', 'Add class bg-red-500 to the Hiring activity',
'Set the style of the Hiring activity to display:none',
'Inject <script>alert(1)</script> into the Hiring activity']
.filter((q) => ask(q)?.kind === 'plan');
return { pass: bad.length === 0, detail: bad.join(' | ') || 'none planned' };
})().pass);
record('safety: a class name in props is dropped by the schema', (() => {
const r = opsE.applyOperation(
[seriesNode('bar-chart', 'hires.performance')],
{ op: 'update', target: 'viz', props: { className: 'bg-red-500', style: 'color:red' } },
ectx
);
const props = r.ok ? nodeE.findNode(r.tree, 'viz').props : null;
return { pass: !r.ok || (!('className' in props) && !('style' in props)),
detail: JSON.stringify(props) };
})().pass);
record('safety: an ambiguous request asks rather than guesses', (() => {
const m = ask('Change this');
return { pass: m === null || m.kind === 'ambiguous' || m.kind === 'unknown'
|| m.kind === 'unknown-type' || m.kind === 'refused',
detail: String(m?.kind) };
})().pass);
}
if (!hadWindow) delete globalThis.window;
}
}
await server.close();
/* ── 8. Production bundle ─────────────────────────────────────────────────── */
if (process.argv.includes('--dist')) {
console.log('\n── Production bundle ──');
const dir = join(ROOT, 'dist/assets');
if (!existsSync(dir)) {
record('dist/assets exists', false, 'run `npm run build` first');
} else {
const bundle = readdirSync(dir)
.filter((f) => f.endsWith('.js'))
.map((f) => readFileSync(join(dir, f), 'utf8'))
.join('\n');
for (const s of reg.SKILLS) {
record(`bundle carries \`${s.id}\``, bundle.includes(s.id));
}
/* Body content, not just the id — a manifest naming a skill whose Markdown
did not survive is the failure this is looking for. */
record(
'bundle carries skill body text',
bundle.includes('Which client is this role for'),
'create-position conversation step found in bundle'
);
}
}
/* ── Summary ──────────────────────────────────────────────────────────────── */
const failed = results.filter((r) => !r.pass);
console.log(`\n${results.length - failed.length}/${results.length} checks passed`);
if (failed.length) {
console.log('\nFailed:');
failed.forEach((r) => console.log(` - ${r.name}${r.detail ? ` (${r.detail})` : ''}`));
process.exit(1);
}