Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ SHIP: cf pr create → cf pr merge
LOOP: Glitch → cf proof capture → New REQ → Enforced forever
```

**Status: CLI ✅ | Server ✅ | ReAct agent ✅ | Web UI ✅ | Agent adapters ✅ | Multi-provider LLM ✅ | Next: Phase 3.5C** — See `docs/PRODUCT_ROADMAP.md`.
**Status: CLI ✅ | Server ✅ | ReAct agent ✅ | Web UI ✅ | Agent adapters ✅ | Multi-provider LLM ✅ | Next: Phase 4A** — See `docs/PRODUCT_ROADMAP.md`.

If you are an agent working in this repo: **do not improvise architecture**. Follow the documents listed below.

Expand All @@ -34,12 +34,11 @@ If you are an agent working in this repo: **do not improvise architecture**. Fol

**Rule 0:** If a change does not directly support the Think → Build → Prove → Ship pipeline, do not implement it.

### Current Focus: Phase 3.5C
### Current Focus: Phase 4A

**Phase 3.5B is complete** — `[Run Gates]` button, live gate progress, per-gate evidence display (`GateEvidencePanel`), and run history panel (`RunHistoryPanel`) are all shipped. New backend endpoints: `GET /api/v2/proof/runs` and `GET /api/v2/proof/runs/{run_id}/evidence`.
**Phase 3.5C is complete** — `CaptureGlitchModal` form (description/markdown, source, scope, gate obligations, severity, expiry) reachable from the PROOF9 page and the persistent sidebar "Capture Glitch" button. REQ detail view (`/proof/[req_id]`) ships markdown description rendering, `ProofScope` metadata display, obligations table with `Latest Run` column, sortable/filterable evidence history, and empty-state CTA. Backend: `ScopeOut` model on `RequirementResponse`. Issues #568, #569.

Next, in order:
- **3.5C**: Glitch capture web UI
- **4A**: PR status tracking + PROOF9 merge gate
- **4B**: Post-merge glitch capture loop
- **5.1–5.5**: Platform completeness (#554–#565)
Expand Down
18 changes: 18 additions & 0 deletions codeframe/ui/routers/proof_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ class RunProofRequest(BaseModel):
gate: Optional[Gate] = Field(default=None, description="Run only this gate (unit, sec, contract, etc.)")


class ScopeOut(BaseModel):
"""Serialized requirement scope."""

routes: list[str] = Field(default_factory=list)
components: list[str] = Field(default_factory=list)
apis: list[str] = Field(default_factory=list)
files: list[str] = Field(default_factory=list)
tags: list[str] = Field(default_factory=list)


class ObligationOut(BaseModel):
"""Serialized proof obligation."""

Expand Down Expand Up @@ -145,6 +155,7 @@ class RequirementResponse(BaseModel):
created_by: str
source_issue: Optional[str]
related_reqs: list[str]
scope: Optional[ScopeOut] = None


class CaptureRequirementResponse(RequirementResponse):
Expand Down Expand Up @@ -265,6 +276,13 @@ def _req_to_response(req) -> RequirementResponse:
created_by=req.created_by,
source_issue=req.source_issue,
related_reqs=req.related_reqs,
scope=ScopeOut(
routes=req.scope.routes,
components=req.scope.components,
apis=req.scope.apis,
files=req.scope.files,
tags=req.scope.tags,
) if req.scope else None,
)


Expand Down
23 changes: 19 additions & 4 deletions docs/PHASE_3_UI_ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ Persistent left sidebar with icon + label navigation:
4. **Execution** (play/monitor icon) - only visible when runs are active
5. **Blockers** (alert icon) - badge count for open blockers
6. **Review** (git branch icon)
7. **PROOF9** (checkmark icon)
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.

⚠️ Potential issue | 🟡 Minor

Correct the navigation label from "PROOF9" to "Proof".

The documentation claims the navigation item is labeled "PROOF9", but the actual implementation in AppSidebar.tsx uses "Proof" as the label (line 37: label: 'Proof'). Update the documentation to match the implemented label.

📝 Proposed fix
-7. **PROOF9** (checkmark icon)
+7. **Proof** (checkmark icon)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
7. **PROOF9** (checkmark icon)
7. **Proof** (checkmark icon)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/PHASE_3_UI_ARCHITECTURE.md` at line 137, Update the documentation entry
that currently reads "PROOF9" to match the implementation by changing the
navigation label to "Proof"; the implemented label is defined in AppSidebar.tsx
as label: 'Proof', so replace the "PROOF9" string in the docs with "Proof" to
keep docs and code consistent.

