Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
933bdaa
test(billing): define tenant evidence export contract
seonghobae Aug 22, 2026
2f2f2b9
test(billing): register reconciliation evidence export RED
seonghobae Aug 22, 2026
e2538bd
feat(billing): add bounded tenant reconciliation evidence export
seonghobae Aug 22, 2026
7ac20d4
test(billing): define authenticated evidence export route
seonghobae Aug 22, 2026
775db53
test(billing): register evidence export API RED
seonghobae Aug 22, 2026
9cabff5
feat(billing): expose tenant reconciliation evidence export
seonghobae Aug 22, 2026
0af7b0c
test(billing): canonically register reconciliation evidence export
seonghobae Aug 22, 2026
e076009
test(billing): remove temporary evidence export registrations
seonghobae Aug 22, 2026
b42a731
docs(billing): trace reconciliation evidence export controls
seonghobae Aug 22, 2026
2056072
test(billing): reject contradictory reconciliation evidence
seonghobae Aug 22, 2026
abfe12a
fix(billing): reject contradictory reconciliation job evidence
seonghobae Aug 22, 2026
2b6c608
test(billing): reject impossible reconciliation attempt timelines
seonghobae Aug 22, 2026
7bb7f95
fix(billing): validate reconciliation attempt chronology
seonghobae Aug 22, 2026
744d412
test(billing): reject impossible recovery chronology
seonghobae Aug 22, 2026
1abe09f
fix(billing): validate reconciliation recovery chronology
seonghobae Aug 22, 2026
209996a
test(billing): reject orphaned recovery evidence
seonghobae Aug 22, 2026
eaaf71a
fix(billing): reject orphaned recovery evidence
seonghobae Aug 22, 2026
e7062c5
test(billing): reject incomplete reconciliation history
seonghobae Aug 22, 2026
2fdc99c
fix(billing): fail closed on incomplete attempt evidence
seonghobae Aug 22, 2026
ab2be7c
test(billing): reject job-attempt lifecycle contradictions
seonghobae Aug 22, 2026
ff5cec1
fix(billing): validate reconciliation lifecycle evidence
seonghobae Aug 22, 2026
fd6c747
test(billing): reject contradictory recovery evidence
seonghobae Aug 22, 2026
6488f6b
fix(billing): cross-check recovery attempt evidence
seonghobae Aug 22, 2026
ce6258a
test(billing): reject contradictory export lease evidence
seonghobae Aug 22, 2026
68d80a4
test(billing): register lease consistency regression
seonghobae Aug 22, 2026
2066ff7
fix(billing): validate reconciliation export lease state
seonghobae Aug 22, 2026
4d342cb
test(billing): reproduce reconciliation export snapshot race
seonghobae Aug 22, 2026
12b7d78
test(billing): register reconciliation snapshot regression
seonghobae Aug 22, 2026
fcb6124
test(billing): isolate reconciliation snapshot race hook
seonghobae Aug 22, 2026
b20b26b
fix(billing): export reconciliation evidence from one snapshot
seonghobae Aug 22, 2026
61b8a26
test(billing): preserve queued evidence before worker seeding
seonghobae Aug 23, 2026
99de86b
fix(billing): export queued reconciliation evidence before worker see…
seonghobae Aug 23, 2026
4ebaea0
test(billing): require auditable evidence exports
seonghobae Aug 23, 2026
43bec36
fix(billing): audit evidence exports before disclosure
seonghobae Aug 23, 2026
49d9010
test(billing): bind evidence export audit to response bytes
seonghobae Aug 24, 2026
ab86927
fix(billing): bind evidence audits to exported bytes
seonghobae Aug 24, 2026
08d89b0
docs(billing): trace exported evidence digest
seonghobae Aug 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions docs/doctoring/stripe-reconciliation-evidence-export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Stripe reconciliation evidence export

Status: **active PR only**. This document describes the bounded evidence-export slice on PR #582. It is not protected-`develop` shipped truth until the prerequisite #488 stack is integrated and the then-current required gates pass on the exact integrated head.

## Buyer and operator decision

Use `GET /api/orgs/:organizationId/billing/reconciliation/evidence` when a workspace owner or administrator needs a portable reconciliation record for incident review, diligence, or customer/auditor evidence. The response is an attachment-oriented JSON document and is deliberately read-only. If an Event is still `pending` or `processing`, inspect the corresponding reconciliation queue before taking a recovery action. If it is `dead_letter`, use the separately authorized recovery endpoint rather than editing evidence state.

## Authority and privacy boundary

