Skip to content

feat: restore open-world evidence foundation - #416

Open
guangyu-reflexio wants to merge 43 commits into
mainfrom
codex/offline-tuner-open-world-phase1-restored
Open

feat: restore open-world evidence foundation#416
guangyu-reflexio wants to merge 43 commits into
mainfrom
codex/offline-tuner-open-world-phase1-restored

Conversation

@guangyu-reflexio

@guangyu-reflexio guangyu-reflexio commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • restore the open-world evidence foundation on current main after the prior rollout was reverted
  • preserve current-main scheduler recovery and agent-success tagging while restoring immutable session outcomes, bounded search-exposure evidence, governance fencing, retention, erasure, and extraction-only billing support
  • keep Phase 1 session outcomes free of an implicit outcome-window policy when no deployment acceptance provider is registered
  • bound direct /api/search_user_playbooks correlation identifiers to 255 characters, matching unified search and preventing amplification into up to 100 indexed exposure records
  • fix the local E2E failures that motivated the rollback by making OSS billing composition and mixed E2E test mode explicit

Clean-Break Scope

This shared foundation does not enable the offline-tuner runner, candidate generation, publication, replay, scoring, or scheduling. It provides the immutable evidence and governance contracts used by later phases.

Enterprise Stack

  • Enterprise foundation: ReflexioAI/reflexio-enterprise#944 (a80caa4c)
  • Stacked operating-contract documentation: ReflexioAI/reflexio-enterprise#945 (03248930)

Keep this PR draft and unmerged until the complete multi-phase stack passes the final end-to-end/evaluation gate.

Verification

  • direct search exposure boundary: 18 passed
  • session outcome integration: 16 passed
  • direct 256-character correlation IDs are rejected before search or recording; exact 255-character IDs are accepted
  • changed-file Ruff and format checks: clean
  • changed-file Pyright: 0 errors, 0 warnings
  • import and diff checks: clean
  • final scoped re-review: SPEC PASS, QUALITY PASS, zero P1-P4 findings

The broader shared and OSS verification from the restored foundation remains recorded in the branch history and prior review reports. Final merge readiness is intentionally deferred to the complete stacked end-to-end run.

Summary by CodeRabbit

  • New Features

    • Search results now support exposure tracking and correlation across requests, sessions, and interactions.
    • Session outcomes include durable identities, revisions, digests, and an unknown outcome.
    • User erasure operations provide safer recovery, ownership, and retry handling.
    • Learning finalization and billing now prevent duplicates and bill only retained learnings.
    • Added open-world optimization evidence support and usage-delivery status handling.
  • Validation

    • Search results and identifiers have defined limits.
    • Source labels follow documented format and privacy requirements.
  • Documentation

    • Updated setup, API, billing, retention, and search guidance, including SQLite 3.35+ requirements.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cf104550-89b9-4893-b770-ac707d11e702

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds durable session-outcome identities, receipt-backed finalization, strict usage delivery, fenced governance erasure, deterministic search-exposure records, retention age limits, always-global sweeps, and SQLite compatibility checks.

Changes

Durable outcomes and finalization

Layer / File(s) Summary
Session outcome identity and migration
reflexio/models/..., reflexio/server/services/storage/..., tests/...session_outcome...
Session outcomes now store canonical IDs, revisions, contract digests, trajectory digests, and conflict reasons. SQLite migrations preserve legacy data.
Receipt finalization and billing
reflexio/server/services/{profile,playbook,extraction}/..., reflexio/server/usage_metrics.py, reflexio/server/billing_meter.py
Finalization uses immutable receipts and durable learning IDs. Strict billing delivery distinguishes retryable and terminal failures.

Governance and exposure

Layer / File(s) Summary
Governance execution claims
reflexio/server/services/governance/..., reflexio/server/services/storage/...governance...
User erasure uses authoritative identity binding, leased execution claims, fencing, heartbeat renewal, and claim-aware lifecycle operations.
Search exposure and retention
reflexio/server/services/search_exposure.py, reflexio/server/routes/search.py, reflexio/server/services/storage/retention*
Unified and direct searches record deterministic playbook exposure events. Retention applies a 14-day minimum age to exposure events.

Scheduler and compatibility

Layer / File(s) Summary
Scheduler, startup, and validation updates
reflexio/server/services/lineage/gc_scheduler.py, reflexio/server/api.py, README.md, reflexio/models/api_schema/..., tests/...
Always-global sweeps run independently of expiry reclamation. OSS startup installs an exempt recorder. SQLite 3.35+ and search limits are documented and validated.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.80% 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
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the PR's main purpose: restoring the open-world evidence foundation.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/offline-tuner-open-world-phase1-restored

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

@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (25)
reflexio/server/services/governance/service.py (1)

418-449: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the tautological purge_id check.

_matching_user_erasure_purge_for_retry fetches the purge by purge_id through get_purge_operation(purge_id), then asserts that purge.purge_id == purge_id. That comparison cannot fail. The remaining checks on operation_type, scope_type, subject_ref, and request_ref carry the actual value.

