Premise
PR #12115 retired the SessionService.mjs:612 hidden default fallback (aiConfig.openAiCompatible?.contextLimitTokens || 32768) per the operator's strict feedback_no_hidden_default_fallbacks contract. The deletion exposed a latent contract-gap downstream: ConsumerFrictionHelper.invokeWithGuardrail does NOT validate contextLimitTokens at function entry.
When contextLimitTokens is undefined (the very scenario the operator contract is designed to surface):
const effectiveSafeTokens = Number.isFinite(safeProcessingLimitTokens)
? safeProcessingLimitTokens
: Math.floor(contextLimitTokens * DEFAULT_SAFE_FRACTION);
if (inputTokensEstimate > effectiveSafeTokens) {
}Guardrail pre-check silently skipped, invocation proceeds unguarded, subsequent friction telemetry stores undefined for contextLimitTokens (silent corruption of the friction record downstream). This is a hidden default fallback in disguise — same anti-pattern class PR #12115 removed at SessionService:612, just one substrate-layer deeper in the helper.
Substrate shape
emitConsumerFriction (line 197) validates symptom, emissionPoint, suggestionKind, serviceDomain via TypeError throw. The same loud-fail discipline should extend to contextLimitTokens since the JSDoc explicitly marks it // durable contract (line 54). safeProcessingLimitTokens remains optional with documented default-derivation; only contextLimitTokens becomes required-and-validated.
Prescription
invokeWithGuardrail: validate contextLimitTokens at entry with TypeError('invokeWithGuardrail: contextLimitTokens must be a finite number') when typeof contextLimitTokens !== 'number' || !Number.isFinite(contextLimitTokens).
emitConsumerFriction: same validation on input.contextLimitTokens at entry.
- Unit tests asserting the TypeError for both helpers under missing-config (stub
aiConfig with no localModels.chat.contextLimitTokens).
Contract Ledger
| Surface |
Before |
After |
Consumer |
invokeWithGuardrail contextLimitTokens validation |
Implicit — NaN propagation silently skips pre-check; downstream friction record gets undefined |
Explicit TypeError loud-fail at function entry |
Direct callers + cascading downstream consumers of friction records |
emitConsumerFriction contextLimitTokens validation |
None (JSDoc says required, no runtime check) |
Explicit TypeError loud-fail at function entry |
All friction-emit call sites |
Acceptance Criteria
Related
- Parent: Epic #12101 (greenfield aiConfig substrate redesign)
- Discovered by: PR #12115 retiring
SessionService.mjs:612 hidden default fallback (|| 32768)
- Sibling: #12114 (role-keyed context-limits — this ticket hardens the helper-layer contract that #12114's consumer migration relies on)
Discipline anchor
The || 32768 fallback at SessionService:612 (now deleted) was the visible-layer contract violation. The NaN-silent-skip at ConsumerFrictionHelper.mjs:336-341 is the invisible-layer counterpart — same anti-pattern class, just one substrate-layer deeper. PR #12115 closes the visible-layer instance; this ticket closes the invisible-layer instance. Both together satisfy the operator's contract that "config IS SSOT; ban ||/?? substitution".
Premise
PR #12115 retired the
SessionService.mjs:612hidden default fallback (aiConfig.openAiCompatible?.contextLimitTokens || 32768) per the operator's strictfeedback_no_hidden_default_fallbackscontract. The deletion exposed a latent contract-gap downstream:ConsumerFrictionHelper.invokeWithGuardraildoes NOT validatecontextLimitTokensat function entry.When
contextLimitTokensisundefined(the very scenario the operator contract is designed to surface):// ai/services/memory-core/helpers/ConsumerFrictionHelper.mjs:336-341 const effectiveSafeTokens = Number.isFinite(safeProcessingLimitTokens) ? safeProcessingLimitTokens : Math.floor(contextLimitTokens * DEFAULT_SAFE_FRACTION); // undefined * 0.75 = NaN // Math.floor(NaN) = NaN if (inputTokensEstimate > effectiveSafeTokens) { // (number > NaN) === false // NEVER FIRES — pre-check silently skipped }Guardrail pre-check silently skipped, invocation proceeds unguarded, subsequent friction telemetry stores
undefinedforcontextLimitTokens(silent corruption of the friction record downstream). This is a hidden default fallback in disguise — same anti-pattern class PR #12115 removed at SessionService:612, just one substrate-layer deeper in the helper.Substrate shape
emitConsumerFriction(line 197) validatessymptom,emissionPoint,suggestionKind,serviceDomainviaTypeErrorthrow. The same loud-fail discipline should extend tocontextLimitTokenssince the JSDoc explicitly marks it// durable contract(line 54).safeProcessingLimitTokensremains optional with documented default-derivation; onlycontextLimitTokensbecomes required-and-validated.Prescription
invokeWithGuardrail: validatecontextLimitTokensat entry withTypeError('invokeWithGuardrail: contextLimitTokens must be a finite number')whentypeof contextLimitTokens !== 'number' || !Number.isFinite(contextLimitTokens).emitConsumerFriction: same validation oninput.contextLimitTokensat entry.aiConfigwith nolocalModels.chat.contextLimitTokens).Contract Ledger
invokeWithGuardrailcontextLimitTokensvalidationTypeErrorloud-fail at function entryemitConsumerFrictioncontextLimitTokensvalidationTypeErrorloud-fail at function entryAcceptance Criteria
invokeWithGuardrailthrowsTypeErrorwhencontextLimitTokensisundefined,null,NaN,Infinity, or non-numberemitConsumerFrictionthrowsTypeErroron same conditionsaiConfig.localModels.chat.contextLimitTokenswhich IS a finite number when overlays are syncedRelated
SessionService.mjs:612hidden default fallback (|| 32768)Discipline anchor
The
|| 32768fallback at SessionService:612 (now deleted) was the visible-layer contract violation. The NaN-silent-skip at ConsumerFrictionHelper.mjs:336-341 is the invisible-layer counterpart — same anti-pattern class, just one substrate-layer deeper. PR #12115 closes the visible-layer instance; this ticket closes the invisible-layer instance. Both together satisfy the operator's contract that "config IS SSOT; ban||/??substitution".