Files
learnharness/tests/zero-keys.test.tsx
T
amosblomqvist 52c02533d1 extraction: the spine stage — cross-stretch assembly between extract and audit
Per-batch pipeline is now extract → spine → audit → merge (owner order,
2026-09-30), the spine inside the serialized audit section so it reads
the graph exactly as the following audit will. Measured motive: 0 of 133
chains in the live corpus crossed a stretch boundary — no stage minted
cross-stretch chains.

- src/agent/subagents/spine.ts: SESSION/NOTES spine preambles + shared
  SPINE_CORE (span doctrine; extraction doctrines carried over), the
  submit_spine TypeBox schema, validateSpine (mechanical span law: every
  chain touches >=1 fresh endpoint AND >=1 existing digest endpoint;
  unknown ids, self-reference, and id collisions are loud), the digest
  prompt builder, and the runner (strong tier, retry-only).
- extraction.ts: 'assembling' phase, spine stage in the per-need gate,
  usage/billing stage, assemble() inside withAuditLock for both front
  doors; first stretch of a source skips the call (empty digest, no
  spend); the audit sees COMBINED propositions (chunk + connectives),
  never chains; merger receives the combined findings.
- core/graph.ts mergeFindings: chain endpoints resolve through the
  job-local map OR pass through verbatim as existing n_ ids (the spine's
  mixed namespace).
- EXTRACTION_CORE: steps-of-reasoning selection bar (examples, notation
  glosses, and restatements that no argument needs are not steps).
- Factory chunk budget 4096 -> 8192 (fewer seams, more local context).
- healStringifiedArrays moved to the shared submit seam (submit-agent.ts).
- Cost panel: Assembly row; usage records carry a spine stage (optional
  on legacy records).
- Tests +5: prompt pins, span-law matrix, digest rendering, seeded
  end-to-end flow (spanning chain + connective, verbatim n_ endpoint,
  spine billed as its own stage), non-spanning refused + retried.
2026-09-30 16:38:45 +03:00

167 lines
6.6 KiB
TypeScript

