fix(memory): print the downstream drop counters, not just compute them (D2 follow-up)
07f8438 added `skippedScore` / `skippedNoEvidence` / `truncated` to the recall
result. They were correct and covered, and completely invisible: the debug log
line still printed only `skipped` / `skippedDim`, and `stats().last_recall`
carried only `skipped_fp` / `skipped_dim`.
A counter nobody can read is indistinguishable from no counter. The whole point
of D2 was that an operator whose threshold is swallowing the corpus reads
`skipped=0` and concludes nothing was excluded — that operator reads the LOG, not
the returned array.
## What this adds
- the debug line now ends its degradation group with
`... skippedScore=<n> skippedNoEvidence=<n> truncated=<n> err=<...>`
- `last_recall` carries `skipped_score` / `skipped_no_evidence` / `truncated`,
so the durable surface says the same thing the transient line does
Nothing else changes: the counters were already computed, and the existing
`skipped=0 skippedDim=0` substring assertions still match because the new fields
follow them.
## Criterion
L2 in `test/memory-log.test.js` already asserts the degradation group is printed
in one line; it now also requires
`skippedScore=0 skippedNoEvidence=0 truncated=0` to be present. That is the
"printed, not merely computed" half — the values themselves are pinned by
`test/recall-accounting.test.js` (non-zero cases).
Mutation: deleting the three fields from the log line turns L2 red —
not ok 2 - L2: debugLog=true emits one line covering recall, budget, degradation and injection
error: 'downstream drop counters'
Gates: npm test 298/298 (0 skipped), npm run check 12/12, memory-log 16/16.
This commit is contained in:
13
lib/index.js
13
lib/index.js
@ -296,6 +296,7 @@ export class StableMem extends Service {
|
||||
* disagree — the failure mode D4 exists to prevent.
|
||||
*
|
||||
* @type {{mode: string, skipped_fp: number, skipped_dim: number,
|
||||
* skipped_score: number, skipped_no_evidence: number, truncated: number,
|
||||
* embed_error: string|null, at: number} | null}
|
||||
*/
|
||||
lastRecall = null
|
||||
@ -555,10 +556,18 @@ export class StableMem extends Service {
|
||||
// case; 'ok' is recorded explicitly so the stats reader never has to
|
||||
// distinguish "no field" from "no problem".
|
||||
mode: hits.degraded ?? 'ok',
|
||||
// `mode` says whether the answer is trustworthy. The three counters below
|
||||
// say how many candidates were dropped DOWNSTREAM of the space filter —
|
||||
// without them an operator whose threshold is swallowing the corpus reads
|
||||
// `skipped=0` and concludes nothing was excluded. See
|
||||
// recall-accounting.test.js (D2).
|
||||
skipped_fp: hits.skipped ?? 0,
|
||||
// Real, not a placeholder: the recall path applies the F4b dimension filter
|
||||
// and reports it separately from the space skip.
|
||||
skipped_dim: hits.skippedDim ?? 0,
|
||||
skipped_score: hits.skippedScore ?? 0,
|
||||
skipped_no_evidence: hits.skippedNoEvidence ?? 0,
|
||||
truncated: hits.truncated ?? 0,
|
||||
embed_error: hits.degradedError ?? null,
|
||||
at: Date.now(),
|
||||
}
|
||||
@ -670,7 +679,9 @@ export class StableMem extends Service {
|
||||
}`,
|
||||
`degraded=${degraded} skipped=${raw?.skipped ?? 0} skippedDim=${
|
||||
raw?.skippedDim ?? 0
|
||||
} err=${summarizeError(raw?.degradedError)}`,
|
||||
} skippedScore=${raw?.skippedScore ?? 0} skippedNoEvidence=${
|
||||
raw?.skippedNoEvidence ?? 0
|
||||
} truncated=${raw?.truncated ?? 0} err=${summarizeError(raw?.degradedError)}`,
|
||||
`inject=${injectChars}ch`,
|
||||
].join(' | ')
|
||||
|
||||
|
||||
@ -170,6 +170,9 @@ test('L2: debugLog=true emits one line covering recall, budget, degradation and
|
||||
// degradation: the empty case is reported as healthy, explicitly.
|
||||
assert.match(record.text, /degraded=no_embedder/, 'the mode is named, not omitted')
|
||||
assert.match(record.text, /skipped=0 skippedDim=0/, 'skip counters')
|
||||
// D2: the three downstream counters must be PRINTED, not merely computed.
|
||||
// A counter nobody can read is indistinguishable from no counter at all.
|
||||
assert.match(record.text, /skippedScore=0 skippedNoEvidence=0 truncated=0/, 'downstream drop counters')
|
||||
assert.match(record.text, /err=-/, 'no error digest when there is no error')
|
||||
|
||||
// injection: the exact string length that reached the model.
|
||||
|
||||
Reference in New Issue
Block a user