diff --git a/.claude/state/orchestrator.json b/.claude/state/orchestrator.json index 2e845ec6..27acec02 100644 --- a/.claude/state/orchestrator.json +++ b/.claude/state/orchestrator.json @@ -1,7 +1,7 @@ { "_comment": "Persistent orchestrator state — survives across Claude Code sessions. Updated by /discover, /sync-backlog, /healthcheck, and /orchestrate.", - "last_updated": "2026-03-11T14:00:00Z", - "last_phase_completed": 15, + "last_updated": "2026-03-11T18:00:00Z", + "last_phase_completed": 16, "last_phase_result": "success", "current_metrics": { "build_errors": 0, @@ -165,12 +165,12 @@ "notification_preferences": true, "user_profile_page": true, "navigation_component": true, - "multi_page_routing": false, - "role_based_ui": false, - "widget_prds_implemented": 13, + "multi_page_routing": true, + "role_based_ui": true, + "widget_prds_implemented": 17, "widget_prds_total": 17, - "component_test_count": 1, - "component_test_coverage_pct": 2, + "component_test_count": 12, + "component_test_coverage_pct": 25, "e2e_tests_real_api": false, "visual_regression": false, "lighthouse_ci": false, @@ -180,16 +180,16 @@ "frontend_terraform": true, "state_management": "zustand", "error_handling": "interceptors+boundaries+toast", - "grade": "B" + "grade": "A-" }, "frontend_backlog": { "p0_critical": { "total": 4, "done": 4, "items": ["FE-001 API client gen [DONE]", "FE-002 Replace mocked APIs [PARTIAL]", "FE-003 SignalR client [DONE]", "FE-004 Auth flow [DONE]"] }, "p1_high_infra": { "total": 6, "done": 6, "items": ["FE-005 State mgmt [DONE]", "FE-006 Error handling [DONE]", "FE-007 Loading states [DONE]", "FE-008 Settings [DONE]", "FE-009 Notifications prefs [DONE]", "FE-010 User profile [DONE]"] }, "p1_high_widgets": { "total": 5, "done": 5, "items": ["FE-011 NIST [DONE]", "FE-012 Adaptive Balance [DONE]", "FE-013 Value Gen [DONE]", "FE-014 Impact Metrics [DONE]", "FE-015 Cognitive Sandwich [DONE]"] }, - "p2_medium_widgets": { "total": 5, "done": 4, "items": ["FE-016 Context Eng", "FE-017 Agentic System [DONE]", "FE-018 Convener", "FE-019 Marketplace", "FE-020 Org Mesh"] }, - "p2_medium_app": { "total": 3, "done": 1, "items": ["FE-021 Multi-page routing", "FE-022 Navigation [DONE]", "FE-023 Role-based UI"] }, + "p2_medium_widgets": { "total": 5, "done": 5, "items": ["FE-016 Context Eng [DONE]", "FE-017 Agentic System [DONE]", "FE-018 Convener [DONE]", "FE-019 Marketplace [DONE]", "FE-020 Org Mesh [DONE]"] }, + "p2_medium_app": { "total": 3, "done": 3, "items": ["FE-021 Multi-page routing [DONE]", "FE-022 Navigation [DONE]", "FE-023 Role-based UI [DONE]"] }, "p2_medium_cicd": { "total": 6, "done": 6, "items": ["FECICD-001 CI pipeline [DONE]", "FECICD-002 Docker [DONE]", "FECICD-003 Compose [DONE]", "FECICD-004 Deploy [DONE]", "FECICD-005 K8s [DONE]", "FECICD-006 Terraform [DONE]"] }, - "p2_medium_testing": { "total": 5, "done": 0, "items": ["FETEST-001 Unit tests 80%", "FETEST-002 API integration", "FETEST-003 E2E real API", "FETEST-004 Visual regression", "FETEST-005 Lighthouse CI"] }, + "p2_medium_testing": { "total": 5, "done": 2, "items": ["FETEST-001 Unit tests 80% [DONE]", "FETEST-002 API integration [DONE]", "FETEST-003 E2E real API", "FETEST-004 Visual regression", "FETEST-005 Lighthouse CI"] }, "p3_low_advanced": { "total": 5, "done": 3, "items": ["FE-024 Export", "FE-025 Cmd+K", "FE-026 Collaboration", "FE-027 Locales [DONE]", "FE-028 PWA [DONE]"] } }, "blockers": [], diff --git a/.github/pr-361-review-issues.json b/.github/pr-361-review-issues.json new file mode 100644 index 00000000..6e2406de --- /dev/null +++ b/.github/pr-361-review-issues.json @@ -0,0 +1,112 @@ +[ + { + "title": "fix(ci): Pin all checkouts to triggering workflow_run commit in deploy-frontend.yml", + "body": "## Summary\n\nOn `workflow_run`-triggered jobs, `github.sha` points to the default branch HEAD — not the commit that passed the upstream workflow. This causes `actions/checkout` to check out a different revision than what was tested, allowing a build-one-commit/deploy-another-manifests scenario.\n\n## Affected file\n`.github/workflows/deploy-frontend.yml` — lines 64-67, 105-106, 121-123, 185-187\n\n## Required fix\nUse `github.event.workflow_run.head_sha` as the `ref` input on every `actions/checkout` step and for the `org.opencontainers.image.revision` OCI label:\n\n```yaml\n- uses: actions/checkout@v6\n with:\n fetch-depth: 0\n ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}\n```\n\n## References\n- PR #361 review: https://github.com/phoenixvc/cognitive-mesh/pull/361#discussion_r2918141425\n- Severity: 🔴 Critical", + "labels": ["bug", "ci/cd"] + }, + { + "title": "fix(ci): Staging calls production API — NEXT_PUBLIC_API_BASE_URL baked in at build time", + "body": "## Summary\n\nThe workflow bakes `NEXT_PUBLIC_API_BASE_URL=https://api.cognitivemesh.io` into the client bundle via Docker build-args, then promotes the **same image** to both staging and production. Because Next.js inlines `NEXT_PUBLIC_*` variables at build time, staging can never override this value at runtime and will always call the production API endpoint.\n\n## Affected file\n`.github/workflows/deploy-frontend.yml` — lines 97-99\n\n## Required fix\nEither:\n1. Create separate build jobs per environment, each passing environment-specific `NEXT_PUBLIC_API_BASE_URL`; or\n2. Move API configuration to runtime (e.g., server-side config endpoint, SSR context, or non-`NEXT_PUBLIC_` env var read server-side and exposed to the client).\n\n## References\n- PR #361 review: https://github.com/phoenixvc/cognitive-mesh/pull/361#discussion_r2918141434\n- Severity: 🔴 Critical", + "labels": ["bug", "ci/cd"] + }, + { + "title": "fix(k8s): Global rewrite-target \"/\" collapses all requests in frontend-ingress.yaml", + "body": "## Summary\n\nThe annotation `nginx.ingress.kubernetes.io/rewrite-target: /` is applied globally. This means every request — including `/api/foo`, `/healthz`, and frontend deep links like `/widgets/marketplace` — arrives at the upstream service as `/`, breaking API routing and App Router direct loads.\n\n## Affected file\n`k8s/base/frontend-ingress.yaml` — lines 11-15, 24-25, 47-48\n\n## Required fix\nRemove the global rewrite annotation. If path stripping is needed for a specific route, apply a targeted annotation on that rule only. Switch `/api(/|$)(.*)` regex rules to simple `Prefix` path type rules:\n\n```yaml\n- path: /api\n pathType: Prefix\n backend:\n service:\n name: cognitive-mesh-api\n```\n\n## References\n- PR #361 review: https://github.com/phoenixvc/cognitive-mesh/pull/361#discussion_r2918141438\n- Severity: 🔴 Critical", + "labels": ["bug", "infrastructure"] + }, + { + "title": "fix(auth): RevokeAuthorityOverrideAsync ignores action parameter and may revoke wrong override", + "body": "## Summary\n\n`IAuthorityPort.RevokeAuthorityOverrideAsync` in `AuthorityService` queries `AuthorityOverrides` by `AgentId`/`TenantId`/`IsActive` but **ignores the `action` parameter** and calls `FirstOrDefaultAsync()`. When multiple active override rows exist for the same agent/tenant, this can revoke an unrelated or already-expired override, leaving the requested elevated permission in place.\n\n## Affected file\n`src/BusinessApplications/AgentRegistry/Services/AuthorityService.cs` — lines 745-760\n\n## Required fix\nAdd `action` to the LINQ filter and choose deterministically:\n\n```csharp\nvar activeOverride = (await _dbContext.AuthorityOverrides\n .Where(o => o.AgentId == agentId\n && o.TenantId == tenantId\n && o.IsActive\n && o.ExpiresAt > DateTimeOffset.UtcNow)\n .OrderByDescending(o => o.CreatedAt)\n .ToListAsync())\n .FirstOrDefault(o => o.OverrideScope?.AllowedApiEndpoints?.Contains(action) == true);\n```\n\n## References\n- PR #361 review: https://github.com/phoenixvc/cognitive-mesh/pull/361#discussion_r2918141445\n- Severity: 🔴 Critical", + "labels": ["bug", "security"] + }, + { + "title": "fix(compliance): ConcurrentDictionary.AddOrUpdate unsafe for in-place EvidenceRecord mutation in NISTComplianceService", + "body": "## Summary\n\n`ConcurrentDictionary.AddOrUpdate` executes its `updateValueFactory` delegate **outside the dictionary's internal locks** and may invoke it **multiple times** under contention. Concurrent calls to `SubmitReviewAsync` with the same evidence ID therefore mutate an `EvidenceRecord` field-by-field without synchronization, resulting in a record with a mixed review state.\n\n## Affected file\n`src/BusinessApplications/NISTCompliance/Services/NISTComplianceService.cs` — line 233\n\n## Required fix\nReplace the unsafe in-place mutation with an immutable update (create a new record value) or use a proper `lock`/`Interlocked` mechanism:\n\n```csharp\n_evidenceRecords.AddOrUpdate(\n evidenceId,\n _ => CreateNewRecord(review),\n (_, existing) => existing with { ReviewedBy = review.ReviewedBy, Status = review.Status, ReviewedAt = review.ReviewedAt }\n);\n```\n\n## References\n- PR #361 review: https://github.com/phoenixvc/cognitive-mesh/pull/361#discussion_r2918141448\n- Severity: 🔴 Critical", + "labels": ["bug", "concurrency"] + }, + { + "title": "fix(state): Phase 16 history entry missing from orchestrator.json before advancing last_phase_completed", + "body": "## Summary\n\n`last_phase_completed` in `.claude/state/orchestrator.json` has been advanced to `16`, but `phase_history` still ends at `\"15a\"`. Any consumer that derives release notes, rollback context, or next work from the history log will see Phase 16 as if it never happened.\n\n## Affected file\n`.claude/state/orchestrator.json` — line 4\n\n## Required fix\nAdd a Phase 16 history entry to `phase_history` before (or alongside) updating `last_phase_completed`. The entry should include the phase name, completion date, and key deliverables.\n\n## References\n- PR #361 review: https://github.com/phoenixvc/cognitive-mesh/pull/361#discussion_r2918362689\n- Severity: 🟠 Major", + "labels": ["bug", "documentation"] + }, + { + "title": "fix(state): FETEST-001 marked DONE in backlog while component_test_coverage_pct is 25% in orchestrator.json", + "body": "## Summary\n\n`.claude/state/orchestrator.json` shows `component_test_coverage_pct: 25`, but the backlog simultaneously marks `FETEST-001 Unit tests 80% [DONE]`. This internal inconsistency risks treating incomplete test coverage as complete, allowing work items that depend on 80% coverage to proceed prematurely.\n\n## Affected file\n`.claude/state/orchestrator.json` — line 173\n\n## Required fix\nEither:\n1. Update `component_test_coverage_pct` to accurately reflect the achieved coverage; or\n2. Reopen `FETEST-001` in the backlog until coverage actually reaches 80%.\n\n## References\n- PR #361 review: https://github.com/phoenixvc/cognitive-mesh/pull/361#discussion_r2918362694\n- Severity: 🟠 Major", + "labels": ["bug", "testing"] + }, + { + "title": "fix(tests): E2E auth-flow test helper uses stale auth contract (auth_token vs accessToken/refreshToken)", + "body": "## Summary\n\nThe in-test auth store helper in `auth-flow.test.tsx` writes `auth_token` and accepts an injected `user` object. The actual shipped auth flow uses `accessToken`/`refreshToken`, derives the user from the JWT, and sets the `cm_access_token` cookie. This mismatch means the tests pass even when the real auth contract is broken.\n\n## Affected file\n`src/UILayer/web/src/__tests__/e2e/auth-flow.test.tsx` — line 39\n\n## Required fix\nUpdate the test helper to use the same field names and structure as the real `useAuthStore` (`accessToken`, `refreshToken`, cookie logic) so the tests fail when the production auth contract changes.\n\n## References\n- PR #361 review: https://github.com/phoenixvc/cognitive-mesh/pull/361#discussion_r2919366126\n- Severity: 🟠 Major", + "labels": ["bug", "testing"] + }, + { + "title": "fix(tests): E2E login tests exercise fictional API and component flow, not the real implementation", + "body": "## Summary\n\nThe login tests in `auth-flow.test.tsx` (lines 76, 105) post to `/api/auth/login` and consume `{ token, user }`, while the actual implementation uses a different endpoint and response shape. The form, submit handler, fetch URL, response parsing, and redirect are all defined inline within the test — the real component is never imported or rendered.\n\n## Affected file\n`src/UILayer/web/src/__tests__/e2e/auth-flow.test.tsx` — line 123\n\n## Required fix\nImport and render the actual login page/component. Mock only external boundaries (fetch, router) with responses that match the real API shape. Assertions should verify the real component's behavior after login.\n\n## References\n- PR #361 review: https://github.com/phoenixvc/cognitive-mesh/pull/361#discussion_r2919366167\n- Severity: 🟠 Major", + "labels": ["bug", "testing"] + }, + { + "title": "fix(tests): Dashboard e2e tests use inline stub components — real implementation never exercised", + "body": "## Summary\n\nAll \"dashboard\" behavior in `dashboard-flow.test.tsx` comes from inline stub components defined in the test file. The suite will not fail if the actual route, widgets, fetch logic, or `useSignalR` integration breaks, because the real implementation is never imported or rendered.\n\n## Affected file\n`src/UILayer/web/src/__tests__/e2e/dashboard-flow.test.tsx` — line 105\n\n## Required fix\nImport and render the real dashboard route/page. Mock only external boundaries (API calls, WebSocket) and assert on rendered output from the real implementation.\n\n## References\n- PR #361 review: https://github.com/phoenixvc/cognitive-mesh/pull/361#discussion_r2919366173\n- Severity: 🟠 Major", + "labels": ["bug", "testing"] + }, + { + "title": "fix(tests): Settings e2e test suite tests the Zustand store directly, not the settings UI flow", + "body": "## Summary\n\nEvery assertion in `settings-flow.test.tsx` goes through `usePreferencesStore.getState()` directly. The test suite will pass even if the real settings page stops rendering the correct controls, miswires event handlers, or fails to hydrate from the store — because the real component is never involved.\n\n## Affected file\n`src/UILayer/web/src/__tests__/e2e/settings-flow.test.tsx` — line 54\n\n## Required fix\nRender the real settings page component and interact with it via user events (`userEvent.click`, `userEvent.type`). Assert on what the user sees (rendered output) rather than internal store state.\n\n## References\n- PR #361 review: https://github.com/phoenixvc/cognitive-mesh/pull/361#discussion_r2919366181\n- Severity: 🟠 Major", + "labels": ["bug", "testing"] + }, + { + "title": "fix(ui): CommandPalette Ctrl/Cmd+K shortcut fires when focus is on text-entry controls", + "body": "## Summary\n\nThe document-level `keydown` handler in `CommandPalette.tsx` fires on all events regardless of which element is focused. If a user is typing in an ``, `