The export derives tenant authority only through the persisted `billing_stripe_subscriptions -> billing_stripe_customers -> organization_id` relationship. The URL tenant identifier never substitutes provider or persisted tenant authority. Authentication plus owner/admin membership is required before export.

The exported document contains bounded reconciliation facts needed to understand what ScopeWeave observed and attempted: Stripe Event and Subscription identifiers, Event type and provider creation time, the stored payload SHA-256, local receipt/queue/completion timing, processing state, attempt history, recovery history, stable error codes, and entitlement claim-decision linkage when present.

The export does **not** include raw webhook payloads, Stripe credentials, active worker lease-token hashes, or plaintext operator recovery references. A recovery reference is exported only as a SHA-256 correlation value so an authorized operator who already possesses the reference can compare it without copying the free-form reference into a portable artifact. The response uses `Cache-Control: no-store`, `X-Content-Type-Options: nosniff`, a JSON content type, and an attachment disposition.

Every successful disclosure is durably access-logged before response bytes are released. ScopeWeave serializes the report once, records the document's SHA-256 as `evidenceDocumentSha256` together with its schema version and Event count, and then returns those exact serialized bytes. This binds the audit event to the specific portable artifact without copying its private contents into the audit stream. If the audit write fails, disclosure fails closed instead of returning an unaudited evidence document.

These choices implement purpose-bound access and data minimization rather than indiscriminate masking. NIST Privacy Framework Control-P calls for managing data at sufficient granularity to manage privacy risk, and CT.DM-P8 specifically connects audit/log records with data minimization. The export therefore keeps decision-relevant provenance while excluding unrelated secret or free-form payload material.

## Boundedness and failure behavior

- `limit` defaults to 50 Events and cannot exceed 100.
- Event selection is tenant-scoped and bounded before nested history is loaded.
- The combined selected attempt/recovery history is capped at 1,000 rows before materialization; larger exports fail closed with HTTP-compatible `413` and `stripe_reconciliation_evidence_export_too_large`.
- Persisted identifiers, hashes, times, states, outcomes, and stable error codes are revalidated during export. Corrupt or contradictory evidence fails closed instead of being serialized as authoritative audit evidence.
- The selected Event rows and their nested attempt/recovery histories are read under one SQLite snapshot so concurrent worker progress cannot create a mixed-time artifact.
- An unknown tenant identifier produces no cross-tenant evidence. The HTTP route separately hides workspace membership with the existing not-found boundary.
- A successful response is emitted only after its audit event has been persisted; an unavailable audit sink returns `stripe_reconciliation_evidence_export_audit_failed` and no evidence document.

## Why immutable Event evidence is retained

Stripe documents that webhook deliveries can be duplicated, retried, and delivered out of order. It recommends tracking processed Event IDs to prevent duplicate processing, and its undelivered-Event guidance notes that automatic retries can continue while operators manually process events. ScopeWeave therefore exports durable Event identity, ordering/provenance timestamps, processing state, and attempt/recovery evidence rather than treating receipt order as entitlement authority. Authoritative reconciliation remains responsible for re-reading current provider state.

## Acceptance evidence

The implementation was developed test-first on the existing #488 stack. RED evidence first proved the repository module was absent and then proved the authenticated HTTP route was absent. Later realistic regressions cover recovery/attempt linkage, append-only history completeness, contradictory job/attempt lifecycle state, lease consistency, one-snapshot concurrent reads, nested-evidence bounds, audit-before-disclosure, and exact exported-document digest binding.

The repository's normal unit/API suite, browser cloud E2E, dependency review, and OSV scan have produced useful behavioral evidence on PR-associated revisions, but synthetic pull-request merge checkouts are **not** exact-head merge authorization. Exact-head Server Tests control remains owned by the repository-wide CI repair path (#523), and centrally reusable SAST/Security exact-head attestation remains on its separate organization-owned repair path. Current-head evidence must be regenerated after every contributor-head change; queued, cancelled, predecessor, synthetic-only, absent, or model-only results are non-passing.

The production export module and route are explicitly included by the canonical coverage contract, and the unit/API regressions are registered in the canonical test scripts. No release or certification claim is made by this slice.

## References

National Institute of Standards and Technology. (2020). *NIST privacy framework: A tool for improving privacy through enterprise risk management, version 1.0*. https://www.nist.gov/privacy-framework

Stripe. (n.d.). *Process undelivered webhook events*. https://docs.stripe.com/webhooks/process-undelivered-events

Stripe. (n.d.). *Receive Stripe events in your webhook endpoint*. https://docs.stripe.com/webhooks
Loading