Skip to content

scientific(selection): bind candidate K to fitted topic dimension #676

Open

Description

@seonghobae

Finding

model_selection::statistical_candidate_from_fit(input, candidate_k, model) accepts the candidate label separately from the fitted model. The function computes a finite Schwarz-style score and free-parameter count from model, but it never verifies that candidate_k equals the fitted topic dimension.

That means a dimensionally valid 2-topic ReferenceTopicModel can be scored and emitted as ModelCandidate::statistical(3, ...). select_candidate_k(...) then treats the mislabeled K as statistical authority, so the model-selection decision can attribute one fit's diagnostic to a different topic count.

The ordinary select_fitted_candidate_k(...) loop currently passes matching values, but the public scoring API itself is not fail closed and can manufacture a scientifically false K/diagnostic association.

Required RED

At the public statistical_candidate_from_fit boundary:

  • a valid model scored with its actual topic count remains accepted;
  • the same model passed with a different candidate_k returns ModelSelectionError::InvalidDiagnostic before creating ModelCandidate;
  • the repair does not alter likelihood arithmetic, free-parameter counting, candidate Pareto logic, fitted estimator arithmetic, or LLM refusal rules.

A small synthetic unit/integration fixture is sufficient because this is an API identity invariant, not scientific acceptance evidence.

Minimal causal direction

Derive the fitted topic count from the model's document-topic coordinate width, validate that it is representable as u32, and require exact equality with candidate_k before calculating or emitting the candidate score. Keep existing dimensional validation in free_parameter_count as defense in depth.

Boundary

This issue closes only K/fit label integrity. It does not solve the separate detached input/model substitution path in statistical_candidate_from_fit, Evidence provenance (#527/#658/#671/#675), fitted-topic semantic identity (#663), or release projection (#664).

Refs #638 #639 #668 #670.

Activity

  1. seonghobae commented on Sep 22, 2026

    @seonghobae
    ContributorAuthor

    Ordinary-forward implementation on canonical #639 lane:

    • RED a60bd819d5b4cbadf2e8a2723451eb102467a8e0: public integration contract constructs a valid two-topic model, proves K=2 remains accepted, and requires the same fit mislabeled as K=3 to fail closed. The superseded RED head did not produce a hosted failing result; Security/Semgrep/CodeQL/Documentation were cancelled and Rust Foundation remained queued when the repair advanced.
    • minimal repair 30ba85058a374f25d8cff1d41d477832ba38f0dd: statistical_candidate_from_fit preserves candidate_k < 2 -> NonPositiveCandidateK, runs existing model-dimension validation, derives the fitted topic count from topic_term_probabilities.len() with checked u32 conversion, and rejects a mismatched label as InvalidDiagnostic before likelihood/score emission. RED→repair compare is one production file, +14/-3; estimator arithmetic, likelihood formula, free-parameter formula, Pareto gate and LLM refusal are unchanged.

    This closes only K/fit-label integrity. The public scorer still accepts detached ReferenceTopicInput + ReferenceTopicModel; a dimension-compatible A-input/B-model substitution remains a separate owner-binding gap and should be repaired through ReferenceTopicFit, not by inventing a digest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions