@memhtml/eval: the fixture corpus generator, the refusable retrieval discrimination gate, and the write-path discrimination arm. The retrieval corpus is never committed; each run regenerates it from a seed. The write-path corpus is committed, because its labels are the oracle.
The gate asks one question: can the retrieval stack rank a memory above its own high-similarity WRONG twin? controls turns a true claim into a plausible impostor along three divergence families (negation, numeric, variant), validates each control against its own family’s predicate from @memhtml/domain, and discards a control that failed to diverge. corpus is a pure function of (seed, now): it yields the same memory specs and probes on any machine, with every timestamp anchored as an offset behind the run so that salience decay cannot move the ranks from one calendar day to the next. fixture is the one module that writes, rendering a spec into a real git repository under a temp directory. harness builds the stack the gate measures (that fixture, a real database with the shipped migrations, the real indexer and the real four-arm retrieval) and substitutes only the embedder: a deterministic one whose cosine relations are a pure function of the text, so the numbers reproduce without credentials. discriminate runs the probes and reports two numbers, of which the strict one is the gate: mrr against a floor of 0.85, and a per-probe inversion count where one inversion fails the run regardless of MRR. run picks the mode and shapes the outcome so that a skipped gate never looks like a passing one: fake mode runs everywhere, and live mode without credentials reports skipped: true and passed: false.
The write-path arm asks the other question the 2026-08-27 eval plan posed: does the WRITE path tell a good candidate memory from a bad one? write-path-corpus is a labeled corpus of 62 candidate memories (31 the gate should write, 31 it should refuse), each with the provenance of its label (the contract clause or design rule that decides it) and two fixture transcripts every good quote is a verbatim span of. write-path-gate composes the production gate from its own functions in the order the fleet runs them: the consolidator door’s schema decode, readable-session check and verbatim-quote check, then the sleep phase’s per-candidate refusal (candidateRefusalFor). Nothing is re-implemented. write-path-metrics scores the decisions with refusal as the positive class (precision, recall, F1, the four-cell confusion matrix, and a per-class breakdown naming the items that disagreed with their label). write-path-run runs the arm, derives the floor (the plan named none, so it is the measured baseline F1 0.9333 minus five points, 0.88, stated as such), and freezes every decision into fixtures/write-path-discrimination.manifest.json, which the test:eval tier replays decision for decision. pnpm freeze:write-path is the one way that manifest changes. Three failure classes are labeled bad and accepted by the gate today (a duplicated evidence quote, twice; a claim that is a verbatim transcript span), and one good item is refused for a whitespace-padded session id; KNOWN_GAP_CLASSES states exactly that set and the tier fails if the set moves in either direction.
The write-path ACCEPTANCE arm asks the question the discrimination arm cannot: does dedup-merge FOLD the pairs it should? A phase that refuses everything scores perfectly on refusal, which is what production did on 2026-09-10 and 2026-09-11 (vetoed: 171/171, vetoed: 178/178, merged: 0). write-path-acceptance-corpus is 19 labeled groups (12 merge, 7 keep), each the answer a model would have given, with the provenance of its label. write-path-acceptance runs them through the phase’s own fold path — dedupMergeTextFor, groupPairsFor, and mergeOutcomes under DEDUP_ADMIT_FLOOR, all imported — with a fold as the positive class, reports acceptance (folds over merge pairs), precision, and where every refused pair stopped (read off the filter’s own MergeStage, a veto refined to its predicate by mergeVetoReasons), and gates on an acceptance floor (measured baseline 1.0 minus five points, 0.95) plus zero folds of a keep pair. ACCEPTANCE_KNOWN_GAP_CLASSES is empty: the two mechanisms behind the all-veto runs — numericTokenDivergent over the whole article vetoing the same claim for an incidental number in the body (incidental-number), and a role guard that claimed both roles folding at most one member of a model group (group-fanout) — measured 8 of 12 under the old rules and 14 of 14 under the current ones, which read numbers off the gist and admit a repeated keeper. The keep side holds the counterexamples the new rules must still refuse (a numeric conflict in the claim, a negation or variant qualifier only in the body, a templated series whose claims quote different ids), and BODY_NUMBER_ONLY_GROUP states the shape the gist rule no longer refuses, measured outside the gated corpus.
This package sits above @memhtml/contracts, @memhtml/domain, @memhtml/html, @memhtml/index, @memhtml/llm, @memhtml/store, @memhtml/sleep and @memhtml/consolidator (the last two only for the write-path arm, which imports the gate it measures), and apps/cli depends on it: memhtml eval discriminate is runDiscrimination, and @memhtml/sleep’s merge takes discriminationGate as its pre-merge gate from the CLI’s wiring, because the sleep package supplies no default.
Modules
Section titled “Modules”The package publishes one import path, @memhtml/eval, which re-exports the twelve modules below, so the reference on this page is the whole exported surface. gen-fixture.ts is the body of pnpm gen:fixture and is not exported; scripts/freeze-write-path.mjs is the body of pnpm freeze:write-path and lives outside src/ because it resolves a repo fixture the published package does not ship.
| Module | What it holds |
|---|---|
controls.ts |
DIVERGENCE_FAMILIES, the three flips (negationFlip, numericFlip, variantFlip), familyPredicate and deriveControl. |
corpus.ts |
MemorySpec, Probe, CorpusSpec, the defaults (DEFAULT_SEED, DEFAULT_CORPUS_SIZE, DEFAULT_PROBE_COUNT), quantizeNow, articleFor, queryFor and buildCorpus. |
discriminate.ts |
MRR_FLOOR, PROBE_LIMIT, runProbes, summarize, discriminate, runFloor, describeFailure and the report shapes. |
fixture.ts |
FixtureCorpus, FixtureOptions, memoryFileFor, writeCorpus and makeFixtureCorpus. |
harness.ts |
EvalStack, StackOptions, the embedders (fakeEmbedder, failingEmbedder, liveEmbedder, fakeVector, FAKE_DIM), buildStack and withStack. |
run.ts |
EvalOptions, EvalOutcome, BEDROCK_TOKEN_VAR, hasBedrockCredentials, runDiscrimination, DiscriminationFailed and discriminationGate. |
write-path-corpus.ts |
WRITE_PATH_CORPUS (62 labeled candidates), TRANSCRIPTS, KNOWN_GAP_CLASSES, CEILINGS, MANY_SPANS, padTo and the fixture session ids. |
write-path-gate.ts |
admitCandidate, GATE_STAGES, GateDecision, GateBatch: the production gate composed stage by stage. |
write-path-metrics.ts |
summarizeWritePath, confusionOf, breakdownByClass, agrees, describeWritePath and the report shapes. |
write-path-acceptance-corpus.ts |
ACCEPTANCE_CORPUS (16 labeled groups), ACCEPTANCE_KNOWN_GAP_CLASSES and the group shapes. |
write-path-acceptance.ts |
runWritePathAcceptance, ACCEPTANCE_FLOOR, ACCEPTANCE_BASELINE, impliedPairs, decideAcceptance, summarizeAcceptance, acceptanceByClass, describeAcceptance and the report shapes. |
write-path-run.ts |
runWritePathDiscrimination, WRITE_PATH_F1_FLOOR, WRITE_PATH_BASELINE_F1, GATE_SOURCES, MANIFEST_FILENAME, manifestFor, replayDrift, readManifest, writeManifest, withBatch, decideAll. |
Classes
Section titled “Classes”DiscriminationFailed
Section titled “DiscriminationFailed”A failed gate, as a tagged error.
Tagged so it is a failure like every other in the system rather than a bare value. The CLI’s
codeFor switches on _tag, and ERR_DISCRIMINATION_FAILED, the error code design §8 named for
exactly this refusal, would otherwise have no producer at all and degrade to ERR_UNKNOWN.
The whole outcome rides along, because a refusal an operator cannot reproduce is a refusal they will
override. seed regenerates the corpus that failed, and inversions says which probes.
Constructors
Section titled “Constructors”Constructor
Section titled “Constructor”new DiscriminationFailed(outcome): DiscriminationFailed;Parameters
Section titled “Parameters”outcome
Section titled “outcome”Returns
Section titled “Returns”Properties
Section titled “Properties”readonly _tag: "DiscriminationFailed" = "DiscriminationFailed";outcome
Section titled “outcome”readonly outcome: EvalOutcome;Accessors
Section titled “Accessors”reason
Section titled “reason”Get Signature
Section titled “Get Signature”get reason(): string;The one-line summary, used as the envelope’s human message.
Returns
Section titled “Returns”string
Interfaces
Section titled “Interfaces”AcceptanceClassBreakdown
Section titled “AcceptanceClassBreakdown”One class’s agreement with its labels.
Properties
Section titled “Properties”agreed
Section titled “agreed”readonly agreed: number;Decisions that matched the label: folded for merge, refused for keep.
readonly class: string;disagreed
Section titled “disagreed”readonly disagreed: readonly string[];readonly label: AcceptanceLabel;readonly pairs: number;AcceptanceMember
Section titled “AcceptanceMember”One member of a group, as the phase’s CorpusRow would carry it. Order in the array is corpus order.
Properties
Section titled “Properties”readonly body: string;The <article>’s text content, files.body_text.
readonly gist: string;The <mark> claim, files.gist.
readonly path: string;AcceptanceOptions
Section titled “AcceptanceOptions”What runWritePathAcceptance takes.
Properties
Section titled “Properties”acceptanceFloor?
Section titled “acceptanceFloor?”readonly optional acceptanceFloor?: number;corpus?
Section titled “corpus?”readonly optional corpus?: readonly LabeledGroup[];AcceptanceReport
Section titled “AcceptanceReport”The report the gate reads.
Properties
Section titled “Properties”acceptance
Section titled “acceptance”readonly acceptance: number;accepted / eligible, four decimals; 0 when nothing was eligible.
acceptanceFloor
Section titled “acceptanceFloor”readonly acceptanceFloor: number;accepted
Section titled “accepted”readonly accepted: number;Folds among the eligible.
decisions
Section titled “decisions”readonly decisions: readonly PairDecision[];eligible
Section titled “eligible”readonly eligible: number;Pairs from merge groups: the ones that should fold.
readonly f1: number;foldedKeep
Section titled “foldedKeep”readonly foldedKeep: number;Folds among the guarded. Any value above zero fails the tier.
groups
Section titled “groups”readonly groups: number;guarded
Section titled “guarded”readonly guarded: number;Pairs from keep groups.
readonly pairs: number;passed
Section titled “passed”readonly passed: boolean;True when both labels are present AND no keep pair folded AND acceptance >= acceptanceFloor.
One label alone is a failure, not a vacuous pass, for the reason the discrimination arm gives.
perClass
Section titled “perClass”readonly perClass: readonly AcceptanceClassBreakdown[];precision
Section titled “precision”readonly precision: number;accepted / (accepted + foldedKeep), four decimals; 0 when nothing folded.
recall
Section titled “recall”readonly recall: number;Same as acceptance, stated under the name the discrimination arm uses.
ClaimText
Section titled “ClaimText”The text of a memory as the flips see it, meaning the claim then each body paragraph.
Extended by
Section titled “Extended by”Properties
Section titled “Properties”readonly body: readonly string[];readonly claim: string;ClassBreakdown
Section titled “ClassBreakdown”One class’s agreement with its labels.
Properties
Section titled “Properties”agreed
Section titled “agreed”readonly agreed: number;Decisions that matched the label: rejected for bad, accepted for good.
readonly class: string;disagreed
Section titled “disagreed”readonly disagreed: readonly string[];Ids of the items whose decision contradicted the label.
readonly items: number;readonly label: WritePathLabel;ConfusionMatrix
Section titled “ConfusionMatrix”The four cells, named by what happened rather than by TP/FP letters a reader has to re-derive.
badRejected is the true positive, goodRejected the false positive, badAccepted the false
negative, goodAccepted the true negative.
Properties
Section titled “Properties”badAccepted
Section titled “badAccepted”readonly badAccepted: number;badRejected
Section titled “badRejected”readonly badRejected: number;goodAccepted
Section titled “goodAccepted”readonly goodAccepted: number;goodRejected
Section titled “goodRejected”readonly goodRejected: number;CorpusSpec
Section titled “CorpusSpec”The whole fixture: the memories to write, their access history, and the probes.
Properties
Section titled “Properties”access
Section titled “access”readonly access: readonly AccessSpec[];state.access rows the harness seeds before running the probes.
memories
Section titled “memories”readonly memories: readonly MemorySpec[];readonly now: number;The quantized run instant every stamp is anchored behind, UTC millis. See quantizeNow.
probes
Section titled “probes”readonly probes: readonly Probe[];readonly seed: number;DerivedControl
Section titled “DerivedControl”A derived control, holding the flipped text plus the family that produced it.
Extends
Section titled “Extends”Properties
Section titled “Properties”readonly body: readonly string[];Inherited from
Section titled “Inherited from”readonly claim: string;Inherited from
Section titled “Inherited from”family
Section titled “family”readonly family: DivergenceFamily;readonly note: string;What changed, for the probe’s own record.
DiscriminationReport
Section titled “DiscriminationReport”The whole run’s outcome. This is the eval.discrimination envelope payload.
Extended by
Section titled “Extended by”Properties
Section titled “Properties”corpusMrr
Section titled “corpusMrr”readonly corpusMrr: number;Mean reciprocal rank over the WHOLE hit list, unitless in [0, 1], four decimals.
Reported rather than gated, because corpus size dominates it more than ranking quality does.
DEFAULT_ARM_LIMIT is 40, which is 13% of a 300-file fixture and under 1% of a real corpus, so
the two query-blind arms cover a far larger share of a fixture than of production. The value is
seed- and parameter-dependent — it moves with the corpus size, the probe count, and the seed —
but it rises with corpus size on this generator while the inversion count stays flat, which is
exactly why a gate on this number would be a gate on how big the fixture is.
degradedProbes
Section titled “degradedProbes”readonly degradedProbes: number;Probes ranked by a degraded (vector-arm-free) search.
discriminated
Section titled “discriminated”readonly discriminated: number;Probes whose target strictly outranked all of its controls.
inversions
Section titled “inversions”readonly inversions: readonly ProbeResult[];Probes with at least one inversion. The gate fails when this is non-zero.
readonly mode: EvalMode;readonly mrr: number;Mean reciprocal rank over {target} ∪ controls, unitless in [0, 1], four decimals.
This is the gated number, and its coordinate space is the discrimination set, not the corpus.
Design §5 states the floor in the same clause as rank(target) < min(rank(control)), so the space
the sentence is about is the target against its own impostors. 1.0 means every target beat every
control outright. See corpusMrr for the other reading, which is reported and not gated.
mrrFloor
Section titled “mrrFloor”readonly mrrFloor: number;The floor mrr was measured against.
passed
Section titled “passed”readonly passed: boolean;True when there are zero inversions AND mrr >= mrrFloor.
probes
Section titled “probes”readonly probes: number;results
Section titled “results”readonly results: readonly ProbeResult[];EvalEmbedder
Section titled “EvalEmbedder”Both embed ports plus a call counter.
Extends
Section titled “Extends”EmbedPort.QueryEmbedPort
Properties
Section titled “Properties”readonly calls: () => number;Returns
Section titled “Returns”number
readonly embed: (texts) => Effect<readonly Float32Array<ArrayBufferLike>[], ModelUnavailable>;Parameters
Section titled “Parameters”readonly string[]
Returns
Section titled “Returns”Effect<readonly Float32Array<ArrayBufferLike>[], ModelUnavailable>
Inherited from
Section titled “Inherited from”EmbedPort.embedembedQuery
Section titled “embedQuery”readonly embedQuery: (text) => Effect<Float32Array<ArrayBufferLike>, ModelUnavailable>;Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”Effect<Float32Array<ArrayBufferLike>, ModelUnavailable>
Inherited from
Section titled “Inherited from”QueryEmbedPort.embedQueryEvalOptions
Section titled “EvalOptions”What runDiscrimination takes.
Properties
Section titled “Properties”readonly optional env?: Readonly<Record<string, string | undefined>>;Injected for tests, so the credential branch is drivable without touching the environment.
readonly optional mode?: EvalMode;fake by default, the mode that works in CI and whose numbers are reproducible.
mrrFloor?
Section titled “mrrFloor?”readonly optional mrrFloor?: number;readonly optional now?: number;The run instant to anchor the corpus’s stamps behind, UTC millis. The clock when absent.
probes?
Section titled “probes?”readonly optional probes?: number;readonly optional seed?: number;readonly optional size?: number;EvalOutcome
Section titled “EvalOutcome”What memhtml eval discriminate emits, the report plus which mode was asked for.
Extends
Section titled “Extends”Properties
Section titled “Properties”corpusMrr
Section titled “corpusMrr”readonly corpusMrr: number;Mean reciprocal rank over the WHOLE hit list, unitless in [0, 1], four decimals.
Reported rather than gated, because corpus size dominates it more than ranking quality does.
DEFAULT_ARM_LIMIT is 40, which is 13% of a 300-file fixture and under 1% of a real corpus, so
the two query-blind arms cover a far larger share of a fixture than of production. The value is
seed- and parameter-dependent — it moves with the corpus size, the probe count, and the seed —
but it rises with corpus size on this generator while the inversion count stays flat, which is
exactly why a gate on this number would be a gate on how big the fixture is.
Inherited from
Section titled “Inherited from”DiscriminationReport.corpusMrr
corpusSize
Section titled “corpusSize”readonly corpusSize: number;degradedProbes
Section titled “degradedProbes”readonly degradedProbes: number;Probes ranked by a degraded (vector-arm-free) search.
Inherited from
Section titled “Inherited from”DiscriminationReport.degradedProbes
discriminated
Section titled “discriminated”readonly discriminated: number;Probes whose target strictly outranked all of its controls.
Inherited from
Section titled “Inherited from”DiscriminationReport.discriminated
inversions
Section titled “inversions”readonly inversions: readonly ProbeResult[];Probes with at least one inversion. The gate fails when this is non-zero.
Inherited from
Section titled “Inherited from”DiscriminationReport.inversions
readonly mode: EvalMode;Inherited from
Section titled “Inherited from”readonly mrr: number;Mean reciprocal rank over {target} ∪ controls, unitless in [0, 1], four decimals.
This is the gated number, and its coordinate space is the discrimination set, not the corpus.
Design §5 states the floor in the same clause as rank(target) < min(rank(control)), so the space
the sentence is about is the target against its own impostors. 1.0 means every target beat every
control outright. See corpusMrr for the other reading, which is reported and not gated.
Inherited from
Section titled “Inherited from”mrrFloor
Section titled “mrrFloor”readonly mrrFloor: number;The floor mrr was measured against.
Inherited from
Section titled “Inherited from”readonly now: number;The quantized run instant the corpus’s stamps were anchored behind, UTC millis. The other half
of reproducing a failing run, since the corpus is a function of (seed, now). 0 on a skipped
run, where no corpus was generated.
passed
Section titled “passed”readonly passed: boolean;True when there are zero inversions AND mrr >= mrrFloor.
Inherited from
Section titled “Inherited from”probes
Section titled “probes”readonly probes: number;Inherited from
Section titled “Inherited from”requested
Section titled “requested”readonly requested: EvalMode;The mode the caller asked for, which differs from mode only on a refused live run.
results
Section titled “results”readonly results: readonly ProbeResult[];Inherited from
Section titled “Inherited from”readonly seed: number;The seed the corpus was generated at, so a failing run is reproducible.
skipped
Section titled “skipped”readonly skipped: boolean;True when live mode was requested with no credentials present. A skipped run is passed: false
and carries zero probes, so it is a refusal rather than a result.
skipReason?
Section titled “skipReason?”readonly optional skipReason?: string;Why it was skipped, absent otherwise.
EvalStack
Section titled “EvalStack”A built stack, holding the fixture, the database, and retrieval over both.
Properties
Section titled “Properties”readonly db: DatabaseShape;embedCalls
Section titled “embedCalls”readonly embedCalls: () => number;Returns
Section titled “Returns”number
fixture
Section titled “fixture”readonly fixture: FixtureCorpus;indexed
Section titled “indexed”readonly indexed: number;How many files the indexer projected, so a caller can assert the corpus really landed.
retrieval
Section titled “retrieval”readonly retrieval: RetrievalShape;FixtureCorpus
Section titled “FixtureCorpus”A generated fixture repo, with the root, the git service over it, and the spec behind it.
Properties
Section titled “Properties”cleanup
Section titled “cleanup”readonly cleanup: () => Promise<void>;Returns
Section titled “Returns”Promise<void>
readonly git: GitShape;readonly root: string;readonly spec: CorpusSpec;written
Section titled “written”readonly written: number;How many files were written, generated artifacts excluded.
FixtureOptions
Section titled “FixtureOptions”What makeFixtureCorpus takes. Every field has a default, so a caller can pass nothing.
Extended by
Section titled “Extended by”Properties
Section titled “Properties”readonly optional now?: number;The run instant the corpus’s stamps are anchored behind, UTC millis. Effect’s Clock when
absent, which is the one place the fixture pipeline consults time at all — buildCorpus
requires it, so a test pinning this value pins every stamp in the tree.
probes?
Section titled “probes?”readonly optional probes?: number;readonly optional root?: string;Where to generate. A fresh temp directory when absent.
readonly optional seed?: number;readonly optional size?: number;FixtureTranscript
Section titled “FixtureTranscript”One fixture transcript, as the batch would offer it.
Properties
Section titled “Properties”readonly jsonl: string;JSONL, one object per line, in the shape Claude Code writes under ~/.claude/projects.
sessionId
Section titled “sessionId”readonly sessionId: string;FloorReport
Section titled “FloorReport”The lexical-floor scenario runs the same suite with no vector arm.
Design §5 requires two things of it: no error, and the lexical arm still beating the controls on the probes that carry lexical signal. Only the SECOND half is a subset claim, and it has to be. A numeric-family control differs from its target by one token, which the FTS arm cannot order, so demanding the full gate without vectors would be demanding the vector arm be unnecessary.
lexicallyDiscriminated is therefore the number reported and asserted, not passed.
Properties
Section titled “Properties”allDegraded
Section titled “allDegraded”readonly allDegraded: boolean;True when every probe was ranked degraded, which asserts that the arm really is absent.
lexicallyDiscriminated
Section titled “lexicallyDiscriminated”readonly lexicallyDiscriminated: number;Probes the lexical floor still discriminated.
probes
Section titled “probes”readonly probes: number;results
Section titled “results”readonly results: readonly ProbeResult[];Every result, so a caller can see which families survive without vectors.
FrozenDecision
Section titled “FrozenDecision”One frozen decision: enough to detect drift, nothing that needs a transcript to read.
Properties
Section titled “Properties”accepted
Section titled “accepted”readonly accepted: boolean;readonly class: string;readonly id: string;readonly label: "good" | "bad";readonly stage: GateStage;GateBatch
Section titled “GateBatch”What one batch made readable: the transcripts on disk, keyed the way the door keys them.
Properties
Section titled “Properties”reachable
Section titled “reachable”readonly reachable: readonly ReachableTranscript[];GateDecision
Section titled “GateDecision”One candidate’s outcome.
Properties
Section titled “Properties”accepted
Section titled “accepted”readonly accepted: boolean;reason
Section titled “reason”readonly reason: string | null;The production function’s own reason text, null when accepted. Truncated to one line.
readonly stage: GateStage;LabeledCandidate
Section titled “LabeledCandidate”One labeled candidate.
candidate is unknown on purpose: a bad item may violate the type the door decodes (a bare
string where an entity object belongs, a missing field), and a typed field would make those shapes
unrepresentable. The gate’s first stage IS the decode, so the corpus has to be able to hand it
something the type refuses.
Properties
Section titled “Properties”candidate
Section titled “candidate”readonly candidate: unknown;readonly class: string;For a bad item, its failure class (what is wrong with it). For a good item, its shape (what
the gate has to get RIGHT to accept it). The per-class breakdown groups on this.
readonly id: string;Stable id, g01…g31 for good, b01…b31 for bad. The manifest keys on it.
readonly label: WritePathLabel;provenance
Section titled “provenance”readonly provenance: string;The rule that decides the label, with the file that holds it.
LabeledDecision
Section titled “LabeledDecision”One candidate’s decision beside its label.
Properties
Section titled “Properties”accepted
Section titled “accepted”readonly accepted: boolean;readonly class: string;readonly id: string;readonly label: WritePathLabel;reason
Section titled “reason”readonly reason: string | null;readonly stage: GateStage;LabeledGroup
Section titled “LabeledGroup”One labeled group.
Properties
Section titled “Properties”readonly class: string;For a merge group, its shape (what the fold has to get RIGHT to accept it). For a keep group,
its divergence (why the veto must refuse it). The per-class breakdown groups on this.
readonly id: string;Stable id, m01… for merge, k01… for keep. Reports and gap sets key on it.
readonly label: AcceptanceLabel;members
Section titled “members”readonly members: readonly AcceptanceMember[];Two or more members, oldest first.
provenance
Section titled “provenance”readonly provenance: string;The rule that decides the label, with the file that holds it.
MemorySpec
Section titled “MemorySpec”One memory to write, before it becomes HTML.
Properties
Section titled “Properties”archivedAt?
Section titled “archivedAt?”readonly optional archivedAt?: string;Set on the archived members, which carry memhtml-status: archived and a memhtml-archived stamp.
readonly body: readonly string[];readonly claim: string;The sentence the memory turns on. It becomes the <mark> span and therefore files.gist.
confidence
Section titled “confidence”readonly confidence: number;createdAt
Section titled “createdAt”readonly createdAt: string;entities
Section titled “entities”readonly entities: readonly string[];extras
Section titled “extras”readonly extras: readonly string[];Extra article markup after the claim paragraph, the element kit this memory exercises.
importance
Section titled “importance”readonly importance: number;readonly links: readonly object[];memoryType
Section titled “memoryType”readonly memoryType: string;readonly path: string;sessionId?
Section titled “sessionId?”readonly optional sessionId?: string;readonly tags: readonly string[];readonly title: string;updatedAt
Section titled “updatedAt”readonly updatedAt: string;validUntil?
Section titled “validUntil?”readonly optional validUntil?: string;PairDecision
Section titled “PairDecision”One oriented pair’s decision beside its group’s label.
Properties
Section titled “Properties”readonly class: string;dropPath
Section titled “dropPath”readonly dropPath: string;folded
Section titled “folded”readonly folded: boolean;readonly group: string;readonly id: string;<group id>/<drop basename>, stable across runs.
keepPath
Section titled “keepPath”readonly keepPath: string;readonly label: AcceptanceLabel;readonly stage: AcceptanceStage;One discrimination probe: a query, the memory that answers it, and the near-twins that do not.
controlPaths are ordered by family so a failure names the axis. Each control is validated
against its OWN family’s divergence predicate at generation time. See controls.ts.
Properties
Section titled “Properties”controlPaths
Section titled “controlPaths”readonly controlPaths: readonly string[];families
Section titled “families”readonly families: readonly DivergenceFamily[];readonly query: string;targetPath
Section titled “targetPath”readonly targetPath: string;ProbeResult
Section titled “ProbeResult”One probe’s outcome.
Properties
Section titled “Properties”controlRanks
Section titled “controlRanks”readonly controlRanks: readonly object[];Each control’s 1-based rank in the whole hit list, null when absent from the hits.
corpusReciprocalRank
Section titled “corpusReciprocalRank”readonly corpusReciprocalRank: number;1 / targetRank, or 0 when the target was absent. The term of corpusMrr.
degraded
Section titled “degraded”readonly degraded: boolean;True when the vector arm did not fire for this probe.
discriminated
Section titled “discriminated”readonly discriminated: boolean;True when the target strictly outranks EVERY control. A control the search never returned counts as outranked, since being absent is worse than being last.
discriminationRank
Section titled “discriminationRank”readonly discriminationRank: number;1-based rank of the target within {target} ∪ controls ALONE, the space the gate is stated in.
1 means the target beat every one of its own impostors. Never null, because the target is
always a member of this set, so an absent target ranks last rather than nowhere.
readonly query: string;reciprocalRank
Section titled “reciprocalRank”readonly reciprocalRank: number;1 / discriminationRank. The term of DiscriminationReport.mrr.
targetPath
Section titled “targetPath”readonly targetPath: string;targetRank
Section titled “targetRank”readonly targetRank: number | null;1-based rank of the target within the WHOLE returned hit list, or null when it was not returned.
The scope is every active memory in the corpus. Compare against discriminationRank, which
measures the same position in a different space.
ReplayDrift
Section titled “ReplayDrift”One decision that replayed differently from the frozen one.
Properties
Section titled “Properties”readonly fresh: | { accepted: boolean; stage: GateStage;} | null;frozen
Section titled “frozen”readonly frozen: | { accepted: boolean; stage: GateStage;} | null;readonly id: string;StackOptions
Section titled “StackOptions”What buildStack takes beyond the fixture options.
Extends
Section titled “Extends”Properties
Section titled “Properties”embedder?
Section titled “embedder?”readonly optional embedder?: EvalEmbedder;The embedder to measure through. fakeEmbedder() when absent.
readonly optional now?: number;The run instant the corpus’s stamps are anchored behind, UTC millis. Effect’s Clock when
absent, which is the one place the fixture pipeline consults time at all — buildCorpus
requires it, so a test pinning this value pins every stamp in the tree.
Inherited from
Section titled “Inherited from”probes?
Section titled “probes?”readonly optional probes?: number;Inherited from
Section titled “Inherited from”readonly optional root?: string;Where to generate. A fresh temp directory when absent.
Inherited from
Section titled “Inherited from”readonly optional seed?: number;Inherited from
Section titled “Inherited from”readonly optional size?: number;Inherited from
Section titled “Inherited from”VariantOptions
Section titled “VariantOptions”What a variant-family derivation needs beyond the target’s own text.
Properties
Section titled “Properties”anchor
Section titled “anchor”readonly anchor: string;The token the qualifier is inserted after, a product or host name in the claim.
qualifier
Section titled “qualifier”readonly qualifier: string;A VARIANT_QUALIFIERS member, such as pro, beta, or legacy.
WritePathManifest
Section titled “WritePathManifest”The freeze/replay manifest.
Properties
Section titled “Properties”corpusSha256
Section titled “corpusSha256”readonly corpusSha256: string;sha256 of the corpus as JSON, so a changed candidate is attributable.
decisions
Section titled “decisions”readonly decisions: readonly FrozenDecision[];f1Floor
Section titled “f1Floor”readonly f1Floor: number;frozenAt
Section titled “frozenAt”readonly frozenAt: string;When the freeze ran, ISO-8601. Informational; not compared on replay.
readonly gate: readonly object[];The stages, in order, and the production functions each one calls.
memhtmlVersion
Section titled “memhtmlVersion”readonly memhtmlVersion: string;The memhtml workspace version the decisions were frozen at.
metrics
Section titled “metrics”readonly metrics: object;accuracy
Section titled “accuracy”readonly accuracy: number;confusion
Section titled “confusion”readonly confusion: ConfusionMatrix;readonly f1: number;readonly items: number;precision
Section titled “precision”readonly precision: number;recall
Section titled “recall”readonly recall: number;schemaVersion
Section titled “schemaVersion”readonly schemaVersion: number;transcriptsSha256
Section titled “transcriptsSha256”readonly transcriptsSha256: string;sha256 of the two transcripts’ JSONL, so a changed fixture line is attributable.
WritePathOptions
Section titled “WritePathOptions”What runWritePathDiscrimination takes.
Properties
Section titled “Properties”corpus?
Section titled “corpus?”readonly optional corpus?: readonly LabeledCandidate[];f1Floor?
Section titled “f1Floor?”readonly optional f1Floor?: number;transcripts?
Section titled “transcripts?”readonly optional transcripts?: readonly FixtureTranscript[];WritePathReport
Section titled “WritePathReport”The report the gate reads. This is the eval.write-path payload shape.
Properties
Section titled “Properties”accuracy
Section titled “accuracy”readonly accuracy: number;(badRejected + goodAccepted) / items, four decimals.
readonly bad: number;confusion
Section titled “confusion”readonly confusion: ConfusionMatrix;decisions
Section titled “decisions”readonly decisions: readonly LabeledDecision[];readonly f1: number;Harmonic mean of the two, four decimals; 0 when either is 0.
f1Floor
Section titled “f1Floor”readonly f1Floor: number;readonly good: number;readonly items: number;passed
Section titled “passed”readonly passed: boolean;True when the corpus is non-empty AND carries both labels AND f1 >= f1Floor.
An empty corpus, or one with a single label, is a FAILURE rather than a vacuous pass: a mean over nothing is not a measurement, and a gate scored only on good items (or only on bad) cannot have discriminated anything.
perClass
Section titled “perClass”readonly perClass: readonly ClassBreakdown[];precision
Section titled “precision”readonly precision: number;badRejected / (badRejected + goodRejected), four decimals; 0 when nothing was rejected.
recall
Section titled “recall”readonly recall: number;badRejected / (badRejected + badAccepted), four decimals; 0 when nothing was bad.
Type Aliases
Section titled “Type Aliases”AcceptanceLabel
Section titled “AcceptanceLabel”type AcceptanceLabel = "merge" | "keep";Which way the label points. merge: every member folds into the oldest. keep: none does.
AcceptanceStage
Section titled “AcceptanceStage”type AcceptanceStage = | "threshold" | "veto:negation" | "veto:numeric" | "veto:variant" | "self" | "role-guard" | "cap" | "folded";Where a pair’s fate was decided. folded is the outcome the corpus’s merge label asks for.
DivergenceFamily
Section titled “DivergenceFamily”type DivergenceFamily = "negation" | "numeric" | "variant";Which divergence a control embodies. Named on the probe, so a failure says which axis broke.
EvalMode
Section titled “EvalMode”type EvalMode = "live" | "fake";Which embedder produced the numbers. Recorded on the report so a pass is never ambiguous.
GateStage
Section titled “GateStage”type GateStage = | "door:schema" | "door:grounding" | "door:quote" | "phase:refusal" | "accepted";Where a candidate’s fate was decided. accepted is the fifth outcome: it cleared all four.
WritePathLabel
Section titled “WritePathLabel”type WritePathLabel = "good" | "bad";Which way the label points.
Variables
Section titled “Variables”ACCEPTANCE_BASELINE
Section titled “ACCEPTANCE_BASELINE”const ACCEPTANCE_BASELINE: 1 = 1;The measured baseline this floor was derived from, so the derivation is checkable.
Measured 2026-09-11 at memhtml 0.14.0 on corpus v2 (12 merge groups implying 14 pairs, 7 keep groups
implying 7 pairs): acceptance 1.0 (14 of 14), precision 1.0 (no keep pair folded). Corpus v1 under
the rules before this one measured 0.6667 (8 of 12): three incidental-number pairs stopped at
veto:numeric because the numeric predicate read the whole article, and one group-fanout pair
stopped at role-guard because the guard claimed the keeper after the first fold. Both classes now
fold, and the known-gap set is empty.
ACCEPTANCE_CORPUS
Section titled “ACCEPTANCE_CORPUS”const ACCEPTANCE_CORPUS: ReadonlyArray<LabeledGroup>;ACCEPTANCE_FLOOR
Section titled “ACCEPTANCE_FLOOR”const ACCEPTANCE_FLOOR: number;Acceptance floor: the baseline minus five points, floored to two decimals — the discrimination arm’s derivation, for the same reason. The floor is the alarm for a class-sized regression (a predicate that starts firing on a class it did not), and the known-gap set is the alarm for a one-pair one. A floor at the baseline would fire on every deliberate corpus edit.
ACCEPTANCE_KNOWN_GAP_CLASSES
Section titled “ACCEPTANCE_KNOWN_GAP_CLASSES”const ACCEPTANCE_KNOWN_GAP_CLASSES: ReadonlyArray<string> = [];The classes whose label the fold path is KNOWN to disagree with, stated so the tier fails if the set
moves in either direction. Empty as of the rules measured 2026-09-11: every merge pair folds and
every keep pair is refused.
Two classes used to be here. incidental-number (the same claim, one side carrying a date, a ticket
id, or an exit code the other lacks) stopped at veto:numeric while numericTokenDivergent read the
whole gist\nbody_text; it reads the gist now. group-fanout (a group of three implying two pairs
with one keeper) stopped its second pair at role-guard while the guard claimed both roles of a
committed pair; it admits a repeated keeper now.
BATCH_SESSION_IDS
Section titled “BATCH_SESSION_IDS”const BATCH_SESSION_IDS: ReadonlyArray<string>;The batch’s session ids: what the door treats as READABLE.
BEDROCK_TOKEN_VAR
Section titled “BEDROCK_TOKEN_VAR”const BEDROCK_TOKEN_VAR: "AWS_BEARER_TOKEN_BEDROCK" = "AWS_BEARER_TOKEN_BEDROCK";The variable whose presence decides whether live mode can run.
CEILINGS
Section titled “CEILINGS”const CEILINGS: object;The ceilings, restated from apps/consolidator/src/contract.ts so the corpus reads on its own.
Type Declaration
Section titled “Type Declaration”readonly claim: 300 = 300;entities
Section titled “entities”readonly entities: 64 = 64;evidence
Section titled “evidence”readonly evidence: 32 = 32;readonly gist: 1500 = 1500;readonly quote: 600 = 600;DEFAULT_CORPUS_SIZE
Section titled “DEFAULT_CORPUS_SIZE”const DEFAULT_CORPUS_SIZE: 200 = 200;How many base memories a default corpus carries, before controls and the archive tier.
DEFAULT_PROBE_COUNT
Section titled “DEFAULT_PROBE_COUNT”const DEFAULT_PROBE_COUNT: 36 = 36;How many probes a default corpus carries. Design §5 requires at least 30.
DEFAULT_SEED
Section titled “DEFAULT_SEED”const DEFAULT_SEED: 20260802 = 20_260_802;The default seed. Named so a caller changing it is making a visible choice.
DIVERGENCE_FAMILIES
Section titled “DIVERGENCE_FAMILIES”const DIVERGENCE_FAMILIES: ReadonlyArray<DivergenceFamily>;FAKE_DIM
Section titled “FAKE_DIM”const FAKE_DIM: 1024 = EMBED_DIM;The vector width the fake produces. It is the real one, so a width check cannot pass by luck.
GATE_SOURCES
Section titled “GATE_SOURCES”const GATE_SOURCES: ReadonlyArray<{ source: string; stage: GateStage;}>;The gate’s stage table as the manifest records it. One place, so the manifest cannot drift.
GATE_STAGES
Section titled “GATE_STAGES”const GATE_STAGES: ReadonlyArray<GateStage>;KNOWN_GAP_CLASSES
Section titled “KNOWN_GAP_CLASSES”const KNOWN_GAP_CLASSES: ReadonlyArray<string>;The failure classes the gate is KNOWN not to cover, as of the frozen manifest.
Stated in code so the eval can assert the gap is exactly this and no wider: a class that starts failing here is a regression, and a class that starts passing is a change the manifest should record. Three classes:
restatement/*: the TRACE-2 bar is checked as a COUNT of quotes, and nothing dedupes them, so the same line twice clears it.content-leak/*: nothing compares a claim against its own evidence, so a claim that is a verbatim transcript span is written into the corpus as prose.shape/padded-session-id: the door’sungroundedEvidenceReasoncompares ids untrimmed while the phase trims them, so a real quote from a real session is refused for its whitespace. A GOOD item the gate rejects, which is the one false rejection on this corpus.
MANIFEST_FILENAME
Section titled “MANIFEST_FILENAME”const MANIFEST_FILENAME: "write-path-discrimination.manifest.json" = "write-path-discrimination.manifest.json";The frozen manifest’s file name, under packages/eval/fixtures/.
The PATH is the caller’s to resolve: the eval tier resolves it from its own location and
scripts/freeze-write-path.mjs from its own. This module deliberately holds no module-relative URL
resolution, because @memhtml/eval is bundled into the published binary and the packaging census
(tests-integration/tests/packaging.test.ts) requires every run-time asset resolution in shipped
source to be a declared, shipped asset. The manifest is a repo fixture, not a shipped one: no
command reads it, so declaring it would ship a file nothing in the package uses.
MANIFEST_SCHEMA_VERSION
Section titled “MANIFEST_SCHEMA_VERSION”const MANIFEST_SCHEMA_VERSION: 1 = 1;MANY_SPANS
Section titled “MANY_SPANS”const MANY_SPANS: ReadonlyArray<{ quote: string; sessionId: string;}>;Distinct verbatim spans, enough for the evidence-count ceiling cases.
Twenty-six named spans plus windows cut from the long line. Every entry is a substring of its transcript, and the corpus test asserts that for each one.
MRR_FLOOR
Section titled “MRR_FLOOR”const MRR_FLOOR: 0.85 = 0.85;The MRR floor, from design §5. A gate below this admits a target that loses to one of its own negation-flipped twins on one probe in seven. An agent cannot trust that retrieval layer to answer with the right fact.
PROBE_LIMIT
Section titled “PROBE_LIMIT”const PROBE_LIMIT: 40 = 40;How many hits each probe requests.
Wide enough that a control’s rank is observable rather than truncated into null. A window of 10
would report an inversion and a merely-narrow miss identically, and the two need different fixes.
The gate itself compares ranks, so widening the window can only make it stricter.
SESSION_A
Section titled “SESSION_A”const SESSION_A: "session-a" = "session-a";SESSION_B
Section titled “SESSION_B”const SESSION_B: "session-b" = "session-b";SESSION_OUTSIDE_BATCH
Section titled “SESSION_OUTSIDE_BATCH”const SESSION_OUTSIDE_BATCH: "session-z" = "session-z";A session id no transcript in the batch carries.
TRANSCRIPT_A
Section titled “TRANSCRIPT_A”const TRANSCRIPT_A: FixtureTranscript;TRANSCRIPT_B
Section titled “TRANSCRIPT_B”const TRANSCRIPT_B: FixtureTranscript;TRANSCRIPTS
Section titled “TRANSCRIPTS”const TRANSCRIPTS: ReadonlyArray<FixtureTranscript>;WRITE_PATH_BASELINE_F1
Section titled “WRITE_PATH_BASELINE_F1”const WRITE_PATH_BASELINE_F1: 0.9333 = 0.9333;The measured baseline this floor was derived from. Recorded so the derivation is checkable.
WRITE_PATH_CORPUS
Section titled “WRITE_PATH_CORPUS”const WRITE_PATH_CORPUS: ReadonlyArray<LabeledCandidate>;The whole labeled corpus: 31 good, 31 bad, in id order.
WRITE_PATH_F1_FLOOR
Section titled “WRITE_PATH_F1_FLOOR”const WRITE_PATH_F1_FLOOR: number;F1 floor: the baseline minus five points, rounded down to two decimals.
Five points and not zero, because the floor is the alarm for a class-sized regression and the manifest is the alarm for a one-item one; a floor pinned at the baseline would fire on every deliberate corpus edit and train the reader to refreeze without looking.
Functions
Section titled “Functions”acceptanceAgrees()
Section titled “acceptanceAgrees()”function acceptanceAgrees(decision): boolean;True when the decision matches the label.
Parameters
Section titled “Parameters”decision
Section titled “decision”Returns
Section titled “Returns”boolean
acceptanceByClass()
Section titled “acceptanceByClass()”function acceptanceByClass(decisions): readonly AcceptanceClassBreakdown[];Group by class, in first-seen order.
Parameters
Section titled “Parameters”decisions
Section titled “decisions”readonly PairDecision[]
Returns
Section titled “Returns”readonly AcceptanceClassBreakdown[]
admitCandidate()
Section titled “admitCandidate()”function admitCandidate(raw, batch): Effect<GateDecision>;Run one candidate through the gate.
Never fails: a decision is the answer, and the only I/O (fabricatedQuoteReason re-reading a
transcript) already folds an unreadable file into a refusal rather than an error.
Parameters
Section titled “Parameters”unknown
Returns
Section titled “Returns”Effect<GateDecision>
agrees()
Section titled “agrees()”function agrees(decision): boolean;True when the decision matches the label.
Parameters
Section titled “Parameters”decision
Section titled “decision”Returns
Section titled “Returns”boolean
articleFor()
Section titled “articleFor()”function articleFor(spec): string;The article markup for a spec: the claim paragraph, the first body paragraph joined onto it, then the remaining paragraphs and the element kit.
Written here rather than left to renderTemplate’s claim/body path because the kits carry real
markup, and renderTemplate escapes its body strings as text. That is correct for a tool
parameter and wrong for a <dl>.
Parameters
Section titled “Parameters”Returns
Section titled “Returns”string
breakdownByClass()
Section titled “breakdownByClass()”function breakdownByClass(decisions): readonly ClassBreakdown[];Group by class, in first-seen order, so the breakdown reads in corpus order.
Parameters
Section titled “Parameters”decisions
Section titled “decisions”readonly LabeledDecision[]
Returns
Section titled “Returns”readonly ClassBreakdown[]
buildCorpus()
Section titled “buildCorpus()”function buildCorpus(options): CorpusSpec;The whole fixture specification.
The order is fixed as people, base, archive tier, then controls, so the generated tree is written
in a stable order and two runs at one (seed, now) produce byte-identical files. now is
REQUIRED rather than defaulted from a clock, because this module’s contract is purity: every
caller threads its own run instant (the harness reads Effect’s Clock once), it is quantized to
the UTC day, and it is recorded on the spec, so a failing run is reproducible by passing the
reported now back in.
Parameters
Section titled “Parameters”options
Section titled “options”number
The run instant to anchor stamps behind, UTC millis.
probes?
Section titled “probes?”number
number
number
Returns
Section titled “Returns”buildStack()
Section titled “buildStack()”function buildStack(options?): Effect<EvalStack, never, Scope>;Build the whole stack inside a scope: generate the corpus, index it, and return retrieval over it.
Effect.acquireRelease owns the database, so the caller’s Effect.scoped closes the connection,
and the fixture’s own cleanup removes the temp tree. Both matter in a CLI, because a command
that leaked a database handle would keep a WAL file alive under /tmp for the life of the process.
Parameters
Section titled “Parameters”options?
Section titled “options?”StackOptions = {}
Returns
Section titled “Returns”Effect<EvalStack, never, Scope>
confusionOf()
Section titled “confusionOf()”function confusionOf(decisions): ConfusionMatrix;The four cells from the decisions.
Parameters
Section titled “Parameters”decisions
Section titled “decisions”readonly LabeledDecision[]
Returns
Section titled “Returns”corpusSha256()
Section titled “corpusSha256()”function corpusSha256(corpus?): string;The corpus hash. Plain JSON.stringify over static data with a fixed key order.
Parameters
Section titled “Parameters”corpus?
Section titled “corpus?”readonly LabeledCandidate[] = WRITE_PATH_CORPUS
Returns
Section titled “Returns”string
decideAcceptance()
Section titled “decideAcceptance()”function decideAcceptance(corpus?): readonly PairDecision[];Run the corpus through the composed fold path. Pure.
Parameters
Section titled “Parameters”corpus?
Section titled “corpus?”readonly LabeledGroup[] = ACCEPTANCE_CORPUS
Returns
Section titled “Returns”readonly PairDecision[]
decideAll()
Section titled “decideAll()”function decideAll(batch, corpus?): Effect<readonly LabeledDecision[]>;Run every labeled candidate through the gate, in corpus order.
Parameters
Section titled “Parameters”corpus?
Section titled “corpus?”readonly LabeledCandidate[] = WRITE_PATH_CORPUS
Returns
Section titled “Returns”Effect<readonly LabeledDecision[]>
deriveControl()
Section titled “deriveControl()”function deriveControl( target, family, options?): DerivedControl | undefined;Derive one control from a target, or refuse.
Refusal, returned as undefined, is the behavior callers depend on. It has two causes, and each
one would otherwise produce a control that LOOKS adversarial and is not:
- The family does not apply (no number to flip).
- The pair fails the family’s own predicate. For
negationthat means the target body already carried a marker, so the flip is invisible to the guard that defines the family.
A caller that ignores the refusal and ships the pair anyway gets a probe whose control is a paraphrase, and a gate built on paraphrases passes no matter how badly retrieval discriminates.
Parameters
Section titled “Parameters”target
Section titled “target”family
Section titled “family”options?
Section titled “options?”Returns
Section titled “Returns”DerivedControl | undefined
describeAcceptance()
Section titled “describeAcceptance()”function describeAcceptance(report): string;A one-line summary: the numbers, then the first disagreement.
Parameters
Section titled “Parameters”report
Section titled “report”Returns
Section titled “Returns”string
describeFailure()
Section titled “describeFailure()”function describeFailure(report): string;A one-line summary of a failure, for stderr and for the sleep merge’s refusal log.
Names the first inversion rather than every one, because an operator needs a probe to reproduce and a thirty-line dump of a failing gate is a thirty-line dump nobody reads. The full list is on the report.
Parameters
Section titled “Parameters”report
Section titled “report”Returns
Section titled “Returns”string
describeWritePath()
Section titled “describeWritePath()”function describeWritePath(report): string;A one-line summary for stderr and a report subject: the four numbers, then the first disagreement.
Parameters
Section titled “Parameters”report
Section titled “report”Returns
Section titled “Returns”string
discriminate()
Section titled “discriminate()”function discriminate( retrieval, probes, options): Effect<DiscriminationReport, StorageFailure>;Run the suite and summarize it in one call.
Parameters
Section titled “Parameters”retrieval
Section titled “retrieval”RetrievalShape
probes
Section titled “probes”readonly Probe[]
options
Section titled “options”limit?
Section titled “limit?”number
mrrFloor?
Section titled “mrrFloor?”number
Returns
Section titled “Returns”Effect<DiscriminationReport, StorageFailure>
discriminationGate()
Section titled “discriminationGate()”function discriminationGate(options?): Effect<EvalOutcome, DiscriminationFailed, never>;The gate, for MergeOptions.preMergeGate.
A failing gate FAILS this effect, which is what @memhtml/sleep’s merge reads to refuse. It wraps
the gate in Effect.result and turns a failure into refusal: "gate-failed" with main never
moving. The shape matters, because a version returning a boolean would let a caller forget to check
it, and a refusable gate must not be optional at its call site.
Parameters
Section titled “Parameters”options?
Section titled “options?”EvalOptions = {}
Returns
Section titled “Returns”Effect<EvalOutcome, DiscriminationFailed, never>
failingEmbedder()
Section titled “failingEmbedder()”function failingEmbedder(): EvalEmbedder;An embedder that always fails, for the lexical-floor scenario.
The failure travels through the ERROR channel as a typed ModelUnavailable rather than a throw.
The floor only holds if retrieval can catch it, and a defect would kill the fiber instead of
narrowing the search.
Returns
Section titled “Returns”fakeEmbedder()
Section titled “fakeEmbedder()”function fakeEmbedder(): EvalEmbedder;Returns
Section titled “Returns”fakeVector()
Section titled “fakeVector()”function fakeVector(text): Float32Array;The deterministic embedder, built as a hash-seeded bag of words, L2-normalized.
The same construction @memhtml/index and @memhtml/sleep use in their own harnesses. Two texts sharing
vocabulary have a genuinely high cosine and two disjoint texts a low one, which makes a
negation-flipped control a real adversary here instead of a random vector the arm trivially
separates. A random fake would make the gate meaningless in the easy direction and a constant fake
in the hard one.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”Float32Array
familyPredicate()
Section titled “familyPredicate()”function familyPredicate(family): (textA, textB) => boolean;The predicate a family’s control must satisfy against its target.
Parameters
Section titled “Parameters”family
Section titled “family”Returns
Section titled “Returns”(textA, textB) => boolean
hasBedrockCredentials()
Section titled “hasBedrockCredentials()”function hasBedrockCredentials(env?): boolean;True when a Bedrock bearer token is present in the environment.
Parameters
Section titled “Parameters”Readonly<Record<string, string | undefined>> = process.env
Returns
Section titled “Returns”boolean
impliedPairs()
Section titled “impliedPairs()”function impliedPairs(corpus?): readonly object[];The oriented pairs the corpus implies, in group order, exactly as the phase would hand them to
mergeCandidates: groupPairsFor over each group, with corpus order as the age order and
dedupMergeTextFor as the veto’s texts. simFor is empty because no pair here was mined, so every
similarity is the recall floor — the phase’s own value for a group pair it never scored.
A path named by two groups takes its age from its FIRST appearance, the way one corpus row has one offset however many groups a model puts it in. The labeled corpus never repeats a path; the role-guard cases the tests build do, and a last-wins map would re-age a keeper under them.
Parameters
Section titled “Parameters”corpus?
Section titled “corpus?”readonly LabeledGroup[] = ACCEPTANCE_CORPUS
Returns
Section titled “Returns”readonly object[]
liveEmbedder()
Section titled “liveEmbedder()”function liveEmbedder(): Effect<EvalEmbedder>;The real Bedrock embedder, for live mode. Built only when a caller asks for it.
Returns
Section titled “Returns”Effect<EvalEmbedder>
makeFixtureCorpus()
Section titled “makeFixtureCorpus()”function makeFixtureCorpus(options?): Effect<FixtureCorpus>;A scaffolded memory repo carrying a generated corpus.
initRepo is the real one, so the fixture carries the real .gitignore, the real
.gitattributes, and the real merge.ours.driver config. That config is per-clone, and the
merge=ours attribute does nothing without it.
Parameters
Section titled “Parameters”options?
Section titled “options?”FixtureOptions = {}
Returns
Section titled “Returns”Effect<FixtureCorpus>
manifestFor()
Section titled “manifestFor()”function manifestFor(report, context): WritePathManifest;Build the manifest a run would freeze.
Parameters
Section titled “Parameters”report
Section titled “report”context
Section titled “context”frozenAt
Section titled “frozenAt”string
memhtmlVersion
Section titled “memhtmlVersion”string
Returns
Section titled “Returns”memoryFileFor()
Section titled “memoryFileFor()”function memoryFileFor(spec): string;One spec as a memory file’s bytes.
Hand-assembled rather than routed through @memhtml/html’s renderTemplate, because of the
element kits. renderTemplate escapes each body string as TEXT, which is right for an agent’s
tool parameter and wrong for a <dl> the fixture means as markup. The head is written in
META_ORDER so a generated file is byte-identical to what the serializer would emit for the same
metadata. The rest of the system then treats the fixture as an ordinary document.
Parameters
Section titled “Parameters”Returns
Section titled “Returns”string
negationFlip()
Section titled “negationFlip()”function negationFlip(claim): string;The affirmative claim as its negation.
Total. When no verb anchor is present the whole sentence is wrapped rather than left unchanged. A transform that returned its input would produce a “control” identical to its target, and an identical control is a duplicate the content-hash index refuses at index time, one layer too late to explain itself.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”string
numericFlip()
Section titled “numericFlip()”function numericFlip(claim): string | undefined;The claim with its first numeric token replaced by a different one.
undefined when the claim carries no number, because the family does not apply. Inventing a
number to flip would produce a control that differs from its target by an ADDED fact rather than
by a contradicted one. The probe builder reads the undefined and skips the family instead of
emitting a weaker control under the same name.
The replacement is value + 10 for a small integer and value * 2 otherwise, so the wrong
number stays in the plausible range for whatever the sentence counts. A retry budget of 13 is a
believable misremembering of 3, and 3000 is not.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”string | undefined
padTo()
Section titled “padTo()”function padTo(text, length): string;Pad a sentence to EXACTLY length characters with prose filler, for the at-the-ceiling cases.
The filler is words, so the result still slugs to a title and still reads as a sentence, and the last character is forced to a letter so a boundary case cannot end in a space the door might trim.
Parameters
Section titled “Parameters”string
length
Section titled “length”number
Returns
Section titled “Returns”string
quantizeNow()
Section titled “quantizeNow()”function quantizeNow(nowMillis): number;Quantize a run instant to its UTC day.
The anchor is the day rather than the millisecond so that every run on one calendar date builds a byte-identical corpus at a given seed. The gate’s determinism checks compare two full runs, and a millisecond-precision anchor would make “same seed, same numbers” true only for runs started in the same millisecond.
Math.floor rather than a % remainder, because JS % keeps the DIVIDEND’s sign: at
nowMillis = -1, nowMillis % DAY_MILLIS is -1 and subtracting it lands on 0 — an anchor AHEAD
of the instant asked for, which is the one thing the anchoring exists to rule out.
A non-finite instant is refused here rather than carried: NaN passes every arithmetic step and
surfaces hundreds of lines later as RangeError: Invalid time value out of a stamp formatter, which
names neither the input nor the caller that supplied it.
Parameters
Section titled “Parameters”nowMillis
Section titled “nowMillis”number
Returns
Section titled “Returns”number
queryFor()
Section titled “queryFor()”function queryFor(spec, limit?): string;A probe query holds the target’s own content words, in order, capped.
Content words rather than the claim verbatim, and the query design decides what the probe can measure. A NUMERIC-family control differs from its target in exactly one numeric token, so a query that dropped the number would have identical overlap with both and the vector arm could not order them at all. The probe would measure a tie-break instead. Keeping the digits is what makes the numeric family a probe rather than a coin toss. The same reasoning keeps a variant qualifier’s ANCHOR in the query while the qualifier itself, which only the control carries, stays out.
Stopwords go so that not cannot hide behind them. A query carrying the target’s function words
would raise the negation control’s lexical overlap for a reason unrelated to the fact either
states.
Parameters
Section titled “Parameters”limit?
Section titled “limit?”number = 12
Returns
Section titled “Returns”string
readManifest()
Section titled “readManifest()”function readManifest(path): Effect<WritePathManifest>;Read a frozen manifest from path.
Parameters
Section titled “Parameters”string | URL
Returns
Section titled “Returns”Effect<WritePathManifest>
replayDrift()
Section titled “replayDrift()”function replayDrift(frozen, fresh): readonly ReplayDrift[];Every id whose fresh decision differs from the frozen one, plus ids present on one side only.
Per item rather than a hash of the whole list, because a replay that fails has to say WHICH candidate moved and where it now stops; a boolean would send the reader back to run it by hand.
Parameters
Section titled “Parameters”frozen
Section titled “frozen”Returns
Section titled “Returns”readonly ReplayDrift[]
runDiscrimination()
Section titled “runDiscrimination()”function runDiscrimination(options?): Effect<EvalOutcome, never, never>;Run the gate.
Never fails, because the report IS the answer and a caller maps passed to an exit code. An eval
whose error channel could fire would hand a caller a run that both happened and errored, with no
way to say whether the corpus, the index, or the ranking was at fault.
Parameters
Section titled “Parameters”options?
Section titled “options?”EvalOptions = {}
Returns
Section titled “Returns”Effect<EvalOutcome, never, never>
runFloor()
Section titled “runFloor()”function runFloor( retrieval, probes, options?): Effect<FloorReport, StorageFailure>;Parameters
Section titled “Parameters”retrieval
Section titled “retrieval”RetrievalShape
probes
Section titled “probes”readonly Probe[]
options?
Section titled “options?”limit?
Section titled “limit?”number
Returns
Section titled “Returns”Effect<FloorReport, StorageFailure>
runProbes()
Section titled “runProbes()”function runProbes( retrieval, probes, options?): Effect<readonly ProbeResult[], StorageFailure>;Run every probe through the real retrieval service.
includeArchived stays false. The corpus carries an archived tier and an archived memory must not
be a candidate, so a probe that ranked one would be reporting a scope leak rather than a ranking
failure. The controls are ACTIVE files, which is what makes them adversaries.
Parameters
Section titled “Parameters”retrieval
Section titled “retrieval”RetrievalShape
probes
Section titled “probes”readonly Probe[]
options?
Section titled “options?”limit?
Section titled “limit?”number
Returns
Section titled “Returns”Effect<readonly ProbeResult[], StorageFailure>
runWritePathAcceptance()
Section titled “runWritePathAcceptance()”function runWritePathAcceptance(options?): AcceptanceReport;Run the arm end to end. Pure and total: the report is the answer.
Parameters
Section titled “Parameters”options?
Section titled “options?”AcceptanceOptions = {}
Returns
Section titled “Returns”runWritePathDiscrimination()
Section titled “runWritePathDiscrimination()”function runWritePathDiscrimination(options?): Effect<WritePathReport>;Run the arm end to end.
Never fails, for the reason run.ts gives for the retrieval arm: the report IS the answer and a
caller maps passed to an exit code or an assertion. A failing report logs one line at error level.
Parameters
Section titled “Parameters”options?
Section titled “options?”WritePathOptions = {}
Returns
Section titled “Returns”Effect<WritePathReport>
summarize()
Section titled “summarize()”function summarize( mode, results, mrrFloor?): DiscriminationReport;Aggregate probe results into the report the gate reads.
An empty suite is a FAILURE, not a vacuous pass. Zero probes yields mrr: 0, which is below any
floor, so a corpus whose probe generation produced nothing refuses instead of reporting a green gate
over no measurement. A skipped quality gate must not look like a passing one, and “no probes ran” is
the purest form of skipped.
Parameters
Section titled “Parameters”results
Section titled “results”readonly ProbeResult[]
mrrFloor?
Section titled “mrrFloor?”number = MRR_FLOOR
Returns
Section titled “Returns”summarizeAcceptance()
Section titled “summarizeAcceptance()”function summarizeAcceptance( decisions, acceptanceFloor, groups): AcceptanceReport;Aggregate the decisions into the report the gate reads.
Parameters
Section titled “Parameters”decisions
Section titled “decisions”readonly PairDecision[]
acceptanceFloor
Section titled “acceptanceFloor”number
groups
Section titled “groups”number
Returns
Section titled “Returns”summarizeWritePath()
Section titled “summarizeWritePath()”function summarizeWritePath(decisions, f1Floor): WritePathReport;Aggregate the decisions into the report the gate reads.
Parameters
Section titled “Parameters”decisions
Section titled “decisions”readonly LabeledDecision[]
f1Floor
Section titled “f1Floor”number
Returns
Section titled “Returns”transcriptsSha256()
Section titled “transcriptsSha256()”function transcriptsSha256(transcripts?): string;Parameters
Section titled “Parameters”transcripts?
Section titled “transcripts?”readonly FixtureTranscript[] = TRANSCRIPTS
Returns
Section titled “Returns”string
variantFlip()
Section titled “variantFlip()”function variantFlip( claim, anchor, qualifier): string;The claim with a variant qualifier inserted after anchor.
qualifier must be a token @memhtml/domain’s VARIANT_QUALIFIERS knows, or the pair is not
variant-divergent and deriveControl refuses it. Total, so an absent anchor appends a
scope sentence naming the qualifier. That states a different fact about a different variant
instead of a paraphrase.
Parameters
Section titled “Parameters”string
anchor
Section titled “anchor”string
qualifier
Section titled “qualifier”string
Returns
Section titled “Returns”string
wholeText()
Section titled “wholeText()”function wholeText(text): string;The whole text one memory contributes, claim first. What the veto predicates compare.
Parameters
Section titled “Parameters”Returns
Section titled “Returns”string
withBatch()
Section titled “withBatch()”function withBatch<A, E, R>(body, transcripts?): Effect<A, E, R>;Write the transcripts into a fresh temp directory and hand back the batch the door reads.
fabricatedQuoteReason re-reads a cited transcript from its host path, which is the production
shape (the phase never holds transcript bytes), so the fixture has to be on disk. Scoped: the
directory is removed when the caller’s scope closes, on the failure path too.
Type Parameters
Section titled “Type Parameters”A
E
R
Parameters
Section titled “Parameters”(batch) => Effect<A, E, R>
transcripts?
Section titled “transcripts?”readonly FixtureTranscript[] = TRANSCRIPTS
Returns
Section titled “Returns”Effect<A, E, R>
withStack()
Section titled “withStack()”function withStack<A, E, R>(body, options?): Effect<A, E, R>;Build the stack, run body, then tear both down.
A scoped helper rather than a fixture the caller assembles, so the temp tree is removed on the
failure path too. An eval that exits 1 on an inversion must not leave a 200-file corpus under
/tmp every time the gate refuses.
Type Parameters
Section titled “Type Parameters”A
E
R
Parameters
Section titled “Parameters”(stack) => Effect<A, E, R>
options?
Section titled “options?”StackOptions = {}
Returns
Section titled “Returns”Effect<A, E, R>
writeCorpus()
Section titled “writeCorpus()”function writeCorpus( root, git, spec): Effect<number>;Generate the corpus into root, committing it.
ONE commit for the whole corpus. A commit per memory would make the fixture’s git history the
dominant cost of every eval run. The gate is about ranking, and git log over a generated corpus
tells a reader nothing.
Parameters
Section titled “Parameters”string
GitShape
Returns
Section titled “Returns”Effect<number>
writeManifest()
Section titled “writeManifest()”function writeManifest(manifest, path): Effect<void>;Write a frozen manifest to path. Only scripts/freeze-write-path.mjs calls this.
Parameters
Section titled “Parameters”manifest
Section titled “manifest”string | URL
Returns
Section titled “Returns”Effect<void>