/**
* The zero-keys wave's pure seams (owner-locked over Platform's 66dbea9
* key-presence gate, 2026-09-25): the provider-key truth (hasProviderKey
* — tool keys don't count, they unlock no models), the pill's SSR shape,
* and the SetupDialog's self-contained first-key screen (the dialog STAYS
* MOUNTED after a save; these pins hold the screen's structure).
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { hasProviderKey, strongKeyless, ZeroKeysPill } from "../components/setup-cta.tsx";
import type { ModelCatalogResponse } from "../src/core/index.ts";
import { SetupKeysBody } from "../components/setup-dialog.tsx";
import { DICTATION_KEYLESS, DEEPGRAM_KEY_ID } from "../components/dictation.tsx";
import type { AuthKeyStatus } from "../src/core/auth-keys.ts";
import type { ProviderKeyInfo } from "../components/settings/keys-section.tsx";
const status = (id: string, present: boolean, source: AuthKeyStatus["source"] = present ? "file" : "none"): AuthKeyStatus => ({
id,
info: null,
present,
masked: present ? "…ab12 (64 chars)" : null,
source,
shadowedByEnv: false,
});
const PROVIDERS: ProviderKeyInfo[] = [
{ id: "openrouter", envVar: "OPENROUTER_API_KEY", label: "OpenRouter" },
{ id: "anthropic", envVar: "ANTHROPIC_API_KEY", label: "Anthropic" },
];
describe("hasProviderKey — the zero-keys pill's truth", () => {
it("a present provider key (env or file) is a key", () => {
assert.equal(hasProviderKey([status("OPENROUTER_API_KEY", true, "env")], PROVIDERS), true);
assert.equal(hasProviderKey([status("ANTHROPIC_API_KEY", true, "file")], PROVIDERS), true);
});
it("a TOOL key alone does not count — Deepgram unlocks no models", () => {
// Deepgram is not in the providers roster: it must never suppress the pill.
assert.equal(hasProviderKey([status("DEEPGRAM_API_KEY", true)], PROVIDERS), false);
});
it("absent provider keys = zero keys (the pill shows)", () => {
assert.equal(hasProviderKey([status("OPENROUTER_API_KEY", false)], PROVIDERS), false);
assert.equal(hasProviderKey([], PROVIDERS), false);
});
});
describe("ZeroKeysPill — the composer-hoisted notice", () => {
it("the pill takes the CTA's chrome with its own testid and copy", () => {
const html = renderToStaticMarkup(createElement(ZeroKeysPill, { onOpen: () => {} }));
assert.ok(html.includes('data-testid="setup-cta-keys"'));
assert.ok(html.includes("Add a provider key"));
// the models CTA's testid must not appear on the keys pill
assert.ok(!html.includes('data-testid="setup-cta"'));
});
});
describe("SetupKeysBody — stage 1's self-contained first-key screen", () => {
const base = {
value: "",
saving: false,
error: null,
menuOpen: false,
onToggleMenu: () => {},
onPick: () => {},
onValue: () => {},
onSave: () => {},
menuAnchor: { current: null },
};
it("before a pick: the provider trigger, no key input yet", () => {
const html = renderToStaticMarkup(
createElement(SetupKeysBody, { ...base, providers: PROVIDERS, provider: null }),
);
assert.match(html, /First, add a provider key\./);
assert.ok(html.includes("The model pickers list only what your keys unlock"));
assert.ok(html.includes('data-testid="setup-key-provider"'));
assert.ok(html.includes("Pick a provider…"));
assert.ok(!html.includes('data-testid="setup-key-input"'), "no paste field before a provider is picked");
});
it("after a pick: the paste input + Save join", () => {
const html = renderToStaticMarkup(
createElement(SetupKeysBody, { ...base, providers: PROVIDERS, provider: PROVIDERS[0]! }),
);
assert.ok(html.includes('data-testid="setup-key-input"'));
assert.ok(html.includes('data-testid="setup-key-save"'));
assert.ok(html.includes("Save key"));
assert.ok(html.includes("OpenRouter"), "the chosen provider's label rides the trigger");
});
it("a shape-gate 400 keeps its line inline — no Settings round-trip chrome", () => {
const html = renderToStaticMarkup(
createElement(SetupKeysBody, {
...base,
providers: PROVIDERS,
provider: PROVIDERS[0]!!,
error: "Expected 20+ characters",
}),
);
assert.ok(html.includes('data-testid="setup-key-error"'));
assert.ok(html.includes("Expected 20+ characters"));
// the old "Open Keys settings" round-trip died with the self-contained screen
assert.ok(!html.includes("setup-open-keys"));
});
});
describe("the dictation keyless seam (owner dictation trio)", () => {
it("the client-authored short line — not the route's dev-speak", () => {
assert.equal(DICTATION_KEYLESS, "Dictation needs a Deepgram key");
});
it("the Deepgram row id matches the keys store (KeysSection openRow)", () => {
assert.equal(DEEPGRAM_KEY_ID, "DEEPGRAM_API_KEY");
});
});
/** A minimal catalog double — only the two halves the strong-keyless guard
* reads: the strong pin and the present-key providers list. */
const catalog = (strongProvider: string | null, ...keyedProviders: string[]): ModelCatalogResponse => ({
models: [],
defaults: null,
rolePins: {
researcher: null,
demoBuilder: null,
visualMaker: null,
extractor: null,
spine: null,
auditor: null,
partitioner: null,
problemGenerator: null,
grader: null,
},
slots: {
strong: strongProvider
? { model: { provider: strongProvider, id: `${strongProvider}/model` }, thinkingLevel: "low" }
: null,
cheap: null,
vision: null,
},
providers: keyedProviders.map((id) => ({ id, envVar: `${id.toUpperCase()}_API_KEY`, keyPresent: true })),
});
describe("strongKeyless — the pre-send guard's truth (owner interlude, no-orphan-chats)", () => {
it("strong pinned to a provider whose key is gone: the guard FIRES", () => {
// keys exist generally (anthropic), but the strong slot pins openrouter
assert.equal(strongKeyless(catalog("openrouter", "anthropic")), true);
// no keys at all with a strong pin: also keyless (guard case 1 fires too)
assert.equal(strongKeyless(catalog("openrouter")), true);
});
it("strong provider's key present: the guard does NOT fire — the normal send path stays open", () => {
assert.equal(strongKeyless(catalog("openrouter", "openrouter", "anthropic")), false);
// belt check: a provider row never counts when keyPresent is false
const lying = catalog("openrouter", "openrouter", "anthropic");
lying.providers[0]!.keyPresent = false;
assert.equal(strongKeyless(lying), true);
});
it("no strong pin (the models guard owns that case) or catalog not loaded: false — the server gate remains the belt", () => {
assert.equal(strongKeyless(catalog(null, "openrouter")), false);
assert.equal(strongKeyless(undefined), false);
});
});