feat(dashboard): quantify cases and preserve project journeys - #640
feat(dashboard): quantify cases and preserve project journeys#640seonghobae wants to merge 286 commits into
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Important Review skippedToo many files! This PR contains 157 files, which is 57 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (157)
You can disable this status message by setting the 📝 WalkthroughWalkthrough이번 변경은 운영 대시보드에 외부 정보 범위, 사례 생명주기, 누락 근거, 토픽 컨텍스트를 추가합니다. Ask 응답은 인용 이벤트 타임라인을 제공합니다. 잔여 계산은 Changes운영 분석과 대시보드
Estimated code review effort: 5 (Critical) | ~120 minutes Merge Risk: 🟠 High · up to The PR adds persisted topic and case evidence plus dashboard journey rendering, but the current head still contains migration paths that can fail during deployment or leave accepted evidence without required provenance, alongside missing citation fallbacks and incorrect journey/status rendering. These are concrete data and correctness risks, so the PR is not merge-ready until the migration and display issues are fixed or explicitly accepted. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 59.79% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 194 functions across 52 files. (31 skipped: 31 unsupported.) ✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
|
Refreshed |
|
Local validation on the exact baseline-refresh tree: |
|
Exact-head review reconciliation (2026-08-26):
These are exact-head validations; no stale review snapshot was transferred as merge evidence. Hosted Checks and independent approval remain authoritative. |
|
Refreshed the baseline at |
|
Verified and fixed the current Devin findings at head |
| const voiceLabels = { | ||
| voc: "Voice of Customer", | ||
| vocc: "Voice of Customer's customer", | ||
| voco: "Voice of Competitor", | ||
| vom: "Voice of Market", | ||
| vop: "Voice of Partner", | ||
| } as const; |
There was a problem hiding this comment.
🟡 Blank category names for new Voice-of-X codes
voiceLabels maps only the five original voice codes, but the summary aggregates all twelve codes that source posts can carry (ADR 0246 adds seven). A supplier, employee, regulator, investor, society, business, or process voice category resolves through t(voiceLabels[code]) to an empty label and renders its count with no name.
Prompt for agents
VoiceTaxonomySummary's voiceLabels map only covers the original five voc_type codes (voc, vocc, voco, vom, vop), but the voice-taxonomy summary aggregates category counts over post_voice_classification_assertion, which ADR 0246 / migration 0230 expanded to twelve codes (vos, voe, vob, vor, voi, voso, vops). When a category for one of the seven new codes is returned, voiceLabels[code] is undefined and the row renders with no name. Add English labels (and matching i18n entries in frontend/src/i18n.ts for each supported locale) for the seven new codes, and widen the category_memberships voice_concept_code union type in api.ts so the type reflects all twelve codes.
Was this helpful? React with 👍 or 👎 to provide feedback.
| async def defer_post_content_job( | ||
| conn: asyncpg.Connection, | ||
| post_id: str, | ||
| *, | ||
| expected_attempt_count: int, | ||
| retry_after_seconds: int, | ||
| ) -> bool: | ||
| """Return one unadmitted lease to queued without consuming an attempt.""" | ||
| if type(retry_after_seconds) is not int or retry_after_seconds <= 0: | ||
| raise ValueError("retry_after_seconds must be a positive integer") | ||
| updated = await conn.execute( | ||
| """ | ||
| update post_content_ingestion_job | ||
| set status_code = $2, | ||
| attempt_count = attempt_count - 1, | ||
| queued_at = now(), | ||
| next_attempt_at = now() + make_interval(secs => $5), | ||
| started_at = null, | ||
| completed_at = null, | ||
| updated_at = now(), | ||
| last_error_code = $6, | ||
| last_error_detail = $7 | ||
| where post_id = $1 | ||
| and status_code = $3 | ||
| and attempt_count = $4 | ||
| and attempt_count > 0 | ||
| """, | ||
| post_id, | ||
| QUEUED, | ||
| RUNNING, | ||
| expected_attempt_count, | ||
| retry_after_seconds, | ||
| "no_viable_agent", | ||
| "Analysis capacity is being restored; this record will retry automatically.", | ||
| ) | ||
| if not updated.endswith(" 1"): | ||
| return False | ||
| await _record_status( | ||
| conn, | ||
| post_id, | ||
| QUEUED, | ||
| failure_code="no_viable_agent", | ||
| detail_text="Analysis capacity is being restored; this record will retry automatically.", | ||
| ) | ||
| return True |
There was a problem hiding this comment.
📝 Info: Admission deferral correctly does not consume an attempt
defer_post_content_job decrements the attempt increment applied by _claim_job, fenced on the exact running attempt, so a no_viable_agent deferral consumes no retry budget and a stale worker cannot defer a newer lease.
Was this helpful? React with 👍 or 👎 to provide feedback.
…t-double-contract ci: restore Dashboard stack validation contracts
| ), scoped_post as ( | ||
| select visible_post.post_id | ||
| from visible_post | ||
| where $5::boolean is false | ||
| or exists ( | ||
| select 1 | ||
| from classified | ||
| where classified.post_id = visible_post.post_id | ||
| and classified.case_kind_code = 'external_information' | ||
| ) | ||
| ) | ||
| select (select count(*) from visible_post) as total_post_count, | ||
| (select count(*) from classified) as total_event_count, | ||
| (select count(*) | ||
| from post_summary_event summary_event | ||
| where exists ( | ||
| select 1 from classified | ||
| where classified.post_id = summary_event.post_id | ||
| and ($5::boolean is false | ||
| or classified.case_kind_code = 'external_information') | ||
| )) as total_event_count, | ||
| (select count(distinct post_id) from classified | ||
| where case_kind_code = 'external_information') as external_post_count, | ||
| (select count(*) from visible_post | ||
| (select count(*) from scoped_post | ||
| where not exists ( | ||
| select 1 from operations_case_analysis analysis | ||
| where analysis.post_id = visible_post.post_id | ||
| where analysis.post_id = scoped_post.post_id | ||
| ) and not exists ( | ||
| select 1 from post_content_ingestion_job job | ||
| where job.post_id = visible_post.post_id | ||
| where job.post_id = scoped_post.post_id | ||
| and job.status_code = 'post_content_ingestion_failed' | ||
| )) as pending_analysis_count, | ||
| (select count(*) from visible_post | ||
| (select count(*) from scoped_post | ||
| where exists ( | ||
| select 1 from post_content_ingestion_job job | ||
| where job.post_id = visible_post.post_id | ||
| where job.post_id = scoped_post.post_id | ||
| and job.status_code = 'post_content_ingestion_failed' | ||
| )) as failed_analysis_count | ||
| """, |
There was a problem hiding this comment.
📝 Info: External-only scoping keeps full coverage denominator
total_post_count, total_event_count, and external_percent use visible_post (all authorized posts), while pending/failed counts use scoped_post, which narrows to external_information only under external_only. The frontend hides pending/failed in that mode, so the split matches the stated coverage-denominator intent. No count inconsistency found.
Was this helpful? React with 👍 or 👎 to provide feedback.
| union all | ||
| select fact.post_id, fact.case_kind_code, fact.fact_type_code | ||
| from operations_case_fact fact | ||
| join source_post post on post.post_id = fact.post_id | ||
| join source_post evidence_post on evidence_post.post_id = fact.evidence_post_id | ||
| where {visible} | ||
| and not ({visible_evidence}) | ||
| and ($5::boolean is false or fact.case_kind_code = 'external_information') | ||
| order by post_id, case_kind_code, fact_type_code | ||
| """, | ||
| *args, | ||
| ) |
There was a problem hiding this comment.
📝 Info: Facts with unauthorized evidence surface as missing facts
The missing_rows UNION ALL reclassifies operations_case_fact rows whose evidence post is no longer visible as missing facts. This matches ADR 0206's re-check of current ABAC for each cited evidence post before returning a span. Behavior is intentional.
Was this helpful? React with 👍 or 👎 to provide feedback.
…t-double-contract fix: restore Global Ask worker capabilities
* fix(ui): replace internal-boundary customer copy * fix(ui): remove remaining internal customer copy --------- Co-authored-by: Codex <codex@localhost>
* test(storybook): cover Global Ask next action * fix(ask): offer an actionable retry path --------- Co-authored-by: Codex <codex@localhost>
* ops: capture worker cgroup memory evidence * fix: preserve terminal worker memory evidence * fix: reject ambiguous worker containers * fix(ops): preserve optional cgroup event evidence * docs: refresh Dashboard exact-head evidence * docs: record Storybook follow-up merge --------- Co-authored-by: Codex <codex@localhost>
* feat(leftover): persist leftover-map explained share Name e = R̂² / R² of raw residual after two-axis Gabriel reconstruction on leftover pair rows (ADR 0232 / migration 0232). Unexplained leftover share s stays omitted. A finite share greater than 1 is stored, never clamped. Next action opens the named post. * fix(report): align comparison leftover evidence * fix(report): project Rust explained share * build: pin reviewed interaction-map contract * test(schema): apply explained-share migration * build: pin unified Rust envelope head * build: pin envelope parity proof --------- Co-authored-by: Codex <codex@localhost>
* fix: pin validated structured workflow runtime * fix: install structured schema runtime dependency * fix: install orchestrator locked runtime manifest * test: smoke locked telemetry exporter * fix: pin protected orchestrator delivery * Revert "fix: pin protected orchestrator delivery" This reverts commit 3dec60c. --------- Co-authored-by: Codex <codex@localhost>
| def heartbeat_has_advanced( | ||
| heartbeat_path: Path = HEARTBEAT_PATH, | ||
| state_path: Path = HEALTHCHECK_STATE_PATH, | ||
| ) -> bool: | ||
| """Return whether the heartbeat advanced since the prior health probe.""" | ||
| try: | ||
| current = int(heartbeat_path.read_text(encoding="ascii")) | ||
| except (FileNotFoundError, ValueError): | ||
| return False | ||
| previous: int | None = None | ||
| try: | ||
| previous = int(state_path.read_text(encoding="ascii")) | ||
| except (FileNotFoundError, ValueError): | ||
| pass | ||
| state_path.write_text(str(current), encoding="ascii") | ||
| return current >= 0 and (previous is None or current > previous) |
There was a problem hiding this comment.
📝 Info: Cross-process monotonic heartbeat comparison
The heartbeat file stores time.monotonic_ns() written by the worker while heartbeat_has_advanced compares it across processes from the healthcheck. Python leaves the monotonic reference undefined across processes; correctness relies on Linux CLOCK_MONOTONIC being boot-based. Fragile if the platform assumption changes.
Was this helpful? React with 👍 or 👎 to provide feedback.
Persist leftover-map unexplained leftover share s = U² / R² of raw residual on leftover post–criterion pairs so the leftover the truncated two-axis map cannot reconstruct is not read as leftover residual R, leftover-map distance d, unexplained leftover U, or leftover-map cross share x. Do not persist leftover-map explained share e. After make seed, closest and farthest leftover pairs sit above the member list with U²/R² next to leftover-map distance d; click opens that post. Missing or non-finite share omits the badge rather than inventing a leftover score. A share greater than 1 is stored, never clamped. The grouping comparison strip stays on its reduced leftover payload (distance, residual, reconstruction). Independent of leftover stacks #640, #680, #720 and dashboard explained share ADR 0232 (#728).
Persist leftover-map unexplained leftover share s = U² / R² of raw residual on leftover post–criterion pairs so the leftover the truncated two-axis map cannot reconstruct is not read as leftover residual R, leftover-map distance d, unexplained leftover U, or leftover-map cross share x. Do not persist leftover-map explained share e. After make seed, closest and farthest leftover pairs sit above the member list with U²/R² next to leftover-map distance d; click opens that post. Missing or non-finite share omits the badge rather than inventing a leftover score. A share greater than 1 is stored, never clamped. The grouping comparison strip stays on its reduced leftover payload (distance, residual, reconstruction). Independent of leftover stacks #640, #680, #720 and dashboard explained share ADR 0232 (#728).
Summary
Verification
uv run --extra dev pytest -q tests/test_operations_dashboard.py tests/test_public_docstrings.py(4 passed before the multi-project extension; focused dashboard rerun 2 passed afterward)corepack pnpm exec vitest run src/components/OperationsDashboard.test.tsx src/components/WorkspaceNav.test.tsx src/i18n.test.ts src/App.test.tsx(173 passed)corepack pnpm lintcorepack pnpm buildcorepack pnpm build-storybookNo keyword, heuristic, arbitrary threshold, or local measurement weight is introduced.
Summary by CodeRabbit
새 기능
버그 수정
문서