8. **Sessions** (command-line icon) - badge count for active sessions

**Sidebar action button**: A **"Capture Glitch"** button (Add01Icon) is always visible at the bottom of the sidebar. Clicking it opens `CaptureGlitchModal` without navigating away from the current page. This is the primary entry point for the glitch capture closed loop from anywhere in the app.

### Secondary Navigation
- **Workspace breadcrumb** at top: shows current repo path, links to workspace root
Expand Down Expand Up @@ -367,10 +371,21 @@ ProofPage (/proof)
└── (click → loads GateEvidencePanel for that run)

ProofRequirementPage (/proof/[req_id])
├── RequirementDetail
│ ├── ObligationsList
│ └── EvidenceHistory
└── WaiveForm
├── RequirementHeader
│ ├── Title, severity badge, ProofStatusBadge
│ ├── MarkdownDescription (ReactMarkdown, images disallowed)
│ ├── MetadataRow (created_at, source, source_issue, created_by, waiver expiry)
│ └── ScopeChips (files, routes, components, APIs, tags from ProofScope)
├── ObligationsTable ← new (Phase 3.5C)
│ └── ObligationRow[] (gate name, Latest Run pass/fail badge, link to evidence)
│ └── Latest Run column: shows most-recent run result per gate
├── EvidenceHistory
│ ├── FilterBar (gate select, result select, search input, Reset Filters)
│ ├── EvidenceTable (sortable: gate, result, run_id, timestamp, artifact)
│ │ └── EvidenceRow[] (click run_id → focusRun filter)
│ └── EmptyState CTA: "Capture a Glitch" link when no evidence exists
├── GateEvidencePanel (loads artifact content for latest run)
└── WaiveDialog (modal, opens via Waive button in header)
Comment on lines 373 to +388
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.

⚠️ Potential issue | 🟠 Major

Architectural mismatch: Documented components don't exist as discrete exports.

The documentation describes the /proof/[req_id] page as using named components (RequirementHeader, ObligationsTable, EvidenceHistory, GateEvidencePanel, WaiveDialog), but the actual implementation in web-ui/src/app/proof/[req_id]/page.tsx uses inline JSX sections (marked with comments like {/* Header */}, {/* Obligations */}, {/* Evidence history */}) without separate component exports.

While the described functionality is correctly implemented, developers looking for these named components will not find them in the codebase. Update the documentation to accurately reflect the inline implementation pattern or refactor the implementation to match the documented component hierarchy.

📝 Proposed documentation fix to match actual implementation
 ProofRequirementPage (/proof/[req_id])
