# P3-WP12 Metering and Showback Engineering Evidence ## Boundary - Local engineering evidence only: `local-metering-fixture-v1@1`; no external cost, billing, finance, email or collaboration provider was accessed. - Enterprise input remains `cost=TBD_EXTERNAL`, owner `enterprise_finance_owner`. This is not enterprise billing, UAT or production acceptance. ## TDD evidence (2026-08-18) - RED: `tests/core/governance/test_wp12_metering_showback.py` initially failed because the WP12 module was absent. - GREEN: `PYTHONPATH=. .venv/bin/pytest -q tests/core/governance/test_wp12_metering_showback.py` passed `6` tests for closed eight-kind events, Decimal unit conversion, exact replay/conflict, correction chain, allocation replay, budget alert and Chargeback denial. - RED: migration contract initially failed because revision `20260818_546` and mirror were absent. - GREEN: `PYTHONPATH=. .venv/bin/pytest -q tests/test_wp12_metering_showback_migration_contract.py` passed the base `2` tests. - RED: Flask/OpenAPI contract initially failed because the route/spec were absent. - GREEN: `PYTHONPATH=. .venv/bin/pytest -q tests/test_wp12_metering_showback_api.py` passed the base `2` tests after generator output. - RED (follow-up): the control-route/OpenAPI/mirror tests failed with the allocation and budget routes, generated paths and revision `20260818_547` deployment mirror absent. - GREEN (follow-up): `PYTHONPATH=. .venv/bin/pytest -q tests/test_wp12_metering_showback_api.py tests/test_wp12_metering_showback_migration_contract.py` passed `6` tests after the closed control gateway, API routes, OpenAPI generator entries and mirror were added. - RED (self-review follow-up): 547 persisted rules but the read path did not yet replay an allocation from persisted facts; no completion claim was made at that point. - GREEN (self-review follow-up): `20260818_548` adds a runtime-only persisted allocation-replay function and closed no-store read endpoint. Its missing API/mirror RED failed in `2` expected places; after the mirror/generator update the API/migration contracts passed `7` tests. - Final targeted non-integration run: `PYTHONPATH=. .venv/bin/pytest -q tests/core/governance/test_wp12_metering_showback.py tests/test_wp12_metering_showback_api.py tests/test_wp12_metering_showback_migration_contract.py` passed `13` tests; `git diff --check` and all required source/deployment byte comparisons passed. - Final controlled PostgreSQL run: `TEST_DATABASE_URL= PYTHONPATH=. .venv/bin/pytest -q tests/integration/test_wp12_metering_showback_postgres.py` passed `2` tests. Before the shared round trip, its head was `20260818_548` and exact WP12 fact-row sum was `0`; the test restored head `20260818_548` with fact-row sum `0`. ## Real PostgreSQL evidence - Local Docker PostgreSQL was available at the controlled project test endpoint. An isolated database was stamped to verified 545 and actually migrated 545→548. It passed distinct runtime/control-login Flask commits for allocation, budget and persisted allocation replay, closed control-body/RBAC/no-store handling, direct runtime-facts ACL denial, runtime denial of the control function, lease contention/fence, exact replay, concurrent exact-once disabled outbox insert, allocation replay/conflict, cross-domain rejection, new-connection read, Chargeback DB denial and nonempty downgrade refusal. - Shared local test DB was read-checked as head `20260818_547` with zero WP12 fact rows, then executed 547→548→547→548. A real role-init replay with temporary runtime/control identities preserved no direct runtime table privilege and retained only control-side claim/control-function execution. Temporary roles were removed. The shared DB remains at 548 with no WP12 facts. - A full empty-database historical chain run as the cluster administrator correctly stopped at the pre-existing WP06 restricted-migrator identity gate; it is not WP12 chain evidence. ## State ## Specification-review follow-up (2026-08-18) - RED: review found the old Showback aggregate did not consume rules, correction scope was incomplete, duplicate/zero allocation targets and overlapping windows were not rejected, evidence references admitted script-like segments, and OpenAPI omitted required query/body/nested closure details. - GREEN: `20260818_549` adds gateway-only event-time rollup with persisted rule/version/digest details, strict deterministic integer-micros conservation, explicit `unallocated_micros`/`variance_micros`, correction/event evidence guards, non-overlapping effective windows and duplicate/zero target rejection. The OpenAPI generator now declares required WP12 query parameters, required request bodies and closed nested evidence/mapping/allocation objects. - Validation: targeted core/API/migration suite passed `14`; isolated controlled PostgreSQL `545→549` passed allocation-rollup, expired-window variance, correction/domain/chain, duplicate/zero/overlap and script-reference database attacks; the shared empty database passed `548→549→548→549` plus role-init replay and ended at head `20260818_549` with zero WP12 fact rows. ## Unified replay and legacy ACL follow-up (2026-08-18) - RED: review demonstrated the old standalone replay could allocate an event outside a specified rule's effective window; the renamed legacy read function also retained runtime execute privilege. - GREEN: `20260818_550` replaces allocation replay with a claim-bound event-time coverage gate. A specified rule returns allocations only when it covers every matching event; expired/partial/multi-version coverage is denied instead of reporting an inconsistent total. Superseded replay/read/control functions are explicitly revoked from `PUBLIC`, `dataops_app`, `dataops_app_runtime` and the control role. - Upgrade preflight: before enabling the fence, 550 scans persisted correction scope/chain, evidence references, rule overlaps and allocation weights/sums. A real nonempty 548→549→550 migration with a legacy cross-domain correction failed closed; clean 549 data upgrades and the empty shared `549→550→549→550` round trip passed. - Validation: targeted core/API/migration suite passed `15`; controlled PostgreSQL integration passed `3` tests covering expired and partial/multi-rule replay denial, direct legacy function ACL denial, role-init replay and the nonempty upgrade preflight refusal. Shared head is `20260818_550` with zero WP12 fact rows. - Final re-run after the explicit post-role-init legacy ACL assertion: target suite remained `15 passed`; real PostgreSQL integration remained `3 passed` after shared `549→550→549→550`, ending at head `20260818_550` with `metering_events=0`. Regenerated OpenAPI matched `docs/architecture/OPENAPI.yaml` (493 operations), all WP12 source/deployment mirrors matched byte-for-byte, JSON validation, Python compilation and `git diff --check` passed. ## HTTP failure boundary and allocation atomicity follow-up (2026-08-18) - RED: a real Flask changed-idempotency POST raised PostgreSQL `metering_replay_conflict` through the route as HTTP 500. The quality review also reproduced concurrent same-version changed allocation and concurrent overlapping effective-window races against the prior gateway. - GREEN: routes now convert all SQLAlchemy gateway/connection/commit failures to generic no-store 4xx responses; `record_for_principal` writes the lease claim and event in one runtime transaction, so a failed changed replay rolls the lease transaction back and the next POST commits. The request mapping remains closed to `department`, `project`, `cost_center`; `business_domain` remains server-derived and a supplied field is rejected. - GREEN: `20260818_551` adds same-rule and scope advisory transaction locks, post-lock digest replay/conflict checking, a deferred exact-total allocation trigger and `btree_gist` scope/window exclusion constraint. Real two-connection Barrier tests produced exactly one changed winner, exact replay only for the winner, a single persisted 1,000,000-micros allocation, and one overlapping-window rejection. - Validation: real Docker PostgreSQL integration passed `3`; target core/API/migration suite passed `16`. The shared database completed empty `550→551→550→551`, is at head `20260818_551`, and has `metering_events=0`; source/deployment mirrors and `git diff --check` passed. ## Fixed definer-owner follow-up (2026-08-18) - RED: a restricted-migrator review found `metering_showback_allocation_total_guard()` could retain the transient migration-login owner. That owner could not safely be removed and violated the fixed NOLOGIN SECURITY DEFINER boundary. - GREEN: `20260818_552` transfers every `metering_showback_%` SECURITY DEFINER function, including the total-weight trigger and all renamed legacy paths, to `dataops_tenant_foundation_owner`; it first revokes `PUBLIC`, app/runtime and control execution, then grants only the current approved gateways. It refuses upgrade unless role-init has a NOLOGIN/NOSUPER/NOCREATEROLE owner with required `public` CREATE capability. - Validation: targeted core/API/migration suite passed `17`; real Docker PostgreSQL integration passed `4`, including a temporary NOSUPER/NOCREATEROLE migrator that owned 551 functions, owner census, temporary-role cleanup, isolated 545→552, allocation concurrency/ACL/restart and shared empty 551→552→551→552 role-init replay. Scoped Ruff, Python compilation, source/deployment mirror comparison and `git diff --check` passed. The shared database ends at `20260818_552` with zero WP12 facts. Current engineering state is `ENGINEERING_BASELINE_COMPLETE_SHOWBACK_ACTIVATION_BLOCKED`: Showback, reconciliation and replay now share event-time persisted allocation semantics; all remaining behavior is local engineering evidence only. Enterprise activation remains separately blocked pending approved cost-center/master data, sources, pricing method, budget, Showback decision, finance approval and enterprise UAT.