Skip to content

feat(proof): PROOF9 quality memory system (#422)#447

Merged
frankbria merged 2 commits intomainfrom
feature/issue-422-proof9
Mar 16, 2026
Merged

feat(proof): PROOF9 quality memory system (#422)#447
frankbria merged 2 commits intomainfrom
feature/issue-422-proof9

Conversation

@frankbria
Copy link
Owner

@frankbria frankbria commented Mar 16, 2026

Summary

Implements #422: PROOF9 — Quality Memory System with Evidence-Based Verification

The complete PROOF9 subsystem that turns every failure into a permanent proof obligation:

  • 9 proof gates: UNIT, CONTRACT, E2E, VISUAL, A11Y, PERF, SEC, DEMO, MANUAL
  • Requirements ledger: SQLite storage for REQs with scope, obligations, evidence
  • Glitch classification: Keyword heuristics map glitch descriptions → gate obligations
  • Scope intersection: Changed files → matching requirements via git diff
  • Evidence system: SHA-256 checksummed artifacts prove compliance
  • Test stubs: Auto-generated skeleton tests for each gate type
  • Waiver system: Temporary exemptions with expiry tracking
  • 6 CLI commands: capture, run, list, show, waive, status

Files Changed (12 files, ~2185 lines)

  • New: codeframe/core/proof/ package (8 modules)
  • New: codeframe/cli/proof_commands.py — CLI commands
  • Modified: codeframe/cli/app.py — Wire proof_app into main CLI
  • New: tests/core/test_proof9.py — 42 tests

Acceptance Criteria

  • REQ schema defined and stored in SQLite
  • cf proof capture creates REQ with obligations from glitch description
  • Obligation mapping classifies glitch type → gate set
  • Test stubs generated for each obligation
  • cf proof run executes relevant obligations based on changed files
  • Evidence artifacts attached with checksums
  • Failing obligations block (or warn) on merge
  • Waiver system with expiry
  • REQs accumulate across sessions (the "memory" property)
  • cf proof status shows satisfied/failing/waived counts

Test Plan

  • 42 tests (TDD approach) covering models, ledger, obligations, scope, evidence, stubs, capture, runner, CLI
  • Ruff linting clean
  • No AI artifacts

Implementation Notes

  • Uses raw sqlite3 (not SQLAlchemy) — follows existing codebase patterns
  • Uses keyword heuristics for glitch classification (no LLM dependency for MVP)
  • Tables in workspace's state.db — consistent with existing modules
  • Inline template strings for stubs — no Jinja2 dependency
  • Integrates with existing core/gates.py for UNIT gate execution

Closes #422

Summary by CodeRabbit

  • New Features

    • Added a PROOF9 quality proof system: new top-level "proof" CLI with commands to capture glitches as requirements, run proof obligations (with scoped or full runs), list/show requirements, apply waivers (with optional expiry), and view system status.
    • Persistent per-workspace ledger and automated evidence tracking with waiver expiry handling and test-stub generation.
  • Tests

    • Comprehensive end-to-end test suite covering models, persistence, obligation execution, scope logic, evidence handling, runner behavior, and CLI workflows.

…ation (#422)

Implement the complete PROOF9 subsystem:
- Core proof package (models, ledger, obligations, scope, evidence, stubs, capture, runner)
- 9 proof gates: UNIT, CONTRACT, E2E, VISUAL, A11Y, PERF, SEC, DEMO, MANUAL
- SQLite ledger for requirements and evidence (raw sqlite3, follows existing patterns)
- Keyword-based glitch classification → obligation mapping
- Scope intersection engine using git changed files
- Evidence attachment with SHA-256 checksums
- Test stub generator for each gate type
- Waiver system with expiry tracking
- CLI commands: capture, run, list, show, waive, status
- 42 tests covering all modules
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

Walkthrough

Adds a new PROOF9 quality-memory subsystem: data models, SQLite ledger, capture/run CLI commands, obligation classification, scope intersection, evidence attachment, stub generation, and a runner that executes gates and updates requirement status.

Changes

Cohort / File(s) Summary
CLI Integration
codeframe/cli/app.py, codeframe/cli/proof_commands.py
Registers proof_app on the main Typer CLI and adds commands: capture, run, list/list_reqs, show, waive, status. CLI wiring and Rich-based output added.
Public API Exports
codeframe/core/proof/__init__.py
Re-exports PROOF9 domain types from models.py via __all__.
Domain Models
codeframe/core/proof/models.py
New enums and dataclasses: Gate, GlitchType, ReqStatus, Source, Severity, RequirementScope, Obligation, EvidenceRule, Waiver, Evidence, Requirement.
Capture & Obligations
codeframe/core/proof/capture.py, codeframe/core/proof/obligations.py, codeframe/core/proof/stubs.py
Capture flow: classify glitch, build scope, assemble obligations/evidence rules, persist requirement, generate test stubs. Obligation map, classification heuristics, and stub templates implemented.
Scope Engine
codeframe/core/proof/scope.py
Parses where inputs into RequirementScope, obtains changed scope from git, and implements intersection logic to determine applicable REQs.
Ledger & Evidence Persistence
codeframe/core/proof/ledger.py, codeframe/core/proof/evidence.py
SQLite-backed ledger with table init, JSON (de)serialization for scope/obligations/evidence_rules/waiver, REQ sequencing (REQ-XXXX), CRUD, evidence storage with SHA-256 checksums, waiver handling and expiry checks.
Runner / Orchestration
codeframe/core/proof/runner.py
run_proof executes applicable gates (optionally filtered or full), writes artifacts, attaches evidence, updates obligation and requirement statuses, and returns per-REQ outcomes.
Tests
tests/core/test_proof9.py
Comprehensive end-to-end test suite covering models, ledger, obligations, scope, evidence, stubs, capture, runner flows, waivers, and CLI interactions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as CLI (cf proof capture)
    participant Capture as Capture Engine
    participant Ledger as Ledger
    participant Stubs as Stub Generator

    User->>CLI: cf proof capture --title ... --description ...
    CLI->>Capture: capture_requirement(workspace, title, description, where, ...)
    Capture->>Capture: classify_glitch(description)
    Capture->>Capture: get_obligations(glitch_type)
    Capture->>Capture: build_scope_from_capture(where)
    Capture->>Ledger: next_req_id(...) / save_requirement(req)
    Capture->>Stubs: generate_stubs(req)
    Stubs-->>Capture: stubs dict
    Capture-->>CLI: (Requirement, stubs)
    CLI->>User: print created REQ summary
Loading
sequenceDiagram
    participant User
    participant CLI as CLI (cf proof run)
    participant Runner as Runner Engine
    participant Gates as Core Gates
    participant Ledger as Ledger
    participant Evidence as Evidence API

    User->>CLI: cf proof run [--full] [--gate unit]
    CLI->>Runner: run_proof(workspace, full, gate_filter)
    Runner->>Ledger: check_expired_waivers(workspace)
    Runner->>Ledger: list_requirements(workspace, status=OPEN)
    Runner->>Runner: get_changed_scope(workspace)
    loop per applicable requirement
        loop per obligation
            Runner->>Gates: _run_gate(gate)
            Gates-->>Runner: (passed, output)
            Runner->>Evidence: attach_evidence(workspace, req_id, gate, artifact, passed, run_id)
            Evidence->>Ledger: save_evidence(...)
        end
        Runner->>Ledger: save_requirement(updated_req_status)
    end
    Runner-->>CLI: results mapping
    CLI->>User: display results table / exit code
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I found a glitch and wrote it down,

REQ-0001 now wears the crown,
Gates to prove it, evidence to keep,
The ledger hums while bunnies leap,
Quality memory — hop, never sleep.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(proof): PROOF9 quality memory system (#422)' accurately summarizes the main feature being introduced — the PROOF9 quality memory system with evidence-based verification.
Linked Issues check ✅ Passed The PR implements all major coding objectives from issue #422: REQ schema/ledger (ledger.py, models.py), capture flow (capture.py, CLI commands), obligation mapping (obligations.py), scope intersection (scope.py), evidence system (evidence.py), test stubs (stubs.py), and runner (runner.py) with waiver support and CLI integration.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing PROOF9 as defined in #422: new proof subsystem, CLI wiring, comprehensive tests, and no unrelated modifications to other systems.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/issue-422-proof9
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link

claude bot commented Mar 16, 2026

PROOF9 Quality Memory System — Code Review

Good architectural foundation here. The package structure is clean, the separation of concerns is well thought out, and the CLI commands follow the established project conventions. A few issues need attention before merge, ranging from a critical data integrity bug to some lower-priority improvements.


Critical

ID collision in ledger.py next_req_id

The current implementation uses COUNT(*) to generate the next ID. Two problems: (1) if any row is deleted and re-created, the count produces a duplicate ID; (2) under concurrent calls, two processes can read the same count before either commits. Since save_requirement uses INSERT OR REPLACE, duplicate IDs silently overwrite existing records.

Recommend switching to MAX(req_id) with sequence parsing, or a dedicated auto-increment counter row.


Important

Obligation status not persisted on partial-gate runs (runner.py)

obl.status = "satisfied" if passed else "failed" mutates in memory, but ledger.save_requirement is only called when all_satisfied is True and all obligations are covered. When obligations fail or when gate_filter is active, those status changes are lost on process exit. After a failed run, cf proof show will still display stale "pending" statuses.

Fix: persist requirement state after every gate execution loop, not only on full success.

Obligation.status is a raw str instead of an Enum

The valid values (pending, satisfied, failed) are documented only in a comment. The rest of the codebase uses str, Enum for status fields (see Gate, Severity, Source). An Enum here would make invalid states impossible and allow exhaustive matching in CLI display code.

Connection leak in ledger functions

No ledger function uses try/finally or a context manager. If _row_to_requirement raises on a corrupted DB value, the connection is never closed. This is non-deterministic but can cause "database is locked" errors in tests using tmp_path. Recommend try/finally: conn.close() around each function body.

E2E stub template raises KeyError on brace characters in titles (stubs.py)

The template uses {title} substitution via .format(). If a user captures a requirement with a title like "Object {id} not found", the call raises KeyError. Use str.format_map() with a fallback-safe mapping, or escape user-supplied fields before insertion.


Low Priority

_ensure_tables called on every ledger operation — opens a DB connection and queries sqlite_master on every call. A module-level set of initialized workspace IDs would reduce this to a one-time cost per process.

approved_by hardcoded to "cli-user" for all waivers — until real identity is integrated, storing None is more honest than implying an audit trail that does not exist.

check_obligation_satisfied in evidence.py does a full evidence scan — fetches and deserializes all evidence rows then filters in Python. Should be a WHERE gate = ? AND satisfied = 1 LIMIT 1 query in the ledger layer.

import re inside _slugify in stubs.py — move to module level.

Rich markup injection in artifact_path display (proof_commands.py) — if artifact_path contains Rich markup brackets, output is malformed. Wrap with rich.markup.escape(ev.artifact_path).


Test Coverage Gaps

The 42 tests are a solid foundation, but these scenarios are missing:

  1. Partial gate-filter run — run proof with gate_filter active and one obligation failing; verify the persisted status reflects the failure (exposes the persistence bug above).
  2. ID collision — delete a requirement then add a new one; verify the new ID does not reuse the deleted one.
  3. Brace characters in titlecapture_requirement(..., title="Object {id} not found") should not raise.
  4. Ambiguous scope pathbuild_scope_from_capture("auth/login") (no leading slash, no extension) — is this a file or a tag? Behavior should be specified and tested.

What Is Working Well

  • Module separation is clean: obligations.py classifies, scope.py intersects, runner.py executes, capture.py orchestrates.
  • Keyword matching in obligations.py uses word boundaries, avoiding false positives.
  • Severity, Gate, Source use str, Enum consistently with the rest of the codebase.
  • workspace_id isolation is present on all tables.
  • pytestmark = pytest.mark.v2 convention is correctly applied.
  • No FastAPI or HTTP imports in core — architecturally compliant.

The critical ID collision issue and the obligation persistence bug should be resolved before merge. The other items can be addressed in a follow-up.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@codeframe/cli/proof_commands.py`:
- Around line 178-191: The command currently computes all_pass and prints the
summary but always exits zero; change the failure path to exit non-zero by
invoking sys.exit(1) (or raise SystemExit(1)) when all_pass is False after the
summary printing. Add an import sys at the top of the module if missing and
apply the exit call in the block following the console.print that shows
"[red]Some obligations failed.[/red]" so the CLI (proof run) returns a non-zero
status for CI.
- Around line 323-358: The CLI currently hardcodes approved_by="cli-user" and
leaves manual_checklist empty; change waive to determine the real approver
(prefer workspace metadata if available, e.g., check workspace.current_user or
workspace.user and fall back to getpass.getuser()) and include that value when
constructing Waiver, and collect manual_checklist from the user (add a new
Option --manual-checklist that accepts a comma-separated string or prompt
interactively via typer.prompt, split into a list) before calling
ledger.waive_requirement; update the Waiver instantiation in waive to set
approved_by to the resolved approver and manual_checklist to the parsed list.

In `@codeframe/core/proof/evidence.py`:
- Around line 53-56: The current loop returns True if any historical evidence
(from evidence_list returned by ledger.list_evidence(workspace, req.id)) has
ev.gate == gate and ev.satisfied, which lets an old passing record permanently
mark the gate satisfied; instead, filter evidence_list to the entries for the
target gate, pick the most recent evidence (by a timestamp/created_at field or
run identifier on ev), and derive satisfaction from that latest ev.satisfied
(also prefer evidence from the current run if present), i.e., replace the
early-return loop with logic that selects the newest matching ev and returns its
satisfied value.
- Around line 20-23: Instead of hashing the literal path when the artifact is
missing, make the code reject missing artifacts: in the block that currently
does if path.exists(): h.update(path.read_bytes()) else
h.update(file_path.encode()), replace the else branch with an explicit failure
(raise an appropriate exception such as FileNotFoundError or return an error
result) so missing files cannot produce a checksum and thus cannot be marked
satisfied by attach_evidence(..., satisfied=True, ...); ensure the error
includes the file_path and context so callers can handle/report the missing
artifact.
- Around line 27-46: attach_evidence currently persists Evidence but doesn't
emit an audit event; after saving the evidence with
ledger.save_evidence(workspace, evidence) call core/events.py's event emission
function (e.g., import and call emit_transition or emit from core.events) to
record a transition like "evidence.attached" with a payload containing at least
req_id, gate, run_id, satisfied, artifact_path, artifact_checksum, and timestamp
(use the Evidence instance fields), then return the evidence; place the emit
call immediately after ledger.save_evidence and ensure the import of the emitter
is added at top of the module.

In `@codeframe/core/proof/ledger.py`:
- Around line 178-204: The current ID allocation is racy because next_req_id()
relies on COUNT(*) and save_requirement() uses INSERT OR REPLACE, allowing
concurrent captures to get the same REQ ID and overwrite each other; fix by
moving to an atomic DB-generated ID (e.g., add an INTEGER PRIMARY KEY
AUTOINCREMENT sequence or a dedicated seq table) and change next_req_id() to
obtain the ID atomically via an INSERT + lastrowid (or SELECT from
sqlite_sequence) rather than COUNT(*), and update save_requirement() to perform
a plain INSERT that uses that atomic ID-derived REQ identifier (and NOT INSERT
OR REPLACE) so uniqueness is enforced by the DB and concurrent captures cannot
overwrite each other; update the proof_requirements schema/migration and adjust
any code paths that build the REQ-#### string to derive it from the atomic
integer returned by the new sequence logic.
- Around line 178-204: save_requirement and other proof write paths (requirement
saves, evidence inserts, waiver saves, expired-waiver reopenings) currently
mutate PROOF9 state but do not emit audit events; update each mutating function
(e.g., save_requirement plus the evidence insert, save_waiver, and
reopen-expired-waiver functions referenced) to import the core/events module and
emit a descriptive event after the DB commit using a consistent event name
(e.g., "proof.requirement.saved", "proof.evidence.inserted",
"proof.waiver.saved", "proof.waiver.reopened"), including a payload with
workspace_id, affected entity id(s) (req.id, evidence id, waiver id), actor
(created_by or current user), timestamp, and before/after or changed fields
where available; ensure emissions occur only after successful commit and include
error handling so failures to emit do not corrupt DB state.

In `@codeframe/core/proof/obligations.py`:
- Around line 29-32: The SECURITY_ISSUE heuristic is too permissive because
common domain tokens ("auth", "password", "token") inflate the SECURITY_ISSUE
bucket; remove those strings from GlitchType.SECURITY_ISSUE and instead place
them in a less-weighted bucket (or create GlitchType.AUTH_ISSUE) so they don't
trigger the stricter security label; also update any scoring/selection code that
returns the first max-scoring bucket (the logic that picks the highest-scoring
GlitchType) to rely on the tightened keyword lists so routine auth terms no
longer push items into SECURITY_ISSUE.

In `@codeframe/core/proof/runner.py`:
- Around line 34-37: The code currently treats unsupported gates as satisfied by
returning True when core_gate_name is missing; change this so unsupported gates
are not marked satisfied—return a non-satisfied state (e.g., False with a clear
message like "unsupported/needs-waiver") or surface a distinct pending/waiver
result instead of True. Update the branch that uses _GATE_TO_CORE and
core_gate_name (and the analogous block around lines 117-126) to return that
non-satisfied/pending tuple so callers cannot record skipped gates as proven
without explicit manual evidence or waiver.
- Around line 98-127: The loop currently updates obl.status but only calls
ledger.save_requirement when every obligation ran and all passed, causing status
changes from failed runs or --gate-filtered runs to be lost; after setting
obl.status inside the loop (the line that assigns "satisfied" or "failed"),
persist that change immediately by saving the requirement or obligation (e.g.,
call ledger.save_requirement(workspace, req) or the appropriate
ledger.save_obligation API) so each updated obligation status is written to the
ledger; additionally, when you detect all_satisfied and set req.status =
ReqStatus.SATISFIED, also stamp req.satisfied_at before calling
ledger.save_requirement(workspace, req) so the satisfied transition is
persisted.

In `@codeframe/core/proof/scope.py`:
- Around line 52-59: The current except block around get_status() swallows
errors and leaves scope.files empty, which makes file-scoped REQs appear
out-of-scope; change the handler so failures are surfaced instead of failing
open: in the except for get_status(workspace) (the block that sets scope.files),
after logging the warning include the original exception (or log as error) and
re-raise the exception so cf proof run will fail fast (or alternatively set
scope.files = None and adjust intersects() to treat None as "unknown/all files"
so enforcement still runs); update logging to include the exception details and
ensure references to get_status, scope.files and intersects() are adjusted
accordingly.
- Around line 78-86: The current loop over req_scope.files vs
changed_scope.files treats any req_file as a directory prefix and returns True
for any changed_file under that path; restrict the directory/prefix match so it
only applies when the captured scope is explicitly a directory. Update the logic
in the loop that iterates req_scope.files and changed_scope.files (symbols:
req_scope.files, changed_scope.files, req_file, changed_file) to first check
whether req_file denotes a directory (for example by requiring a trailing "/" or
another explicit directory marker on req_file) before performing the req_dir
prefix check (req_file.rsplit("/") and changed_file.startswith(req_dir + "/")).
Keep the exact concrete-file equality check (req_file == changed_file) as-is.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 97ef5a91-54e4-44e9-915c-2a91fd4bc0e6

📥 Commits

Reviewing files that changed from the base of the PR and between 933ffcc and f2f066e.

📒 Files selected for processing (12)
  • codeframe/cli/app.py
  • codeframe/cli/proof_commands.py
  • codeframe/core/proof/__init__.py
  • codeframe/core/proof/capture.py
  • codeframe/core/proof/evidence.py
  • codeframe/core/proof/ledger.py
  • codeframe/core/proof/models.py
  • codeframe/core/proof/obligations.py
  • codeframe/core/proof/runner.py
  • codeframe/core/proof/scope.py
  • codeframe/core/proof/stubs.py
  • tests/core/test_proof9.py

Comment on lines +323 to +358
def waive(
req_id: str = typer.Argument(help="Requirement ID to waive"),
reason: str = typer.Option(..., "--reason", "-r", help="Why this is being waived"),
expires: Optional[str] = typer.Option(
None, "--expires", help="Expiry date (YYYY-MM-DD)",
),
repo_path: Optional[Path] = typer.Option(
None, "--workspace", "-w", help="Workspace path",
),
) -> None:
"""Waive a requirement with reason and optional expiry.

Example:
codeframe proof waive REQ-0001 --reason "No automated test yet" --expires 2026-04-01
"""
from codeframe.core.workspace import get_workspace
from codeframe.core.proof import ledger
from codeframe.core.proof.models import Waiver

workspace_path = repo_path or Path.cwd()
try:
workspace = get_workspace(workspace_path)
except Exception as e:
console.print(f"[red]Error:[/red] {e}")
raise typer.Exit(1)

expiry_date = None
if expires:
try:
expiry_date = date.fromisoformat(expires)
except ValueError:
console.print(f"[red]Error:[/red] Invalid date format: {expires} (use YYYY-MM-DD)")
raise typer.Exit(1)

waiver_obj = Waiver(reason=reason, expires=expiry_date, approved_by="cli-user")
updated = ledger.waive_requirement(workspace, req_id, waiver_obj)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't hard-code waiver approvals to cli-user.

Every waiver gets the same approved_by value, so later audits cannot tell who actually granted the exemption. The CLI also leaves manual_checklist empty even though the model supports it.

Minimal fix direction
 def waive(
     req_id: str = typer.Argument(help="Requirement ID to waive"),
     reason: str = typer.Option(..., "--reason", "-r", help="Why this is being waived"),
+    approved_by: str = typer.Option(..., "--approved-by", help="Who approved this waiver"),
     expires: Optional[str] = typer.Option(
         None, "--expires", help="Expiry date (YYYY-MM-DD)",
     ),
@@
-    waiver_obj = Waiver(reason=reason, expires=expiry_date, approved_by="cli-user")
+    waiver_obj = Waiver(reason=reason, expires=expiry_date, approved_by=approved_by)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/cli/proof_commands.py` around lines 323 - 358, The CLI currently
hardcodes approved_by="cli-user" and leaves manual_checklist empty; change waive
to determine the real approver (prefer workspace metadata if available, e.g.,
check workspace.current_user or workspace.user and fall back to
getpass.getuser()) and include that value when constructing Waiver, and collect
manual_checklist from the user (add a new Option --manual-checklist that accepts
a comma-separated string or prompt interactively via typer.prompt, split into a
list) before calling ledger.waive_requirement; update the Waiver instantiation
in waive to set approved_by to the resolved approver and manual_checklist to the
parsed list.

Comment on lines +27 to +46
def attach_evidence(
workspace: Workspace,
req_id: str,
gate: Gate,
artifact_path: str,
satisfied: bool,
run_id: str,
) -> Evidence:
"""Create and persist an evidence record with artifact checksum."""
evidence = Evidence(
req_id=req_id,
gate=gate,
satisfied=satisfied,
artifact_path=artifact_path,
artifact_checksum=_sha256(artifact_path),
timestamp=datetime.now(timezone.utc),
run_id=run_id,
)
ledger.save_evidence(workspace, evidence)
return evidence
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Emit an audit event when evidence is recorded.

attach_evidence() persists new proof state but never emits a core/events.py transition, so the audit trail misses when a requirement gains new evidence.

As per coding guidelines, codeframe/core/**/*.py: All core modules must emit events for state transitions via core/events.py for audit and observability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/proof/evidence.py` around lines 27 - 46, attach_evidence
currently persists Evidence but doesn't emit an audit event; after saving the
evidence with ledger.save_evidence(workspace, evidence) call core/events.py's
event emission function (e.g., import and call emit_transition or emit from
core.events) to record a transition like "evidence.attached" with a payload
containing at least req_id, gate, run_id, satisfied, artifact_path,
artifact_checksum, and timestamp (use the Evidence instance fields), then return
the evidence; place the emit call immediately after ledger.save_evidence and
ensure the import of the emitter is added at top of the module.

Comment on lines +53 to +56
evidence_list = ledger.list_evidence(workspace, req.id)
for ev in evidence_list:
if ev.gate == gate and ev.satisfied:
return True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't let one historical pass satisfy the gate forever.

This returns True as soon as any prior satisfied=True evidence exists for the gate. After one passing run, a newer failing run for the same gate will still look satisfied forever. Use the latest evidence for that gate, or evidence from the current run, when deriving satisfaction.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/proof/evidence.py` around lines 53 - 56, The current loop
returns True if any historical evidence (from evidence_list returned by
ledger.list_evidence(workspace, req.id)) has ev.gate == gate and ev.satisfied,
which lets an old passing record permanently mark the gate satisfied; instead,
filter evidence_list to the entries for the target gate, pick the most recent
evidence (by a timestamp/created_at field or run identifier on ev), and derive
satisfaction from that latest ev.satisfied (also prefer evidence from the
current run if present), i.e., replace the early-return loop with logic that
selects the newest matching ev and returns its satisfied value.

Comment on lines +29 to +32
GlitchType.SECURITY_ISSUE: [
"security", "xss", "injection", "auth", "csrf", "vulnerability",
"exploit", "credential", "password", "token", "permission",
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Tighten the security heuristic before it under-enforces auth regressions.

"auth", "password", and "token" are common domain terms, not strong security signals. Because Line 77 returns the first max-scoring bucket, routine auth logic defects can land in SECURITY_ISSUE, which shrinks required proof from UNIT/CONTRACT to only SEC.

Also applies to: 69-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/proof/obligations.py` around lines 29 - 32, The SECURITY_ISSUE
heuristic is too permissive because common domain tokens ("auth", "password",
"token") inflate the SECURITY_ISSUE bucket; remove those strings from
GlitchType.SECURITY_ISSUE and instead place them in a less-weighted bucket (or
create GlitchType.AUTH_ISSUE) so they don't trigger the stricter security label;
also update any scoring/selection code that returns the first max-scoring bucket
(the logic that picks the highest-scoring GlitchType) to rely on the tightened
keyword lists so routine auth terms no longer push items into SECURITY_ISSUE.

Comment on lines +52 to +59
try:
from codeframe.core.git import get_status
status = get_status(workspace)
all_files = status.modified_files + status.staged_files + status.untracked_files
scope.files = list(set(all_files))
except Exception as exc:
logger.warning("Could not detect changed files: %s", exc)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Don't fail open when changed-file detection breaks.

If get_status() raises, this silently returns an empty scope. With the current intersects() behavior, that makes file-scoped REQs look out of scope and can let cf proof run skip enforcement entirely instead of surfacing the error or falling back to a full run.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/proof/scope.py` around lines 52 - 59, The current except block
around get_status() swallows errors and leaves scope.files empty, which makes
file-scoped REQs appear out-of-scope; change the handler so failures are
surfaced instead of failing open: in the except for get_status(workspace) (the
block that sets scope.files), after logging the warning include the original
exception (or log as error) and re-raise the exception so cf proof run will fail
fast (or alternatively set scope.files = None and adjust intersects() to treat
None as "unknown/all files" so enforcement still runs); update logging to
include the exception details and ensure references to get_status, scope.files
and intersects() are adjusted accordingly.

Critical fixes:
- Reject missing artifacts instead of hashing path string
- Use MAX(id) for REQ ID allocation to prevent overwrites
- Unsupported gates return False (not True) to fail closed
- Git detection failure returns None → runner matches everything

Major fixes:
- Non-zero exit code when proof checks fail
- Always persist obligation status after run (not just on full success)
- Exact file matching only (no directory expansion)
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (5)
codeframe/cli/proof_commands.py (1)

358-358: ⚠️ Potential issue | 🟠 Major

Hard-coded approved_by="cli-user" loses audit trail.

Every waiver gets the same approver value, making it impossible to trace who actually granted exemptions. Consider adding an --approved-by option or inferring from environment (e.g., getpass.getuser()).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/cli/proof_commands.py` at line 358, The waiver creation currently
hardcodes approved_by="cli-user" in the line creating waiver_obj = Waiver(...),
which loses the real approver; change the CLI that constructs the waiver (the
command handler that calls Waiver) to accept an --approved-by option and/or
default to the current OS user via getpass.getuser(), propagate that value into
the Waiver(...) call instead of the literal "cli-user", and ensure any related
help text or argument parsing for the command is updated to document the new
option.
codeframe/core/proof/evidence.py (2)

26-45: ⚠️ Potential issue | 🟠 Major

Missing audit event emission for evidence attachment.

attach_evidence() persists evidence state but doesn't emit an event via core/events.py. This violates the observability requirement for core modules. Based on learnings: "All core modules must emit events for state transitions via core/events.py for audit and observability".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/proof/evidence.py` around lines 26 - 45, attach_evidence
currently persists Evidence via ledger.save_evidence but never emits an audit
event; update attach_evidence to import the event emitter from core.events and
emit a descriptive event (e.g., event type "evidence.attached") after
ledger.save_evidence returns, including key fields from the Evidence object
(req_id, gate, run_id, artifact_path, artifact_checksum, timestamp, satisfied)
as the event payload so state transitions are observable; ensure the emitter
call occurs after successful save and include any returned evidence id if
available.

48-56: ⚠️ Potential issue | 🟠 Major

Any historical pass permanently satisfies the gate.

check_obligation_satisfied returns True as soon as any prior evidence with satisfied=True exists. After one passing run, a subsequent failing run will still appear satisfied. Consider checking only the most recent evidence for the gate, or evidence from the current run.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/proof/evidence.py` around lines 48 - 56,
check_obligation_satisfied currently returns True if any historical evidence for
the gate has ev.satisfied=True, which incorrectly treats a past pass as
permanent; update it to consider only the most recent evidence for that
requirement+gate (or evidence from the current run if a run identifier is
available). Specifically, in check_obligation_satisfied replace the any-history
check by filtering ledger.list_evidence(workspace, req.id) for ev.gate == gate,
then select the single most recent evidence (e.g., by ev.timestamp /
ev.created_at or by using a ledger.get_latest_evidence API if present) and
return that evidence.satisfied (or if run-based semantics are required, filter
by ev.run_id == current_run_id before selecting). Ensure you reference the
existing symbols: check_obligation_satisfied, ledger.list_evidence, ev.gate,
ev.satisfied, and any timestamp/run-id field on ev.
codeframe/core/proof/ledger.py (2)

254-269: ⚠️ Potential issue | 🔴 Critical

REQ ID allocation race condition persists.

While using MAX(id) is better than COUNT(*), two concurrent captures can still both receive the same ID before either calls save_requirement(). With INSERT OR REPLACE, the second write silently overwrites the first requirement. Consider atomic ID generation via a sequence table or INSERT with conflict detection.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/proof/ledger.py` around lines 254 - 269, The current
next_req_id function is vulnerable to races because it reads MAX(id) then
returns a value that can be duplicated by concurrent callers; replace that
read-modify step with an atomic increment inside the DB (e.g. create a
per-workspace sequence table like proof_req_seq(workspace_id PRIMARY KEY, last
INTEGER) in _ensure_tables and then in next_req_id perform a single SQL
statement that atomically increments and returns the new value (for modern
SQLite: INSERT INTO proof_req_seq(workspace_id,last) VALUES(?,1) ON
CONFLICT(workspace_id) DO UPDATE SET last=last+1 RETURNING last; for older
SQLite, wrap UPDATE/INSERT in a transaction and SELECT last after UPDATE) and
format that returned integer as REQ-####; reference next_req_id, _ensure_tables,
and proof_requirements/save_requirement to ensure callers use this atomic
allocator instead of MAX(id).

178-204: ⚠️ Potential issue | 🟠 Major

Audit event emission missing from write paths.

save_requirement (and other mutating functions) persist state without emitting events via core/events.py. This leaves the capture/run/waive flows without audit trails. Based on learnings: "All core modules must emit events for state transitions via core/events.py for audit and observability".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/proof/ledger.py` around lines 178 - 204, The save_requirement
write path currently persists state but does not emit an audit event; import the
event-emission API from core.events (e.g., emit_event or events.emit) and, after
committing the DB change in save_requirement, call it to emit a concise audit
event (e.g., event name "requirement.saved" or similar) with a payload
containing requirement id (req.id), workspace id (workspace.id), actor
(req.created_by), action (create/update), timestamp (use the same
created_at/satisfied_at values), and the serialized requirement snapshot; apply
the same pattern to all other mutating functions in this module so every state
transition emits a corresponding event via core/events.py.
🧹 Nitpick comments (1)
codeframe/core/proof/scope.py (1)

61-67: Docstring mentions prefix matching but implementation uses exact match only.

The docstring at lines 64-66 says "File matching supports prefix matching" but the implementation at lines 75-79 performs exact set intersection only. Update the docstring to match the actual behavior.

📝 Suggested fix
 def intersects(req_scope: RequirementScope, changed_scope: RequirementScope) -> bool:
     """Check if a requirement scope overlaps with changed scope.
 
-    Returns True if any field has common elements. File matching
-    supports prefix matching (req file "src/auth/" matches changed
-    file "src/auth/login.py").
+    Returns True if any field has common elements. File matching
+    uses exact equality only (no directory expansion).
     """
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/proof/scope.py` around lines 61 - 67, The docstring for the
function intersects incorrectly claims "File matching supports prefix matching"
while the function (intersects(req_scope, changed_scope) in scope.py) currently
only performs exact set intersections; update the docstring to remove the
prefix-matching statement and clearly state that file comparisons use exact
matches (i.e., intersection of file path sets), and ensure the description of
the return value and behavior matches the actual implementation of intersects.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@codeframe/core/proof/ledger.py`:
- Around line 353-357: The UPDATE that clears expired waivers on
proof_requirements uses only the id filter and must also restrict by
workspace_id to avoid cross-workspace collisions; update the cursor.execute call
in the same block (the code referencing req.id and req.waiver.expires) to
include req.workspace_id in the WHERE clause (e.g., add "AND workspace_id = ?"
and pass req.workspace_id as a parameter) so the UPDATE mirrors other queries'
workspace scoping.

---

Duplicate comments:
In `@codeframe/cli/proof_commands.py`:
- Line 358: The waiver creation currently hardcodes approved_by="cli-user" in
the line creating waiver_obj = Waiver(...), which loses the real approver;
change the CLI that constructs the waiver (the command handler that calls
Waiver) to accept an --approved-by option and/or default to the current OS user
via getpass.getuser(), propagate that value into the Waiver(...) call instead of
the literal "cli-user", and ensure any related help text or argument parsing for
the command is updated to document the new option.

In `@codeframe/core/proof/evidence.py`:
- Around line 26-45: attach_evidence currently persists Evidence via
ledger.save_evidence but never emits an audit event; update attach_evidence to
import the event emitter from core.events and emit a descriptive event (e.g.,
event type "evidence.attached") after ledger.save_evidence returns, including
key fields from the Evidence object (req_id, gate, run_id, artifact_path,
artifact_checksum, timestamp, satisfied) as the event payload so state
transitions are observable; ensure the emitter call occurs after successful save
and include any returned evidence id if available.
- Around line 48-56: check_obligation_satisfied currently returns True if any
historical evidence for the gate has ev.satisfied=True, which incorrectly treats
a past pass as permanent; update it to consider only the most recent evidence
for that requirement+gate (or evidence from the current run if a run identifier
is available). Specifically, in check_obligation_satisfied replace the
any-history check by filtering ledger.list_evidence(workspace, req.id) for
ev.gate == gate, then select the single most recent evidence (e.g., by
ev.timestamp / ev.created_at or by using a ledger.get_latest_evidence API if
present) and return that evidence.satisfied (or if run-based semantics are
required, filter by ev.run_id == current_run_id before selecting). Ensure you
reference the existing symbols: check_obligation_satisfied,
ledger.list_evidence, ev.gate, ev.satisfied, and any timestamp/run-id field on
ev.

In `@codeframe/core/proof/ledger.py`:
- Around line 254-269: The current next_req_id function is vulnerable to races
because it reads MAX(id) then returns a value that can be duplicated by
concurrent callers; replace that read-modify step with an atomic increment
inside the DB (e.g. create a per-workspace sequence table like
proof_req_seq(workspace_id PRIMARY KEY, last INTEGER) in _ensure_tables and then
in next_req_id perform a single SQL statement that atomically increments and
returns the new value (for modern SQLite: INSERT INTO
proof_req_seq(workspace_id,last) VALUES(?,1) ON CONFLICT(workspace_id) DO UPDATE
SET last=last+1 RETURNING last; for older SQLite, wrap UPDATE/INSERT in a
transaction and SELECT last after UPDATE) and format that returned integer as
REQ-####; reference next_req_id, _ensure_tables, and
proof_requirements/save_requirement to ensure callers use this atomic allocator
instead of MAX(id).
- Around line 178-204: The save_requirement write path currently persists state
but does not emit an audit event; import the event-emission API from core.events
(e.g., emit_event or events.emit) and, after committing the DB change in
save_requirement, call it to emit a concise audit event (e.g., event name
"requirement.saved" or similar) with a payload containing requirement id
(req.id), workspace id (workspace.id), actor (req.created_by), action
(create/update), timestamp (use the same created_at/satisfied_at values), and
the serialized requirement snapshot; apply the same pattern to all other
mutating functions in this module so every state transition emits a
corresponding event via core/events.py.

---

Nitpick comments:
In `@codeframe/core/proof/scope.py`:
- Around line 61-67: The docstring for the function intersects incorrectly
claims "File matching supports prefix matching" while the function
(intersects(req_scope, changed_scope) in scope.py) currently only performs exact
set intersections; update the docstring to remove the prefix-matching statement
and clearly state that file comparisons use exact matches (i.e., intersection of
file path sets), and ensure the description of the return value and behavior
matches the actual implementation of intersects.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 628393d2-3b23-49d5-8490-794de2b897b4

📥 Commits

Reviewing files that changed from the base of the PR and between f2f066e and b56415f.

📒 Files selected for processing (6)
  • codeframe/cli/proof_commands.py
  • codeframe/core/proof/evidence.py
  • codeframe/core/proof/ledger.py
  • codeframe/core/proof/runner.py
  • codeframe/core/proof/scope.py
  • tests/core/test_proof9.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • codeframe/core/proof/runner.py

Comment on lines +353 to +357
if req.waiver and req.waiver.expires and req.waiver.expires.isoformat() <= today:
cursor.execute(
"UPDATE proof_requirements SET status = 'open', waiver = NULL WHERE id = ?",
(req.id,),
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing workspace_id filter in UPDATE for expired waivers.

The UPDATE statement filters only by id but should also filter by workspace_id for consistency with other queries and to prevent cross-workspace updates if IDs ever collide.

🔧 Suggested fix
             cursor.execute(
-                "UPDATE proof_requirements SET status = 'open', waiver = NULL WHERE id = ?",
-                (req.id,),
+                "UPDATE proof_requirements SET status = 'open', waiver = NULL WHERE id = ? AND workspace_id = ?",
+                (req.id, workspace.id),
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/proof/ledger.py` around lines 353 - 357, The UPDATE that
clears expired waivers on proof_requirements uses only the id filter and must
also restrict by workspace_id to avoid cross-workspace collisions; update the
cursor.execute call in the same block (the code referencing req.id and
req.waiver.expires) to include req.workspace_id in the WHERE clause (e.g., add
"AND workspace_id = ?" and pass req.workspace_id as a parameter) so the UPDATE
mirrors other queries' workspace scoping.

@claude
Copy link

claude bot commented Mar 16, 2026

Code Review — PROOF9 Quality Memory System (#422)

This is a solid architectural addition. The core/CLI separation is respected throughout — no FastAPI imports in codeframe/core/proof/, the package is explicitly marked headless in its docstring, and CLI imports from core happen at call time (inside the command functions), which is good practice.

CodeRabbit has already flagged a set of valid automated issues. This review focuses on higher-level concerns and items the automated pass did not cover.


Critical

1. _run_gate returns False for gates without automated runners, which is wrong
In codeframe/core/proof/runner.py, when core_gate_name is None (i.e., the gate has no automated tool — VISUAL, A11Y, PERF, E2E, DEMO, MANUAL), the function returns (False, "Gate X has no automated runner — cannot verify"). This means running cf proof run against any REQ with those obligations will always mark them FAILED, generate artifacts saying they can't be verified, and persist status="failed" on the obligation. That is worse than returning a neutral "pending" state, because it actively misrepresents the result. Those 6 of the 9 gates are purely manual or require external tooling — they should return a distinct "not-runnable" signal (e.g., None or a separate enum value) so the runner can skip evidence attachment entirely rather than recording a false failure.

2. Requirement satisfaction check is stale-evidence vulnerable
check_obligation_satisfied in codeframe/core/proof/evidence.py returns True if any historical evidence record for that gate is passing — including evidence from months ago for code that has since changed. The system has no mechanism to invalidate old evidence when files in the REQ's scope are modified. As a result, a REQ can remain SATISFIED forever once it passes once, defeating the "quality memory" promise. At minimum, the check should be limited to evidence from the most recent run (by run_id or timestamp), or cf proof run should explicitly re-evaluate and overwrite evidence on each execution.


Important

3. approved_by is hardcoded to "cli-user"
In the waive command (codeframe/cli/proof_commands.py, line ~336), Waiver(reason=reason, expires=expiry_date, approved_by="cli-user") always stores the literal string "cli-user". This makes the audit trail meaningless. Even a simple getpass.getuser() fallback would be better. The approved_by field exists precisely for accountability, so hardcoding it undermines the waiver system's intent.

4. check_expired_waivers UPDATE is not workspace-scoped
In codeframe/core/proof/ledger.py, the check_expired_waivers function SELECTs waived reqs filtered by workspace_id, but the UPDATE that reverts them to open only filters by req.id — not workspace_id. In a multi-workspace environment this could mutate a requirement in a different workspace if two workspaces share a REQ-#### collision. The fix is one additional AND workspace_id = ? on the UPDATE query.

5. Obligation status mutations in run_proof are not fully persisted
In codeframe/core/proof/runner.py, obl.status is updated in the loop, but ledger.save_requirement is only called at the end and only when req_results is non-empty. When --gate filters to a single gate, the other obligations' statuses are never written. Since obligation status is serialized as JSON inside the requirement row, a partial run leaves all other obligations in their prior state — cf proof show will display stale statuses.

Also in this block: when all_satisfied is True, req.status is set to SATISFIED but req.satisfied_at is never stamped before ledger.save_requirement is called, so that timestamp column will always be None for satisfied requirements.

6. Late import pattern in CLI is inconsistent with the rest of the codebase
All CLI command functions in codeframe/cli/proof_commands.py defer their from codeframe.core.* imports inside the function body. While functional, this is inconsistent with how the rest of cli/app.py is written (module-level imports). If the motivation is startup performance or avoiding circular imports, document it. Inconsistency here will confuse future contributors.


Nice to Have

7. Scope docstring vs. implementation mismatch
The intersects docstring in codeframe/core/proof/scope.py says "File matching supports prefix matching (req file src/auth/ matches changed file src/auth/login.py)" — but the implementation only does exact set intersection (req_files & changed_files). The comment describes aspirational behavior. Either implement the prefix logic or remove the comment; users who capture a REQ with a directory-level scope will silently get no matches.

8. Stubs are generated but never written to disk
generate_stubs returns content strings, and capture_requirement passes them back to the CLI, which only prints a line count per gate. The actual stub files are never created on disk. The PR description says "Auto-generated skeleton tests for each gate type" — but there is no way for a user to retrieve the content after capture returns. Consider writing stubs to a proof_stubs/ directory under the workspace, or add a cf proof show-stub REQ-0001 unit command.

9. No integration with cf pr create/cf pr merge
The CLAUDE.md roadmap positions PROOF9 as the PROVE step before SHIP. There is currently no integration with the PR commands to warn or block when open obligations exist. Even a non-blocking warning from cf pr create when open REQs are detected would close the pipeline loop from the product vision. Recommend tracking this as a follow-up issue.


Testing Gaps

The 42-test suite covers the happy path well. Gaps worth tracking:

  • No test for _run_gate with gates that have no core runner (the False-for-unrunnable issue in point 1)
  • No test for check_expired_waivers where workspace_id differs between SELECT and UPDATE (point 4)
  • No test for run_proof with gate_filter verifying that partial obligation statuses are persisted (point 5)
  • Runner tests mock _run_gate at the private level, so the actual gate dispatch logic has zero coverage

Architecture Compliance

  • No FastAPI imports in codeframe/core/proof/ — compliant
  • Uses raw sqlite3 following existing workspace patterns — compliant
  • CLI does not require a server — compliant
  • No tasks.update_status() calls from core proof modules — compliant

Overall this is well-structured work. Address points 1-5 before merging — they affect the correctness of evidence recording and satisfaction tracking, which are the core promise of PROOF9.

@frankbria frankbria merged commit e87c4a1 into main Mar 16, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Phase 5] PROOF9: Quality Memory System with Evidence-Based Verification

1 participant