Skip to content

3 of 6 InspectionResult values are declared but unreachable #764

Open

Description

@saintmalik

Summary

InspectionResult in cmcp_runtime.audit.chain declares six Literal values, but only three are ever assigned to response_inspection_result anywhere in the codebase:

Value Emitted? Where
size_exceeded ✅ proxy.py — size-check terminal path
injection_detected ✅ proxy.py — injection-scan terminal path
n/a ✅ chain.py — _append_session_start default
schema_violation ❌ Stage 2 schema violations flow to policy_decision, never to response_inspection_result
surplus_stripped ❌ Schema redaction emits policy_decision="redact", not a response_inspection_result
pass ❌ Inspection pipeline returns "allow"/"deny" as decisions; "pass" is never written

The type definition at chain.py:44–46:

InspectionResult = Literal[
    "pass", "injection_detected", "schema_violation",
    "surplus_stripped", "size_exceeded", "n/a"
]

Repro

# from repo root, at main HEAD
rg '"schema_violation"|"surplus_stripped"' src/cmcp_runtime/ --type py
# returns zero matches for response_inspection_result assignment

rg '"pass"' src/cmcp_runtime/audit/ --type py
# only match is the "pass" literal inside the Literal[] type itself

Impact

  • Downstream consumers that branch on "schema_violation" or "surplus_stripped" from a TRACE claim or audit record will never see those values.
  • The type overstates the vocabulary the gateway actually emits, which is a spec-vs-implementation drift.

Options

  1. Implement the missing paths — wire Stage 2 schema violations to emit response_inspection_result="schema_violation", surplus-field stripping to "surplus_stripped", and clean inspection passes to "pass".
  2. Trim the type — remove the three unreachable values from InspectionResult and document that schema violations are recorded only in policy_decision.
  3. Intentional forward-reservation — if these are placeholders for planned inspection stages, document that in the type or a # TODO so the gap is explicit.

Activity

  1. dinakarjs commented on Oct 16, 2026

    @dinakarjs
    Contributor

    Independent reproduction at main 19895e4 (Linux/Python 3.12.14) confirms the gateway emission gap, with a useful qualification: these labels are not globally unreachable. AuditChain.append accepts all six declared values; I appended each and verified the resulting hash chain. They are absent from the gateway's own emitting paths.

    Observed gateway case (mocked upstream/scanner) allowed policy_decision response_inspection_result
    Clean response true allow null
    Scanner-sanitized response true allow null
    Scanner refusal false deny injection_detected
    Oversized response false deny size_exceeded

    AST inspection across src/cmcp_runtime finds three constant-producing call sites: session_start -> n/a, proxy size refusal -> size_exceeded, proxy scanner refusal -> injection_detected. The other call sites forward the caller-supplied value. The standalone InspectionPipeline separately reports schema denial through final_decision/stage_results and redaction through stripped_fields/modified_response; its result is a different class from audit.chain.InspectionResult. It is not evidence that the gateway emits schema_violation or surplus_stripped.

    Recommendation for a bounded next change: retain reader/type compatibility while the maintainer decides whether pass/schema_violation/surplus_stripped are reserved or must be wired to explicitly defined producer paths. Do not equate generic scanner sanitization with schema-surplus stripping, or delete historical vocabulary merely because the current producer omits it. Please rule the producer contract and whether clean calls should emit pass versus null before a runtime change.

    This is source inspection plus executed synthetic software controls, not live-provider or TRACE conformance acceptance. No production code or audit semantics changed; #764 remains open for that decision.

  2. imran-siddique commented on Oct 16, 2026

    @imran-siddique
    Member

    @saintmalik confirmed, and @dinakarjs's reproduction matches. Wire all three and keep the type: an inspected response that passed writes pass, schema-surplus stripping writes surplus_stripped, a schema refusal writes schema_violation. null stays only where no inspection ran, so "not inspected" and "inspected and clean" read differently. Scanner sanitization is not surplus stripping; say in the PR what it writes.

    Want to take it? Each value needs a positive and a refusal case, and audit/ is a security path, so two approvals.

  3. saintmalik commented on Nov 9, 2026

    @saintmalik
    Author

    @imran-siddique Thank you, i believe @dinakarjs is already working on it

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions