Skip to content

fix(rubric): harden text schema callback boundary - #992

Merged
seonghobae merged 16 commits into
mainfrom
fix/rubric-text-callback-safety-991
Aug 24, 2026
Merged

fix(rubric): harden text schema callback boundary#992
seonghobae merged 16 commits into
mainfrom
fix/rubric-text-callback-safety-991

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Closes #991.
Closes #996.

Scope

Harden the shared rubric/scoring text and metadata-scalar trust boundary without moving any psychometric/statistical arithmetic out of Rust.

  • rubric.models._text() admits exact built-in strings before normalization, preventing hostile str subclasses from executing overridden text callbacks.
  • shared rubric enum admission rejects hostile string subclasses before Enum value lookup while retaining exact built-in serialized values and exact enum members.
  • scoring contracts that reuse the shared rubric helpers inherit the same zero-dispatch text boundary.
  • rubric.audit._enum_value() applies the same exact-member/exact-built-in-string admission rule to CandidateAuditFinding.severity, CandidateAuditReport.lifecycle_state, and pilot-admission lifecycle state.
  • scoring metadata preflight converts admitted str/int/float subclasses through inert built-in descriptors before generic JSON freezing, so subclass encode, comparison, or numeric-conversion callbacks cannot execute during canonicalization.

Evidence lineage

  • RED/GREEN rubric and enum line: 8881e5d...35dd1fa...3363717... / 6f7fd8b....
  • Audit enum RED/GREEN: b424d17...ecb481a....
  • Scoring metadata RED/GREEN: 5e34ccc...744b9dd....
  • CI repair: 59722b8fb5c0854069d64c06a7f485789222c581.

CI root cause and correction

The previous exact-head Python 3.12 and 3.14 failures occurred during pytest collection, not product execution: placing _HostileText("pilot") directly in pytest.mark.parametrize caused pytest ID generation to call the intentionally hostile .encode() method. The regression now parametrizes trusted raw values/types and constructs the hostile scalar inside the test body, preserving the public callback-safety assertion without executing caller code during collection.

Current exact-head evidence

Current head: 59722b8fb5c0854069d64c06a7f485789222c581 against protected main@04d0bc21a2a20693bcf16108cd76d394fe844d23.

Repository CI, Security Scan, CodeQL, and Semgrep are terminal-success on this exact head, and current inline review threads are empty. Two formal OpenCode CHANGES_REQUESTED reviews remain in history: the first evaluated predecessor 9a46ff2...; the second evaluated this exact head and failed only because central coverage-evidence run 32213304749 could not materialize the trusted base Python lock before PR-controlled tests ran. The first causal boundary was central .github tooling, not this repository's product source. Central protected main has since advanced to 9e9f59f3ac1e96a960c021b131d768c238f4c21a via .github#1136, which repairs the trusted-uv target-triple verification boundary. Fresh same-head OpenCode/coverage review has been requested under that corrected central contract. Keep this PR unmerged until the new same-head required evidence is terminal-success; do not transfer either predecessor-head evidence or the pre-fix infrastructure verdict as product approval.

This is Python schema validation/canonicalization only. No likelihood, estimator, optimizer, score statistic, or other psychometric arithmetic moves out of Rust.


Open in Devin Review

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ef38fc1-bf0d-4a24-9094-5e1e7979f61c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a8f07d7-11ec-45dc-af55-d488cf3e2a94

📥 Commits

Reviewing files that changed from the base of the PR and between 04d0bc2 and 9a46ff2.

📒 Files selected for processing (4)
  • docs/changelog.d/991-rubric-text-trust-boundary.md
  • python/fast_mlsirm/rubric/models.py
  • tests/test_rubric_text_callback_safety.py
  • tests/test_scoring_contract_public_callback_safety.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Rubric schema validation now rejects caller-defined str subclasses before invoking overridden callbacks. Exact built-in strings retain normalization. Enum validation rejects unsupported input types. Regression tests cover rubric fields, nested evidence text, error callbacks, collection exceptions, and compatibility behavior.

Changes

Rubric text trust boundary

Layer / File(s) Summary
Exact string and enum validation
python/fast_mlsirm/rubric/models.py
Text fields now require exact built-in str values. Enum validation accepts enum members or exact built-in strings and reports allowed values for other inputs.
Callback-safety regression coverage
tests/test_rubric_text_callback_safety.py, tests/test_scoring_contract_public_callback_safety.py, docs/changelog.d/991-rubric-text-trust-boundary.md
Tests verify that hostile callbacks are not executed, built-in strings remain normalized, collection KeyboardInterrupt behavior remains unchanged, and the changelog documents the boundary.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 9a46f