♻️ Proposed simplification
         purge = self.storage.get_purge_operation(purge_id)
         expected_identity = {
-            "purge_id": purge_id,
             "operation_type": operation_type,
             "scope_type": scope_type,
             "subject_ref": subject_ref,
             "request_ref": request_ref,
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/services/governance/service.py` around lines 418 - 449,
Remove the tautological purge_id entry from expected_identity in
_matching_user_erasure_purge_for_retry, while preserving validation of
operation_type, scope_type, subject_ref, and request_ref and the
authoritative-user check.
tests/server/services/governance/test_subject_write_barrier_sqlite.py (2)

622-629: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the duplicate barrier fetch.

barrier and stored_barrier read the same row through get_subject_write_barrier(subject_ref) and then assert the same status == "erased". Keep one.

♻️ Proposed change
     barrier = storage.get_subject_write_barrier(subject_ref)
     assert barrier is not None
-    stored_barrier = storage.get_subject_write_barrier(subject_ref)
     stored_purge = storage.get_purge_operation(purge.purge_id)
 
     assert barrier.status == "erased"
-    assert stored_barrier is not None
-    assert stored_barrier.status == "erased"
     assert stored_purge is not None
     assert stored_purge.status == "complete"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/governance/test_subject_write_barrier_sqlite.py` around
lines 622 - 629, Remove the redundant get_subject_write_barrier(subject_ref)
call in this test, keeping a single barrier fetch and its associated non-null
and status assertions; preserve the stored_purge retrieval and its independent
assertions.

42-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the deliberate lease steal in _claim_purge.

When a claim is already held, this helper forcibly expires execution_claim_expires_at and re-claims. Each call also increments the fence, so any claim obtained by an earlier call becomes stale. The current call sites use each claim before the next _claim_purge call, so the tests are correct. Add a comment stating that the helper always takes over the lease, and that callers must not hold a claim across two _claim_purge calls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/governance/test_subject_write_barrier_sqlite.py` around
lines 42 - 62, Add a concise comment in `_claim_purge` documenting that it
deliberately expires and steals any existing execution lease, increments the
fence, and makes claims from earlier calls stale; state that callers must use
each returned claim before invoking `_claim_purge` again.
tests/server/services/governance/test_governance_local_e2e.py (3)

963-966: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use time.sleep instead of a throwaway Event.

threading.Event().wait(0.001) creates an event that is never set, only to block for 1 ms. time.sleep(0.001) states the intent directly.

♻️ Proposed change
     def release_winner_on_duplicate_poll(_seconds: float) -> None:
         lifecycle.release_first_call.set()
-        threading.Event().wait(0.001)
+        time.sleep(0.001)

Add import time at the top of the file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/governance/test_governance_local_e2e.py` around lines
963 - 966, Update release_winner_on_duplicate_poll to use time.sleep(0.001)
instead of creating and waiting on a throwaway threading.Event, and add the
required time import at the file’s imports.

1052-1110: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the fake-clock arithmetic.

This test depends on a precise relationship that is not stated: the claim starts at _epoch_now() == 100 with a 300-second lease, so it expires at 400. The lifecycle advances the clock to 350, waits for three renewals that push the expiry to 650, then advances to 401. The duplicate caller at 401 must therefore still see a live lease. A reader who changes _PURGE_EXECUTION_LEASE_SECONDS or one of these literals will get a confusing flake. Add a comment stating the intended invariant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/governance/test_governance_local_e2e.py` around lines
1052 - 1110, Document the fake-clock invariant in
test_healthy_slow_lifecycle_renews_lease_and_duplicate_converges: state that the
claim begins at 100 with a 300-second lease expiring at 400, three renewals
extend it to 650, and advancing to 401 keeps the duplicate caller within the
live lease. Place the comment next to the relevant fake_now updates and renewal
wait.

1044-1049: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Derive the backoff expectations from the module constants.

The assertions hardcode 0.05, 1.0, and 5.0. Those values duplicate _DUPLICATE_ERASE_POLL_SECONDS, _DUPLICATE_ERASE_MAX_POLL_SECONDS, and _DUPLICATE_ERASE_WAIT_SECONDS. If a constant changes, this test fails on the literal rather than on the backoff shape. Read the constants from governance_service_module so the test keeps checking the doubling behavior and the total bound.

♻️ Proposed change
-    assert service.sleep_delays[:5] == pytest.approx([0.05, 0.1, 0.2, 0.4, 0.8])
-    assert max(service.sleep_delays) == 1.0
-    assert sum(service.sleep_delays) == pytest.approx(5.0)
+    base = governance_service_module._DUPLICATE_ERASE_POLL_SECONDS
+    assert service.sleep_delays[:5] == pytest.approx(
+        [base * 2**step for step in range(5)]
+    )
+    assert max(service.sleep_delays) == (
+        governance_service_module._DUPLICATE_ERASE_MAX_POLL_SECONDS
+    )
+    assert sum(service.sleep_delays) == pytest.approx(
+        governance_service_module._DUPLICATE_ERASE_WAIT_SECONDS
+    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/governance/test_governance_local_e2e.py` around lines
1044 - 1049, Update the backoff assertions in the governance erase retry test to
derive expected initial, maximum, and total delays from
governance_service_module’s _DUPLICATE_ERASE_POLL_SECONDS,
_DUPLICATE_ERASE_MAX_POLL_SECONDS, and _DUPLICATE_ERASE_WAIT_SECONDS constants.
Preserve the existing checks for doubling behavior, maximum delay, and total
wait bound without hardcoded duplicate values.
reflexio/server/services/storage/sqlite_storage/governance/_purge.py (1)

449-497: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate the duplicated claim assertion.

assert_purge_operation_execution_claim and _assert_purge_operation_execution_claim_locked contain the same validation and the same five-condition row check. Only the row-fetch mechanism differs. Extract the row check so a future change to the fencing rule cannot be applied to one copy only.

♻️ Proposed refactor
+    def _assert_claim_row(self, row: Any, claim: PurgeExecutionClaim) -> None:
+        if row is None:
+            raise ValueError(f"Purge operation {claim.purge_id!r} not found")
+        if (
+            row["status"] != "running"
+            or row["execution_claim_owner"] != claim.owner
+            or int(row["execution_claim_fence"]) != claim.fence
+            or row["execution_claim_expires_at"] is None
+            or int(row["execution_claim_expires_at"]) <= _epoch_now()
+        ):
+            raise ValueError("purge execution claim is no longer active")
+
     def assert_purge_operation_execution_claim(
         self, purge_id: str, execution_claim: PurgeExecutionClaim
     ) -> None:
         purge_id = _validate_governance_purge_id("purge_id", purge_id)
         claim = validate_purge_execution_claim(purge_id, execution_claim)
-        now = _epoch_now()
-        row = self._deps()._fetchone(
+        self._assert_claim_row(
+            self._deps()._fetchone(
             """SELECT status, execution_claim_owner, execution_claim_fence,
                       execution_claim_expires_at
                FROM purge_operations
                WHERE purge_id = ? AND org_id = ?""",
-            (purge_id, self.org_id),
-        )
-        if row is None:
-            raise ValueError(f"Purge operation {purge_id!r} not found")
-        if (
-            row["status"] != "running"
-            or row["execution_claim_owner"] != claim.owner
-            or int(row["execution_claim_fence"]) != claim.fence
-            or row["execution_claim_expires_at"] is None
-            or int(row["execution_claim_expires_at"]) <= now
-        ):
-            raise ValueError("purge execution claim is no longer active")
+                (purge_id, self.org_id),
+            ),
+            claim,
+        )

Apply the same delegation in _assert_purge_operation_execution_claim_locked.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/services/storage/sqlite_storage/governance/_purge.py` around
lines 449 - 497, Consolidate the duplicated validation in
assert_purge_operation_execution_claim and
_assert_purge_operation_execution_claim_locked by having the locked method
delegate to the shared assertion logic while preserving its locked row-fetch
behavior. Extract or reuse a common row-check helper so both paths apply the
same five claim conditions, with only the database fetch mechanism differing.
tests/server/services/storage/sqlite_storage/test_governance_storage.py (2)

1677-1704: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the caller scan fail when the production root resolves incorrectly.

production_root is derived from parents[5], which couples the test to its exact directory depth. If this file moves one level, parents[5] points at a different directory, rglob("*.py") yields nothing, and assert violations == [] passes without scanning anything. The test then reports success while enforcing nothing.

Add an assertion that the root exists and that the scan visited a non-zero number of files.

🛡️ Proposed guard
     production_root = Path(__file__).resolve().parents[5] / "reflexio"
+    assert production_root.is_dir(), production_root
     violations: list[str] = []
+    scanned = 0
     for path in production_root.rglob("*.py"):
+        scanned += 1
         tree = ast.parse(path.read_text(), filename=str(path))
+    assert scanned > 0
     assert violations == []
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/storage/sqlite_storage/test_governance_storage.py`
around lines 1677 - 1704, Strengthen the production scan in the test around
production_root by asserting that the derived root exists and is a directory
before scanning. Track the files yielded by production_root.rglob("*.py") and
assert that the scan visited at least one file, while preserving the existing
violation checks.

218-238: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document that _claim_purge steals a live lease.

When a live unexpired lease already exists, claim_purge_operation_execution returns None. _claim_purge then force-expires the lease and re-claims, which increments the fence and invalidates every previously issued claim for that purge_id.

Several tests call _claim_purge more than once for the same purge_id, for example Lines 701, 708, and 722. Each call currently supersedes the prior one. That works today only because every call site uses its claim immediately. A future test that captures a claim into a variable and then calls _claim_purge again before using it will fail with purge execution claim is no longer active, and the cause will not be obvious.

Add a docstring stating the takeover behavior, or split the helper into _claim_purge (first claim only) and _take_over_purge (force-expire and re-claim).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/storage/sqlite_storage/test_governance_storage.py`
around lines 218 - 238, Document the lease-takeover behavior in the test helper
_claim_purge: add a docstring stating that when a live lease exists it
force-expires that lease, reclaims the purge, and invalidates previously issued
claims for the same purge_id. Keep the existing claiming logic unchanged.
tests/server/services/lineage/test_gc_scheduler_global_sweep.py (2)

30-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Import the always-sweep helpers directly instead of guarding with getattr.

register_always_global_sweep and clear_always_global_sweeps exist in gc_scheduler. Two other tests in this file already call gc_scheduler.register_always_global_sweep directly at Line 135 and Line 153.

The fixture guard is the larger risk. If clear_always_global_sweeps is renamed or removed, the fixture silently stops clearing registrations, and always-sweep hooks leak into every later test in the session. A direct import fails loudly instead.

♻️ Proposed change
 def _isolate_hooks():
     clear_global_sweeps()
-    clear_always = getattr(gc_scheduler, "clear_always_global_sweeps", None)
-    if clear_always is not None:
-        clear_always()
+    gc_scheduler.clear_always_global_sweeps()
     yield
     clear_global_sweeps()
-    if clear_always is not None:
-        clear_always()
+    gc_scheduler.clear_always_global_sweeps()
 def test_run_once_invokes_always_global_sweep_when_expiry_disabled(monkeypatch):
     calls: list[int] = []
-    register = getattr(gc_scheduler, "register_always_global_sweep", None)
-    assert callable(register)
-    register(lambda now: calls.append(now) or 1)
+    gc_scheduler.register_always_global_sweep(lambda now: calls.append(now) or 1)

Also applies to: 110-112

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/lineage/test_gc_scheduler_global_sweep.py` around lines
30 - 36, Update the fixture cleanup to import and call
gc_scheduler.clear_always_global_sweeps directly, removing the getattr guard and
clear_always variable; likewise import register_always_global_sweep directly
where the fixture or tests use it, so missing helpers fail loudly while
preserving cleanup before and after each test.

143-143: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert against the scheduler constants instead of literals.

5 and 86400 duplicate _BOOTSTRAP_RETRY_INTERVAL_SECONDS and _DEFAULT_POLL_INTERVAL_SECONDS in gc_scheduler.py. If either constant changes, these tests fail with an opaque numeric mismatch that does not name the constant.

Import the constants and compare against them so the failure message points at the source of truth.

Also applies to: 170-170

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/lineage/test_gc_scheduler_global_sweep.py` at line 143,
Update the assertions in the global sweep scheduler tests, including the
assertion around scheduler._run_once() and the one at the referenced second
location, to compare against _BOOTSTRAP_RETRY_INTERVAL_SECONDS and
_DEFAULT_POLL_INTERVAL_SECONDS from gc_scheduler.py instead of numeric literals.
Import both constants from the scheduler module and preserve the existing
expected-value behavior.
reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py (1)

188-215: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the duplicated scope_type check.

Line 190 already rejects any purge whose scope_type is not "user". Line 207 repeats the same comparison inside the composite condition, so it can never be true at that point.

♻️ Proposed simplification
         if (
-            purge_row["scope_type"] != "user"
-            or purge_row["subject_ref"] != subject_ref
+            purge_row["subject_ref"] != subject_ref
             or self._subject_ref_for_user_id(authoritative_user_id) != subject_ref
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py`
around lines 188 - 215, Remove the redundant purge_row["scope_type"] != "user"
comparison from the composite validation condition after the initial guard. Keep
the existing scope validation at the start of the completion flow and preserve
all other authoritative identity checks unchanged.
reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py (1)

92-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tighten the _assert_purge_operation_execution_claim_locked MRO hints to a non-optional claim.

All three mixins declare the MRO-provided callable as Callable[[str, PurgeExecutionClaim | None], None]. The implementation in reflexio/server/services/storage/sqlite_storage/governance/_purge.py declares execution_claim: PurgeExecutionClaim, and test_claimed_erasure_mutation_signatures_and_callers_require_claim asserts the public annotation is non-optional. The | None widens the type only for callers inside these three mixins, so Pyright would accept a None claim there without an error, and the failure would surface at runtime inside validate_purge_execution_claim.

  • reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py#L92-L94: change the hint to Callable[[str, PurgeExecutionClaim], None].
  • reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py#L85-L87: change the hint to Callable[[str, PurgeExecutionClaim], None].
  • reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py#L80-L82: change the hint to Callable[[str, PurgeExecutionClaim], None].
♻️ Proposed change (identical at each site)
-    _assert_purge_operation_execution_claim_locked: Callable[
-        [str, PurgeExecutionClaim | None], None
-    ]
+    _assert_purge_operation_execution_claim_locked: Callable[
+        [str, PurgeExecutionClaim], None
+    ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py`
around lines 92 - 94, Update the MRO-provided
_assert_purge_operation_execution_claim_locked callable annotation to require
PurgeExecutionClaim rather than allowing None in
reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py:92-94,
_rebuild_hide.py:85-87, and _subject_barrier.py:80-82. Keep the callable
signature as Callable[[str, PurgeExecutionClaim], None] at all three sites,
matching the implementation and public contract.
tests/models/test_session_outcome_identity.py (1)

133-135: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Annotate the golden digest, and consider versioning the trajectory shape.

This literal pins the output of trajectory_digest over CanonicalSessionTrajectory. Any field added to CanonicalRequest or CanonicalInteraction, and any change to _canonical_trajectory_json, fails this assertion with an opaque hex mismatch. Add a comment that states what the value covers and what to do when it changes intentionally.

The deeper point: canonical_session_trajectory embeds no schema version, unlike outcome_contract_digest, which hashes an explicit schema_version, and unlike user_playbook_full_version_fingerprint in reflexio/server/services/search_exposure.py lines 103-118, which pins "user-playbook-full-version-v1" and documents the bump requirement. Persisted finalized_trajectory_digest values are compared against freshly computed ones on retry. After a field is added to the canonical shape, every stored digest silently stops matching, and callers receive conflicting_finalization for identical payloads. A schema_version key inside the trajectory payload makes that transition explicit and diagnosable.

♻️ Proposed comment for the golden value
     assert sqlite_projection == postgres_projection
     assert sqlite_projection["requests"][0]["request"]["evaluation_only"] is False
+    # Golden digest over the canonical trajectory shape. Update this value only
+    # together with an intentional change to CanonicalRequest,
+    # CanonicalInteraction, or _canonical_trajectory_json. Stored
+    # finalized_trajectory_digest values do not survive such a change.
     assert trajectory_digest(sqlite_projection) == (
         "73d0f738bb5a3c7668787c678230c4758b68923e12a16e3851b401db780e4272"
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/models/test_session_outcome_identity.py` around lines 133 - 135,
Annotate the golden digest assertion for trajectory_digest with a comment
describing that it covers CanonicalSessionTrajectory and instructing maintainers
to review and intentionally update the value when the canonical shape or
_canonical_trajectory_json changes. Also add an explicit schema_version to the
canonical_session_trajectory payload, following the existing versioning pattern,
and ensure intentional version bumps make persisted digest changes diagnosable
rather than causing opaque retry conflicts.
tests/server/api_endpoints/test_session_outcomes_integration.py (1)

232-245: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive session_id from the parameter id, not from the string length.

session_id uses len(stored_metadata). The three current values have lengths 0, 10, and 20000, so they do not collide today. A future parameter whose length equals an existing one reuses the same session_id. The second run would then post against a session that the first run already finalized, and the failure would point at the outcome logic rather than at the duplicate id.

Pass an explicit id alongside each metadata value.

♻️ Proposed fix
 `@pytest.mark.parametrize`(
-    "stored_metadata",
+    ("case_id", "stored_metadata"),
     [
-        pytest.param("", id="empty"),
-        pytest.param("{malformed", id="malformed"),
-        pytest.param("[" * 10_000 + "]" * 10_000, id="pathological-nesting"),
+        pytest.param("empty", "", id="empty"),
+        pytest.param("malformed", "{malformed", id="malformed"),
+        pytest.param(
+            "pathological-nesting",
+            "[" * 10_000 + "]" * 10_000,
+            id="pathological-nesting",
+        ),
     ],
 )
 def test_retry_rejects_invalid_stored_metadata(
-    client_with_org: tuple[TestClient, str], stored_metadata: str
+    client_with_org: tuple[TestClient, str], case_id: str, stored_metadata: str
 ) -> None:
     client, org_id = client_with_org
     storage = get_reflexio(org_id=org_id).get_storage()
-    session_id = f"invalid-stored-metadata-{len(stored_metadata)}"
+    session_id = f"invalid-stored-metadata-{case_id}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/api_endpoints/test_session_outcomes_integration.py` around lines
232 - 245, Update test_retry_rejects_invalid_stored_metadata to parameterize an
explicit identifier alongside each stored_metadata value, then derive session_id
from that identifier instead of len(stored_metadata). Preserve the existing
metadata cases and use unique parameter ids such as empty, malformed, and
pathological-nesting to prevent collisions.
reflexio/models/api_schema/domain/entities.py (1)

829-867: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the outcome-identity validation into one shared definition.

The digest validator and the all-or-none identity rule now exist three times:

  • reflexio/models/api_schema/domain/entities.py lines 843-866 (SessionOutcomeRecord)
  • reflexio/models/api_schema/domain/entities.py lines 918-941 (SetSessionOutcomeResponse)
  • reflexio/server/services/storage/storage_base/_session_outcomes.py lines 26-38 (SessionOutcomeWriteResult.__post_init__)

If a fifth identity field is added later, all three copies must change together. A missed copy produces a model that accepts a partial identity. Define the identity tuple and its check once, then reuse it. The two Pydantic models can share a mixin base class; the dataclass can call the same helper function.

♻️ Proposed shared mixin for the two Pydantic models
+class OutcomeIdentityMixin(BaseModel):
+    outcome_id: NonEmptyStr | None = None
+    outcome_revision: int | None = Field(default=None, ge=1)
+    outcome_contract_digest: Sha256Digest | None = None
+    finalized_trajectory_digest: Sha256Digest | None = None
+
+    `@field_validator`("outcome_contract_digest", "finalized_trajectory_digest")
+    `@classmethod`
+    def validate_sha256_digest(cls, value: str | None) -> str | None:
+        if value is not None and (
+            len(value) != 64 or any(char not in "0123456789abcdef" for char in value)
+        ):
+            raise ValueError("outcome identity digests must be lowercase SHA-256 hex")
+        return value
+
+    `@model_validator`(mode="after")
+    def validate_identity_shape(self) -> Self:
+        identity = (
+            self.outcome_id,
+            self.outcome_revision,
+            self.outcome_contract_digest,
+            self.finalized_trajectory_digest,
+        )
+        if any(value is None for value in identity) and not all(
+            value is None for value in identity
+        ):
+            raise ValueError(
+                "outcome identity fields must be all populated or all null"
+            )
+        return self

SessionOutcomeRecord and SetSessionOutcomeResponse then inherit from OutcomeIdentityMixin and drop their local copies. Field order in serialized output changes, so confirm the expected dict in tests/models/test_session_outcome_identity.py lines 160-176 still matches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/models/api_schema/domain/entities.py` around lines 829 - 867,
Extract the shared outcome identity fields, lowercase SHA-256 digest validation,
and all-or-none identity check from SessionOutcomeRecord and
SetSessionOutcomeResponse into one reusable OutcomeIdentityMixin/helper. Update
both Pydantic models to reuse it and change
SessionOutcomeWriteResult.__post_init__ to call the same shared identity
validator, preserving partial-identity rejection and digest validation. Verify
serialized field ordering and expected dictionaries in the session outcome
identity tests.
tests/server/services/storage/test_storage_contract_clear_user_data.py (1)

176-186: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The monkeypatch.setenv call has no effect on this test.

The storage fixture is constructed before the test body runs, so setting REFLEXIO_GOVERNANCE_REF_SECRET here does not change any secret the storage instance already resolved. The test also passes "test-governance-secret" explicitly to governance_subject_ref, and clear_user_data deletes by user_id without deriving a subject ref. Remove the monkeypatch parameter and the setenv call, or move the environment setup into the fixture if the storage-derived ref is meant to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/storage/test_storage_contract_clear_user_data.py`
around lines 176 - 186, Remove the unused monkeypatch parameter from the
affected test and delete the REFLEXIO_GOVERNANCE_REF_SECRET setenv call; keep
the explicit "test-governance-secret" arguments to governance_subject_ref and
the existing SQLiteStorage setup unchanged.
tests/server/services/storage/sqlite_storage/test_session_outcome_migration.py (1)

199-254: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Derive the expected query count from the chunk size.

row_count = 501 and assert len(trajectory_input_queries) == 4 both encode the chunked() default chunk size implicitly. If that default changes, the assertion fails with no indication of the cause. Import the chunk-size constant (or compute ceil(row_count / chunk_size) * 2) so the bound the test protects is explicit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tests/server/services/storage/sqlite_storage/test_session_outcome_migration.py`
around lines 199 - 254, Update the migration test around
_migrate_session_outcomes_schema to derive the expected trajectory_input_queries
count from the shared chunk-size constant, accounting for two query types per
chunk (for example, ceil(row_count / chunk_size) * 2). Replace the hard-coded
501/4 relationship while preserving the existing row-count assertion.
tests/server/services/storage/test_storage_contract_session_outcomes.py (1)

100-102: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Guard the SQLite-only tests with isinstance instead of cast.

This module is a cross-backend storage contract suite typed against BaseStorage. cast(SQLiteStorage, storage) is a static-only assertion. If the storage fixture is later parametrized with Supabase or PostgreSQL, these tests fail with an opaque AttributeError on .conn rather than a clear skip or assertion. tests/server/services/storage/test_storage_contract_clear_user_data.py already uses assert isinstance(sqlite_storage, SQLiteStorage). Apply the same pattern here for the four SQLite-specific tests.

♻️ Proposed change (repeat at each cast site)
-    sqlite_storage = cast(SQLiteStorage, storage)
+    sqlite_storage = storage
+    assert isinstance(sqlite_storage, SQLiteStorage)

Also applies to: 124-131

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/storage/test_storage_contract_session_outcomes.py`
around lines 100 - 102, Update the four SQLite-specific tests in this contract
module, including
test_legacy_all_null_identity_exact_retry_uses_available_context, to assert
isinstance(storage, SQLiteStorage) before accessing SQLite-only attributes such
as conn, then use the narrowed storage directly instead of cast. Follow the
guard pattern from test_storage_contract_clear_user_data.py so non-SQLite
backends fail clearly or skip rather than raising an opaque AttributeError.
reflexio/server/services/base_generation_service.py (1)

727-729: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename the billing parameter to match its new meaning.

emit_generation_side_effects now passes plan.billable_count into a parameter still named generated_count. The mixin docstring already describes it as "retained write-plan learnings". The name now contradicts the value at every call site and invites a future caller to pass raw generated counts again. Rename the keyword in _record_billing_learning_events to billable_count and update the two call sites.

♻️ Proposed rename at this call site
         self._record_billing_learning_events(
-            prepared=plan.prepared, generated_count=plan.billable_count
+            prepared=plan.prepared, billable_count=plan.billable_count
         )

Apply the matching signature change in reflexio/server/services/base_generation/_usage_billing.py:

def _record_billing_learning_events(
    self, *, prepared: "PreparedGenerationRun[Any]", billable_count: int
) -> None:
    ...
    record_learnings_generated(
        org_id=ctx["org_id"],
        count=billable_count,
        ...
    )

Also update tests/server/services/test_generation_billing_emission.py line 493.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/services/base_generation_service.py` around lines 727 - 729,
Rename the _record_billing_learning_events keyword parameter from
generated_count to billable_count, use billable_count when calling
record_learnings_generated, and update both call sites, including
emit_generation_side_effects and the affected billing emission test.
tests/server/test_usage_metrics.py (1)

31-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add fail-open coverage for record_usage_event on the same two inputs.

This test proves that a legacy recorder returning None yields UNKNOWN from record_usage_event_strict. That behavior is correct for the strict API. The untested consequence is on the non-strict API: record_usage_event now funnels the same UNKNOWN into its except handler and emits a warning for every event. See the issue raised on reflexio/server/usage_metrics.py Lines 118-157.

Add two cases that pin the intended fail-open contract:

  • record_usage_event with no recorder configured must not log a warning.
  • record_usage_event with a None-returning recorder must deliver the event and must not log a warning.

Use caplog at WARNING level to assert the absence of records.

💚 Proposed tests
def test_ordinary_recording_is_silent_without_a_recorder(caplog):
    usage_metrics.configure_usage_event_recorder(None)

    with caplog.at_level("WARNING", logger=usage_metrics.logger.name):
        usage_metrics.record_usage_event(
            org_id="7",
            event_name="search_request",
            event_category="application",
        )

    assert caplog.records == []


def test_ordinary_recording_is_silent_for_legacy_none_recorder(caplog):
    captured = []
    usage_metrics.configure_usage_event_recorder(captured.append)
    try:
        with caplog.at_level("WARNING", logger=usage_metrics.logger.name):
            usage_metrics.record_usage_event(
                org_id="7",
                event_name="search_request",
                event_category="application",
                event_key="search:1",
            )
    finally:
        usage_metrics.configure_usage_event_recorder(None)

    assert [event.event_key for event in captured] == ["search:1"]
    assert caplog.records == []
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/test_usage_metrics.py` around lines 31 - 46, Add fail-open
coverage in tests/server/test_usage_metrics.py for record_usage_event: add one
test with no recorder configured and another with a legacy recorder returning
None, using caplog at WARNING level to assert no warnings are emitted; verify
the latter still delivers the event and always reset the recorder configuration.
reflexio/server/services/playbook/service.py (1)

661-735: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Extract the shared receipt-finalization flow.

This method duplicates ProfileGenerationService._finalize_extracted_items_with_outcome in reflexio/server/services/profile/service.py almost line for line: the pre-transaction receipt read, the in-transaction re-read, the persist call, the save_agent_run_finalization_receipt insert, the _FinalizationReceiptAlreadyExistsError rollback signal, and the "receipt disappeared" guard. Only two things differ: the entity_type constant, the point where learning ids become available, and the playbook-only scheduler dispatch.

The receipt protocol is the correctness-critical part of this PR. Two copies mean a future fix to one copy silently misses the other. Extract the protocol into one helper on the base service or a mixin, parameterized by entity_type, an id-extraction callback, and an optional post-commit hook.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/services/playbook/service.py` around lines 661 - 735, Extract
the duplicated receipt protocol from _finalize_extracted_items_with_outcome and
ProfileGenerationService._finalize_extracted_items_with_outcome into one shared
base-service or mixin helper. Parameterize it with entity_type, a callback that
derives learning IDs after _persist_write_plan, and an optional post-commit hook
for playbook scheduler dispatch; preserve the pre-transaction read,
transactional re-read/persist/receipt insert, conflict rollback signal, and
disappeared-receipt guard while making both services use the shared flow.
tests/server/test_billing_meter.py (1)

123-146: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the UNKNOWN mapping.

The parametrization covers FAILED and REJECTED. It does not cover the except Exception branch at Line 489 of billing_meter.py, which maps any non-UsageEventDeliveryError failure to ReceiptBillingDeliveryError(UsageEventDeliveryStatus.UNKNOWN).

That status decides the retry class in _finalization_failure_status: REJECTED terminates the run, everything else retries. Add a case that raises a plain RuntimeError from record_usage_event_strict and asserts the resulting status is UNKNOWN.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/test_billing_meter.py` around lines 123 - 146, Extend
test_strict_receipt_billing_preserves_delivery_status to cover a plain
RuntimeError from record_usage_event_strict, alongside the existing
UsageEventDeliveryError cases. Assert that
emit_learnings_generated_records_strict raises ReceiptBillingDeliveryError with
status UsageEventDeliveryStatus.UNKNOWN.
reflexio/server/billing_meter.py (1)

268-288: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Type record_event instead of using Any.

record_event: Any disables signature checking on the only call in this helper. record_usage_event and record_usage_event_strict must keep identical keyword parameters for both call paths to work. A Protocol or Callable alias makes drift a type error.

♻️ Proposed typing
+class _UsageEventRecorder(Protocol):
+    def __call__(
+        self,
+        *,
+        org_id: str,
+        event_name: str,
+        event_category: str,
+        pipeline: str | None,
+        user_id: str | None,
+        request_id: str | None,
+        session_id: str | None,
+        source: str | None,
+        agent_version: str | None,
+        playbook_name: str | None,
+        entity_type: str | None,
+        entity_id: str | None,
+        event_key: str | None,
+        count_value: int,
+        platform_llm: bool | None,
+        platform_storage: bool | None,
+        caller_type: str | None,
+        metadata: Mapping[str, Any] | None,
+        created_at: float | None,
+    ) -> Any: ...
+
+
 def _record_learnings_generated_records(
     *,
-    record_event: Any,
+    record_event: _UsageEventRecorder,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/billing_meter.py` around lines 268 - 288, The
_record_learnings_generated_records helper currently accepts record_event as
Any, bypassing signature validation. Define a Protocol or Callable type
describing the exact keyword arguments used by its record_event call, and apply
it to record_event so both record_usage_event and record_usage_event_strict
remain compatible and parameter drift is caught by type checking.
reflexio/server/services/profile/service.py (1)

360-422: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Extract the shared receipt-fencing flow.

This method duplicates PlaybookGenerationService._finalize_extracted_items_with_outcome almost line for line: the pre-scope receipt read, the in-scope receipt re-read, the persist call, save_agent_run_finalization_receipt, the _FinalizationReceiptAlreadyExistsError conflict rollback, and the post-conflict re-read that raises RuntimeError when the receipt is missing.

Only three parts differ: the entity_type constant, when learning_ids are derived (before persist for profiles, after persist for playbooks), and the playbook scheduler dispatch. A shared helper that takes entity_type, a persist callback, and a learning-id callback would remove the copy. This is a money and durability path, so divergence between the two copies is expensive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/services/profile/service.py` around lines 360 - 422, Extract
the duplicated receipt-fencing logic from profile
`_finalize_extracted_items_with_outcome` and
`PlaybookGenerationService._finalize_extracted_items_with_outcome` into a shared
helper. Parameterize the helper with `entity_type`, a persist callback, and a
learning-ID callback so profiles can derive IDs before persistence while
playbooks derive them afterward; keep playbook scheduler dispatch outside the
helper. Preserve the pre-scope and in-scope receipt checks, conflict rollback,
post-conflict receipt reread, and missing-receipt `RuntimeError` behavior
exactly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reflexio/server/api.py`:
- Around line 475-481: Move the cleanup scope in the startup flow to begin
immediately after configure_usage_event_recorder(exempt_usage_event_recorder),
before run_startup_config_guards, data-plane initialization, or scheduler
startup can raise. Ensure every failure after configuring the recorder clears or
restores the global recorder, including failures occurring before the existing
finally block.

In `@reflexio/server/billing_meter.py`:
- Around line 398-411: Update the docstrings for emit_learnings_generated,
record_learnings_generated, and the ordinary emitter described near the
learning-ID parameters so they no longer claim resumable finalization uses the
non-strict helpers. Document ExtractionResumeWorker._record_finalized_learnings
as using emit_learnings_generated_records_strict, while preserving the existing
ownership and fail-open semantics for the ordinary paths.

In `@reflexio/server/services/extraction/resume_worker.py`:
- Around line 943-956: Align _record_finalized_learnings with the
FinalizationResult contract by either removing billing from the winner-only
side-effects wording in FinalizationResult’s docstring, or gating its invocation
on result.won_receipt; preserve the intended idempotent billing replay behavior
indicated by the existing test. Move emit_learnings_generated_records_strict
from the method-local import into the module-level billing_meter import
alongside ReceiptBillingDeliveryError.
- Around line 93-106: Update _finalization_failure_status so
ReceiptBillingDeliveryError cases other than REJECTED also check
next_attempt_count against max_finalization_attempts before returning
FINALIZATION_FAILED. Preserve immediate FAILED status for REJECTED, and return
FAILED when the attempt ceiling is reached for UNKNOWN and other non-rejected
receipt failures.

In `@reflexio/server/services/search_exposure.py`:
- Around line 172-183: Sort the exported names in __all__ alphabetically using
the project formatter, updating only the ordering in this list while preserving
all existing exports.

In `@reflexio/server/services/storage/session_outcome_identity.py`:
- Around line 11-17: Sort the exported names in __all__ alphabetically to
satisfy Ruff RUF022, preserving all existing entries and their spellings.
- Around line 165-188: The _canonical_trajectory_json function needs a
maximum-depth guard while recursively encoding mappings, tuples, and lists.
Track recursion depth and raise the established validation error when persisted
interaction JSON exceeds the permitted depth, ensuring trajectory_digest and
session-outcome finalization cannot hit RecursionError.

In `@reflexio/server/services/storage/sqlite_storage/_base.py`:
- Around line 1183-1187: Update the outcome_revision value in the INSERT
construction around the existing column-presence check to fall back to revision
1 when the column is missing or row["outcome_revision"] is NULL. Preserve
non-NULL stored revisions while ensuring legacy rows always satisfy the database
constraints.

In `@reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py`:
- Around line 27-34: Update the docstring for get_agent_run_finalization_receipt
to explicitly define the return contract: None means no receipt exists and
finalization should proceed, while an empty list means a receipt was committed
despite producing no durable learnings and must be treated as finalized.
Preserve this distinction across all backend implementations.

In `@reflexio/server/usage_metrics.py`:
- Around line 118-157: The fail-open wrapper currently logs warnings for
expected UNKNOWN delivery outcomes. In reflexio/server/usage_metrics.py lines
118-157, update record_usage_event to catch UsageEventDeliveryError separately,
suppress warnings when exc.status is UNKNOWN, log other statuses, add
exc_info=True to the generic recorder-exception handler, and document that
missing or legacy recorders are non-errors; in
tests/server/test_usage_metrics.py lines 31-46, add caplog tests covering both
no configured recorder and a legacy recorder returning None, verifying no
warning is emitted and the event reaches the legacy recorder.

In `@tests/server/api_endpoints/test_session_outcomes_integration.py`:
- Line 188: Update the retry response assertion to validate that
retry.json()["reason"] is None rather than asserting the key is absent. Keep the
existing conflict assertion that accesses the reason key so both tests verify
the response value consistently.

In `@tests/server/services/test_non_extraction_learning_metering.py`:
- Around line 45-51: Reset the module-level usage-event recorder after every
test in this file. Add an autouse pytest fixture near _capture_events that
yields for the test and calls configure_usage_event_recorder(None) in a finally
block, ensuring cleanup also occurs when assertions fail; keep _capture_events
responsible only for installing its recorder.

---

Nitpick comments:
In `@reflexio/models/api_schema/domain/entities.py`:
- Around line 829-867: Extract the shared outcome identity fields, lowercase
SHA-256 digest validation, and all-or-none identity check from
SessionOutcomeRecord and SetSessionOutcomeResponse into one reusable
OutcomeIdentityMixin/helper. Update both Pydantic models to reuse it and change
SessionOutcomeWriteResult.__post_init__ to call the same shared identity
validator, preserving partial-identity rejection and digest validation. Verify
serialized field ordering and expected dictionaries in the session outcome
identity tests.

In `@reflexio/server/billing_meter.py`:
- Around line 268-288: The _record_learnings_generated_records helper currently
accepts record_event as Any, bypassing signature validation. Define a Protocol
or Callable type describing the exact keyword arguments used by its record_event
call, and apply it to record_event so both record_usage_event and
record_usage_event_strict remain compatible and parameter drift is caught by
type checking.

In `@reflexio/server/services/base_generation_service.py`:
- Around line 727-729: Rename the _record_billing_learning_events keyword
parameter from generated_count to billable_count, use billable_count when
calling record_learnings_generated, and update both call sites, including
emit_generation_side_effects and the affected billing emission test.

In `@reflexio/server/services/governance/service.py`:
- Around line 418-449: Remove the tautological purge_id entry from
expected_identity in _matching_user_erasure_purge_for_retry, while preserving
validation of operation_type, scope_type, subject_ref, and request_ref and the
authoritative-user check.

In `@reflexio/server/services/playbook/service.py`:
- Around line 661-735: Extract the duplicated receipt protocol from
_finalize_extracted_items_with_outcome and
ProfileGenerationService._finalize_extracted_items_with_outcome into one shared
base-service or mixin helper. Parameterize it with entity_type, a callback that
derives learning IDs after _persist_write_plan, and an optional post-commit hook
for playbook scheduler dispatch; preserve the pre-transaction read,
transactional re-read/persist/receipt insert, conflict rollback signal, and
disappeared-receipt guard while making both services use the shared flow.

In `@reflexio/server/services/profile/service.py`:
- Around line 360-422: Extract the duplicated receipt-fencing logic from profile
`_finalize_extracted_items_with_outcome` and
`PlaybookGenerationService._finalize_extracted_items_with_outcome` into a shared
helper. Parameterize the helper with `entity_type`, a persist callback, and a
learning-ID callback so profiles can derive IDs before persistence while
playbooks derive them afterward; keep playbook scheduler dispatch outside the
helper. Preserve the pre-scope and in-scope receipt checks, conflict rollback,
post-conflict receipt reread, and missing-receipt `RuntimeError` behavior
exactly.

In
`@reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py`:
- Around line 92-94: Update the MRO-provided
_assert_purge_operation_execution_claim_locked callable annotation to require
PurgeExecutionClaim rather than allowing None in
reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py:92-94,
_rebuild_hide.py:85-87, and _subject_barrier.py:80-82. Keep the callable
signature as Callable[[str, PurgeExecutionClaim], None] at all three sites,
matching the implementation and public contract.

In `@reflexio/server/services/storage/sqlite_storage/governance/_purge.py`:
- Around line 449-497: Consolidate the duplicated validation in
assert_purge_operation_execution_claim and
_assert_purge_operation_execution_claim_locked by having the locked method
delegate to the shared assertion logic while preserving its locked row-fetch
behavior. Extract or reuse a common row-check helper so both paths apply the
same five claim conditions, with only the database fetch mechanism differing.

In
`@reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py`:
- Around line 188-215: Remove the redundant purge_row["scope_type"] != "user"
comparison from the composite validation condition after the initial guard. Keep
the existing scope validation at the start of the completion flow and preserve
all other authoritative identity checks unchanged.

In `@tests/models/test_session_outcome_identity.py`:
- Around line 133-135: Annotate the golden digest assertion for
trajectory_digest with a comment describing that it covers
CanonicalSessionTrajectory and instructing maintainers to review and
intentionally update the value when the canonical shape or
_canonical_trajectory_json changes. Also add an explicit schema_version to the
canonical_session_trajectory payload, following the existing versioning pattern,
and ensure intentional version bumps make persisted digest changes diagnosable
rather than causing opaque retry conflicts.

In `@tests/server/api_endpoints/test_session_outcomes_integration.py`:
- Around line 232-245: Update test_retry_rejects_invalid_stored_metadata to
parameterize an explicit identifier alongside each stored_metadata value, then
derive session_id from that identifier instead of len(stored_metadata). Preserve
the existing metadata cases and use unique parameter ids such as empty,
malformed, and pathological-nesting to prevent collisions.

In `@tests/server/services/governance/test_governance_local_e2e.py`:
- Around line 963-966: Update release_winner_on_duplicate_poll to use
time.sleep(0.001) instead of creating and waiting on a throwaway
threading.Event, and add the required time import at the file’s imports.
- Around line 1052-1110: Document the fake-clock invariant in
test_healthy_slow_lifecycle_renews_lease_and_duplicate_converges: state that the
claim begins at 100 with a 300-second lease expiring at 400, three renewals
extend it to 650, and advancing to 401 keeps the duplicate caller within the
live lease. Place the comment next to the relevant fake_now updates and renewal
wait.
- Around line 1044-1049: Update the backoff assertions in the governance erase
retry test to derive expected initial, maximum, and total delays from
governance_service_module’s _DUPLICATE_ERASE_POLL_SECONDS,
_DUPLICATE_ERASE_MAX_POLL_SECONDS, and _DUPLICATE_ERASE_WAIT_SECONDS constants.
Preserve the existing checks for doubling behavior, maximum delay, and total
wait bound without hardcoded duplicate values.

In `@tests/server/services/governance/test_subject_write_barrier_sqlite.py`:
- Around line 622-629: Remove the redundant
get_subject_write_barrier(subject_ref) call in this test, keeping a single
barrier fetch and its associated non-null and status assertions; preserve the
stored_purge retrieval and its independent assertions.
- Around line 42-62: Add a concise comment in `_claim_purge` documenting that it
deliberately expires and steals any existing execution lease, increments the
fence, and makes claims from earlier calls stale; state that callers must use
each returned claim before invoking `_claim_purge` again.

In `@tests/server/services/lineage/test_gc_scheduler_global_sweep.py`:
- Around line 30-36: Update the fixture cleanup to import and call
gc_scheduler.clear_always_global_sweeps directly, removing the getattr guard and
clear_always variable; likewise import register_always_global_sweep directly
where the fixture or tests use it, so missing helpers fail loudly while
preserving cleanup before and after each test.
- Line 143: Update the assertions in the global sweep scheduler tests, including
the assertion around scheduler._run_once() and the one at the referenced second
location, to compare against _BOOTSTRAP_RETRY_INTERVAL_SECONDS and
_DEFAULT_POLL_INTERVAL_SECONDS from gc_scheduler.py instead of numeric literals.
Import both constants from the scheduler module and preserve the existing
expected-value behavior.

In `@tests/server/services/storage/sqlite_storage/test_governance_storage.py`:
- Around line 1677-1704: Strengthen the production scan in the test around
production_root by asserting that the derived root exists and is a directory
before scanning. Track the files yielded by production_root.rglob("*.py") and
assert that the scan visited at least one file, while preserving the existing
violation checks.
- Around line 218-238: Document the lease-takeover behavior in the test helper
_claim_purge: add a docstring stating that when a live lease exists it
force-expires that lease, reclaims the purge, and invalidates previously issued
claims for the same purge_id. Keep the existing claiming logic unchanged.

In
`@tests/server/services/storage/sqlite_storage/test_session_outcome_migration.py`:
- Around line 199-254: Update the migration test around
_migrate_session_outcomes_schema to derive the expected trajectory_input_queries
count from the shared chunk-size constant, accounting for two query types per
chunk (for example, ceil(row_count / chunk_size) * 2). Replace the hard-coded
501/4 relationship while preserving the existing row-count assertion.

In `@tests/server/services/storage/test_storage_contract_clear_user_data.py`:
- Around line 176-186: Remove the unused monkeypatch parameter from the affected
test and delete the REFLEXIO_GOVERNANCE_REF_SECRET setenv call; keep the
explicit "test-governance-secret" arguments to governance_subject_ref and the
existing SQLiteStorage setup unchanged.

In `@tests/server/services/storage/test_storage_contract_session_outcomes.py`:
- Around line 100-102: Update the four SQLite-specific tests in this contract
module, including
test_legacy_all_null_identity_exact_retry_uses_available_context, to assert
isinstance(storage, SQLiteStorage) before accessing SQLite-only attributes such
as conn, then use the narrowed storage directly instead of cast. Follow the
guard pattern from test_storage_contract_clear_user_data.py so non-SQLite
backends fail clearly or skip rather than raising an opaque AttributeError.

In `@tests/server/test_billing_meter.py`:
- Around line 123-146: Extend
test_strict_receipt_billing_preserves_delivery_status to cover a plain
RuntimeError from record_usage_event_strict, alongside the existing
UsageEventDeliveryError cases. Assert that
emit_learnings_generated_records_strict raises ReceiptBillingDeliveryError with
status UsageEventDeliveryStatus.UNKNOWN.

In `@tests/server/test_usage_metrics.py`:
- Around line 31-46: Add fail-open coverage in
tests/server/test_usage_metrics.py for record_usage_event: add one test with no
recorder configured and another with a legacy recorder returning None, using
caplog at WARNING level to assert no warnings are emitted; verify the latter
still delivers the event and always reset the recorder configuration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

Comment thread reflexio/server/api.py
Comment thread reflexio/server/billing_meter.py Outdated
Comment thread reflexio/server/services/extraction/resume_worker.py
Comment thread reflexio/server/services/extraction/resume_worker.py
Comment thread reflexio/server/services/search_exposure.py
Comment thread reflexio/server/services/storage/sqlite_storage/_base.py
Comment thread reflexio/server/usage_metrics.py
Comment thread tests/server/api_endpoints/test_session_outcomes_integration.py Outdated
Comment thread tests/server/services/test_non_extraction_learning_metering.py
@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reflexio/server/services/storage/session_outcome_identity.py`:
- Around line 167-183: Update _canonical_trajectory_json so list, tuple, and
mapping containers are rejected when depth >=
MAX_CANONICAL_TRAJECTORY_JSON_DEPTH, before traversing their contents; retain
scalar handling at the existing boundary. Add a regression test covering nested
empty containers and verify the same limit applies consistently to sequences and
mappings.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 05dad9a2-62a0-4c36-be08-59c34adc074e

📥 Commits

Reviewing files that changed from the base of the PR and between 474d7ce and 90ffba3.

📒 Files selected for processing (15)
  • reflexio/server/api.py
  • reflexio/server/billing_meter.py
  • reflexio/server/services/deferred_learning_plan.py
  • reflexio/server/services/extraction/resume_worker.py
  • reflexio/server/services/search_exposure.py
  • reflexio/server/services/storage/session_outcome_identity.py
  • reflexio/server/services/storage/sqlite_storage/_base.py
  • reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py
  • reflexio/server/usage_metrics.py
  • tests/models/test_session_outcome_identity.py
  • tests/server/api_endpoints/test_session_outcomes_integration.py
  • tests/server/services/extraction/test_resume_worker.py
  • tests/server/services/storage/sqlite_storage/test_session_outcome_migration.py
  • tests/server/test_create_app_capabilities.py
  • tests/server/test_usage_metrics.py
🚧 Files skipped from review as they are similar to previous changes (10)
  • reflexio/server/api.py
  • reflexio/server/usage_metrics.py
  • reflexio/server/services/deferred_learning_plan.py
  • tests/server/test_usage_metrics.py
  • tests/models/test_session_outcome_identity.py
  • reflexio/server/services/search_exposure.py
  • reflexio/server/billing_meter.py
  • reflexio/server/services/storage/sqlite_storage/_base.py
  • tests/server/api_endpoints/test_session_outcomes_integration.py
  • tests/server/services/extraction/test_resume_worker.py

Comment thread reflexio/server/services/storage/session_outcome_identity.py
@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reflexio/server/README.md`:
- Around line 103-104: Update create_app to register a default
SEARCH_EXPOSURE_RECORDER for every authenticated deployment, ensuring
record_search_exposures persists exposure evidence before successful responses.
If universal registration is not intended, revise the README guarantee to
explicitly apply only to deployments that configure the recorder.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2cf8534c-559c-4d1e-89fd-35f1a162c365

📥 Commits

Reviewing files that changed from the base of the PR and between febac98 and ace5b99.

📒 Files selected for processing (9)
  • docs/lib/methods/user-playbooks.ts
  • reflexio/client/client.py
  • reflexio/models/api_schema/retriever_schema.py
  • reflexio/server/README.md
  • reflexio/server/routes/search.py
  • reflexio/server/services/README.md
  • reflexio/server/services/search_exposure.py
  • tests/client/test_search.py
  • tests/server/routes/test_search_exposure_boundary.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • reflexio/client/client.py
  • tests/client/test_search.py
  • reflexio/models/api_schema/retriever_schema.py
  • reflexio/server/services/search_exposure.py

Comment thread reflexio/server/README.md Outdated
@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@guangyu-reflexio
guangyu-reflexio force-pushed the codex/offline-tuner-open-world-phase1-restored branch from 112524b to 2c7713e Compare August 6, 2026 01:19
@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/server/services/storage/sqlite_storage/test_governance_storage.py (1)

2470-2481: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider raising the read-completion timeout.

read_finished.wait(timeout=0.5) decides the assertion at line 2481. On a loaded CI machine the ordinary read can take longer than 0.5 s even when no lock is held. The test then fails intermittently without a real regression.

The blocking embedding waits up to 5 s, so a longer read timeout still proves the property.

♻️ Proposed change
-            read_completed_while_embedding_blocked = read_finished.wait(timeout=0.5)
+            read_completed_while_embedding_blocked = read_finished.wait(timeout=3)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/server/services/storage/sqlite_storage/test_governance_storage.py`
around lines 2470 - 2481, Increase the timeout used by read_finished.wait in the
ordinary-read concurrency test to a more CI-tolerant value, such as the existing
5-second synchronization limit. Keep the assertion that
read_completed_while_embedding_blocked is True and leave the embedding release
and thread-join cleanup unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reflexio/server/services/governance/service.py`:
- Around line 311-330: Invoke _rebuild_agent_playbooks(purge_id,
execution_claim=heartbeat.claim()) before constructing the AuditEvent in the
erase flow, assign its returned IDs to rebuilt_agent_playbook_ids, and reuse
that value in the audit event and response so barrier completion reports the
rebuilt playbooks consistently.
- Around line 109-133: Serialize concurrent renewals in
PurgeExecutionClaimHeartbeat. Add a dedicated renewal lock and acquire it around
the entire renew_now operation, including claim retrieval, storage renewal,
error handling, and updating _claim, so heartbeat and synchronous callers cannot
overlap. Preserve the existing heartbeat-loss propagation and renewal error
behavior.

---

Nitpick comments:
In `@tests/server/services/storage/sqlite_storage/test_governance_storage.py`:
- Around line 2470-2481: Increase the timeout used by read_finished.wait in the
ordinary-read concurrency test to a more CI-tolerant value, such as the existing
5-second synchronization limit. Keep the assertion that
read_completed_while_embedding_blocked is True and leave the embedding release
and thread-join cleanup unchanged.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b53814d5-5ee4-445d-9229-1440d48e9fc4

📥 Commits

Reviewing files that changed from the base of the PR and between 027707d and 2c7713e.

📒 Files selected for processing (79)
  • AI_AGENT_INTEGRATION.md
  • README.md
  • docs/lib/methods/requests-sessions.ts
  • docs/lib/methods/unified-search.ts
  • docs/lib/methods/user-playbooks.ts
  • reflexio/cli/README.md
  • reflexio/client/client.py
  • reflexio/lib/_session_outcome.py
  • reflexio/models/api_schema/domain/entities.py
  • reflexio/models/api_schema/domain/enums.py
  • reflexio/models/api_schema/retriever_schema.py
  • reflexio/models/api_schema/validators.py
  • reflexio/server/README.md
  • reflexio/server/api.py
  • reflexio/server/billing_meter.py
  • reflexio/server/routes/search.py
  • reflexio/server/services/README.md
  • reflexio/server/services/base_generation/_usage_billing.py
  • reflexio/server/services/base_generation_service.py
  • reflexio/server/services/deferred_learning_plan.py
  • reflexio/server/services/extraction/README.md
  • reflexio/server/services/extraction/resume_worker.py
  • reflexio/server/services/governance/service.py
  • reflexio/server/services/lineage/gc_scheduler.py
  • reflexio/server/services/playbook/components/aggregator.py
  • reflexio/server/services/playbook/service.py
  • reflexio/server/services/profile/service.py
  • reflexio/server/services/search_exposure.py
  • reflexio/server/services/storage/governance_claims.py
  • reflexio/server/services/storage/governance_validation.py
  • reflexio/server/services/storage/retention.py
  • reflexio/server/services/storage/retention_mixin.py
  • reflexio/server/services/storage/session_outcome_identity.py
  • reflexio/server/services/storage/sqlite_storage/_base.py
  • reflexio/server/services/storage/sqlite_storage/_governance.py
  • reflexio/server/services/storage/sqlite_storage/_session_outcomes.py
  • reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py
  • reflexio/server/services/storage/sqlite_storage/base/_deletion.py
  • reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py
  • reflexio/server/services/storage/sqlite_storage/governance/_purge.py
  • reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py
  • reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py
  • reflexio/server/services/storage/storage_base/__init__.py
  • reflexio/server/services/storage/storage_base/_session_outcomes.py
  • reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py
  • reflexio/server/services/storage/storage_base/governance/_erase_execution.py
  • reflexio/server/services/storage/storage_base/governance/_purge.py
  • reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py
  • reflexio/server/services/storage/storage_base/governance/_subject_barrier.py
  • reflexio/server/usage_metrics.py
  • tests/client/test_search.py
  • tests/client/test_session_outcomes_client.py
  • tests/e2e_tests/conftest.py
  • tests/models/api_schema/test_input_size_bounds.py
  • tests/models/test_session_outcome_identity.py
  • tests/server/api_endpoints/test_session_outcomes_integration.py
  • tests/server/routes/test_search_exposure_boundary.py
  • tests/server/services/extraction/test_resume_worker.py
  • tests/server/services/governance/test_governance_local_e2e.py
  • tests/server/services/governance/test_subject_write_barrier_sqlite.py
  • tests/server/services/lineage/test_gc_scheduler_global_sweep.py
  • tests/server/services/storage/sqlite_storage/test_agent_run_storage.py
  • tests/server/services/storage/sqlite_storage/test_governance_retrieved_learning.py
  • tests/server/services/storage/sqlite_storage/test_governance_storage.py
  • tests/server/services/storage/sqlite_storage/test_session_id_migration.py
  • tests/server/services/storage/sqlite_storage/test_session_outcome_downgrade_migration.py
  • tests/server/services/storage/sqlite_storage/test_session_outcome_migration.py
  • tests/server/services/storage/test_playbook_aggregation_state_integration.py
  • tests/server/services/storage/test_sqlite_storage.py
  • tests/server/services/storage/test_storage_contract_clear_user_data.py
  • tests/server/services/storage/test_storage_contract_retention.py
  • tests/server/services/storage/test_storage_contract_session_outcomes.py
  • tests/server/services/test_generation_billing_emission.py
  • tests/server/services/test_non_extraction_learning_metering.py
  • tests/server/services/test_search_exposure.py
  • tests/server/test_billing_meter.py
  • tests/server/test_billing_meter_events.py
  • tests/server/test_create_app_capabilities.py
  • tests/server/test_usage_metrics.py
🚧 Files skipped from review as they are similar to previous changes (69)
  • reflexio/server/services/storage/storage_base/init.py
  • reflexio/cli/README.md
  • tests/server/services/storage/test_playbook_aggregation_state_integration.py
  • README.md
  • docs/lib/methods/requests-sessions.ts
  • reflexio/models/api_schema/domain/enums.py
  • tests/models/api_schema/test_input_size_bounds.py
  • AI_AGENT_INTEGRATION.md
  • docs/lib/methods/user-playbooks.ts
  • reflexio/server/services/base_generation/_usage_billing.py
  • reflexio/server/services/storage/sqlite_storage/base/_deletion.py
  • reflexio/server/README.md
  • reflexio/server/services/storage/storage_base/governance/_erase_execution.py
  • tests/server/services/storage/test_storage_contract_retention.py
  • docs/lib/methods/unified-search.ts
  • tests/server/services/storage/sqlite_storage/test_governance_retrieved_learning.py
  • tests/server/test_billing_meter_events.py
  • reflexio/server/routes/search.py
  • reflexio/server/services/extraction/README.md
  • reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py
  • reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py
  • tests/client/test_session_outcomes_client.py
  • tests/server/test_usage_metrics.py
  • reflexio/server/services/storage/storage_base/governance/_purge.py
  • reflexio/server/services/storage/governance_validation.py
  • reflexio/server/services/search_exposure.py
  • reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py
  • reflexio/client/client.py
  • reflexio/server/api.py
  • tests/server/services/storage/test_sqlite_storage.py
  • reflexio/lib/_session_outcome.py
  • reflexio/server/services/storage/governance_claims.py
  • reflexio/server/services/base_generation_service.py
  • tests/e2e_tests/conftest.py
  • reflexio/server/services/storage/sqlite_storage/_governance.py
  • tests/server/services/storage/test_storage_contract_clear_user_data.py
  • tests/models/test_session_outcome_identity.py
  • reflexio/server/services/storage/retention.py
  • tests/server/test_create_app_capabilities.py
  • reflexio/server/services/deferred_learning_plan.py
  • reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py
  • reflexio/models/api_schema/validators.py
  • tests/server/services/test_non_extraction_learning_metering.py
  • reflexio/server/billing_meter.py
  • reflexio/server/usage_metrics.py
  • reflexio/server/services/extraction/resume_worker.py
  • tests/server/services/test_search_exposure.py
  • tests/server/test_billing_meter.py
  • reflexio/server/services/profile/service.py
  • tests/server/services/storage/sqlite_storage/test_session_outcome_downgrade_migration.py
  • tests/server/services/storage/test_storage_contract_session_outcomes.py
  • reflexio/server/services/README.md
  • reflexio/models/api_schema/domain/entities.py
  • tests/server/services/storage/sqlite_storage/test_session_id_migration.py
  • tests/server/api_endpoints/test_session_outcomes_integration.py
  • reflexio/models/api_schema/retriever_schema.py
  • reflexio/server/services/lineage/gc_scheduler.py
  • reflexio/server/services/storage/retention_mixin.py
  • reflexio/server/services/storage/sqlite_storage/governance/_purge.py
  • tests/server/services/test_generation_billing_emission.py
  • tests/server/services/storage/sqlite_storage/test_agent_run_storage.py
  • tests/client/test_search.py
  • reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py
  • tests/server/services/lineage/test_gc_scheduler_global_sweep.py
  • reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py
  • reflexio/server/services/storage/storage_base/governance/_subject_barrier.py
  • reflexio/server/services/playbook/service.py
  • reflexio/server/services/playbook/components/aggregator.py
  • tests/server/services/extraction/test_resume_worker.py

Comment thread reflexio/server/services/governance/service.py
Comment thread reflexio/server/services/governance/service.py
@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/server/services/governance/test_governance_local_e2e.py`:
- Around line 197-203: Update the exception handler in the renew function to
catch Exception instead of BaseException, while continuing to append expected
renew_now failures to errors for assertion handling.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 65a87cd9-ee00-4e77-98eb-fd5786b67fe3

📥 Commits

Reviewing files that changed from the base of the PR and between 2c7713e and e7a6b12.

📒 Files selected for processing (2)
  • reflexio/server/services/governance/service.py
  • tests/server/services/governance/test_governance_local_e2e.py

Comment thread tests/server/services/governance/test_governance_local_e2e.py Outdated
@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Persist non-empty direct user-playbook search results through the canonical synchronous exposure boundary before metering or response release.
Align direct user-playbook search admission with the 100-event exposure contract and publish the bound through shared client metadata.
Clarify that authenticated enterprise capability wiring supplies the fail-closed recorder while shared create_app intentionally has no default implementation.
Require non-empty session outcome producer labels to use the approved lowercase machine-label contract while preserving empty and optional source semantics.
Use the shared request-source authority across read and generation filters, and derive runtime plus JSON Schema constraints from one constrained type.
@guangyu-reflexio
guangyu-reflexio force-pushed the codex/offline-tuner-open-world-phase1-restored branch from 50db6db to 59d9519 Compare August 11, 2026 04:51
@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
reflexio/server/services/storage/sqlite_storage/_base.py (1)

2091-2106: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Handle offline_tuner_open_world during the migration

_classify_legacy_playbook_optimization_jobs leaves non-null offline_tuner_open_world rows unchanged. The rebuild copies that value into a table whose CHECK constraint rejects it, so migrate() rolls back for affected databases.

Map it to offline_tuner_legacy and mark active rows as skipped with decision_reason = 'retired_by_replay_redesign'. Add a regression test that seeds this value and asserts migration succeeds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/server/services/storage/sqlite_storage/_base.py` around lines 2091 -
2106, Update _classify_legacy_playbook_optimization_jobs so non-null
offline_tuner_open_world values are converted to offline_tuner_legacy, with
active rows changed to skipped and decision_reason set to
retired_by_replay_redesign before the table rebuild. Add a regression test that
seeds offline_tuner_open_world data and verifies migrate() completes
successfully with the transformed values.
🧹 Nitpick comments (1)
tests/server/services/storage/test_playbook_optimization_replay_contract_integration.py (1)

125-131: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider asserting the constraint intent, not the driver message.

match="CHECK constraint failed" couples this test to the SQLite driver error text. If the migration later replaces the CHECK with an application-level guard, or the driver message changes, the test fails for a reason unrelated to the fenced behavior. The behavior under test is that SQLite rejects offline_tuner_open_world jobs.

One option is to match on the constraint or column name as well, so the failure message identifies which constraint fired.

♻️ Proposed tightening
-    with pytest.raises(StorageError, match="CHECK constraint failed"):
+    with pytest.raises(StorageError, match="optimizer_kind"):
         storage.create_or_get_playbook_optimization_job(open_world_job)

This is optional. The current assertion is valid while the constraint stays in SQLite.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tests/server/services/storage/test_playbook_optimization_replay_contract_integration.py`
around lines 125 - 131, Strengthen the assertion in
test_sqlite_rejects_open_world_optimizer_jobs to verify rejection of the
offline_tuner_open_world optimizer kind without depending solely on SQLite’s
driver-specific “CHECK constraint failed” text. Match a stable constraint or
column identifier if available, while preserving the existing StorageError
expectation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@reflexio/server/services/storage/sqlite_storage/_base.py`:
- Around line 2091-2106: Update _classify_legacy_playbook_optimization_jobs so
non-null offline_tuner_open_world values are converted to offline_tuner_legacy,
with active rows changed to skipped and decision_reason set to
retired_by_replay_redesign before the table rebuild. Add a regression test that
seeds offline_tuner_open_world data and verifies migrate() completes
successfully with the transformed values.

---

Nitpick comments:
In
`@tests/server/services/storage/test_playbook_optimization_replay_contract_integration.py`:
- Around line 125-131: Strengthen the assertion in
test_sqlite_rejects_open_world_optimizer_jobs to verify rejection of the
offline_tuner_open_world optimizer kind without depending solely on SQLite’s
driver-specific “CHECK constraint failed” text. Match a stable constraint or
column identifier if available, while preserving the existing StorageError
expectation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e7626863-46e8-4bd1-8b6f-9fa1abeb9bb1

📥 Commits

Reviewing files that changed from the base of the PR and between a52f064 and 59d9519.

📒 Files selected for processing (23)
  • README.md
  • reflexio/cli/README.md
  • reflexio/models/api_schema/domain/entities.py
  • reflexio/models/api_schema/domain/enums.py
  • reflexio/server/README.md
  • reflexio/server/api.py
  • reflexio/server/routes/search.py
  • reflexio/server/services/README.md
  • reflexio/server/services/extraction/README.md
  • reflexio/server/services/extraction/resume_worker.py
  • reflexio/server/services/playbook/components/aggregator.py
  • reflexio/server/services/playbook/service.py
  • reflexio/server/services/storage/error.py
  • reflexio/server/services/storage/session_outcome_identity.py
  • reflexio/server/services/storage/sqlite_storage/_base.py
  • reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py
  • tests/e2e_tests/conftest.py
  • tests/models/test_open_world_optimization_identity.py
  • tests/models/test_session_outcome_identity.py
  • tests/server/routes/test_search_exposure_boundary.py
  • tests/server/services/extraction/test_resume_worker.py
  • tests/server/services/storage/sqlite_storage/test_governance_storage.py
  • tests/server/services/storage/test_playbook_optimization_replay_contract_integration.py
🚧 Files skipped from review as they are similar to previous changes (15)
  • README.md
  • tests/e2e_tests/conftest.py
  • reflexio/server/README.md
  • reflexio/server/api.py
  • tests/server/routes/test_search_exposure_boundary.py
  • reflexio/server/routes/search.py
  • reflexio/server/services/playbook/service.py
  • reflexio/server/services/playbook/components/aggregator.py
  • reflexio/server/services/README.md
  • reflexio/server/services/extraction/README.md
  • reflexio/server/services/extraction/resume_worker.py
  • reflexio/models/api_schema/domain/enums.py
  • reflexio/models/api_schema/domain/entities.py
  • tests/server/services/extraction/test_resume_worker.py
  • reflexio/cli/README.md

@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@guangyu-reflexio
guangyu-reflexio marked this pull request as ready for review August 11, 2026 22:18
@guangyu-reflexio

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

1 participant