fix(rectification): close round A/2 tail gaps in tests, CI, and stale-score reuse #47

Closed
jesse wants to merge 0 commits from codex/rectification-tails-20260901 into staging
Owner

Summary

Round A/2 tail work from TASK-rectification-tails-20260901.md, based on origin/staging @ 26ea3f06 (task brief on top of 8743dcb1).

Hard red lines kept: no change to deliveryCapability / canAdopt / canConfirmExactMinute / engine gate semantics; CI checks only added, never removed or relaxed; skill version not bumped.

Task A — window_scan from_sign / to_sign

tests/test_rectification_diagnostics_clusters.py::test_window_scan_reports_d9_diversity_with_sign_names expected {layer, at, user_meaning} only. window_scan already emits from_sign/to_sign (金牛座天蝎座); that contract is used by frontend varga contrast. The assertion was updated to include those fields. The engine output was not stripped.

Before (unittest on this test):

FAIL: test_window_scan_reports_d9_diversity_with_sign_names
AssertionError: Lists differ: [{'layer': 'd9', 'at': '05:14', 'user_meaning': 'D9 在 05:14 发生变化', 'from_sign': '金牛座', 'to_sign': '天蝎座'}] != [{'layer': 'd9', 'at': '05:14', 'user_meaning': 'D9 在 05:14 发生变化'}]
Ran 1 test in 0.000s
FAILED (failures=1)

After:

Ran 1 test in 0.000s
OK

Full discover after the fix: PYTHONPATH="$PWD:$PWD/scripts" python -m unittest discover -s tests -p "test_*rectification*.py"79 tests, 0 fail.

Task B — why the red test was invisible in CI

Workflow Trigger What it runs
.gitea/workflows/backend-quality-gate.yml push: branches: [staging] (auto) python scripts/run_quality_gate.py --profile quick --skip-yoga-logic --skip-frontend-runtime
.gitea/workflows/test.yml workflow_dispatch only full python -m pytest
.gitea/workflows/ci.yml workflow_dispatch only same quick profile as staging, also manual

The auto staging gate’s pytest list is CORE_PYTEST_TARGETS in scripts/run_quality_gate.py. That list had tests/test_candidate_discriminator_contract.py (BUG-393) but not tests/test_*rectification*.py. The diagnostics assertion could stay red on origin/staging indefinitely because the only pipelines that would have seen it never run on push.

Fix (add-only): append "tests/test_rectification_*.py" to CORE_PYTEST_TARGETS. No workflow trigger conditions were changed; no existing target was removed. A pin test (test_staging_quick_gate_runs_rectification_python_suite) keeps the glob in that list.

python -m pytest tests/test_rectification_*.py locally: 137 collected, 0 fail. This PR does not claim a green Gitea run until backend-quality-gate actually executes on this SHA, and does not claim staging deployed.

Task C — human smoke sheet (not executed)

No staging/production credentials in this session. Do not treat this as pass or fail. Leave the sheet for someone who can open https://staging.jyotisha.chat after this SHA is on staging.

Executor: unassigned
Environment: not run
Time: not run

# Check Result
1 New account opens a case: natural opening, no fixed questionnaire dump not executed
2 3 domains, 5 dated events (at least one day-level); each confirmed; recap uses display_date_label not executed
3 After server distinguish questions, current_question is non-empty or case is adoptable (non-terminal exit) not executed
4 Occupation asked then answered or refused; candidate card appears; can_adopt=true, can_confirm_exact_minute=false not executed
5 Adopt: RPC succeeds, status candidate_accepted, writes accepted (not confirmed), enters verify_adopted_time (at most two items) not executed
6 Holdout “doesn’t match” path can switch to another candidate not executed
7 Post-adopt narration has credible interval + representative minute + “not a confirmed unique birth minute”; prospective_probes narrated if present not executed
8 Accident case f83d9b42 (if still reachable): GET refresh is adoptable; adopt succeeds not executed
9 After new-evidence rescore: receipt algorithm_version = rectification-v5-matrix-scoring-7, policy_version = rectification-candidate-policy-v3, audit row 「换运贴近度」 not executed

Any mismatch → new BUG record, no drive-by code change.

Task D — option 1 (chosen)

Choice: option 1. Stale stored policy_version / algorithm_version is treated as “must rescore” on the compare-candidates cache skip in scoreAndPersistCurrentEvidence.

Why not 2: GET overlay scoreableSnapshotCurrentFromDossier copying stored policy onto both sides is a display-staleness signal. It does not force the compare path that actually writes a new result. Adopt is already allowed; the miss is transition-proximity rescoring, which lives on compare.

Why not 3: Idle cases would keep v6/v2 scores until the next evidence write. That leaves “换运贴近度” off the card after a policy bump even when the user compares again with unchanged evidence.