-├── RequirementHeader
-│   ├── Title, severity badge, ProofStatusBadge
-│   ├── MarkdownDescription (ReactMarkdown, images disallowed)
-│   ├── MetadataRow (created_at, source, source_issue, created_by, waiver expiry)
-│   └── ScopeChips (files, routes, components, APIs, tags from ProofScope)
-├── ObligationsTable      ← new (Phase 3.5C)
-│   └── ObligationRow[]   (gate name, Latest Run pass/fail badge, link to evidence)
-│       └── Latest Run column: shows most-recent run result per gate
-├── EvidenceHistory
-│   ├── FilterBar (gate select, result select, search input, Reset Filters)
-│   ├── EvidenceTable (sortable: gate, result, run_id, timestamp, artifact)
-│   │   └── EvidenceRow[] (click run_id → focusRun filter)
-│   └── EmptyState CTA: "Capture a Glitch" link when no evidence exists
-├── GateEvidencePanel (loads artifact content for latest run)
-└── WaiveDialog (modal, opens via Waive button in header)
+├── Header section (inline JSX)
+│   ├── Title, severity badge, ProofStatusBadge
+│   ├── Description (ReactMarkdown, images disallowed)
+│   ├── Metadata row (created_at, source, source_issue, created_by, waiver expiry)
+│   └── Scope chips (files, routes, components, APIs, tags from ProofScope)
+├── Obligations section (inline JSX table)
+│   └── Obligation rows (gate name, status, Latest Run link)
+│       └── Latest Run column: clickable run_id that filters evidence history
+├── Evidence History section (inline JSX)
+│   ├── Filter controls (gate select, result select, search input, Reset Filters)
+│   ├── Evidence table (gate, result, run_id, timestamp, artifact)
+│   │   └── Evidence rows (click run_id → focusRun filter)
+│   └── Empty state with "Run Gates →" CTA linking to /review
+└── Waiver section (inline JSX with modal dialog)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ProofRequirementPage (/proof/[req_id])
├── RequirementDetail
│ ├── ObligationsList
│ └── EvidenceHistory
└── WaiveForm
├── RequirementHeader
│ ├── Title, severity badge, ProofStatusBadge
│ ├── MarkdownDescription (ReactMarkdown, images disallowed)
│ ├── MetadataRow (created_at, source, source_issue, created_by, waiver expiry)
│ └── ScopeChips (files, routes, components, APIs, tags from ProofScope)
├── ObligationsTable ← new (Phase 3.5C)
│ └── ObligationRow[] (gate name, Latest Run pass/fail badge, link to evidence)
│ └── Latest Run column: shows most-recent run result per gate
├── EvidenceHistory
│ ├── FilterBar (gate select, result select, search input, Reset Filters)
│ ├── EvidenceTable (sortable: gate, result, run_id, timestamp, artifact)
│ │ └── EvidenceRow[] (click run_id → focusRun filter)
│ └── EmptyState CTA: "Capture a Glitch" link when no evidence exists
├── GateEvidencePanel (loads artifact content for latest run)
└── WaiveDialog (modal, opens via Waive button in header)
ProofRequirementPage (/proof/[req_id])
├── Header section (inline JSX)
│ ├── Title, severity badge, ProofStatusBadge
│ ├── Description (ReactMarkdown, images disallowed)
│ ├── Metadata row (created_at, source, source_issue, created_by, waiver expiry)
│ └── Scope chips (files, routes, components, APIs, tags from ProofScope)
├── Obligations section (inline JSX table)
│ └── Obligation rows (gate name, status, Latest Run link)
│ └── Latest Run column: clickable run_id that filters evidence history
├── Evidence History section (inline JSX)
│ ├── Filter controls (gate select, result select, search input, Reset Filters)
│ ├── Evidence table (gate, result, run_id, timestamp, artifact)
│ │ └── Evidence rows (click run_id → focusRun filter)
│ └── Empty state with "Run Gates →" CTA linking to /review
└── Waiver section (inline JSX with modal dialog)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/PHASE_3_UI_ARCHITECTURE.md` around lines 373 - 388, The docs claim named
exports (RequirementHeader, ObligationsTable, EvidenceHistory,
GateEvidencePanel, WaiveDialog) exist for /proof/[req_id], but the actual
implementation uses inline JSX sections in
web-ui/src/app/proof/[req_id]/page.tsx (commented as {/* Header */}, {/*
Obligations */}, {/* Evidence history */} etc. Fix by either: 1) updating
PHASE_3_UI_ARCHITECTURE.md to describe the inline implementation and list the
documented sections as inline JSX blocks found in page.tsx, or 2) refactor
page.tsx by extracting the commented sections into discrete exported components
named RequirementHeader, ObligationsTable, EvidenceHistory, GateEvidencePanel,
and WaiveDialog, preserving existing props/hooks/state and replacing the inline
JSX with imports/usages so the documented component hierarchy matches the code.

```