The change narrowly hardens schema validation for hostile string subclasses while preserving exact-string behavior, and no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes reject string subclasses before callbacks, preserve built-in string behavior, and add the required public-constructor regression tests for issue #991.
Out of Scope Changes check ✅ Passed The changes remain within rubric schema validation, related scoring-contract tests, and changelog documentation; no report, estimator, or psychometric changes are included.
Docstring Coverage ✅ Passed Docstring coverage is 89.47% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: hardening the rubric text schema callback boundary.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/rubric-text-callback-safety-991

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae
seonghobae marked this pull request as ready for review August 18, 2026 18:02
@seonghobae
seonghobae enabled auto-merge (squash) August 18, 2026 18:02

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 9a46ff2f21c62d9d4b7be49af798c0962387a7ed.

  • Head SHA: 9a46ff2f21c62d9d4b7be49af798c0962387a7ed

  • Workflow run: 32174385733

  • Workflow attempt: 1

Coverage evidence

Coverage evidence job did not run or did not publish coverage evidence.

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Docs: 991-rubric-text-trust-boundary.md"]
  S1 --> I1["operator or user guidance"]
  I1 --> R1["Review risk: Docs: 991-rubric-text-trust-boundary.md"]
  R1 --> V1["docs review"]
  Evidence --> S2["Changed file: models.py"]
  S2 --> I2["repository behavior"]
  I2 --> R2["Review risk: Changed file: models.py"]
  R2 --> V2["required checks"]
  Evidence --> S3["Test (2 files)"]
  S3 --> I3["regression suite"]
  I3 --> R3["Review risk: Test (2 files)"]
  R3 --> V3["targeted test run"]
Loading

@opencode-agent

opencode-agent Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: d559e55f1e5d5d07d3da47c1b2d67e0ea9a083ca
  • Workflow run: 32623122947
  • Workflow attempt: 1
  • Gate result: REQUEST_CHANGES (approval step)

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head d559e55f1e5d5d07d3da47c1b2d67e0ea9a083ca.

  • Head SHA: d559e55f1e5d5d07d3da47c1b2d67e0ea9a083ca

  • Workflow run: 32623122947

  • Workflow attempt: 1

Coverage evidence

Coverage Decision

  • Result: FAIL
  • Test evidence: not proven passing
  • Docstring evidence: not proven passing when configured
  • Failure count: 2

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Docs: 991-rubric-text-trust-boundary.md"]
  S1 --> I1["operator or user guidance"]
  I1 --> R1["Review risk: Docs: 991-rubric-text-trust-boundary.md"]
  R1 --> V1["docs review"]
  Evidence --> S2["Changed file (4 files)"]
  S2 --> I2["repository behavior"]
  I2 --> R2["Review risk: Changed file (4 files)"]
  R2 --> V2["required checks"]
  Evidence --> S3["Test (4 files)"]
  S3 --> I3["regression suite"]
  I3 --> R3["Review risk: Test (4 files)"]
  R3 --> V3["targeted test run"]
Loading

@opencode-agent
opencode-agent Bot disabled auto-merge August 18, 2026 20:51

Copy link
Copy Markdown
Contributor Author

@opencode-agent Please re-review the current exact head 7efbe904da3766dcf2e8cdeb5f76c4a531df79e8. The existing formal CHANGES_REQUESTED evaluated predecessor head 9a46ff2f21c62d9d4b7be49af798c0962387a7ed, so it is historical for this head. The central coverage/tooling owner has since advanced on .github main; evaluate only current-head evidence and do not transfer predecessor checks/reviews.

@seonghobae
seonghobae marked this pull request as draft August 19, 2026 00:06

Copy link
Copy Markdown
Contributor Author

@opencode-agent Please re-review the current exact head ecb481a05a16587ac2af70202340d9ccf6b9d20d. The existing REQUEST_CHANGES is anchored to predecessor head 9a46ff2f21c62d9d4b7be49af798c0962387a7ed and coverage run 32174385733. Current-head CodeQL, Security Scan, and Semgrep are successful; repository CI/coverage evidence must be evaluated on this exact head before any Ready/merge decision. Please do not transfer predecessor-head evidence.

@seonghobae
seonghobae marked this pull request as ready for review August 19, 2026 02:23
@seonghobae
seonghobae enabled auto-merge (squash) August 19, 2026 02:23

Copy link
Copy Markdown
Contributor Author