Why 1: Smallest change that hits the real skip (evidenceLedgerFingerprint + candidateRangeFingerprint only). Live identity is not hardcoded as v3 / matrix-scoring-7 in TS. Order:

  1. RECTIFICATION_DECISION_POLICY_VERSION / RECTIFICATION_ALGORITHM_VERSION
  2. RECTIFICATION_ENGINE_VERSION only when it is not the coarse product id rectification-v5
  3. GET /api/rectification/v5/versions, which returns the same algorithm_version / decision_policy_version fields as /api/rectification/v5/score without scoring

Cache reuse still uses the existing persist idempotency keys. No silent batch rescore of all cases. Old results keep invalidated_at / superseded semantics. Matching fingerprints + matching live identity still skip the engine (existing test updated to mock GET versions).

New tests:

  • matching fingerprints still rescore when stored policy lags the live engine
  • compare-candidates rescores when stored policy lags the live engine

Verification

  • unittest discover -s tests -p "test_*rectification*.py": 79, fail=0
  • python -m pytest tests/test_rectification_*.py: 137, fail=0
  • cd frontend && ./node_modules/.bin/tsc --noEmit: exit 0
  • tsx --test tests/rectification-*.test.ts tests/skill-registry.test.ts: 756 pass, fail=0 (rectification ≥738; skill-registry 16/16)

Not done here: FF-push to staging, Task C smoke, skill bump.

Follow-up — expand pytest globs before argv (901cdb96)

run() uses subprocess.run(..., shell=False), so a literal tests/test_rectification_*.py in argv is not expanded by a shell. A pin that only asserted the glob string existed could stay green while pytest never collected those files.