**API Endpoints Used:**
Expand Down
22 changes: 5 additions & 17 deletions docs/PRODUCT_ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,11 @@ Fully shipped: `[Run Gates]` button on the PROOF9 page, live gate progress view

---

### Milestone C: Glitch Capture UI ❌ NOT STARTED
### Milestone C: Glitch Capture UI ✅ COMPLETE (#568, #569)

**Current state**: The CLI has `cf proof capture` for converting a production glitch into a permanent PROOF9 requirement. The proof page has a glitch_type *filter* for reading existing requirements but no capture form (verified 2026-04-06).
Fully shipped: `CaptureGlitchModal` form reachable from the PROOF9 page header and the sidebar "Capture Glitch" button. Form collects description (markdown), source (production/QA/dogfooding/monitoring), scope (files/routes/components, stored as `ScopeOut` on the backend and `ProofScope` in the frontend types), gate obligations (multi-select), severity, and optional expiry. On submit, creates a new REQ in the requirements ledger immediately.

**What to build**:

- A **"Capture Glitch"** entry point reachable from the PROOF9 page and the sidebar
- A structured form collecting:
- Description of the failure (free text, supports markdown)
- Where it was found (production / QA / dogfooding / monitoring)
- Scope selector: which files, routes, or components are affected
- Which PROOF9 gates should be required as proof obligations (multi-select)
- Severity and optional expiry (for time-bounded obligations)
- On submit: creates a new REQ in the requirements ledger, associates obligations, and shows the new requirement in the PROOF9 table immediately
- A **REQ detail view** that shows the glitch description, its obligations, and the evidence history across all gate runs

**Why it matters for the vision**: The glitch capture closed loop — *Ship → Discover glitch → Capture → Enforce forever → Ship with higher confidence* — is described as "the defining feature of the system." Without a web UI for capture, this loop requires CLI access and will be skipped by most users. This is the most differentiated feature in CodeFRAME and it is currently invisible to web users.
REQ detail view (`/proof/[req_id]`): markdown-rendered description, scope metadata display, obligations table with a `Latest Run` column showing pass/fail per gate from the most recent run, sortable/filterable evidence history, and a "Capture Glitch" empty-state CTA when no evidence exists yet.

---

Expand Down Expand Up @@ -201,7 +189,7 @@ These are items that were considered and excluded because they do not serve the
|---|---|---|---|
| 3.5A | Bidirectional agent chat | ✅ Complete | #500–509 |
| 3.5B | Run gates from the web UI | ✅ Complete | #566, #567, #574, #575 |
| 3.5C | Glitch capture UI | ❌ Not started | — |
| 3.5C | Glitch capture UI | ✅ Complete | #568, #569 |
| 4A | PR status + PROOF9 merge gate | ❌ Not started | — |
| 4B | Post-merge glitch capture loop | ❌ Not started | — |
| 5.1 | Settings page | ❌ Not started | #554–556 |
Expand All @@ -210,6 +198,6 @@ These are items that were considered and excluded because they do not serve the
| 5.4 | PRD stress-test web UI | ❌ Not started | #561–562 |
| 5.5 | GitHub Issues import | ❌ Not started | #563–565 |

**Current focus**: Phase 3.5CGlitch capture web UI.
**Current focus**: Phase 4APR status tracking + PROOF9 merge gate.