@opencode-agent Please re-review current exact head 59722b8fb5c0854069d64c06a7f485789222c581. The only formal CHANGES_REQUESTED review evaluates predecessor 9a46ff2f21c62d9d4b7be49af798c0962387a7ed; since then this branch added the audit-enum and scoring-metadata callback boundaries plus the pytest-collection-safe regression repair. Repository CI, Security Scan, CodeQL, and Semgrep are terminal-success on 59722b8..., and current inline review threads are empty. Generate fresh formal current-head evidence; do not transfer predecessor-head coverage/review conclusions.

Copy link
Copy Markdown
Contributor Author

@opencode-agent @cwl-noema-review Please independently review exact current head 59722b8fb5c0854069d64c06a7f485789222c581 against live main. Repository CI, Security Scan, CodeQL, and Semgrep are terminal-success on this head and inline review threads are empty. Reassess the callback-free rubric/scoring trust boundaries and do not transfer the historical OpenCode coverage review from predecessor head 9a46ff2f21c62d9d4b7be49af798c0962387a7ed.

Copy link
Copy Markdown
Contributor Author

@opencode-agent review

Fresh exact-head re-review requested for 59722b8fb5c0854069d64c06a7f485789222c581 against protected main@04d0bc21a2a20693bcf16108cd76d394fe844d23. The prior same-head coverage-evidence failure occurred in central trusted-uv materialization before PR-controlled tests ran; ContextualWisdomLab/.github#1136 has since merged the target-triple verification repair. Please regenerate coverage/review evidence on this unchanged head; predecessor and pre-repair verdicts remain historical only.

@seonghobae
seonghobae enabled auto-merge (squash) August 20, 2026 17:55
@opencode-agent

Copy link
Copy Markdown
Contributor

Scheduled review-feedback autofix for this PR head.

  • Head SHA: 59722b8fb5c0854069d64c06a7f485789222c581

@opencode-agent
opencode-agent Bot disabled auto-merge August 20, 2026 19:52
@seonghobae
seonghobae enabled auto-merge (squash) August 20, 2026 20:04

Copy link
Copy Markdown
Contributor Author

@opencode-agent @cwl-noema-review Re-review the exact current head 59722b8fb5c0854069d64c06a7f485789222c581.

The prior current-head REQUEST_CHANGES only reported unavailable/failed coverage evidence from an earlier run. Current CI, Security Scan, SAST Semgrep, and CodeQL runs for this exact head are terminal-success. Re-evaluate this head and publish independent approvals only from fresh evidence; do not reuse prior-head reviews. Protected merge policy remains unchanged.

@opencode-agent
opencode-agent Bot disabled auto-merge August 20, 2026 22:11
@opencode-agent

Copy link
Copy Markdown
Contributor

Scheduled review-feedback autofix for this PR head.

  • Head SHA: 59722b8fb5c0854069d64c06a7f485789222c581

@seonghobae
seonghobae enabled auto-merge (squash) August 20, 2026 22:44

Copy link
Copy Markdown
Contributor Author

@opencode-agent Please perform a review-only formal review of exact current PR head 59722b8fb5c0854069d64c06a7f485789222c581. Re-check changed-file scope, current-head findings, unresolved threads, mergeability, and every required Check. Do not reuse a stale review, mutate the branch, self-approve, or merge.

@opencode-agent
opencode-agent Bot disabled auto-merge August 21, 2026 00:48
@seonghobae

Copy link
Copy Markdown
Contributor Author

Current-head review record: 777a8c8. CodeGraph review confirmed the PR keeps callback-sensitive rubric text and enum normalization at the Python trust boundary, with scoring metadata scalar normalization producing inert built-in values. The only local 100% docstring gap was the new nested hostile text fixture; its class and strip method now have docstrings. Focused behavioral tests: 96 passed; Ruff, compileall, interrogate 100%, and diff clean. Hosted checks must rerun and be read against this exact SHA. Normal squash auto-merge is enabled as the only merge path and still requires the independent formal review gate.

@seonghobae
seonghobae enabled auto-merge (squash) August 21, 2026 01:06

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

@seonghobae

Copy link
Copy Markdown
Contributor Author

Added current-head callback-boundary remediation: item-bank lifecycle evidence enums now admit only exact built-in strings or exact enum members, and a hostile-string regression proves no equality/hash callback dispatch. Focused tests: 24 passed; Ruff, compileall, interrogate 100%, and diff checks passed. The synchronized head is now d559e55; waiting only for the normal exact-head protected checks/review path.

@opencode-agent opencode-agent Bot added area: data Database, schema, migration, ETL, or lineage area: security Security boundary, hardening, or vulnerability prevention priority: medium Normal-priority or P2 work status: needs-review Open pull request requiring current-head review or checks type: bug Defect or incorrect behavior labels Aug 22, 2026

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head d559e55f1e5d5d07d3da47c1b2d67e0ea9a083ca.

  • Head SHA: d559e55f1e5d5d07d3da47c1b2d67e0ea9a083ca

  • Workflow run: 32623122947

  • Workflow attempt: 1

Coverage evidence

Coverage Decision

  • Result: FAIL
  • Test evidence: not proven passing
  • Docstring evidence: not proven passing when configured
  • Failure count: 2

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Docs: 991-rubric-text-trust-boundary.md"]
  S1 --> I1["operator or user guidance"]
  I1 --> R1["Review risk: Docs: 991-rubric-text-trust-boundary.md"]
  R1 --> V1["docs review"]
  Evidence --> S2["Changed file (4 files)"]
  S2 --> I2["repository behavior"]
  I2 --> R2["Review risk: Changed file (4 files)"]
  R2 --> V2["required checks"]
  Evidence --> S3["Test (4 files)"]
  S3 --> I3["regression suite"]
  I3 --> R3["Review risk: Test (4 files)"]
  R3 --> V3["targeted test run"]
Loading

@opencode-agent
opencode-agent Bot disabled auto-merge August 23, 2026 07:27
@seonghobae
seonghobae enabled auto-merge (squash) August 24, 2026 01:08
@opencode-agent
opencode-agent Bot disabled auto-merge August 24, 2026 02:18
@opencode-agent

Copy link
Copy Markdown
Contributor

Scheduled review-feedback autofix for this PR head.

  • Head SHA: d559e55f1e5d5d07d3da47c1b2d67e0ea9a083ca

@seonghobae
seonghobae merged commit bc0377a into main Aug 24, 2026
38 checks passed
@seonghobae
seonghobae deleted the fix/rubric-text-callback-safety-991 branch August 24, 2026 06:06
seonghobae added a commit that referenced this pull request Aug 24, 2026
…st (#1324)

* fix(ci): pin grm-recovery rust toolchain and align metadata scalar test

Two independent contract-test regressions on main, both caught by the
repository's own CI (python 3.12/3.14 jobs), unrelated to any in-flight
PR's diff:

- .github/workflows/statistical-studies.yml: PR #1012 added the
  grm-recovery job's dtolnay/rust-toolchain step without the
  `with: toolchain: 1.97.1` pin every sibling step carries, letting it
  silently float to the default stable channel. Added the missing pin
  and updated test_rust_toolchain_contract.py's expected step count for
  statistical-studies.yml from 4 to 5 to reflect the now-fully-pinned,
  legitimate fifth job.

- tests/test_scoring_contract_metadata.py: PR #992 introduced
  _normalize_metadata_scalar, which safely accepts str/int/float
  subclasses by copying their value through the inert base-type
  descriptor (str.__str__, matching the existing int.__int__/
  float.__float__ handling in the same function) without invoking any
  subclass-defined method. The pre-existing
  test_metadata_rejects_string_subclasses_before_callbacks predates
  that change and still asserted outright rejection. Updated the test
  to assert the actual, intentional behavior: zero callback
  invocations and safe normalization to a plain str.

* fix(tests): strengthen hostile metadata subclass check, fix stale changelog ref

Addresses two CodeRabbit findings on this PR:

- The changelog fragment still named the test's old identifier
  (test_metadata_rejects_string_subclasses_before_callbacks) after it
  was renamed to test_metadata_normalizes_string_subclasses_without_callbacks.
- _HostileMetadataText only overrode strip/encode, so the test could
  not detect whether normalization happened through the safe
  str.__str__ base descriptor or the caller-overridable str(value)
  path. Added a __str__ override that raises, closing that gap.
  Confirmed the test still passes: _normalize_metadata_scalar genuinely
  uses the safe str.__str__ descriptor, not the overridable path.

* fix(changelog): resync rendered CHANGELOG.md with the updated fragment

The prior commit renamed the test referenced in
docs/changelog.d/1300-ci-contract-drift.md but didn't re-run
render_changelog_fragments.py --update, leaving the rendered
Unreleased block carrying the old test name. Caught by Devin review:
render_changelog_fragments.py --check, run by the release-tag
workflow, fails closed on this exact mismatch.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: data Database, schema, migration, ETL, or lineage area: security Security boundary, hardening, or vulnerability prevention priority: medium Normal-priority or P2 work status: needs-review Open pull request requiring current-head review or checks type: bug Defect or incorrect behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(rubric): harden audit enum callback boundary Harden rubric text schema boundary against caller string subclasses

1 participant