Skip to content

fix(console): Show inbox blockers and decision outcomes in the decision inbox - #1086

Merged
Pigbibi merged 4 commits into
mainfrom
fix/console-m09-decision-gaps
Nov 8, 2026
Merged

Pigbibi merged 4 commits into
mainfrom
fix/console-m09-decision-gaps

Conversation

@Pigbibi

@Pigbibi Pigbibi commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

AI assistance

Claude Code (model: Claude Opus 5.5) wrote the change, the tests and this description. A separate independent review accepted exact head ebd305d8a0e390b99cccd18c3459d8dabb6818b7.

What changed

This change closes four confirmed frontend-only M09 gaps in the legacy decision inbox (?page=decisions). Paths are under web/strategy-switch-console/frontend/src/.

  1. Show server blocker codes in the decision detail.
    • The inbox already sends actionable and blocking_reason_codes. They are now read through a new helper, inboxBlockers() (pages/decisions/helpers.ts).
    • Each emitted code maps to the existing bilingual cockpit.blocker.* copy.
    • The list renders as ul.cockpit-blockers and replaces the generic "server does not allow approval" note.
    • Items without these fields behave as before.
  2. Show approved or rejected for decided items.
    • decisionStatusKey() reads decision_state.action: approve, accept and approve_limited_live_canary show as approved; reject and keep_parked show as rejected.
    • An absent or unknown action keeps the existing "decided" label. New zh-CN/en copy: decisions.status.approved and decisions.status.rejected.
    • When decision_state records a decision, the item's actions are hidden.
  3. Wire three tests into CI. console_todo_state_validation, console_load_lifecycle_validation and promotion_queue_partial_validation already passed but did not run in CI. They are now in the frontend test:operations script. No workflow file changed.
  4. Add tests. The new tests/console_decision_inbox_gaps_validation.mjs has 5 node:test cases covering items 1 and 2 and the ?page=decisions&decision=<id> deep link. It is also in test:operations.
    • The deep-link check moved into a pure helper, deepLinkTarget(), so it can be tested. Its behaviour is unchanged.

web/strategy-switch-console/v2_asset_map.js was regenerated with npm run build followed by npm run sync:v2-assets.

Fail-closed semantics

  • An item that carries either field stays blocked unless actionable === true and there are no codes.

    • actionable: false with no codes shows "not allowed".
    • A missing or non-boolean actionable shows the generic line.
    • Codes take precedence over actionable: true.
  • The generic line, "当前不可审批 / Approval is unavailable", is also used for:

    • unmapped codes;
    • malformed entries;
    • a code list that is not an array.

    Repeats collapse to one line. Machine codes are never rendered on screen, which follows the existing "unknowns never render raw" rule.

  • A blocked item disables both approve and decline through decisionActionAllowed(), which also guards onDecide. This matches the server, which returns empty allowed_actions when an item is not actionable.

How verified

All checks ran locally on Node v24.22.1 and Python 3.13.5. CI uses Node 22 and Python 3.12.

Fail-before and pass-after

Run Result
New test file against main (884b780), in a temporary worktree 5 tests: 0 pass, 5 fail
Review-fix assertions against the previous head e16df5f 5 tests: 3 pass, 2 fail (unknown code shown raw; codes without actionable === true not blocked)
New test file at head ebd305d 5 tests: 5 pass, 0 fail

Checks at head

Check Result
npm run build (tsc, vite, size budget) PASS
npm run sync:v2-assets after a rebuild Asset map diff: 0 lines. A sync on unmodified main also gives 0 lines, so the output is deterministic.
console_v2_assets_validation, console_v2_worker_validation PASS (134 references, 42 assets)
npm run test:operations --prefix web/strategy-switch-console PASS; node --test subtests 41/41 (33 before, plus 3 newly wired and 5 new)
19 targeted console, worker and decision scripts 19/19 PASS. research_promotion_application_worker_validation was run without --experimental-default-type, a flag Node 24 removed; CI on Node 22 still accepts it.
UI and i18n checks: console_v2_i18n, console_frontend_i18n, initial_enrollment_ui, console_cockpit_decision, console_read_recovery_ui, credential_maintenance_presentation, console_inbox_requests 7/7 PASS
python3 -m unittest discover -s python/tests Ran 1147, OK (skipped=10)
python3 python/scripts/build_config.py --check; python3 scripts/check_style.py PASS; 0 blocking, 1 pre-existing advisory note

Publication check

The checker is publication-check/2.0-advisory, build 83d9d1130377, in advisory mode (exit 0). Output is masked.

  • Whole repository at head: 4037 findings on 4034 matching lines. The first 20 are all pre-existing CODEOWNERS owner entries:
