LearnNewsExamplesServices
Frontmatter
id12116
titleConsumerFrictionHelper: loud-fail on undefined contextLimitTokens (close NaN-silent-skip hole exposed by SessionService:612 fallback removal)
stateClosed
labels
enhancementaiarchitecturemodel-experience
assigneesneo-opus-ada
createdAtMay 28, 2026, 12:33 AM
updatedAtMay 28, 2026, 8:39 AM
githubUrlhttps://github.com/neomjs/neo/issues/12116
authorneo-opus-ada
commentsCount0
parentIssue12101
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 28, 2026, 8:39 AM

ConsumerFrictionHelper: loud-fail on undefined contextLimitTokens (close NaN-silent-skip hole exposed by SessionService:612 fallback removal)

Closed v13.0.0/archive-v13-0-0-chunk-14 enhancementaiarchitecturemodel-experience
neo-opus-ada
neo-opus-ada commented on May 28, 2026, 12:33 AM

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):

// 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 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

  1. invokeWithGuardrail: validate contextLimitTokens at entry with TypeError('invokeWithGuardrail: contextLimitTokens must be a finite number') when typeof contextLimitTokens !== 'number' || !Number.isFinite(contextLimitTokens).
  2. emitConsumerFriction: same validation on input.contextLimitTokens at entry.
  3. 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

  • AC1: invokeWithGuardrail throws TypeError when contextLimitTokens is undefined, null, NaN, Infinity, or non-number
  • AC2: emitConsumerFriction throws TypeError on same conditions
  • AC3: Error messages name the specific contract so call-site debugging is unambiguous
  • AC4: Unit tests cover all 5 invalid-input cases for both helpers
  • AC5: No regression on the 4 consumer sites migrated by PR #12115 — all pass aiConfig.localModels.chat.contextLimitTokens which IS a finite number when overlays are synced

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".

tobiu referenced in commit 5d99900 - "fix(memory-core): loud-fail invokeWithGuardrail on undefined contextLimitTokens (#12116) (#12121) on May 28, 2026, 8:39 AM
tobiu closed this issue on May 28, 2026, 8:39 AM