B4.3R4: Apply manual owner billing adjustments
Objective
Add a narrow internal service for deliberate manual owner billing adjustments that remains distinct from automatic
late-usage correction and preserves exact replay semantics.
This gives Grace operators a controlled way to correct an owner's ledger without weakening the immutable initial-charge
or automatic-correction contracts.
Parent and dependency
Product V1 contract
- Manual posting appends only an
Adjustment row to an existing Closed or Corrected owner billing period.
- The caller supplies a nonempty stable correlation id, reason, actor/provenance, and signed amount under the accepted
money/rounding constraints.
- Repeating the exact same correlation and payload returns the existing adjustment.
- Reusing a correlation with any conflicting scope, amount, reason, actor, or provenance is rejected.
- Manual provenance is reserved from automatic work provenance; a manual row can never satisfy automatic late-work
completion or uniqueness.
- Initial charges and prior corrections/adjustments remain immutable.
- Database UTC timestamps own persisted posting time.
- A manual adjustment does not change Closed/Corrected state except that an already Corrected period stays Corrected.
Forbidden implementation shapes
- No public route, CLI command, SDK surface, OpenAPI operation, or generated client in this leaf.
- No generic ledger-write primitive exposed to callers.
- No automatic late-work processing or failed-period settlement.
- No update/delete path for any ledger row.
- No synthetic
UsageFactId or other automatic-work identity for a manual adjustment.
- No source-text assertions as the primary database-constraint or replay proof.
- No compatibility behavior for imagined production rows.
Owned paths
src/Grace.Operations/Grace.Operations.Data/** for the internal manual-adjustment service and constraints.
src/Grace.Operations/Grace.Operations.Tests/** for real-database proof.
- Relevant
*.fsproj compile items.
- Worker changes are N/A unless the current architecture requires dependency registration for an internal service; no
scheduled manual worker is allowed.
Sensitive or excluded paths
src/Grace.slnx
- Usage ingestion, initial close, and automatic correction except consuming their settled types
- Grace CLI, SDKs, OpenAPI, generated contracts, and unrelated server routes
Minimum detail gate
Positive tests
- A valid adjustment appends once and preserves the exact correlation and provenance.
- Exact replay returns the existing row without changing the ledger.
- Positive and negative amounts follow the accepted money constraints.
- Closed and Corrected periods accept adjustments while retaining their state.
Negative tests
- Open and Provisional periods reject manual posting.
- Empty correlation, reason, actor, or required provenance is rejected.
- Conflicting correlation reuse is rejected for every payload dimension.
- A manual correlation cannot collide with or complete automatic late work.
- Update and delete attempts fail at the database boundary.
Regression and boundary tests
- Two concurrent exact replays produce one row and the same result.
- Concurrent conflicting reuse produces one success and one deterministic conflict.
- Transaction rollback leaves no adjustment or success marker.
- Maximum accepted amount and overflow boundaries are explicit and deterministic.
High-risk adversarial examples
- Same correlation reused for another owner, organization, repository, or month is rejected.
- Same reason and amount with a different actor is a conflict, not an exact replay.
- Automatic provenance prefixes/identities are rejected for manual input.
- A negative adjustment cannot be represented as mutation of an earlier charge.
Contract propagation map
- Shared DTOs/parameters/events: internal service request/result only; public DTOs N/A.
- Persisted shapes and migrations: required only for manual replay/provenance constraints not already delivered.
- HTTP routes: N/A.
- CLI/SDK/OpenAPI/generated artifacts: N/A.
- Documentation: XML documentation for new F# declarations and nearby Operations notes where needed.
- Tests: real database constraints, concurrency, replay, and rollback.
Stale-state preflight
- Source before decision: persisted period state and any ledger row with the supplied correlation.
- Revalidation point: in the posting transaction before append.
- Abort state: no ledger mutation; return a deterministic validation or conflict result.
- Retry/cleanup: exact replay converges on the existing row; rollback leaves nothing to clean up.
- Proof: concurrent exact or conflicting requests cannot both append.
Validation profile
- Format touched F#.
- Build affected Release projects before focused
--no-build tests.
- Run focused real-database tests for replay, conflict, concurrency, constraints, immutability, and rollback.
- Run
git diff --check.
- GitHub
Validate and one fresh current-head review are required before merge.
Epic execution policy
Epic #554's 4+1 worker policy applies. Workers 1-3 are the normal implementation and proof budget. After worker 3
and before worker 4, audit the remaining invariant families; if more than one substantial family remains unproven or
the slice needs scope, architecture, lifecycle, public-contract, durable-state, or primary-invariant expansion,
supersede or split before worker 4. Worker 4 is the completion target and must produce a complete local proof ledger and
reviewable candidate. Worker 5 is reserved only for one finite, grounded repair set from fresh exact-head review or
PR-owned CI on that candidate; it may not finish planned implementation, multiple outstanding proof families,
environment diagnosis, or redesign confirmation. Any valid addressable finding remaining after worker 5 supersedes
automatically; no worker 6. Keep one active implementation/fix worker per PR head, one fresh review per pushed head,
and exact-head GitHub Validate.
Definition of done
- Manual posting is an internal, immutable, replay-safe adjustment path distinct from automatic correction.
- Real-database tests prove exact replay, conflicting reuse, concurrency, rollback, and provenance separation.
- No public adjustment surface or failed-period settlement is introduced.
- The PR targets the epic integration branch, links this issue, has current-head
Validate, and receives a fresh
no-findings review verdict.
Readiness Refinement After #865
The manual path shares the immutable ledger but not the automatic-work lifecycle. A fresh worker must preserve these
boundaries explicitly.
Identity and transaction contract
- The replay identity is exact billing scope plus an ordinal, width-validated manual correlation ID. Exact equality is
ordinal and includes signed amount, currency, reason code/text, initiating principal, and every persisted provenance
field. No normalization may make two caller payloads equivalent implicitly.
- The posting transaction resolves the exact period, acquires the shared scope lock, rereads period state and existing
manual correlation, then appends or returns the existing row. A stale pre-read cannot post into an Open,
Provisional, or failed period.
- The ledger row uses a database-generated UTC creation time and a stable kind/source discriminator reserved for manual
adjustments. It cannot carry UsageFactId, automatic-work ID, automatic reason code, or automatic initiating
principal values.
- Manual adjustment changes neither automatic pending/blocked/completed work nor period state. Closed remains Closed;
Corrected remains Corrected.
Durable enforcement and proof
- SQL uniqueness and constraints make the exact manual correlation unambiguous within its billing period and keep
manual provenance disjoint from initial charge and automatic-correction provenance.
- Runtime model, migration, frozen target, and snapshot propagate any new constraint identically. Migration behavior
is self-contained and does not call mutable runtime helpers.
- Real-SQL proof must drive two independent connections through the production internal service for exact replay and
conflicting reuse. It must prove one row, deterministic conflict, no automatic-work mutation, and unchanged period
state in both lock orders.
- Injected failure or cancellation after ledger staging must roll back the row and release the scope lock. Retry then
posts once.
- Direct update/delete, forged automatic provenance, wrong period/scope, amount overflow, and every maximum-width plus
one input must be rejected without partial mutation.
B4.3R4: Apply manual owner billing adjustments
Objective
Add a narrow internal service for deliberate manual owner billing adjustments that remains distinct from automatic
late-usage correction and preserves exact replay semantics.
This gives Grace operators a controlled way to correct an owner's ledger without weakening the immutable initial-charge
or automatic-correction contracts.
Parent and dependency
epic/560-operations-pricing-billing-usageafter those dependencies merge.Product V1 contract
Adjustmentrow to an existing Closed or Corrected owner billing period.money/rounding constraints.
completion or uniqueness.
Forbidden implementation shapes
UsageFactIdor other automatic-work identity for a manual adjustment.Owned paths
src/Grace.Operations/Grace.Operations.Data/**for the internal manual-adjustment service and constraints.src/Grace.Operations/Grace.Operations.Tests/**for real-database proof.*.fsprojcompile items.scheduled manual worker is allowed.
Sensitive or excluded paths
src/Grace.slnxMinimum detail gate
Positive tests
Negative tests
Regression and boundary tests
High-risk adversarial examples
Contract propagation map
Stale-state preflight
Validation profile
--no-buildtests.git diff --check.Validateand one fresh current-head review are required before merge.Epic execution policy
Epic #554's 4+1 worker policy applies. Workers 1-3 are the normal implementation and proof budget. After worker 3
and before worker 4, audit the remaining invariant families; if more than one substantial family remains unproven or
the slice needs scope, architecture, lifecycle, public-contract, durable-state, or primary-invariant expansion,
supersede or split before worker 4. Worker 4 is the completion target and must produce a complete local proof ledger and
reviewable candidate. Worker 5 is reserved only for one finite, grounded repair set from fresh exact-head review or
PR-owned CI on that candidate; it may not finish planned implementation, multiple outstanding proof families,
environment diagnosis, or redesign confirmation. Any valid addressable finding remaining after worker 5 supersedes
automatically; no worker 6. Keep one active implementation/fix worker per PR head, one fresh review per pushed head,
and exact-head GitHub
Validate.Definition of done
Validate, and receives a freshno-findings review verdict.
Readiness Refinement After #865
The manual path shares the immutable ledger but not the automatic-work lifecycle. A fresh worker must preserve these
boundaries explicitly.
Identity and transaction contract
ordinal and includes signed amount, currency, reason code/text, initiating principal, and every persisted provenance
field. No normalization may make two caller payloads equivalent implicitly.
manual correlation, then appends or returns the existing row. A stale pre-read cannot post into an Open,
Provisional, or failed period.
adjustments. It cannot carry
UsageFactId, automatic-work ID, automatic reason code, or automatic initiatingprincipal values.
Corrected remains Corrected.
Durable enforcement and proof
manual provenance disjoint from initial charge and automatic-correction provenance.
is self-contained and does not call mutable runtime helpers.
conflicting reuse. It must prove one row, deterministic conflict, no automatic-work mutation, and unchanged period
state in both lock orders.
posts once.
one input must be rejected without partial mutation.