.github/CODEOWNERS:4  | /.github/CODEOWNERS @<PROJECT_ID>
.github/CODEOWNERS:5  | /.github/workflows/ @<PROJECT_ID>
.github/CODEOWNERS:6  | /actions/publish-runtime-execution-evidence/ @<PROJECT_ID>
.github/CODEOWNERS:7  | /python/scripts/check_credential_maintenance_read.py @<PROJECT_ID>
.github/CODEOWNERS:8  | /python/scripts/credential_maintenance_notify.py @<PROJECT_ID>
.github/CODEOWNERS:9  | /python/scripts/deployment_bundle_contract.py @<PROJECT_ID>
.github/CODEOWNERS:10 | /python/scripts/deterministic_risk_gate.py @<PROJECT_ID>
.github/CODEOWNERS:11 | /python/scripts/execution_evidence_projection.py @<PROJECT_ID>
.github/CODEOWNERS:12 | /python/scripts/forward_observation_risk_control.py @<PROJECT_ID>
.github/CODEOWNERS:13 | /python/scripts/long_horizon_risk_composer.py @<PROJECT_ID>
.github/CODEOWNERS:14 | /python/scripts/long_horizon_risk_composer_v2.py @<PROJECT_ID>
.github/CODEOWNERS:15 | /python/scripts/long_horizon_risk_observation_ingress.py @<PROJECT_ID>
.github/CODEOWNERS:16 | /python/scripts/long_horizon_risk_observation_ingress_v2.py @<PROJECT_ID>
.github/CODEOWNERS:17 | /python/scripts/paper_risk_admission_receipt.py @<PROJECT_ID>
.github/CODEOWNERS:18 | /schemas/qsl-deployment-bundle.v1.schema.json @<PROJECT_ID>
.github/CODEOWNERS:19 | /schemas/qsl-forward-observation-risk-control.v1.schema.json @<PROJECT_ID>
.github/CODEOWNERS:20 | /schemas/qsl-reconcile-only-risk-control.v1.schema.json @<PROJECT_ID>
.github/CODEOWNERS:21 | /scripts/provision_binance_recovery_credentials.sh @<PROJECT_ID>
.github/CODEOWNERS:22 | /scripts/provision_soxl_v7_review_credential.py @<PROJECT_ID>
.github/CODEOWNERS:23 | /web/strategy-switch-console/application/console_api/strategy_execution.js @<PROJECT_ID>
  • The 7 changed files: 20 findings.
    • 7 are the word "synthetic" in the new test's fixture ids and comments. Existing tests use the same convention.
    • The rest are pre-existing lines in the touched files, such as the synthetic copy keys and account.key / className="decision-account-picker", which the masker flags as host-like (false positives).
    • The diff adds no real account, host, bucket, credential or digest value.

Why

The M09 to-do lifecycle review found that the inbox API already returns blocker reasons and the recorded decision outcome, but the console dropped both:

  • an operator saw only a generic "server does not allow approval" note;
  • approved and rejected decisions looked the same.

Three passing tests were not run by CI, and the decision deep link had no test.

Risk

Risk tier: low. The change is frontend display and gating plus tests:

  • web/strategy-switch-console/frontend/src/pages/decisions/{DecisionsPage.tsx,helpers.ts}
  • frontend/src/i18n/{zh-CN,en}.ts
  • frontend/package.json
  • tests/console_decision_inbox_gaps_validation.mjs
  • the generated web/strategy-switch-console/v2_asset_map.js

No backend (worker.js, application/*), schema, settings contract or workflow file changes.

Merge only. This is NOT a release.

  • The uploaded Meridian release candidate stays on HOLD (run 42324348494, uploaded configuration hash mismatch).
  • This PR deploys, uploads and activates nothing, and must not ride on that candidate.
  • Merging updates v2_asset_map.js on main only. Shipping it needs a separate future release with its own acceptance.

Behaviour impact:

  • Client gating is now at least as strict as the server's allowed_actions. If the server ever sent blocker codes while also allowing an action, the client would block it.
  • Items without the new fields are unchanged.

Known non-blocking P3 follow-ups:

  1. The deep-link test reads the page effect's source text, and the render harness patches the page by string replace. Both fail loudly if the code is refactored, but they are brittle.
  2. onDecide still checks the coarse item.status instead of the derived status. The rendered actions are already hidden, and decisionActionAllowed blocks decided items, so this is defense in depth only.
  3. recovery_blocked and recovery_not_awaiting_confirmation use the generic "Approval is unavailable" copy. Specific copy can follow.

Also, the pending view still lists only items whose status is pending or expired. The approved/rejected split therefore shows mainly when decision_state is decided while the coarse status lags.

🤖 Generated with Claude Code

Render server blocking_reason_codes in the decision detail with the
existing cockpit.blocker copy, fail closed on malformed actionability,
split decided items into approved or rejected from decision_state.action,
and extract the deep-link target check so it can be tested.
Regenerate v2_asset_map.js with sync:v2-assets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add console_todo_state, console_load_lifecycle, promotion_queue_partial
and the new decision inbox gap tests to the frontend test:operations
script. The workflow file is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Map unmapped or malformed inbox blocker codes to the generic
"approval unavailable" line instead of rendering the raw code, drop
the cockpit.blocker.other_code_label copy, and require actionable to be
exactly true before an item with blocker fields is treated as open.
Regenerate v2_asset_map.js with sync:v2-assets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 8, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-08T13:44:47.514688Z 5f485fa New commits
🔒 Security Review ✅ Completed 2026-11-08T13:46:32.859826Z 5f485fa New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@Pigbibi Pigbibi added the agent-approved An agent cleared this high-risk change on the current head; not an owner confirmation label Nov 8, 2026
@Pigbibi
Pigbibi merged commit 084ba42 into main Nov 8, 2026
21 of 22 checks passed
@Pigbibi
Pigbibi deleted the fix/console-m09-decision-gaps branch November 8, 2026 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-approved An agent cleared this high-risk change on the current head; not an owner confirmation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants