Skip to content
39 changes: 34 additions & 5 deletions dashboard/basic_mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@
<div class="pv-control"><button type="button" class="secondary" id="pvStripeSource" onclick="openLocalStripeSource()">Manage Stripe read-only source</button></div>
</div>
<div class="pv-row pv-sub" id="pvOpenWorkRow">
<div><h3>Open Work: invoice preview</h3><div class="muted">Optional read-only check for open invoices in your connected Stripe account, including invoices never shown in OWG. Past-due dates indicate candidates for review, not verified collection duties. No customer details or amounts are stored or shared with AI.</div><div class="muted" id="pvOpenWorkResult" role="status" aria-live="polite"></div><div id="pvOpenWorkCases" aria-label="Open invoice candidates"></div><div class="muted" id="pvOpenWorkReviewResult" role="status" aria-live="polite"></div><button type="button" class="secondary" id="pvOpenWorkReviewLoad">View saved reviews</button><div id="pvOpenWorkReviews" aria-label="Locally saved case reviews"></div></div>
<div><h3>Open Work: invoice preview</h3><div class="muted">Optional read-only check for open invoices in your connected Stripe account, including invoices never shown in OWG. Past-due dates indicate candidates for review, not verified collection duties. No customer details or amounts are stored or shared with AI.</div><div class="muted" id="pvOpenWorkResult" role="status" aria-live="polite"></div><div id="pvOpenWorkCases" aria-label="Open invoice candidates"></div><div class="muted" id="pvOpenWorkReviewResult" role="status" aria-live="polite"></div><button type="button" class="secondary" id="pvOpenWorkReviewLoad">View saved reviews</button><button type="button" class="secondary" id="pvOpenWorkReviewRecheck">Recheck source status</button><div id="pvOpenWorkReviews" aria-label="Locally saved case reviews"></div></div>
<div class="pv-control"><button type="button" class="secondary" id="pvOpenWorkScan">Preview open invoices</button></div>
</div>
<div class="pv-row pv-sub" id="pvGitHubOpenWorkRow">
Expand Down Expand Up @@ -442,6 +442,8 @@
if (status) status.textContent = 'Preview unavailable: ' + (error.message || 'request failed');
} finally { gitHubPreview.disabled = false; }
};
const recheckReviews = panel.querySelector('#pvOpenWorkReviewRecheck');
let currentReviewStatuses = null;
const reviewLoad = panel.querySelector('#pvOpenWorkReviewLoad');
const reviews = panel.querySelector('#pvOpenWorkReviews');
const reviewStatus = panel.querySelector('#pvOpenWorkReviewResult');
Expand All @@ -463,8 +465,16 @@
const line = document.createElement('div');
line.className = 'pv-row pv-sub';
const description = document.createElement('span');
const statusLabels = {
source_reported_open: 'still reported open by the source',
not_in_complete_open_inventory_outcome_unknown: 'no longer in open inventory; outcome unverified',
unknown_incomplete_inventory: 'unknown: inventory limit reached',
unknown_source_unavailable: 'unknown: provider unavailable',
};
const latest = currentReviewStatuses && currentReviewStatuses[item.work_item_ref];
description.textContent = (labels[item.review_intent] || 'Review candidate') +
' · source status not refreshed · expires ' + String(item.expires_at || '').slice(0, 10);
' · ' + (statusLabels[latest] || 'source status not refreshed') +
' · expires ' + String(item.expires_at || '').slice(0, 10);
const remove = document.createElement('button');
remove.type = 'button';
remove.className = 'secondary';
Expand All @@ -483,9 +493,28 @@
reviews.append(line);
}
};
if (reviewLoad) reviewLoad.onclick = () => refreshSavedReviews().catch(() => {
if (reviewStatus) reviewStatus.textContent = 'Local review queue unavailable.';
});
if (reviewLoad) reviewLoad.onclick = () => {
currentReviewStatuses = null;
refreshSavedReviews().catch(() => {
if (reviewStatus) reviewStatus.textContent = 'Local review queue unavailable.';
});
};
if (recheckReviews) recheckReviews.onclick = async () => {
if (!confirm('Check the currently connected Stripe open-invoice inventory for your saved reviews? This is a read-only source request; absence is not proof of payment or completion.')) return;
recheckReviews.disabled = true;
if (reviewStatus) reviewStatus.textContent = 'Checking the permitted source…';
try {
const outcome = await api('/v1/open-work/reviews/recheck', send('POST', {confirm_scan: true}));
currentReviewStatuses = outcome.status_by_work_item || {};
await refreshSavedReviews();
if (reviewStatus) reviewStatus.textContent = outcome.availability === 'source_reported'
? 'Fresh source check complete. Customer outcome and business obligations remain unverified.'
: 'Source check unavailable or no saved reviews. Nothing was marked complete.';
} catch (_) {
currentReviewStatuses = null;
if (reviewStatus) reviewStatus.textContent = 'Could not verify the current provider inventory.';
} finally { recheckReviews.disabled = false; }
};
const openWork = panel.querySelector('#pvOpenWorkScan');
if (openWork) openWork.onclick = async () => {
const status = panel.querySelector('#pvOpenWorkResult');
Expand Down
24 changes: 24 additions & 0 deletions docs/OPEN_WORK_LOCAL_REVIEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,27 @@ satisfy issue #247's durable, tenant/actor-scoped, cross-provider case graph,
declarative SOPs, verified customer outcomes, or agent A/B/C uplift study.
That broader work requires a different explicit organizational scope and
deletion/retention acceptance review before being called production-ready.


## Owner-triggered fresh source recheck (stacked addition)

The Advanced dashboard's **Recheck source status** button asks permission
on every click and re-reads the connected read-only Stripe open-invoice
inventory. It maps only locally stored opaque review references, returning:

- `source_reported_open`: Stripe still lists the invoice as open;
- `not_in_complete_open_inventory_outcome_unknown`: a bounded but complete
list no longer includes it. This does **not** establish whether the invoice
was paid, voided, deleted, disputed or written off;
- `unknown_incomplete_inventory`: three-page cap prevents exhaustive proof;
- `unknown_source_unavailable`: independent provider read failed.

No request happens automatically in the background, on queue load, or when
a source status is missing. The response is transient, never stored in
the review-intention file. It never claims a verified business objective,
agent causation, customer outcome or permission to issue a payment request.

The associated endpoint is
`POST /v1/open-work/reviews/recheck` with
`{"confirm_scan":true}`, behind the existing privileged local dashboard
session only.
70 changes: 70 additions & 0 deletions server/local_source_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,73 @@ async def delete_local_review_intention(request: Request, work_item_ref: str):
except ValueError:
raise HTTPException(status_code=422, detail="invalid review reference") from None
return {"removed": removed, "source_read_performed": False}


@router.post("/v1/open-work/reviews/recheck")
async def recheck_local_review_sources(request: Request):
"""Explicit read-only fresh source status for locally saved review intents.

A missing item from a complete *open invoice inventory* proves only
it is not currently reported open; it does not prove paid, resolved,
customer acceptance or any agent's involvement.
"""
_require_dashboard(request)
if len(await request.body()) > 256:
raise HTTPException(status_code=413, detail="review recheck request too large")
try:
payload = await request.json()
except (TypeError, ValueError):
raise HTTPException(status_code=422, detail="invalid recheck request") from None
if not isinstance(payload, dict) or payload.get("confirm_scan") is not True:
raise HTTPException(status_code=403, detail="explicit provider recheck required")
saved = await asyncio.to_thread(list_review_intentions)
reviews = saved.get("items") or []
if not reviews:
return {
"availability": "no_local_reviews",
"provider_read_performed": False,
"source_outcome_verified": False,
"business_objective_verified": False,
"status_by_work_item": {},
}
scan = await asyncio.to_thread(
stripe_open_work_preview, explicit_scan_authorized=True, max_pages=3,
)
if scan.get("availability") != "source_reported":
return {
"availability": "unknown",
"reason": "source_read_unavailable",
"provider_read_performed": True,
"source_outcome_verified": False,
"business_objective_verified": False,
"status_by_work_item": {
item["work_item_ref"]: "unknown_source_unavailable"
for item in reviews
},
}
open_refs = {
item.get("work_item_ref") for item in scan.get("candidates", [])
if isinstance(item, dict) and item.get("source_status") == "open"
}
complete = scan.get("source_coverage_complete") is True
states = {
item["work_item_ref"]: (
"source_reported_open" if item["work_item_ref"] in open_refs
else "not_in_complete_open_inventory_outcome_unknown" if complete
else "unknown_incomplete_inventory"
)
for item in reviews
}
return {
"format": "openworkgraph.review-source-recheck.v1",
"availability": "source_reported",
"verified_at": scan.get("verified_at"),
"provider_read_performed": True,
"source_coverage_complete": complete,
"source_outcome_verified": False,
"business_objective_verified": False,
"agent_caused_outcome_verified": False,
"automatic_actions": False,
"review_intentions_unchanged": True,
"status_by_work_item": states,
}
6 changes: 3 additions & 3 deletions server/open_work_reviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,6 @@ def _write(items: dict[str, dict[str, Any]]) -> None:

def list_review_intentions(*, now: datetime | None = None) -> dict[str, Any]:
current = now or datetime.now(timezone.utc)
max_days = _allowed_days()
if max_days is None:
raise ValueError("saved reviews are unavailable with session-only history retention")
with _LOCK:
items = _read(current)
return {
Expand All @@ -164,6 +161,9 @@ def save_review_intention(
if provider_candidate_currently_open is not True:
raise ValueError("current provider source did not verify candidate")
current = now or datetime.now(timezone.utc)
max_days = _allowed_days()
if max_days is None:
raise ValueError("saved reviews are unavailable with session-only history retention")
with _LOCK:
items = _read(current)
if work_item_ref not in items and len(items) >= _MAX_ITEMS:
Expand Down
94 changes: 94 additions & 0 deletions tests/test_open_work_review_recheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""Explicit read-only source recheck does not equate absent invoice with payment."""
from __future__ import annotations

import os
from pathlib import Path
import subprocess
import sys


def test_saved_case_recheck_fails_closed_without_claiming_completion(tmp_path):
code = r'''
from fastapi.testclient import TestClient
import server.secure_app
from server.secure_app import app
from server.local_auth import create_dashboard_session, ensure_api_token
from server import local_source_routes as routes
from server import open_work_reviews as reviews

routes.stripe_source_status = lambda: {"configured": True}
ref = "owg:w:" + "a"*32
reviews.save_review_intention(
ref, "assess_follow_up", provider_candidate_currently_open=True)
c = TestClient(app)
owner = {"Authorization": "OWG-Session " + create_dashboard_session()}
bearer = {"Authorization": "Bearer " + ensure_api_token()}
calls = []

def source(**kwargs):
calls.append(kwargs)
return {"availability": "source_reported",
"verified_at": "2026-10-20T09:00:00+00:00",
"source_coverage_complete": True,
"candidates": [{"work_item_ref": ref, "source_status": "open"}]}
routes.stripe_open_work_preview = source
url = "/v1/open-work/reviews/recheck"

assert c.post(url, headers=bearer, json={"confirm_scan":True}).status_code == 403
assert c.post(url, headers=owner, json={"confirm_scan":False}).status_code == 403
assert calls == []

same = c.post(url, headers=owner, json={"confirm_scan": True})
assert same.status_code == 200, same.text
assert same.json()["status_by_work_item"][ref] == "source_reported_open"
assert same.json()["business_objective_verified"] is False
assert same.json()["agent_caused_outcome_verified"] is False
assert len(calls) == 1
assert calls[0]["max_pages"] == 3

routes.stripe_open_work_preview = lambda **kwargs: {
"availability": "source_reported", "verified_at": "2026-10-20T09:10:00+00:00",
"source_coverage_complete": True, "candidates": []}
missing = c.post(url, headers=owner, json={"confirm_scan": True})
assert missing.status_code == 200, missing.text
assert missing.json()["status_by_work_item"][ref] == (
"not_in_complete_open_inventory_outcome_unknown")
assert missing.json()["source_outcome_verified"] is False

routes.stripe_open_work_preview = lambda **kwargs: {
"availability": "source_reported", "source_coverage_complete": False,
"candidates": []}
partial = c.post(url, headers=owner, json={"confirm_scan": True})
assert partial.json()["status_by_work_item"][ref] == "unknown_incomplete_inventory"

routes.stripe_open_work_preview = lambda **kwargs: {"availability": "unknown"}
outage = c.post(url, headers=owner, json={"confirm_scan": True})
assert outage.json()["availability"] == "unknown"
assert outage.json()["status_by_work_item"][ref] == "unknown_source_unavailable"

assert len(c.get("/v1/open-work/reviews", headers=owner).json()["items"]) == 1
assert c.delete("/v1/open-work/reviews/" + ref, headers=owner).json()["removed"]
routes.stripe_open_work_preview = lambda **kwargs: (_ for _ in ()).throw(
AssertionError("empty queue must not scan provider"))
empty = c.post(url, headers=owner, json={"confirm_scan": True})
assert empty.status_code == 200
assert empty.json()["provider_read_performed"] is False
'''
env = os.environ.copy()
env["WORKFLOW_OBSERVER_AUTH_DIR"] = str(tmp_path / "auth")
env["WORKFLOW_OBSERVER_DATA"] = str(tmp_path / "data")
result = subprocess.run(
[sys.executable, "-c", code],
cwd=Path(__file__).resolve().parents[1], env=env,
capture_output=True, text=True,
timeout=90 if os.name == "nt" else 60,
)
assert result.returncode == 0, result.stderr[-3000:]


def test_explicit_recheck_never_automatically_runs_in_dashboard():
script = (Path(__file__).resolve().parents[1] / "dashboard" / "basic_mode.js").read_text()
assert "pvOpenWorkReviewRecheck" in script
assert "recheckReviews.onclick" in script
assert "send('POST', {confirm_scan: true})" in script
assert "no longer in open inventory; outcome unverified" in script
Loading