_expand_pytest_targets() now expands targets containing *?[ via ROOT.glob, sorted, relative to ROOT. Zero matches raise SystemExit. Ordinary paths are kept as-is. The gate calls:

run([PYTHON, "-m", "pytest", *_expand_pytest_targets(pytest_targets)])

The pin test_staging_quick_gate_runs_rectification_python_suite now also imports _expand_pytest_targets and asserts the glob expands to ≥6 files (zero-match glob raises SystemExit).

CORE_PYTEST_TARGETS rerun (no shell glob)

Invoked as Python argv after _expand_pytest_targets(CORE_PYTEST_TARGETS) — not pytest tests/test_rectification_*.py via zsh.

expanded_count 29
expanded_targets
tests/test_cli_smoke.py
tests/test_api_server_security.py
tests/test_jaimini.py
tests/test_shadbala_complete.py
tests/test_transit_trigger.py
tests/test_oracle_collection_queue.py
tests/test_oracle_evidence_validator.py
tests/test_external_oracle_sanity_closure.py
tests/test_consultation_consumer_context.py
tests/test_declared_window_chart.py
tests/test_candidate_discriminator_contract.py
tests/test_rectification_confirmation_and.py
tests/test_rectification_decision_tree_doc.py
tests/test_rectification_diagnostics_clusters.py
tests/test_rectification_engine_convergence.py
tests/test_rectification_event_probes.py
tests/test_rectification_family_appearance_scoring.py
tests/test_rectification_horary_observation.py
tests/test_rectification_house_table.py
tests/test_rectification_input_contract.py
tests/test_rectification_kp_cusp_observation.py
tests/test_rectification_missing_layer_integration_plan.py
tests/test_rectification_oracle_input_contract.py
tests/test_rectification_provenance.py
tests/test_rectification_refinement_packet.py
tests/test_rectification_technique_contract.py
tests/test_rectification_three_engine_packet.py
tests/test_rectification_v5_services.py
tests/test_rectification_v5_vedastro_validation.py
471 passed, 1 skipped, 147 warnings in 734.40s (0:12:14)
exit 0
## Summary Round A/2 tail work from `TASK-rectification-tails-20260901.md`, based on `origin/staging` @ `26ea3f06` (task brief on top of `8743dcb1`). Hard red lines kept: no change to `deliveryCapability` / `canAdopt` / `canConfirmExactMinute` / engine gate semantics; CI checks only added, never removed or relaxed; skill version not bumped. ## Task A — window_scan `from_sign` / `to_sign` `tests/test_rectification_diagnostics_clusters.py::test_window_scan_reports_d9_diversity_with_sign_names` expected `{layer, at, user_meaning}` only. `window_scan` already emits `from_sign`/`to_sign` (`金牛座` → `天蝎座`); that contract is used by frontend varga contrast. The assertion was updated to include those fields. The engine output was not stripped. **Before** (`unittest` on this test): ``` FAIL: test_window_scan_reports_d9_diversity_with_sign_names AssertionError: Lists differ: [{'layer': 'd9', 'at': '05:14', 'user_meaning': 'D9 在 05:14 发生变化', 'from_sign': '金牛座', 'to_sign': '天蝎座'}] != [{'layer': 'd9', 'at': '05:14', 'user_meaning': 'D9 在 05:14 发生变化'}] Ran 1 test in 0.000s FAILED (failures=1) ``` **After:** ``` Ran 1 test in 0.000s OK ``` Full discover after the fix: `PYTHONPATH="$PWD:$PWD/scripts" python -m unittest discover -s tests -p "test_*rectification*.py"` → **79 tests, 0 fail**. ## Task B — why the red test was invisible in CI | Workflow | Trigger | What it runs | |---|---|---| | `.gitea/workflows/backend-quality-gate.yml` | `push: branches: [staging]` (auto) | `python scripts/run_quality_gate.py --profile quick --skip-yoga-logic --skip-frontend-runtime` | | `.gitea/workflows/test.yml` | `workflow_dispatch` only | full `python -m pytest` | | `.gitea/workflows/ci.yml` | `workflow_dispatch` only | same quick profile as staging, also manual | The auto staging gate’s pytest list is `CORE_PYTEST_TARGETS` in `scripts/run_quality_gate.py`. That list had `tests/test_candidate_discriminator_contract.py` (BUG-393) but **not** `tests/test_*rectification*.py`. The diagnostics assertion could stay red on `origin/staging` indefinitely because the only pipelines that would have seen it never run on push. **Fix (add-only):** append `"tests/test_rectification_*.py"` to `CORE_PYTEST_TARGETS`. No workflow trigger conditions were changed; no existing target was removed. A pin test (`test_staging_quick_gate_runs_rectification_python_suite`) keeps the glob in that list. `python -m pytest tests/test_rectification_*.py` locally: **137 collected, 0 fail**. This PR does not claim a green Gitea run until `backend-quality-gate` actually executes on this SHA, and does not claim staging deployed. ## Task C — human smoke sheet (not executed) No staging/production credentials in this session. **Do not treat this as pass or fail.** Leave the sheet for someone who can open `https://staging.jyotisha.chat` after this SHA is on staging. Executor: _unassigned_ Environment: _not run_ Time: _not run_ | # | Check | Result | |---|---|---| | 1 | New account opens a case: natural opening, no fixed questionnaire dump | not executed | | 2 | 3 domains, 5 dated events (at least one day-level); each confirmed; recap uses `display_date_label` | not executed | | 3 | After server distinguish questions, `current_question` is non-empty or case is adoptable (non-terminal exit) | not executed | | 4 | Occupation asked then answered or refused; candidate card appears; `can_adopt=true`, `can_confirm_exact_minute=false` | not executed | | 5 | Adopt: RPC succeeds, status `candidate_accepted`, writes `accepted` (not confirmed), enters `verify_adopted_time` (at most two items) | not executed | | 6 | Holdout “doesn’t match” path can switch to another candidate | not executed | | 7 | Post-adopt narration has credible interval + representative minute + “not a confirmed unique birth minute”; `prospective_probes` narrated if present | not executed | | 8 | Accident case `f83d9b42` (if still reachable): GET refresh is adoptable; adopt succeeds | not executed | | 9 | After new-evidence rescore: receipt `algorithm_version` = `rectification-v5-matrix-scoring-7`, `policy_version` = `rectification-candidate-policy-v3`, audit row 「换运贴近度」 | not executed | Any mismatch → new BUG record, no drive-by code change. ## Task D — option 1 (chosen) **Choice:** option 1. Stale stored `policy_version` / `algorithm_version` is treated as “must rescore” on the compare-candidates cache skip in `scoreAndPersistCurrentEvidence`. **Why not 2:** GET overlay `scoreableSnapshotCurrentFromDossier` copying stored policy onto both sides is a display-staleness signal. It does not force the compare path that actually writes a new result. Adopt is already allowed; the miss is transition-proximity rescoring, which lives on compare. **Why not 3:** Idle cases would keep v6/v2 scores until the next evidence write. That leaves “换运贴近度” off the card after a policy bump even when the user compares again with unchanged evidence. **Why 1:** Smallest change that hits the real skip (`evidenceLedgerFingerprint` + `candidateRangeFingerprint` only). Live identity is **not** hardcoded as `v3` / `matrix-scoring-7` in TS. Order: 1. `RECTIFICATION_DECISION_POLICY_VERSION` / `RECTIFICATION_ALGORITHM_VERSION` 2. `RECTIFICATION_ENGINE_VERSION` only when it is not the coarse product id `rectification-v5` 3. `GET /api/rectification/v5/versions`, which returns the same `algorithm_version` / `decision_policy_version` fields as `/api/rectification/v5/score` without scoring Cache reuse still uses the existing persist idempotency keys. No silent batch rescore of all cases. Old results keep `invalidated_at` / superseded semantics. Matching fingerprints + matching live identity still skip the engine (existing test updated to mock GET versions). New tests: - `matching fingerprints still rescore when stored policy lags the live engine` - `compare-candidates rescores when stored policy lags the live engine` ## Verification - `unittest discover -s tests -p "test_*rectification*.py"`: **79**, fail=0 - `python -m pytest tests/test_rectification_*.py`: **137**, fail=0 - `cd frontend && ./node_modules/.bin/tsc --noEmit`: exit 0 - `tsx --test tests/rectification-*.test.ts tests/skill-registry.test.ts`: **756** pass, fail=0 (rectification ≥738; skill-registry 16/16) Not done here: FF-push to `staging`, Task C smoke, skill bump. ## Follow-up — expand pytest globs before argv (`901cdb96`) `run()` uses `subprocess.run(..., shell=False)`, so a literal `tests/test_rectification_*.py` in argv is not expanded by a shell. A pin that only asserted the glob string existed could stay green while pytest never collected those files. `_expand_pytest_targets()` now expands targets containing `*?[` via `ROOT.glob`, sorted, relative to `ROOT`. Zero matches raise `SystemExit`. Ordinary paths are kept as-is. The gate calls: ```python run([PYTHON, "-m", "pytest", *_expand_pytest_targets(pytest_targets)]) ``` The pin `test_staging_quick_gate_runs_rectification_python_suite` now also imports `_expand_pytest_targets` and asserts the glob expands to **≥6** files (zero-match glob raises `SystemExit`). ### CORE_PYTEST_TARGETS rerun (no shell glob) Invoked as Python argv after `_expand_pytest_targets(CORE_PYTEST_TARGETS)` — not `pytest tests/test_rectification_*.py` via zsh. ``` expanded_count 29 expanded_targets tests/test_cli_smoke.py tests/test_api_server_security.py tests/test_jaimini.py tests/test_shadbala_complete.py tests/test_transit_trigger.py tests/test_oracle_collection_queue.py tests/test_oracle_evidence_validator.py tests/test_external_oracle_sanity_closure.py tests/test_consultation_consumer_context.py tests/test_declared_window_chart.py tests/test_candidate_discriminator_contract.py tests/test_rectification_confirmation_and.py tests/test_rectification_decision_tree_doc.py tests/test_rectification_diagnostics_clusters.py tests/test_rectification_engine_convergence.py tests/test_rectification_event_probes.py tests/test_rectification_family_appearance_scoring.py tests/test_rectification_horary_observation.py tests/test_rectification_house_table.py tests/test_rectification_input_contract.py tests/test_rectification_kp_cusp_observation.py tests/test_rectification_missing_layer_integration_plan.py tests/test_rectification_oracle_input_contract.py tests/test_rectification_provenance.py tests/test_rectification_refinement_packet.py tests/test_rectification_technique_contract.py tests/test_rectification_three_engine_packet.py tests/test_rectification_v5_services.py tests/test_rectification_v5_vedastro_validation.py ``` ``` 471 passed, 1 skipped, 147 warnings in 734.40s (0:12:14) exit 0 ```
jesse added 1 commit 2026-09-01 18:19:28 +08:00
fix(rectification): close round A/2 tail gaps in tests, CI, and stale-score reuse
Independent Staging Quality Gate / validate (pull_request) Failing after 6m13s
Independent Staging Quality Gate / publish (pull_request) Has been skipped
c48a965640
Window_scan assertions now match the public from_sign/to_sign contract, the
staging quick gate runs the rectification Python suite, and compare-candidates
rescores when stored policy lags the live engine identity.

Co-authored-by: Cursor <cursoragent@cursor.com>
jesse added 1 commit 2026-09-01 19:14:10 +08:00
fix(rectification): expand quality-gate pytest globs before argv
Independent Staging Quality Gate / validate (pull_request) Successful in 13m37s
Independent Staging Quality Gate / publish (pull_request) Has been skipped
901cdb96ef
subprocess.run does not shell-expand tests/test_rectification_*.py, so a
string pin could stay green while the suite never ran. Expand glob targets
to real files and fail closed on zero matches.

Co-authored-by: Cursor <cursoragent@cursor.com>
jesse closed this pull request 2026-09-03 16:57:02 +08:00
jesse deleted branch codex/rectification-tails-20260901 2026-09-03 16:57:03 +08:00
Some checks are pending
Independent Staging Quality Gate / validate (pull_request) Successful in 13m37s
Independent Staging Quality Gate / publish (pull_request) Has been skipped

Pull request closed

Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: root/Jyotisha#47