The ordering within Phase 5 is by onboarding impact. Settings (5.1) and cost (5.2) block new users earliest.
1 change: 1 addition & 0 deletions web-ui/__mocks__/@hugeicons/react.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ module.exports = {
SentIcon: createIconMock('SentIcon'),
// AppSidebar
Home01Icon: createIconMock('Home01Icon'),
Add01Icon: createIconMock('Add01Icon'),
// PipelineProgressBar
Tick01Icon: createIconMock('Tick01Icon'),
// Task Board components
Expand Down
112 changes: 110 additions & 2 deletions web-ui/__tests__/app/proof/req_id/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ jest.mock('@/lib/api', () => ({
proofApi: {
getRequirement: jest.fn(),
getEvidence: jest.fn(),
getRunDetail: jest.fn(),
waive: jest.fn(),
},
}));

jest.mock('react-markdown', () => ({
__esModule: true,
default: ({ children }: { children: string }) => <p data-testid="markdown">{children}</p>,
}));

jest.mock('@/lib/workspace-storage', () => ({
getSelectedWorkspacePath: jest.fn(() => '/test/workspace'),
}));
Expand Down Expand Up @@ -45,6 +51,7 @@ const baseReq = {
source_issue: null,
related_reqs: [],
source: 'manual',
scope: null,
};

const waivedReq = {
Expand All @@ -67,15 +74,116 @@ describe('ProofDetailPage', () => {
localStorageMock.clear();
});

const setupSWR = (req: typeof baseReq) => {
const setupSWR = (req: typeof baseReq, evidence: unknown[] = []) => {
mockUseSWR.mockImplementation((key: any) => {
if (typeof key === 'string' && key.includes('/runs/') && key.includes('/evidence')) {
// Run detail endpoint
return { data: { evidence: [] }, error: undefined, isLoading: false, mutate: jest.fn() } as any;
}
if (typeof key === 'string' && key.includes('/evidence')) {
return { data: mockEvidenceResponse, error: undefined, isLoading: false, mutate: jest.fn() } as any;
return { data: evidence, error: undefined, isLoading: false, mutate: jest.fn() } as any;
}
return { data: req, error: undefined, isLoading: false, mutate: jest.fn() } as any;
});
};

describe('description rendering', () => {
it('renders description via ReactMarkdown', async () => {
setupSWR(baseReq);
render(<ProofDetailPage />);
await waitFor(() => {
expect(screen.getByTestId('markdown')).toBeInTheDocument();
expect(screen.getByTestId('markdown')).toHaveTextContent('Ensure MFA flow is tested');
});
});
});

describe('where found / scope', () => {
it('shows "Where found" when scope has non-empty fields', async () => {
const reqWithScope = {
...baseReq,
scope: { routes: ['/login'], components: ['MFAForm'], apis: [], files: [], tags: [] },
};
setupSWR(reqWithScope as any);
render(<ProofDetailPage />);
await waitFor(() => {
expect(screen.getByText(/where found:/i)).toBeInTheDocument();
expect(screen.getByText(/\/login/i)).toBeInTheDocument();
});
});

it('does not show "Where found" when scope is null', async () => {
setupSWR(baseReq);
render(<ProofDetailPage />);
await waitFor(() => screen.getByText('Login must work with MFA'));
expect(screen.queryByText(/where found:/i)).not.toBeInTheDocument();
});
});

describe('obligations with latest run', () => {
it('shows Latest Run column header when obligations exist', async () => {
const reqWithObs = {
...baseReq,
obligations: [{ gate: 'unit', status: 'pending' }],
};
setupSWR(reqWithObs as any);
render(<ProofDetailPage />);
await waitFor(() => {
expect(screen.getByText('Latest Run')).toBeInTheDocument();
});
});

it('reflects latest gate run result in obligation status', async () => {
const reqWithObs = {
...baseReq,
obligations: [{ gate: 'unit', status: 'pending' }],
};
const evidence = [
{ req_id: 'REQ-001', gate: 'unit', satisfied: true, run_id: 'run-abc', artifact_path: '', artifact_checksum: '', timestamp: '2026-01-15T12:00:00Z' },
];
setupSWR(reqWithObs as any, evidence);
render(<ProofDetailPage />);
await waitFor(() => {
// Obligation status should show 'satisfied' (derived from latest run, not ob.status)
expect(screen.getByText('satisfied')).toBeInTheDocument();
// run-abc appears in obligations Latest Run column AND evidence history — both tables should show it
expect(screen.getAllByText('run-abc').length).toBeGreaterThanOrEqual(2);
});
});

it('shows — for Latest Run when no evidence exists for that gate', async () => {
const reqWithObs = {
...baseReq,
obligations: [{ gate: 'sec', status: 'pending' }],
};
setupSWR(reqWithObs as any, []);
render(<ProofDetailPage />);
await waitFor(() => {
expect(screen.getAllByText('—').length).toBeGreaterThan(0);
});
});
});

describe('evidence empty state CTA', () => {
it('renders "Run Gates" link when there is no evidence', async () => {
setupSWR(baseReq, []);
render(<ProofDetailPage />);
await waitFor(() => {
expect(screen.getByText(/no gate runs yet/i)).toBeInTheDocument();
expect(screen.getByRole('link', { name: /run gates/i })).toBeInTheDocument();
});
});

it('links to /review from the empty state CTA', async () => {
setupSWR(baseReq, []);
render(<ProofDetailPage />);
await waitFor(() => {
const link = screen.getByRole('link', { name: /run gates/i });
expect(link).toHaveAttribute('href', '/review');
});
});
});

describe('waiver audit trail', () => {
it('shows waiver reason in the waiver section', async () => {
setupSWR(waivedReq as any);
Expand Down
21 changes: 21 additions & 0 deletions web-ui/__tests__/components/layout/AppSidebar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ jest.mock('@/lib/workspace-storage', () => ({
getSelectedWorkspacePath: jest.fn(),
}));

// Mock CaptureGlitchModal to avoid Radix UI portal issues in jsdom
jest.mock('@/components/proof', () => ({
CaptureGlitchModal: ({ open }: { open: boolean }) =>
open ? <div data-testid="capture-modal">CaptureGlitchModal</div> : null,
}));

// Mock SWR (used for blocker + session badge counts)
const mockSWRData: Record<string, unknown> = {};
jest.mock('swr', () => ({
Expand Down Expand Up @@ -155,4 +161,19 @@ describe('AppSidebar', () => {
const sessionsLink = screen.getByRole('link', { name: /sessions/i });
expect(sessionsLink.querySelector('.bg-muted')).toBeNull();
});

// ─── Capture Glitch entry point tests ─────────────────────────────────────

it('renders "Capture Glitch" button when workspace is selected', () => {
mockGetWorkspacePath.mockReturnValue('/home/user/projects/test');
render(<AppSidebar />);
expect(screen.getByRole('button', { name: /capture glitch/i })).toBeInTheDocument();
});

it('does not render "Capture Glitch" button when no workspace is selected', () => {
mockGetWorkspacePath.mockReturnValue(null);
const { container } = render(<AppSidebar />);
// Sidebar itself is not rendered
expect(container.firstChild).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import type { ProofEvidence, ProofRequirement, ProofEvidenceSortCol, SortDir } f

// ── Mocks ────────────────────────────────────────────────────────────────

jest.mock('react-markdown', () => ({
__esModule: true,
default: ({ children }: { children: string }) => <p data-testid="markdown">{children}</p>,
}));

jest.mock('swr');
jest.mock('next/navigation', () => ({
useParams: () => ({ req_id: 'REQ-001' }),
Expand Down Expand Up @@ -65,6 +70,7 @@ const REQ: ProofRequirement = {
created_by: 'user',
source_issue: null,
related_reqs: [],
scope: null,
};

function setup(evidence: ProofEvidence[] = EVIDENCE) {
Expand Down
Loading
Loading