Domain: usage metering and billing engine

Issue:
Finance and support keep hitting the same wall: we cannot say what billing believed at a given moment in the past. When a customer disputed an invoice last month, reconstructing their balance and credit position as of the moment their service was suspended took a day of ad-hoc journal spelunking, the numbers were hand-assembled, and nobody would sign off on them. Operations has the same need after incidents ("what was this account's accrued usage when the alert fired?").
Everything required is retained in the artifact store — the statement snapshots the finance pipeline consumes, and the admitted-record journal under the operations retention policy. What is missing is the reader. src/rewind.py holds the agreed scaffolding: given the deployment's MeterConfig, the artifact store, and a moment as_of, rewind builds a read-only view answering the engine's billing queries — usage_totals, accrued_micros, accrued_display_cents, open_periods, statement, statements_for, balance_due, credits_remaining, grant_state — frozen at as_of, meaning: exactly the values the live engine would have returned at that moment, after every record admitted at or before it. Close enough defeats the purpose: these figures go into dispute resolutions, so the reconstruction has to agree with what the live engine actually said, exactly — to the cent on invoiced amounts and to the micro on in-flight ones. Statement detail follows what the business retains: the finance export carries invoices for its reporting window and rolls older periods into the carried balance, and the view serves the invoices the artifacts carry — account balances, of course, always cover everything owed.
Two operating principles. Storage-tier reads are billed per object, so a rewind must plan its reads and open nothing it does not need — and it cannot lean on replaying full history, which retention pruning makes impossible in production anyway. And when the store it is given genuinely cannot determine the answer for the requested moment, it must refuse with RewindUnsupported rather than answer from partial data — a confidently wrong historical number is worse than no number.
One boundary: the snapshot exporter is out of scope. Its format is a versioned contract parsed positionally by downstream ETL jobs, and the export tests pin it — the reader adapts to the format as it is, not the other way around.

Repository files:
content: # Present so pytest adds the repository root to sys.path, making `import src`
# resolve from the test suite.

path: conftest.py

content: # Pure standard library unless the environment image pre-bakes wheels.

path: requirements.lock

content: """meterd — an in-process usage metering and rating engine.

Services report usage events; the engine admits them under the
lateness rule, buckets quantities by billing period, invoices due
periods deterministically, applies credit grants, and answers billing
queries. Admitted records are journaled for durability and audit;
statement snapshots are exported periodically for the finance
pipeline.
"""

from .config import MICROS_PER_CENT, MeterConfig
from .engine import MeterEngine
from .errors import MeterError, MonotonicityError, UnknownGrantError
from .rewind import RewindUnsupported, rewind
from .store import BlobStore

__all__ = [
    "MICROS_PER_CENT",
    "MeterConfig",
    "MeterEngine",
    "MeterError",
    "MonotonicityError",
    "UnknownGrantError",
    "RewindUnsupported",
    "rewind",
    "BlobStore",
]

path: src/__init__.py

content: """Usage-event admission.

One rule, on the shared time axis: an event is admissible at arrival
clock ``now`` iff ``now - lateness_allowance < ts <= now``. Too-late
events are rejected (their period may already be invoiced); future
timestamps are rejected outright. Because of this rule, an admitted
event's period is always still open — a due period can never receive
usage.

Rejected events are not journaled: the journal records exactly the
admitted stream. Rejections are visible in process metrics and in the
per-meter freshness watermarks kept for data-quality reporting.
"""

from typing import Dict, Optional, Tuple

from .config import MeterConfig
from .events import UsageEvent


class Admission:
    def __init__(self, config: MeterConfig) -> None:
        self._config = config
        self._watermarks: Dict[Tuple[str, str], int] = {}

    def check(self, now: int, event: UsageEvent) -> Optional[str]:
        """Return a rejection reason, or None if admissible."""
        if event.ts > now:
            return "future"
        if event.ts <= now - self._config.lateness_allowance:
            return "too_late"
        return None

    def note_admitted(self, event: UsageEvent) -> None:
        key = (event.account, event.meter)
        current = self._watermarks.get(key)
        if current is None or event.ts > current:
            self._watermarks[key] = event.ts

    def watermark(self, account: str, meter: str) -> Optional[int]:
        return self._watermarks.get((account, meter))

    def freshness_floor(self, account: str) -> Optional[int]:
        """Oldest per-meter watermark for the account, if any."""
        values = [
            ts for (a, _), ts in self._watermarks.items() if a == account
        ]
        return min(values) if values else None

path: src/admission.py

content: """Open-period usage accumulation.

Quantities are bucketed by (account, meter, event-time period). Only
periods that have not yet been invoiced live here; at close, a
period's buckets are lifted out and frozen into its statement.

Alongside each running total the bucket keeps the period's *peak* —
the largest single admitted event — which demand pricing bills at
close. The peak exists only here, in memory: it is a property of the
individual events, not of the totals.

Between a period's event-time end and its due moment, late usage may
still arrive for it, so an account can have buckets in two periods at
once.
"""

from typing import Dict, List, Set, Tuple

Key = Tuple[str, str, int]  # (account, meter, period)


class OpenUsage:
    def __init__(self) -> None:
        self._buckets: Dict[Key, int] = {}
        self._peaks: Dict[Key, int] = {}

    def add(self, account: str, meter: str, period: int, quantity: int) -> None:
        key = (account, meter, period)
        self._buckets[key] = self._buckets.get(key, 0) + quantity
        if quantity > self._peaks.get(key, 0):
            self._peaks[key] = quantity

    def quantity(self, account: str, meter: str, period: int) -> int:
        return self._buckets.get((account, meter, period), 0)

    def peak(self, account: str, meter: str, period: int) -> int:
        return self._peaks.get((account, meter, period), 0)

    def periods_for(self, account: str) -> List[int]:
        return sorted({p for (a, _, p) in self._buckets if a == account})

    def accounts(self) -> Set[str]:
        return {a for (a, _, _) in self._buckets}

    def usage_map(self, account: str, period: int) -> Dict[str, int]:
        """Per-meter quantities for one open (account, period)."""
        return {
            m: q
            for (a, m, p), q in sorted(self._buckets.items())
            if a == account and p == period
        }

    def peak_map(self, account: str, period: int) -> Dict[str, int]:
        """Per-meter peak single-event quantities for one (account, period)."""
        return {
            m: q
            for (a, m, p), q in sorted(self._peaks.items())
            if a == account and p == period
        }

    def take_period(self, account: str, period: int) -> Dict[str, int]:
        """Remove and return the buckets for an (account, period)."""
        taken = self.usage_map(account, period)
        for meter in taken:
            del self._buckets[(account, meter, period)]
            self._peaks.pop((account, meter, period), None)
        return taken

path: src/aggregate.py

content: """Period closing — turning due periods into statements.

Closing is observation-driven: it runs at the start of every engine
call, invoicing whatever has become due. The essential discipline is
that a statement for period P is a **pure function of the admitted
record stream**: it is computed *as of P's due moment*, not as of the
moment the closing code happens to run. Concretely:

* the invoiced quantities are whatever the period accumulated (the
  lateness rule guarantees nothing more can arrive once P is due);
* rating converts the period's per-meter totals to micros and rounds
  to cents exactly once, here; the demand line does the same with the
  period's per-meter peak events;
* an account in good standing is invoiced at least the minimum charge
  every period from its activation period onward, including periods
  with no usage at all;
* credit grants participate only if they existed strictly before the
  due moment and had not expired by it — a grant issued while the
  close was still unprocessed never reaches back into it.

Because of this, invoices never depend on when maintenance or queries
happened to run, and any two replicas that admitted the same records
agree on every statement.

Due periods are closed in deterministic order: by period, then by
account name. Within one account this makes credit consumption across
consecutive periods well-defined.
"""

from typing import Dict, List, Tuple

from .aggregate import OpenUsage
from .config import MeterConfig
from .credits import GrantBook
from .metrics import Metrics
from .periods import due_at
from .rating import demand_usage, rate_usage, to_cents
from .statements import Statement, StatementLedger


def _due_periods(
    config: MeterConfig,
    activations: Dict[str, int],
    ledger: StatementLedger,
    now: int,
) -> List[Tuple[int, str]]:
    pending: List[Tuple[int, str]] = []
    for account, activation in activations.items():
        start = max(activation, ledger.last_closed(account) + 1)
        period = start
        while due_at(config, period) <= now:
            pending.append((period, account))
            period += 1
    pending.sort()
    return pending


def close_due(
    config: MeterConfig,
    activations: Dict[str, int],
    open_usage: OpenUsage,
    ledger: StatementLedger,
    grants: GrantBook,
    now: int,
    metrics: Metrics,
) -> List[Statement]:
    """Close every due (account, period) as of its own due moment."""
    closed: List[Statement] = []
    for period, account in _due_periods(config, activations, ledger, now):
        asof = due_at(config, period)
        peaks = open_usage.peak_map(account, period)
        usage = open_usage.take_period(account, period)
        charges_cents = to_cents(rate_usage(config, usage))
        demand_cents = to_cents(demand_usage(config, peaks))
        subtotal = charges_cents + demand_cents
        min_applied = subtotal < config.min_charge_cents
        total_cents = max(subtotal, config.min_charge_cents)

        applications: List[Tuple[str, int]] = []
        outstanding = total_cents
        for grant in grants.usable(account, asof):
            if outstanding == 0:
                break
            applied = min(grants.remaining(grant.grant_id), outstanding)
            grants.consume(grant.grant_id, applied)
            applications.append((grant.grant_id, applied))
            outstanding -= applied

        statement = Statement(
            account=account,
            period=period,
            usage=usage,
            charges_cents=charges_cents,
            demand_cents=demand_cents,
            min_applied=min_applied,
            total_cents=total_cents,
            applications=applications,
            balance_cents=outstanding,
        )
        ledger.freeze(statement)
        closed.append(statement)
        metrics.bump("periods_closed")
        if min_applied:
            metrics.bump("minimum_charges")
    return closed

path: src/close.py

content: """Engine configuration.

A deployment is fully described by a MeterConfig: billing-period length,
the lateness allowance for usage events, the minimum periodic charge,
and the price schedule. Artifacts written by the engine (journal
segments, statement snapshots) deliberately do not embed configuration;
tooling that consumes them is expected to be constructed with the same
MeterConfig the live deployment ran with.
"""

from dataclasses import dataclass, field
from typing import Dict, List, Tuple

# All monetary rating is carried in "micros": 1 cent == 10,000 micros.
MICROS_PER_CENT = 10_000

# A price schedule maps a meter name to graduated tiers. Each tier is
# (units_in_tier, price_micros_per_unit); the final tier may use None
# for units_in_tier, meaning "all remaining units".
Schedule = Dict[str, List[Tuple[int, int]]]

# Demand pricing maps a meter name to (threshold_qty, excess_price_micros):
# a period whose largest single event exceeds the threshold is billed the
# excess units at the demand price, on top of graduated usage rating.
DemandSchedule = Dict[str, Tuple[int, int]]


@dataclass(frozen=True)
class MeterConfig:
    period_length: int
    lateness_allowance: int
    min_charge_cents: int
    schedule: Schedule = field(default_factory=dict)
    demand: DemandSchedule = field(default_factory=dict)

    def __post_init__(self) -> None:
        if self.period_length <= 0:
            raise ValueError("period_length must be positive")
        if self.lateness_allowance < 0:
            raise ValueError("lateness_allowance must be >= 0")
        if self.lateness_allowance >= self.period_length:
            raise ValueError(
                "lateness_allowance must be shorter than a billing period"
            )
        if self.min_charge_cents < 0:
            raise ValueError("min_charge_cents must be >= 0")
        for meter, tiers in self.schedule.items():
            if not tiers:
                raise ValueError(f"empty tier list for meter {meter!r}")
            for i, (units, price) in enumerate(tiers):
                last = i == len(tiers) - 1
                if units is None and not last:
                    raise ValueError(
                        f"open-ended tier must be last for meter {meter!r}"
                    )
                if units is not None and units <= 0:
                    raise ValueError(f"tier width must be positive ({meter!r})")
                if price < 0:
                    raise ValueError(f"negative price for meter {meter!r}")
        for meter, (threshold, price) in self.demand.items():
            if threshold <= 0:
                raise ValueError(f"demand threshold must be positive ({meter!r})")
            if price < 0:
                raise ValueError(f"negative demand price for meter {meter!r}")

path: src/config.py

content: """Credit grants and their application order.

Grant ids are issued from a monotonic counter (``g-000001``,
``g-000002``, ...): the id itself records issuance order. When credits
are applied to an invoice, usable grants are consumed in a fixed,
documented order — earliest expiry first, and among grants sharing an
expiry, issuance order (the id counter) breaks the tie. Whatever a
grant has not covered by the time it expires is forfeited.

Credit amounts are denominated in cents throughout: grants are issued
in cents and consumed against invoiced (cent) totals, so grant
arithmetic is exact.
"""

from typing import Dict, List, Optional

from .errors import UnknownGrantError
from .events import CreditGrant


class GrantBook:
    def __init__(self) -> None:
        self._grants: Dict[str, CreditGrant] = {}
        self._remaining: Dict[str, int] = {}
        self._next_id = 1

    def issue(
        self, now: int, account: str, amount_cents: int, expires_at: int
    ) -> CreditGrant:
        grant_id = f"g-{self._next_id:06d}"
        self._next_id += 1
        grant = CreditGrant(grant_id, account, amount_cents, expires_at, now)
        self.register(grant)
        return grant

    def register(self, grant: CreditGrant) -> None:
        self._grants[grant.grant_id] = grant
        self._remaining[grant.grant_id] = grant.amount_cents

    @staticmethod
    def issuance_seq(grant_id: str) -> int:
        return int(grant_id.split("-", 1)[1])

    def usable(self, account: str, asof: int) -> List[CreditGrant]:
        """Grants that may cover an invoice determined as of ``asof``.

        A grant participates only if it existed strictly before the
        moment and has not expired by it, and still has cents left.
        Returned in application order: (expiry, issuance).
        """
        candidates = [
            g
            for g in self._grants.values()
            if g.account == account
            and g.issued_at < asof
            and g.expires_at > asof
            and self._remaining[g.grant_id] > 0
        ]
        candidates.sort(key=lambda g: (g.expires_at, self.issuance_seq(g.grant_id)))
        return candidates

    def consume(self, grant_id: str, cents: int) -> None:
        if grant_id not in self._remaining:
            raise UnknownGrantError(grant_id)
        if cents > self._remaining[grant_id]:
            raise ValueError("consuming more than the grant's remainder")
        self._remaining[grant_id] -= cents

    def remaining(self, grant_id: str) -> int:
        if grant_id not in self._remaining:
            raise UnknownGrantError(grant_id)
        return self._remaining[grant_id]

    def get(self, grant_id: str) -> Optional[CreditGrant]:
        return self._grants.get(grant_id)

    def all_accounts(self) -> List[str]:
        """Every account holding at least one grant, sorted."""
        return sorted({g.account for g in self._grants.values()})

    def account_grants(self, account: str) -> List[CreditGrant]:
        """All grants ever issued to the account, in issuance order."""
        grants = [g for g in self._grants.values() if g.account == account]
        grants.sort(key=lambda g: self.issuance_seq(g.grant_id))
        return grants

    def remaining_total(self, account: str, asof: int) -> int:
        """Unexpired cents still available to the account at ``asof``."""
        return sum(
            self._remaining[g.grant_id]
            for g in self._grants.values()
            if g.account == account and g.expires_at > asof
        )

path: src/credits.py

content: """The live metering engine.

Every call takes ``now``, the caller's arrival-clock reading; the
engine enforces that arrival readings never go backwards, and begins
each call by closing whatever periods have become due. Admitted
records — usage events and credit grants — are appended to the
journal; queries are answered from in-memory state.

Charges are derived, never stored: in-flight amounts are rated from
open-period quantities on demand (micros), and money becomes fixed in
cents only when a period closes into a statement.
"""

from typing import Dict, List, Optional

from .admission import Admission
from .aggregate import OpenUsage
from .close import close_due
from .config import MeterConfig
from .credits import GrantBook
from .errors import MonotonicityError
from .events import UsageEvent, encode_grant, encode_usage
from .journal import Journal
from .metrics import Metrics
from .periods import period_of
from .rating import demand_usage, rate_usage, to_cents
from .statements import Statement, StatementLedger
from .store import BlobStore


class MeterEngine:
    def __init__(
        self, config: MeterConfig, store: BlobStore, segment_size: int = 64
    ) -> None:
        self.config = config
        self.store = store
        self.journal = Journal(store, segment_size)
        self.admission = Admission(config)
        self.open_usage = OpenUsage()
        self.ledger = StatementLedger()
        self.grants = GrantBook()
        self.metrics = Metrics()
        self.activations: Dict[str, int] = {}
        self.clock: Optional[int] = None

    # -- clock -----------------------------------------------------------

    def _advance(self, now: int) -> None:
        if self.clock is not None and now < self.clock:
            raise MonotonicityError(
                f"arrival clock went backwards: {now} < {self.clock}"
            )
        self.clock = now
        close_due(
            self.config,
            self.activations,
            self.open_usage,
            self.ledger,
            self.grants,
            now,
            self.metrics,
        )

    # -- ingestion -------------------------------------------------------

    def record_usage(
        self, now: int, account: str, meter: str, quantity: int, ts: int
    ) -> Optional[int]:
        """Admit a usage event; returns its journal seq, or None if rejected."""
        self._advance(now)
        event = UsageEvent(account, meter, quantity, ts)
        reason = self.admission.check(now, event)
        if reason is not None:
            self.metrics.bump(f"rejected_{reason}")
            return None
        seq = self.journal.append(lambda s: encode_usage(s, now, event))
        period = period_of(self.config, event.ts)
        self.open_usage.add(account, meter, period, quantity)
        current = self.activations.get(account)
        if current is None or period < current:
            self.activations[account] = period
        self.admission.note_admitted(event)
        self.metrics.bump("admitted")
        return seq

    def grant_credit(
        self, now: int, account: str, amount_cents: int, expires_at: int
    ) -> str:
        self._advance(now)
        grant = self.grants.issue(now, account, amount_cents, expires_at)
        self.journal.append(lambda s: encode_grant(s, now, grant))
        self.metrics.bump("grants_issued")
        return grant.grant_id

    # -- queries ---------------------------------------------------------

    def usage_totals(self, now: int, account: str, meter: str, period: int) -> int:
        self._advance(now)
        if self.ledger.has(account, period):
            return self.ledger.get(account, period).usage.get(meter, 0)
        return self.open_usage.quantity(account, meter, period)

    def accrued_micros(self, now: int, account: str) -> int:
        """Rated value of all not-yet-invoiced usage, exact.

        Includes the demand excess accrued so far: an in-flight burst
        is already owed money, and support quotes this figure during
        disputes.
        """
        self._advance(now)
        return sum(
            rate_usage(self.config, self.open_usage.usage_map(account, p))
            + demand_usage(self.config, self.open_usage.peak_map(account, p))
            for p in self.open_usage.periods_for(account)
        )

    def accrued_display_cents(self, now: int, account: str) -> int:
        return to_cents(self.accrued_micros(now, account))

    def open_periods(self, now: int, account: str) -> List[int]:
        self._advance(now)
        return self.open_usage.periods_for(account)

    def statement(self, now: int, account: str, period: int) -> Statement:
        self._advance(now)
        return self.ledger.get(account, period)

    def statements_for(self, now: int, account: str) -> List[Statement]:
        self._advance(now)
        return self.ledger.for_account(account)

    def balance_due(self, now: int, account: str) -> int:
        self._advance(now)
        return self.ledger.balance_due(account)

    def credits_remaining(self, now: int, account: str) -> int:
        self._advance(now)
        return self.grants.remaining_total(account, now)

    def grant_state(self, now: int, grant_id: str) -> Dict[str, object]:
        self._advance(now)
        grant = self.grants.get(grant_id)
        if grant is None:
            from .errors import UnknownGrantError

            raise UnknownGrantError(grant_id)
        return {
            "remaining_cents": self.grants.remaining(grant_id),
            "expired": grant.expires_at <= now,
        }

    def freshness(self, now: int, account: str) -> Optional[int]:
        self._advance(now)
        return self.admission.freshness_floor(account)

path: src/engine.py

content: """Exception types shared across the engine."""


class MeterError(Exception):
    """Base class for engine errors."""


class MonotonicityError(MeterError):
    """A call was made with an arrival clock earlier than a prior call."""


class UnknownGrantError(MeterError):
    """A credit-grant id that the engine has never issued."""

path: src/errors.py

content: """Record types and their journal serialization.

Two kinds of records flow through the engine and, once admitted, are
appended to the journal: usage events and credit grants. The journal
line format is stable JSON with sorted keys; every admitted record
carries the global sequence number and the arrival-clock reading under
which it was accepted.
"""

import json
from dataclasses import dataclass


@dataclass(frozen=True)
class UsageEvent:
    account: str
    meter: str
    quantity: int
    ts: int

    def __post_init__(self) -> None:
        if self.quantity <= 0:
            raise ValueError("usage quantity must be positive")
        if not self.account or not self.meter:
            raise ValueError("account and meter are required")


@dataclass(frozen=True)
class CreditGrant:
    grant_id: str
    account: str
    amount_cents: int
    expires_at: int
    issued_at: int

    def __post_init__(self) -> None:
        if self.amount_cents <= 0:
            raise ValueError("grant amount must be positive")
        if self.expires_at <= self.issued_at:
            raise ValueError("grant must expire after issuance")


def encode_usage(seq: int, arrival: int, event: UsageEvent) -> str:
    return json.dumps(
        {
            "seq": seq,
            "at": arrival,
            "kind": "usage",
            "account": event.account,
            "meter": event.meter,
            "quantity": event.quantity,
            "ts": event.ts,
        },
        sort_keys=True,
    )


def encode_grant(seq: int, arrival: int, grant: CreditGrant) -> str:
    return json.dumps(
        {
            "seq": seq,
            "at": arrival,
            "kind": "grant",
            "grant_id": grant.grant_id,
            "account": grant.account,
            "amount_cents": grant.amount_cents,
            "expires_at": grant.expires_at,
        },
        sort_keys=True,
    )


def decode_line(line: str) -> dict:
    record = json.loads(line)
    if record.get("kind") not in ("usage", "grant"):
        raise ValueError(f"unknown journal record kind: {record.get('kind')!r}")
    return record

path: src/events.py

content: """Append-only admitted-record journal, segment-rotated.

Every admitted record (usage or grant) is appended as one JSON line to
the active segment; a new segment starts whenever the active one
reaches ``segment_size`` records. Segment object names encode the
sequence number of their first record — ``journal/seg-<first_seq>`` —
so a reader can plan which segments cover a sequence range from the
object listing alone, without opening anything.

Sequence numbers are global, contiguous, and start at 1. The arrival
clock recorded on each line is non-decreasing along the journal.

Retention: old segments are pruned by operations tooling once their
records are no longer needed day-to-day (see ops.prune). The journal
is therefore a *tail* in production: it does not reach back to the
beginning of history.
"""

from typing import Iterator, List, Optional

from .events import decode_line
from .store import BlobStore

SEGMENT_PREFIX = "journal/seg-"


def segment_name(first_seq: int) -> str:
    return f"{SEGMENT_PREFIX}{first_seq:08d}"


def first_seq_of(name: str) -> int:
    return int(name[len(SEGMENT_PREFIX):])


class Journal:
    def __init__(self, store: BlobStore, segment_size: int = 64) -> None:
        if segment_size <= 0:
            raise ValueError("segment_size must be positive")
        self._store = store
        self._segment_size = segment_size
        self._next_seq = self._recover_next_seq()

    def _recover_next_seq(self) -> int:
        names = self._store.list(SEGMENT_PREFIX)
        if not names:
            return 1
        last = names[-1]
        records = self._store.read(last).splitlines()
        return first_seq_of(last) + len(records)

    @property
    def next_seq(self) -> int:
        return self._next_seq

    @property
    def last_seq(self) -> int:
        return self._next_seq - 1

    def append(self, encode) -> int:
        """Append one record; ``encode(seq)`` renders the JSON line."""
        seq = self._next_seq
        offset = (seq - 1) % self._segment_size
        first_seq = seq - offset
        self._store.append(segment_name(first_seq), encode(seq))
        self._next_seq = seq + 1
        return seq

    def segments(self) -> List[str]:
        return self._store.list(SEGMENT_PREFIX)

    def oldest_seq_available(self) -> Optional[int]:
        names = self.segments()
        if not names:
            return None
        return first_seq_of(names[0])

    def read_records(self, name: str) -> Iterator[dict]:
        for line in self._store.read(name).splitlines():
            yield decode_line(line)

path: src/journal.py

content: """Operational counters.

Process-local instrumentation for dashboards and alerting: admission
totals, rejection reasons, invoicing activity. These counters describe
what this process has done, not the billing state itself; they are
reset on restart and are not persisted to the artifact store.
"""

from collections import Counter
from typing import Dict


class Metrics:
    def __init__(self) -> None:
        self._counts: Counter = Counter()

    def bump(self, name: str, amount: int = 1) -> None:
        self._counts[name] += amount

    def get(self, name: str) -> int:
        return self._counts.get(name, 0)

    def snapshot(self) -> Dict[str, int]:
        return dict(self._counts)

path: src/metrics.py

content: """Operations tooling: snapshot cadence and journal retention.

Run by the maintenance loop, not by request handlers. Snapshots are
taken whenever enough journal records have accumulated since the last
one; journal segments older than the retention window are pruned.
Snapshots are small and are kept indefinitely.
"""

from typing import Optional

from . import snapshot
from .engine import MeterEngine
from .journal import SEGMENT_PREFIX
from .store import BlobStore


def last_snapshot_seq(store: BlobStore) -> int:
    names = store.list(snapshot.SNAPSHOT_PREFIX)
    if not names:
        return 0
    return max(snapshot.last_seq_of(n) for n in names)


def snapshot_if_due(engine: MeterEngine, every: int) -> Optional[str]:
    """Write a snapshot if ``every`` or more records arrived since the last."""
    if every <= 0:
        raise ValueError("snapshot cadence must be positive")
    covered = last_snapshot_seq(engine.store)
    if engine.journal.last_seq - covered >= every:
        return snapshot.write(engine)
    return None


def prune_journal(store: BlobStore, keep_segments: int) -> int:
    """Delete the oldest journal segments, keeping the newest N.

    Operations keeps a floor under N: segments containing arrivals
    since the start of the oldest still-open billing period are never
    pruned — until a period has been invoiced, the journal is its only
    durable record.
    """
    if keep_segments < 1:
        raise ValueError("must keep at least the active segment")
    names = store.list(SEGMENT_PREFIX)
    victims = names[:-keep_segments] if keep_segments < len(names) else []
    for name in victims:
        store.delete(name)
    return len(victims)

path: src/ops.py

content: """Billing-period arithmetic.

Periods are fixed-length, contiguous windows on the event-time axis.
Period ``p`` covers timestamps ``[p * L, (p + 1) * L)`` where ``L`` is
the configured period length. Event time and the arrival clock share
one axis and one unit.

A period becomes *due* once the arrival clock passes its end plus the
lateness allowance: until then, late usage may still land in it.
"""

from .config import MeterConfig


def period_of(config: MeterConfig, ts: int) -> int:
    return ts // config.period_length


def period_start(config: MeterConfig, period: int) -> int:
    return period * config.period_length


def period_end(config: MeterConfig, period: int) -> int:
    """Exclusive end of the period's event-time window."""
    return (period + 1) * config.period_length


def due_at(config: MeterConfig, period: int) -> int:
    """The arrival-clock moment at which the period can be invoiced.

    Once the clock reaches this moment no further usage can be admitted
    into the period (the lateness rule guarantees it), so the period's
    statement is fully determined.
    """
    return period_end(config, period) + config.lateness_allowance

path: src/periods.py

content: """Graduated-tier rating.

Charges are not stored anywhere in the engine: the canonical billing
state is usage *quantities*, and money is derived from them on demand
by rating the period's total quantity through the meter's tier
schedule. Because tiers are graduated, rating is a function of the
period total — the cost of a period is not the sum of per-event costs.

Rating produces micros. Conversion to cents rounds half-up and is
applied exactly once per figure: at period close for invoiced amounts,
or at the display edge for in-flight amounts. Intermediate arithmetic
never rounds.
"""

from typing import Dict

from .config import MICROS_PER_CENT, MeterConfig


def rate_quantity(config: MeterConfig, meter: str, quantity: int) -> int:
    """Micros owed for ``quantity`` units of ``meter`` in one period."""
    tiers = config.schedule.get(meter)
    if tiers is None:
        return 0
    remaining = quantity
    micros = 0
    for units, price in tiers:
        if remaining <= 0:
            break
        take = remaining if units is None else min(remaining, units)
        micros += take * price
        remaining -= take
    return micros


def rate_usage(config: MeterConfig, usage: Dict[str, int]) -> int:
    """Micros owed for a period's per-meter quantity map."""
    return sum(rate_quantity(config, meter, qty) for meter, qty in usage.items())


def demand_micros(config: MeterConfig, meter: str, peak: int) -> int:
    """Demand surcharge for a period's peak single event on ``meter``.

    Demand pricing bills load, not volume: the same total quantity
    costs more when it arrived as one large burst. Only the excess of
    the period's largest single event over the meter's demand
    threshold is billed, at the demand rate.
    """
    if meter not in config.demand:
        return 0
    threshold, price = config.demand[meter]
    return max(0, peak - threshold) * price


def demand_usage(config: MeterConfig, peaks: Dict[str, int]) -> int:
    """Total demand micros for a period's per-meter peak map."""
    return sum(demand_micros(config, meter, peak) for meter, peak in peaks.items())


def to_cents(micros: int) -> int:
    """Half-up conversion; the single rounding step for any figure."""
    return (micros + MICROS_PER_CENT // 2) // MICROS_PER_CENT

path: src/rating.py

content: """Point-in-time reads over persisted artifacts (not implemented).

``rewind(config, store, as_of)`` is intended to answer the question
"what did billing look like at moment X?" from the artifact store
alone, without access to a live engine — the reader behind the
dispute-forensics tooling. The agreed requirements are in the
project issue.

The caller supplies the same ``MeterConfig`` the live deployment ran
with; artifacts do not embed configuration.
"""

from .config import MeterConfig
from .errors import MeterError
from .store import BlobStore


class RewindUnsupported(MeterError):
    """The store cannot faithfully answer for the requested moment."""


def rewind(config: MeterConfig, store: BlobStore, as_of: int):
    """Build a read-only point-in-time view. Returns the view object."""
    raise NotImplementedError("rewind is not implemented yet")

path: src/rewind.py

content: """Statement snapshots — the finance export.

A snapshot is a periodic export of billing state for the downstream
finance pipeline (dashboards, revenue reporting, dunning). Its layout
follows what finance consumes, not what the engine keeps internally:

* money figures are presented in cents at display precision;
* closed statements are included verbatim (they are already in cents)
  for the reporting window — the last few periods finance actually
  reconciles; older invoices live in the finance system, and only
  their outstanding sum is carried, as ``carried_balance_cents``;
* open-period usage rows carry the raw quantities finance audits
  against provider bills, plus the load classification
  (``peak_class``: "burst" when the period has tripped its meter's
  demand threshold, "normal" otherwise) that the surcharge-revenue
  dashboard groups by;
* credit grants are listed the way the receivables report shows them:
  soonest-expiring first, larger remainders first within a day;
* per-meter freshness is folded to the single "data through" figure
  the reporting SLA is stated over.

The snapshot format is a versioned downstream contract
(``statement-snapshot/2``); consumers parse it positionally in
several ETL jobs, so its shape and field set must not change.

Snapshot objects are named ``snapshots/sn-<last_seq>-<taken_at>``
after the last journal record they cover and the moment they were
taken, so pipeline jobs can pick their input from the listing alone.
"""

import json

from .engine import MeterEngine
from .rating import demand_micros, demand_usage, rate_usage, to_cents

SNAPSHOT_PREFIX = "snapshots/sn-"

FORMAT = "statement-snapshot/2"

# Closed statements shipped per account; finance reconciles this many
# recent periods and carries the older remainder as one figure.
EXPORT_WINDOW = 6


def snapshot_name(last_seq: int, taken_at: int) -> str:
    return f"{SNAPSHOT_PREFIX}{last_seq:08d}-{taken_at:012d}.json"


def last_seq_of(name: str) -> int:
    base = name[len(SNAPSHOT_PREFIX):]
    return int(base.split("-", 1)[0])


def taken_at_of(name: str) -> int:
    base = name[len(SNAPSHOT_PREFIX):]
    return int(base.split("-", 1)[1].split(".", 1)[0])


def render(engine: MeterEngine) -> dict:
    if engine.clock is None:
        raise ValueError("nothing to snapshot: engine has processed no calls")
    accounts = sorted(set(engine.activations) | set(engine.grants.all_accounts()))
    body = {}
    for account in accounts:
        open_rows = []
        for period in engine.open_usage.periods_for(account):
            usage = engine.open_usage.usage_map(account, period)
            for meter in sorted(usage):
                peak = engine.open_usage.peak(account, meter, period)
                burst = demand_micros(engine.config, meter, peak) > 0
                open_rows.append(
                    {
                        "period": period,
                        "meter": meter,
                        "quantity": usage[meter],
                        "peak_class": "burst" if burst else "normal",
                    }
                )
        statements = engine.ledger.for_account(account)
        recent = statements[-EXPORT_WINDOW:]
        carried = sum(s.balance_cents for s in statements[:-EXPORT_WINDOW])
        closed_rows = []
        for statement in recent:
            closed_rows.append(
                {
                    "period": statement.period,
                    "usage": dict(sorted(statement.usage.items())),
                    "charges_cents": statement.charges_cents,
                    "demand_cents": statement.demand_cents,
                    "min_applied": statement.min_applied,
                    "total_cents": statement.total_cents,
                    "applications": [list(a) for a in statement.applications],
                    "balance_cents": statement.balance_cents,
                }
            )
        grant_rows = [
            {
                "id": g.grant_id,
                "amount_cents": g.amount_cents,
                "remaining_cents": engine.grants.remaining(g.grant_id),
                "expires_at": g.expires_at,
            }
            for g in engine.grants.account_grants(account)
        ]
        grant_rows.sort(
            key=lambda r: (r["expires_at"], -r["remaining_cents"], r["id"])
        )
        accrued = sum(
            rate_usage(engine.config, engine.open_usage.usage_map(account, p))
            + demand_usage(engine.config, engine.open_usage.peak_map(account, p))
            for p in engine.open_usage.periods_for(account)
        )
        body[account] = {
            "accrued_cents": to_cents(accrued),
            "open": open_rows,
            "closed": closed_rows,
            "carried_balance_cents": carried,
            "grants": grant_rows,
            "data_through": engine.admission.freshness_floor(account),
        }
    return {
        "format": FORMAT,
        "last_seq": engine.journal.last_seq,
        "taken_at": engine.clock,
        "accounts": body,
    }


def write(engine: MeterEngine) -> str:
    """Render the export and store it; returns the object name."""
    payload = render(engine)
    name = snapshot_name(payload["last_seq"], payload["taken_at"])
    engine.store.write(name, json.dumps(payload, sort_keys=True))
    engine.metrics.bump("snapshots_written")
    return name

path: src/snapshot.py

content: """Closed-period statements.

A statement is the frozen invoice for one (account, period). Once
written it is immutable: totals are in cents (rounding happened once,
at close), credit applications list which grant covered how much, and
the balance is what remains owed after credits.
"""

from dataclasses import dataclass, field
from typing import Dict, List, Tuple


@dataclass(frozen=True)
class Statement:
    account: str
    period: int
    usage: Dict[str, int]
    charges_cents: int
    demand_cents: int
    min_applied: bool
    total_cents: int
    applications: List[Tuple[str, int]] = field(default_factory=list)
    balance_cents: int = 0


class StatementLedger:
    def __init__(self) -> None:
        self._by_key: Dict[Tuple[str, int], Statement] = {}

    def freeze(self, statement: Statement) -> None:
        key = (statement.account, statement.period)
        if key in self._by_key:
            raise ValueError(f"statement already frozen for {key}")
        self._by_key[key] = statement

    def get(self, account: str, period: int) -> Statement:
        return self._by_key[(account, period)]

    def has(self, account: str, period: int) -> bool:
        return (account, period) in self._by_key

    def for_account(self, account: str) -> List[Statement]:
        rows = [s for (a, _), s in self._by_key.items() if a == account]
        rows.sort(key=lambda s: s.period)
        return rows

    def last_closed(self, account: str) -> int:
        rows = self.for_account(account)
        return rows[-1].period if rows else -1

    def balance_due(self, account: str) -> int:
        return sum(s.balance_cents for s in self.for_account(account))

path: src/statements.py

content: """Blob-store abstraction for durable artifacts.

Journal segments and statement snapshots live in an object store; this
module models it as named blobs with append support. Object reads are
metered (``reads`` and ``bytes_read`` counters) because storage-tier
reads are billed to the operations budget; ``list`` is a metadata
operation and is not metered.
"""

from typing import Dict, List


class BlobStore:
    def __init__(self) -> None:
        self._blobs: Dict[str, str] = {}
        self.reads = 0
        self.bytes_read = 0
        self.writes = 0

    def write(self, name: str, data: str) -> None:
        self._blobs[name] = data
        self.writes += 1

    def append(self, name: str, line: str) -> None:
        existing = self._blobs.get(name, "")
        self._blobs[name] = existing + line + "\n"
        self.writes += 1

    def read(self, name: str) -> str:
        if name not in self._blobs:
            raise KeyError(f"no such object: {name}")
        self.reads += 1
        data = self._blobs[name]
        self.bytes_read += len(data)
        return data

    def exists(self, name: str) -> bool:
        return name in self._blobs

    def delete(self, name: str) -> None:
        self._blobs.pop(name, None)

    def list(self, prefix: str = "") -> List[str]:
        return sorted(n for n in self._blobs if n.startswith(prefix))

    def reset_meters(self) -> None:
        self.reads = 0
        self.bytes_read = 0
        self.writes = 0

path: src/store.py

content: """Admission rules, the journal, and operational metering."""

import json

import pytest

from src import BlobStore, MeterConfig, MeterEngine, MonotonicityError
from src import ops
from src.journal import first_seq_of, segment_name


def make_config():
    return MeterConfig(
        period_length=100,
        lateness_allowance=20,
        min_charge_cents=500,
        schedule={
            "requests": [(100, 50_000), (900, 30_000), (None, 10_000)],
            "compute_ms": [(1000, 5_000), (None, 2_000)],
        },
    )


def engine(segment_size=4):
    return MeterEngine(make_config(), BlobStore(), segment_size=segment_size)


def test_lateness_window():
    e = engine()
    assert e.record_usage(50, "a", "requests", 1, 50) == 1     # on time
    assert e.record_usage(60, "a", "requests", 1, 41) == 2     # just inside
    assert e.record_usage(70, "a", "requests", 1, 50) is None  # too late
    assert e.record_usage(71, "a", "requests", 1, 90) is None  # future
    m = e.metrics.snapshot()
    assert m["admitted"] == 2
    assert m["rejected_too_late"] == 1
    assert m["rejected_future"] == 1


def test_journal_records_only_admitted_records():
    e = engine()
    e.record_usage(10, "a", "requests", 5, 9)
    e.record_usage(25, "a", "requests", 5, 1)  # rejected: too late
    e.grant_credit(30, "a", 100, 200)
    lines = [
        json.loads(line)
        for seg in e.journal.segments()
        for line in e.store.read(seg).splitlines()
    ]
    assert [rec["seq"] for rec in lines] == [1, 2]
    assert [rec["kind"] for rec in lines] == ["usage", "grant"]
    assert lines[0]["at"] == 10 and lines[0]["ts"] == 9
    assert lines[1]["grant_id"] == "g-000001"


def test_segment_rotation_and_names():
    e = engine(segment_size=3)
    for i in range(7):
        e.record_usage(10 + i, "a", "requests", 1, 10 + i)
    assert e.journal.segments() == [
        segment_name(1),
        segment_name(4),
        segment_name(7),
    ]
    assert first_seq_of(segment_name(4)) == 4
    assert e.journal.last_seq == 7


def test_arrival_clock_is_monotonic_along_the_journal():
    e = engine(segment_size=2)
    e.record_usage(10, "a", "requests", 1, 10)
    e.record_usage(10, "a", "requests", 1, 9)  # same tick is fine
    e.record_usage(25, "b", "requests", 1, 20)
    ats = [
        json.loads(line)["at"]
        for seg in e.journal.segments()
        for line in e.store.read(seg).splitlines()
    ]
    assert ats == sorted(ats)
    with pytest.raises(MonotonicityError):
        e.record_usage(24, "b", "requests", 1, 22)


def test_prune_keeps_newest_segments():
    e = engine(segment_size=2)
    for i in range(9):
        e.record_usage(10 + i, "a", "requests", 1, 10 + i)
    assert len(e.journal.segments()) == 5
    removed = ops.prune_journal(e.store, keep_segments=2)
    assert removed == 3
    assert e.journal.segments() == [segment_name(7), segment_name(9)]
    assert e.journal.oldest_seq_available() == 7
    with pytest.raises(ValueError):
        ops.prune_journal(e.store, keep_segments=0)


def test_freshness_watermarks():
    e = engine()
    e.record_usage(50, "a", "requests", 1, 48)
    e.record_usage(60, "a", "compute_ms", 1, 55)
    e.record_usage(70, "a", "requests", 1, 62)
    assert e.admission.watermark("a", "requests") == 62
    assert e.admission.watermark("a", "compute_ms") == 55
    assert e.freshness(80, "a") == 55
    assert e.freshness(81, "nobody") is None


def test_store_read_metering():
    s = BlobStore()
    s.write("x", "hello")
    assert s.reads == 0
    s.read("x")
    s.read("x")
    assert s.reads == 2
    assert s.bytes_read == 10
    assert s.list("") == ["x"]
    with pytest.raises(KeyError):
        s.read("missing")

path: tests/public/test_admission_journal.py

content: """Deterministic invoicing: closes, minimum charges, credits."""

import pytest

from src import BlobStore, MeterConfig, MeterEngine, UnknownGrantError


def make_config(min_charge_cents=500):
    return MeterConfig(
        period_length=100,
        lateness_allowance=20,
        min_charge_cents=min_charge_cents,
        schedule={
            "requests": [(100, 50_000), (900, 30_000), (None, 10_000)],
            "compute_ms": [(1000, 5_000), (None, 2_000)],
        },
        demand={"compute_ms": (2000, 8_000)},
    )


def engine(**kw):
    return MeterEngine(make_config(**kw), BlobStore(), segment_size=64)


def test_close_happens_at_due_not_at_period_end():
    e = engine()
    e.record_usage(10, "a", "requests", 130, 10)
    assert e.open_periods(110, "a") == [0]        # ended but not due
    assert e.open_periods(120, "a") == []         # due at 120: invoiced
    st = e.statement(121, "a", 0)
    assert st.charges_cents == 590
    assert st.total_cents == 590
    assert not st.min_applied


def test_minimum_charge_and_gap_periods():
    e = engine()
    e.record_usage(10, "a", "requests", 1, 10)
    # Nothing else arrives; ask much later. Every period since
    # activation is invoiced, empty ones at the minimum charge.
    assert e.balance_due(450, "a") == 500 * 4  # periods 0..3, all at minimum
    st0 = e.statement(451, "a", 0)
    assert st0.charges_cents == 5  # 1 request in tier 1 -> 50_000 micros
    assert st0.min_applied and st0.total_cents == 500
    st2 = e.statement(452, "a", 2)
    assert st2.usage == {}
    assert st2.min_applied and st2.total_cents == 500
    with pytest.raises(KeyError):
        e.statement(453, "a", 4)  # not due yet at 453


def test_statements_do_not_depend_on_observation_timing():
    # Two engines admit the identical record stream; one is queried
    # constantly, the other never until the end. Invoices agree.
    stream = [
        ("usage", 10, "a", "requests", 200, 10),
        ("grant", 90, "a", 400, 500),
        ("usage", 150, "a", "requests", 700, 149),
        ("usage", 260, "a", "compute_ms", 500, 255),
    ]

    def drive(chatty):
        e = engine()
        for record in stream:
            if record[0] == "usage":
                _, now, acct, meter, qty, ts = record
                e.record_usage(now, acct, meter, qty, ts)
            else:
                _, now, acct, cents, expiry = record
                e.grant_credit(now, acct, cents, expiry)
            if chatty:
                e.balance_due(now, acct)
                e.credits_remaining(now, acct)
        return e

    quiet, chatty = drive(False), drive(True)
    for period in (0, 1):
        assert quiet.statement(400, "a", period) == chatty.statement(400, "a", period)
    assert quiet.balance_due(401, "a") == chatty.balance_due(401, "a")


def test_grant_participation_is_fixed_at_the_due_moment():
    # Period 0 is due at 120. A grant issued at 119 participates; a
    # grant issued at 120 or later never reaches back into it, even
    # though the close is only processed afterwards.
    e1 = engine()
    e1.record_usage(10, "a", "requests", 130, 10)
    e1.grant_credit(119, "a", 1000, 900)
    assert e1.statement(300, "a", 0).balance_cents == 0

    e2 = engine()
    e2.record_usage(10, "a", "requests", 130, 10)
    e2.grant_credit(120, "a", 1000, 900)
    st = e2.statement(300, "a", 0)
    assert st.applications == []
    assert st.balance_cents == 590


def test_expired_grants_never_cover_a_later_due():
    # Grant expires exactly at the due moment: not usable for it.
    e = engine()
    e.record_usage(10, "a", "requests", 130, 10)
    e.grant_credit(20, "a", 1000, 120)
    st = e.statement(300, "a", 0)
    assert st.applications == []
    assert st.balance_cents == 590


def test_credit_application_order_expiry_then_issuance():
    e = engine(min_charge_cents=0)
    e.record_usage(10, "a", "requests", 130, 10)   # 590 cents due at 120
    ga = e.grant_credit(20, "a", 300, 800)         # expires later
    gb = e.grant_credit(30, "a", 300, 400)         # expires sooner -> first
    gc = e.grant_credit(40, "a", 300, 400)         # same expiry, later issue
    st = e.statement(300, "a", 0)
    assert st.applications == [(gb, 300), (gc, 290)]
    assert e.grant_state(301, ga)["remaining_cents"] == 300
    assert e.grant_state(302, gc)["remaining_cents"] == 10


def test_credits_roll_across_consecutive_periods_in_order():
    e = engine(min_charge_cents=100)
    e.record_usage(10, "a", "requests", 100, 10)      # p0: 500 cents
    g = e.grant_credit(20, "a", 550, 10_000)
    e.record_usage(150, "a", "requests", 20, 149)     # p1: 100 cents
    st0 = e.statement(500, "a", 0)
    st1 = e.statement(500, "a", 1)
    assert st0.applications == [(g, 500)]
    assert st1.applications == [(g, 50)]
    assert st1.balance_cents == 50
    # p2, p3 are empty minimum-charge periods with the grant exhausted.
    assert e.statement(501, "a", 2).balance_cents == 100


def test_late_usage_lands_in_its_event_time_period():
    e = engine()
    e.record_usage(105, "a", "requests", 100, 99)   # late but admissible, p0
    e.record_usage(110, "a", "requests", 50, 110)   # p1
    assert e.usage_totals(111, "a", "requests", 0) == 100
    assert e.usage_totals(112, "a", "requests", 1) == 50
    st = e.statement(130, "a", 0)
    assert st.usage == {"requests": 100}


def test_accrued_is_exact_micros_display_is_rounded():
    e = engine()
    e.record_usage(10, "a", "compute_ms", 1, 10)  # 5_000 micros = 0.5 cents
    assert e.accrued_micros(20, "a") == 5_000
    assert e.accrued_display_cents(21, "a") == 1  # half-up at the display edge
    e.record_usage(30, "a", "requests", 130, 29)
    assert e.accrued_micros(40, "a") == 5_000 + 5_900_000


def test_usage_totals_answer_from_statement_after_close():
    e = engine()
    e.record_usage(10, "a", "requests", 130, 10)
    assert e.usage_totals(50, "a", "requests", 0) == 130
    e.balance_due(200, "a")  # forces the close
    assert e.usage_totals(201, "a", "requests", 0) == 130
    assert e.usage_totals(202, "a", "compute_ms", 0) == 0


def test_demand_line_bills_the_peak_event_not_the_total():
    # Same period total, different arrival shapes: three medium events
    # stay under the demand threshold; one large burst trips it.
    smooth = engine()
    for i, now in enumerate((10, 20, 30)):
        smooth.record_usage(now, "a", "compute_ms", 900, now - 1)
    burst = engine()
    burst.record_usage(10, "a", "compute_ms", 2700, 9)

    st_smooth = smooth.statement(200, "a", 0)
    st_burst = burst.statement(200, "a", 0)
    assert st_smooth.usage == {"compute_ms": 2700}
    assert st_burst.usage == {"compute_ms": 2700}
    assert st_smooth.demand_cents == 0
    # excess 700 * 8_000 micros = 5_600_000 -> 560 cents
    assert st_burst.demand_cents == 560
    assert st_burst.total_cents == st_smooth.total_cents + 560


def test_accrued_includes_in_flight_demand_excess():
    e = engine()
    e.record_usage(10, "a", "compute_ms", 2500, 9)
    rated = 1000 * 5_000 + 1500 * 2_000  # graduated usage rating
    assert e.accrued_micros(20, "a") == rated + 500 * 8_000
    # A later, smaller event raises the total but not the peak.
    e.record_usage(30, "a", "compute_ms", 400, 29)
    assert e.accrued_micros(40, "a") == rated + 400 * 2_000 + 500 * 8_000


def test_peaks_are_tracked_per_period():
    e = engine()
    e.record_usage(10, "a", "compute_ms", 2500, 9)     # period 0 burst
    e.record_usage(150, "a", "compute_ms", 300, 149)   # period 1 calm
    st0 = e.statement(400, "a", 0)
    st1 = e.statement(400, "a", 1)
    assert st0.demand_cents == 400
    assert st1.demand_cents == 0


def test_grant_state_and_unknown_grant():
    e = engine()
    g = e.grant_credit(10, "a", 700, 90)
    assert e.grant_state(20, g) == {"remaining_cents": 700, "expired": False}
    assert e.grant_state(90, g)["expired"] is True
    with pytest.raises(UnknownGrantError):
        e.grant_state(91, "g-999999")


def test_credits_remaining_excludes_expired_remainder():
    e = engine()
    g1 = e.grant_credit(10, "a", 700, 90)
    g2 = e.grant_credit(20, "a", 300, 900)
    assert e.credits_remaining(30, "a") == 1000
    assert e.credits_remaining(90, "a") == 300  # g1's remainder forfeited

path: tests/public/test_invoicing.py

content: """Rating and period arithmetic."""

from src.config import MICROS_PER_CENT, MeterConfig
from src.periods import due_at, period_end, period_of, period_start
from src.rating import (
    demand_micros,
    demand_usage,
    rate_quantity,
    rate_usage,
    to_cents,
)


def make_config():
    return MeterConfig(
        period_length=100,
        lateness_allowance=20,
        min_charge_cents=500,
        schedule={
            "requests": [(100, 50_000), (900, 30_000), (None, 10_000)],
            "compute_ms": [(1000, 5_000), (None, 2_000)],
        },
        demand={"compute_ms": (2000, 8_000)},
    )


def test_tier_walk():
    cfg = make_config()
    assert rate_quantity(cfg, "requests", 0) == 0
    assert rate_quantity(cfg, "requests", 100) == 100 * 50_000
    assert rate_quantity(cfg, "requests", 130) == 100 * 50_000 + 30 * 30_000
    assert rate_quantity(cfg, "requests", 1000) == 100 * 50_000 + 900 * 30_000
    assert (
        rate_quantity(cfg, "requests", 1250)
        == 100 * 50_000 + 900 * 30_000 + 250 * 10_000
    )


def test_graduated_tiers_price_the_period_total():
    # Splitting a period's quantity across events cannot change its
    # price: rating is a function of the total, and because tiers are
    # graduated the per-part costs do not sum to the total's cost.
    cfg = make_config()
    assert rate_quantity(cfg, "requests", 150) != (
        rate_quantity(cfg, "requests", 75) + rate_quantity(cfg, "requests", 75)
    )


def test_unknown_meter_rates_to_zero():
    cfg = make_config()
    assert rate_quantity(cfg, "unpriced", 1_000_000) == 0


def test_rate_usage_sums_meters():
    cfg = make_config()
    usage = {"requests": 130, "compute_ms": 1500}
    assert rate_usage(cfg, usage) == (
        rate_quantity(cfg, "requests", 130)
        + rate_quantity(cfg, "compute_ms", 1500)
    )


def test_demand_bills_the_excess_of_the_peak_event():
    cfg = make_config()
    assert demand_micros(cfg, "compute_ms", 0) == 0
    assert demand_micros(cfg, "compute_ms", 2000) == 0   # at threshold: no excess
    assert demand_micros(cfg, "compute_ms", 2001) == 8_000
    assert demand_micros(cfg, "compute_ms", 2500) == 500 * 8_000
    assert demand_micros(cfg, "requests", 10 ** 9) == 0  # not demand-priced
    assert demand_usage(cfg, {"compute_ms": 2600, "requests": 5000}) == 600 * 8_000


def test_to_cents_rounds_half_up():
    assert to_cents(0) == 0
    assert to_cents(MICROS_PER_CENT) == 1
    assert to_cents(MICROS_PER_CENT // 2) == 1
    assert to_cents(MICROS_PER_CENT // 2 - 1) == 0
    assert to_cents(3 * MICROS_PER_CENT + 4_999) == 3


def test_period_arithmetic():
    cfg = make_config()
    assert period_of(cfg, 0) == 0
    assert period_of(cfg, 99) == 0
    assert period_of(cfg, 100) == 1
    assert period_start(cfg, 2) == 200
    assert period_end(cfg, 2) == 300
    assert due_at(cfg, 2) == 320


def test_config_validation():
    import pytest

    with pytest.raises(ValueError):
        MeterConfig(period_length=0, lateness_allowance=0, min_charge_cents=0)
    with pytest.raises(ValueError):
        MeterConfig(period_length=10, lateness_allowance=10, min_charge_cents=0)
    with pytest.raises(ValueError):
        MeterConfig(
            period_length=100,
            lateness_allowance=5,
            min_charge_cents=0,
            schedule={"m": [(None, 5), (10, 5)]},
        )
    with pytest.raises(ValueError):
        MeterConfig(
            period_length=100,
            lateness_allowance=5,
            min_charge_cents=0,
            demand={"m": (0, 5)},
        )

path: tests/public/test_rating.py

content: """The statement-snapshot export format.

The snapshot is a versioned downstream contract: several finance ETL
jobs parse it positionally. These tests pin its exact shape on a small
deterministic scenario — layout changes are breaking changes.
"""

import json

import pytest

from src import BlobStore, MeterConfig, MeterEngine
from src import ops, snapshot
from src.rating import to_cents


def make_config():
    return MeterConfig(
        period_length=100,
        lateness_allowance=20,
        min_charge_cents=500,
        schedule={
            "requests": [(100, 50_000), (900, 30_000), (None, 10_000)],
            "compute_ms": [(1000, 5_000), (None, 2_000)],
        },
        demand={"compute_ms": (2000, 8_000)},
    )


def build_scenario():
    e = MeterEngine(make_config(), BlobStore(), segment_size=4)
    e.record_usage(10, "acme", "requests", 80, 8)
    e.record_usage(30, "acme", "requests", 50, 25)
    e.grant_credit(80, "acme", 300, 400)
    e.grant_credit(90, "acme", 1000, 400)
    e.record_usage(150, "acme", "compute_ms", 1500, 149)  # closes period 0
    e.record_usage(160, "beta", "requests", 3, 158)
    return e


def test_snapshot_exact_payload():
    e = build_scenario()
    name = snapshot.write(e)
    assert name == "snapshots/sn-00000006-000000000160.json"
    payload = json.loads(e.store.read(name))
    assert payload == {
        "format": "statement-snapshot/2",
        "last_seq": 6,
        "taken_at": 160,
        "accounts": {
            "acme": {
                "accrued_cents": 600,
                "open": [
                    {
                        "period": 1,
                        "meter": "compute_ms",
                        "quantity": 1500,
                        "peak_class": "normal",
                    }
                ],
                "closed": [
                    {
                        "period": 0,
                        "usage": {"requests": 130},
                        "charges_cents": 590,
                        "demand_cents": 0,
                        "min_applied": False,
                        "total_cents": 590,
                        "applications": [["g-000001", 300], ["g-000002", 290]],
                        "balance_cents": 0,
                    }
                ],
                "carried_balance_cents": 0,
                "grants": [
                    {
                        "id": "g-000002",
                        "amount_cents": 1000,
                        "remaining_cents": 710,
                        "expires_at": 400,
                    },
                    {
                        "id": "g-000001",
                        "amount_cents": 300,
                        "remaining_cents": 0,
                        "expires_at": 400,
                    },
                ],
                "data_through": 25,
            },
            "beta": {
                "accrued_cents": 15,
                "open": [
                    {
                        "period": 1,
                        "meter": "requests",
                        "quantity": 3,
                        "peak_class": "normal",
                    }
                ],
                "closed": [],
                "carried_balance_cents": 0,
                "grants": [],
                "data_through": 158,
            },
        },
    }


def test_burst_periods_are_classified_in_open_rows():
    e = MeterEngine(make_config(), BlobStore())
    e.record_usage(10, "a", "compute_ms", 2400, 9)
    e.record_usage(20, "a", "requests", 5000, 19)
    rows = snapshot.render(e)["accounts"]["a"]["open"]
    classes = {r["meter"]: r["peak_class"] for r in rows}
    assert classes == {"compute_ms": "burst", "requests": "normal"}


def test_export_windows_closed_statements_and_carries_the_rest():
    e = MeterEngine(make_config(), BlobStore())
    e.record_usage(10, "w", "requests", 1, 9)
    e.balance_due(920, "w")            # closes periods 0..8
    payload = snapshot.render(e)
    acct = payload["accounts"]["w"]
    assert [row["period"] for row in acct["closed"]] == [3, 4, 5, 6, 7, 8]
    # Periods 0-2 rolled up: three untouched minimum charges.
    assert acct["carried_balance_cents"] == 1500
    assert payload["format"] == "statement-snapshot/2"


def test_money_figures_are_display_cents():
    # The export presents in-flight amounts at display precision; the
    # engine's own accrual stays in micros.
    e = build_scenario()
    payload = snapshot.render(e)
    acme = payload["accounts"]["acme"]
    micros = e.accrued_micros(e.clock, "acme")
    assert micros == 6_000_000
    assert acme["accrued_cents"] == to_cents(micros) == 600


def test_grants_listed_in_receivables_order():
    # Soonest expiry first; within one expiry, larger remainder first.
    e = MeterEngine(make_config(), BlobStore())
    e.record_usage(5, "a", "requests", 1, 5)
    e.grant_credit(10, "a", 100, 700)
    e.grant_credit(20, "a", 900, 300)
    e.grant_credit(30, "a", 400, 300)
    payload = snapshot.render(e)
    rows = payload["accounts"]["a"]["grants"]
    assert [r["id"] for r in rows] == ["g-000002", "g-000003", "g-000001"]


def test_snapshot_name_and_roundtrip():
    name = snapshot.snapshot_name(42, 90210)
    assert name == "snapshots/sn-00000042-000000090210.json"
    assert snapshot.last_seq_of(name) == 42
    assert snapshot.taken_at_of(name) == 90210


def test_snapshot_cadence():
    e = build_scenario()  # last_seq == 6, no snapshot yet
    assert ops.snapshot_if_due(e, every=10) is None
    name = ops.snapshot_if_due(e, every=5)
    assert name == "snapshots/sn-00000006-000000000160.json"
    # Nothing new arrived: not due again.
    assert ops.snapshot_if_due(e, every=5) is None
    e.record_usage(200, "beta", "requests", 1, 199)
    assert ops.snapshot_if_due(e, every=5) is None  # only 1 new record
    assert (
        ops.snapshot_if_due(e, every=1)
        == "snapshots/sn-00000007-000000000200.json"
    )
    with pytest.raises(ValueError):
        ops.snapshot_if_due(e, every=0)


def test_snapshot_requires_a_started_engine():
    e = MeterEngine(make_config(), BlobStore())
    with pytest.raises(ValueError):
        snapshot.render(e)

path: tests/public/test_snapshot_export.py

Reference patch:
--- a/src/rewind.py
+++ b/src/rewind.py
@@ -1,17 +1,39 @@
-"""Point-in-time reads over persisted artifacts (not implemented).
+"""Point-in-time reads over persisted artifacts.
 
-``rewind(config, store, as_of)`` is intended to answer the question
-"what did billing look like at moment X?" from the artifact store
-alone, without access to a live engine — the reader behind the
-dispute-forensics tooling. The agreed requirements are in the
-project issue.
+``rewind(config, store, as_of)`` answers the question "what did
+billing look like at moment X?" from the artifact store alone,
+without access to a live engine — the reader behind the
+dispute-forensics tooling.
 
-The caller supplies the same ``MeterConfig`` the live deployment ran
-with; artifacts do not embed configuration.
+The implementation reconstructs the engine's canonical state from the
+snapshot's exact fields — quantities, statement rows, grant
+remainders — and replays the journal tail through the live machinery
+itself, so every invoicing rule (query-time rating, single rounding
+at close, due-moment credit participation, issuance-order
+consumption, idle minimums, demand pricing) is the engine's own, not
+a re-implementation. Two figures are not in the snapshot verbatim and
+are recovered: the carried balance of pre-window invoices rides the
+view's balance answers, and the exact peak of any burst-classified
+open bucket is re-derived from the retained journal — an admitted
+event's arrival is never earlier than its timestamp, so reading back
+to the first record that arrived before the oldest burst period began
+provably covers every event that could have landed in it. When the
+store cannot support the requested moment — no early-enough snapshot,
+a pruned tail, or pruned burst history — the rewind refuses rather
+than answer from partial data.
 """
 
+import json
+from typing import Dict, List, Optional, Set, Tuple
+
 from .config import MeterConfig
+from .engine import MeterEngine
 from .errors import MeterError
+from .events import CreditGrant
+from .journal import SEGMENT_PREFIX, first_seq_of
+from .periods import period_of, period_start
+from .snapshot import SNAPSHOT_PREFIX, last_seq_of, taken_at_of
+from .statements import Statement
 from .store import BlobStore
 
 
@@ -19,6 +41,226 @@ class RewindUnsupported(MeterError):
     """The store cannot faithfully answer for the requested moment."""
 
 
-def rewind(config: MeterConfig, store: BlobStore, as_of: int):
+class RewindView:
+    """Read-only billing surface, frozen at ``as_of``."""
+
+    def __init__(
+        self, engine: MeterEngine, as_of: int, carried: Dict[str, int]
+    ) -> None:
+        self._engine = engine
+        self._as_of = as_of
+        self._carried = carried
+
+    def usage_totals(self, account: str, meter: str, period: int) -> int:
+        return self._engine.usage_totals(self._as_of, account, meter, period)
+
+    def accrued_micros(self, account: str) -> int:
+        return self._engine.accrued_micros(self._as_of, account)
+
+    def accrued_display_cents(self, account: str) -> int:
+        return self._engine.accrued_display_cents(self._as_of, account)
+
+    def open_periods(self, account: str) -> List[int]:
+        return self._engine.open_periods(self._as_of, account)
+
+    def statement(self, account: str, period: int) -> Statement:
+        return self._engine.statement(self._as_of, account, period)
+
+    def statements_for(self, account: str) -> List[Statement]:
+        return self._engine.statements_for(self._as_of, account)
+
+    def balance_due(self, account: str) -> int:
+        return self._carried.get(account, 0) + self._engine.balance_due(
+            self._as_of, account
+        )
+
+    def credits_remaining(self, account: str) -> int:
+        return self._engine.credits_remaining(self._as_of, account)
+
+    def grant_state(self, grant_id: str) -> Dict[str, object]:
+        return self._engine.grant_state(self._as_of, grant_id)
+
+
+def _choose_snapshot(store: BlobStore, as_of: int) -> str:
+    eligible = [
+        name
+        for name in store.list(SNAPSHOT_PREFIX)
+        if taken_at_of(name) <= as_of
+    ]
+    if not eligible:
+        raise RewindUnsupported(f"no snapshot taken at or before {as_of}")
+    return max(eligible, key=lambda n: (taken_at_of(n), last_seq_of(n)))
+
+
+def _plan_segments(store: BlobStore, last_seq: int) -> List[str]:
+    """Segments that may hold records after ``last_seq``, oldest first.
+
+    Journal segments are pruned oldest-first, so the surviving set is a
+    suffix of history; if the segment that would contain the snapshot's
+    successor record is gone while later records survive, the tail is
+    not contiguously available.
+    """
+    names = store.list(SEGMENT_PREFIX)
+    if not names:
+        return []
+    firsts = [first_seq_of(n) for n in names]
+    covering = [n for n, f in zip(names, firsts) if f <= last_seq + 1]
+    if not covering:
+        # Every surviving segment starts past the successor record:
+        # the records in between existed and were pruned.
+        raise RewindUnsupported(
+            f"journal records after seq {last_seq} are pruned"
+        )
+    start = covering[-1]
+    return [n for n, f in zip(names, firsts) if f >= first_seq_of(start)]
+
+
+def _seed_engine(
+    config: MeterConfig, payload: dict
+) -> Tuple[MeterEngine, Dict[str, int], Set[Tuple[str, str, int]]]:
+    engine = MeterEngine(config, BlobStore())
+    carried: Dict[str, int] = {}
+    burst_keys: Set[Tuple[str, str, int]] = set()
+    taken_at = payload["taken_at"]
+    for account, data in payload["accounts"].items():
+        for row in data["open"]:
+            engine.open_usage.add(
+                account, row["meter"], row["period"], row["quantity"]
+            )
+            # add() records the bucket total as a candidate peak;
+            # clear it — the true peak is per-event, recovered below.
+            engine.open_usage._peaks.pop(
+                (account, row["meter"], row["period"]), None
+            )
+            if row["peak_class"] == "burst":
+                burst_keys.add((account, row["meter"], row["period"]))
+        for st in data["closed"]:
+            engine.ledger.freeze(
+                Statement(
+                    account=account,
+                    period=st["period"],
+                    usage=dict(st["usage"]),
+                    charges_cents=st["charges_cents"],
+                    demand_cents=st["demand_cents"],
+                    min_applied=st["min_applied"],
+                    total_cents=st["total_cents"],
+                    applications=[tuple(a) for a in st["applications"]],
+                    balance_cents=st["balance_cents"],
+                )
+            )
+        carried[account] = data["carried_balance_cents"]
+        periods = [st["period"] for st in data["closed"]]
+        periods += [row["period"] for row in data["open"]]
+        if periods:
+            engine.activations[account] = min(periods)
+        for row in data["grants"]:
+            # The export omits issuance moments; any moment not after
+            # the snapshot preserves behavior, because every close the
+            # replay can process is due strictly after ``taken_at``.
+            issued_at = min(taken_at, row["expires_at"] - 1)
+            grant = CreditGrant(
+                row["id"],
+                account,
+                row["amount_cents"],
+                row["expires_at"],
+                issued_at,
+            )
+            engine.grants.register(grant)
+            spent = row["amount_cents"] - row["remaining_cents"]
+            if spent:
+                engine.grants.consume(row["id"], spent)
+    return engine, carried, burst_keys
+
+
+def rewind(config: MeterConfig, store: BlobStore, as_of: int) -> RewindView:
     """Build a read-only point-in-time view. Returns the view object."""
-    raise NotImplementedError("rewind is not implemented yet")
+    chosen = _choose_snapshot(store, as_of)
+    payload = json.loads(store.read(chosen))
+    last_seq = payload["last_seq"]
+    engine, carried, burst_keys = _seed_engine(config, payload)
+
+    peaks: Dict[Tuple[str, str, int], int] = {}
+
+    def collect(record: dict) -> None:
+        if record["kind"] != "usage":
+            return
+        key = (
+            record["account"],
+            record["meter"],
+            period_of(config, record["ts"]),
+        )
+        if key in burst_keys and record["quantity"] > peaks.get(key, 0):
+            peaks[key] = record["quantity"]
+
+    tail_names = _plan_segments(store, last_seq)
+    tail_records: List[dict] = []
+    covered_from: Optional[Tuple[int, int]] = None  # (first_seq, first_at)
+    for name in tail_names:
+        records = [
+            json.loads(line) for line in store.read(name).splitlines()
+        ]
+        if covered_from is None and records:
+            covered_from = (records[0]["seq"], records[0]["at"])
+        stop = False
+        for record in records:
+            if record["seq"] <= last_seq:
+                collect(record)
+                continue
+            if record["at"] > as_of:
+                stop = True
+                break
+            tail_records.append(record)
+        if stop:
+            break
+
+    if burst_keys:
+        floor = min(period_start(config, p) for (_, _, p) in burst_keys)
+        proven = covered_from is not None and (
+            covered_from[0] == 1 or covered_from[1] < floor
+        )
+        older = [n for n in store.list(SEGMENT_PREFIX) if n not in tail_names]
+        for name in reversed(older):
+            if proven:
+                break
+            records = [
+                json.loads(line) for line in store.read(name).splitlines()
+            ]
+            for record in records:
+                collect(record)
+            proven = bool(records) and (
+                records[0]["seq"] == 1 or records[0]["at"] < floor
+            )
+        if not proven:
+            raise RewindUnsupported(
+                "burst-period history is no longer available"
+            )
+        for key, peak in peaks.items():
+            engine.open_usage._peaks[key] = peak
+
+    for record in tail_records:
+        _apply(engine, record)
+    return RewindView(engine, as_of, carried)
+
+
+def _apply(engine: MeterEngine, record: dict) -> None:
+    if record["kind"] == "usage":
+        engine.record_usage(
+            record["at"],
+            record["account"],
+            record["meter"],
+            record["quantity"],
+            record["ts"],
+        )
+    elif record["kind"] == "grant":
+        engine._advance(record["at"])
+        engine.grants.register(
+            CreditGrant(
+                record["grant_id"],
+                record["account"],
+                record["amount_cents"],
+                record["expires_at"],
+                record["at"],
+            )
+        )
+    else:
+        raise ValueError(f"unknown journal record kind: {record['kind']!r}")


Hidden acceptance tests (diff):
diff --git a/tests/hidden/test_hidden.py b/tests/hidden/test_hidden.py
new file mode 100644
--- /dev/null
+++ b/tests/hidden/test_hidden.py
@@ -0,0 +1,577 @@
+"""Hidden acceptance suite for point-in-time rewind (never agent-visible).
+
+Grades rewind() on exact equivalence to the live engine across the
+billing query surface, at many moments, under the artifact-store
+discipline the issue states: minimal billed reads, refusal when the
+store cannot determine the moment, no writes.
+
+v2 additions (task_version 2): demand pricing makes the open periods'
+peak single events monetarily significant, and the export folds peaks
+to a coarse class — exact reconstruction of a burst period requires
+recovering the peak from pre-snapshot journal records (the retention
+floor guarantees they exist), which also reshapes the minimal-read
+plan and the refusal conditions. Statements are export-windowed with
+a carried balance. Seeded sweeps run three configurations, including
+one where bursts are commonplace.
+"""
+
+import random
+
+import pytest
+
+from src import BlobStore, MeterConfig, MeterEngine
+from src import ops, snapshot
+from src.rewind import RewindUnsupported, rewind
+from src.snapshot import EXPORT_WINDOW
+
+
+def make_config():
+    return MeterConfig(
+        period_length=100,
+        lateness_allowance=20,
+        min_charge_cents=500,
+        schedule={
+            "requests": [(100, 50_000), (900, 30_000), (None, 10_000)],
+            "compute_ms": [(1000, 5_000), (None, 2_000)],
+        },
+        demand={"compute_ms": (2000, 8_000)},
+    )
+
+
+# -- equivalence harness -------------------------------------------------
+
+
+def capture_live(engine, now, accounts, grants, meters, max_period, floors):
+    """The full obligated surface, from the live engine at `now`.
+
+    ``floors`` maps account -> earliest statement period the artifacts
+    carry (statement detail is export-windowed); -1 means everything.
+    """
+    out = {}
+    for account in sorted(accounts):
+        floor = floors.get(account, -1)
+        out[account] = {
+            "balance_due": engine.balance_due(now, account),
+            "credits_remaining": engine.credits_remaining(now, account),
+            "accrued_micros": engine.accrued_micros(now, account),
+            "accrued_display_cents": engine.accrued_display_cents(now, account),
+            "open_periods": engine.open_periods(now, account),
+            "statements": [
+                s
+                for s in engine.statements_for(now, account)
+                if s.period >= floor
+            ],
+            "usage": {
+                (meter, period): engine.usage_totals(now, account, meter, period)
+                for meter in meters
+                for period in range(max(floor, 0), max_period + 1)
+            },
+        }
+    out["__grants__"] = {
+        gid: engine.grant_state(now, gid) for gid in sorted(grants)
+    }
+    return out
+
+
+def capture_view(view, accounts, grants, meters, max_period, floors):
+    out = {}
+    for account in sorted(accounts):
+        floor = floors.get(account, -1)
+        out[account] = {
+            "balance_due": view.balance_due(account),
+            "credits_remaining": view.credits_remaining(account),
+            "accrued_micros": view.accrued_micros(account),
+            "accrued_display_cents": view.accrued_display_cents(account),
+            "open_periods": view.open_periods(account),
+            "statements": view.statements_for(account),
+            "usage": {
+                (meter, period): view.usage_totals(account, meter, period)
+                for meter in meters
+                for period in range(max(floor, 0), max_period + 1)
+            },
+        }
+    out["__grants__"] = {gid: view.grant_state(gid) for gid in sorted(grants)}
+    return out
+
+
+class Scenario:
+    """Drives one live engine, snapshotting expectations at probe moments."""
+
+    def __init__(self, config=None, segment_size=4):
+        self.config = config or make_config()
+        self.store = BlobStore()
+        self.engine = MeterEngine(self.config, self.store, segment_size=segment_size)
+        self.accounts = set()
+        self.grants = []
+        self.meters = ("requests", "compute_ms")
+        self.expected = {}
+        self.floors = {}
+
+    def usage(self, now, account, meter, qty, ts):
+        self.accounts.add(account)
+        return self.engine.record_usage(now, account, meter, qty, ts)
+
+    def grant(self, now, account, cents, expires_at):
+        self.accounts.add(account)
+        gid = self.engine.grant_credit(now, account, cents, expires_at)
+        self.grants.append(gid)
+        return gid
+
+    def snap(self):
+        name = snapshot.write(self.engine)
+        # Statement detail available to a rewind is bounded by what the
+        # newest snapshot exported (later closes are all tail-visible).
+        for account in self.accounts:
+            rows = self.engine.ledger.for_account(account)
+            if len(rows) >= EXPORT_WINDOW:
+                self.floors[account] = rows[-EXPORT_WINDOW].period
+        return name
+
+    def prune(self, keep):
+        return ops.prune_journal(self.store, keep)
+
+    def probe(self, now):
+        accounts = sorted(self.accounts)
+        grants = list(self.grants)
+        floors = dict(self.floors)
+        max_period = now // self.config.period_length + 1
+        expected = capture_live(
+            self.engine, now, accounts, grants, self.meters, max_period, floors
+        )
+        self.expected[now] = (expected, accounts, grants, max_period, floors)
+
+    def check(self, as_of):
+        expected, accounts, grants, max_period, floors = self.expected[as_of]
+        view = rewind(self.config, self.store, as_of)
+        got = capture_view(view, accounts, grants, self.meters, max_period, floors)
+        assert got == expected, f"surface mismatch at as_of={as_of}"
+
+
+# -- the ladder: honest snapshot+tail replays ---------------------------
+
+
+def test_tail_replay_of_plain_usage():
+    s = Scenario()
+    s.usage(10, "acme", "requests", 40, 8)
+    s.snap()
+    s.usage(30, "acme", "requests", 10, 28)
+    s.usage(45, "beta", "requests", 20, 44)
+    s.probe(50)
+    s.usage(60, "acme", "requests", 5, 59)
+    s.check(50)
+
+
+def test_as_of_at_the_snapshot_moment():
+    s = Scenario()
+    s.usage(10, "acme", "requests", 40, 8)
+    s.usage(130, "acme", "requests", 60, 129)  # period 0 closes on the way
+    s.snap()
+    s.probe(130)
+    s.usage(200, "acme", "requests", 7, 199)
+    s.check(130)
+
+
+def test_simple_close_and_grant_in_tail():
+    s = Scenario()
+    s.usage(10, "acme", "requests", 100, 9)
+    s.snap()
+    s.grant(20, "acme", 400, 5_000)
+    s.usage(40, "acme", "requests", 30, 39)
+    s.probe(150)  # period 0 due at 120: closed, grant applies
+    s.usage(190, "acme", "requests", 2, 189)
+    s.check(150)
+
+
+# -- exactness: money is derived, and rounded exactly once ---------------
+
+
+def test_accrued_stays_exact_beyond_display_precision():
+    s = Scenario()
+    s.usage(10, "acme", "compute_ms", 3, 9)     # 15_000 micros = 1.5 cents
+    s.usage(20, "acme", "compute_ms", 4, 19)    # running: 35_000 micros
+    s.snap()
+    s.usage(40, "acme", "compute_ms", 9, 39)    # 80_000 micros in flight
+    s.probe(50)
+    s.check(50)
+
+
+def test_tail_usage_crosses_a_tier_boundary():
+    # The snapshot leaves "requests" 20 units below the first tier edge;
+    # the tail crosses it and the period then closes. The invoice must
+    # price the period total through the tiers, not add per-event costs
+    # onto an exported figure.
+    s = Scenario()
+    s.usage(10, "acme", "requests", 80, 9)
+    s.snap()
+    s.usage(50, "acme", "requests", 60, 49)     # total 140: spans two tiers
+    s.usage(70, "acme", "compute_ms", 7, 69)    # sub-cent tail accrual
+    s.probe(90)
+    s.probe(150)                                # after period 0 is due
+    s.check(90)
+    s.check(150)
+
+
+# -- credits: issuance order, due-moment participation -------------------
+
+
+def test_snapshot_scrambles_equal_expiry_grants():
+    # Two grants share an expiry; the export lists the larger remainder
+    # first, which is the opposite of their issuance order. Consumption
+    # in the tail must follow issuance order.
+    s = Scenario()
+    s.usage(10, "acme", "requests", 130, 9)     # 590 cents at close
+    s.grant(20, "acme", 200, 400)               # g1: small, issued first
+    s.grant(30, "acme", 900, 400)               # g2: large, same expiry
+    s.snap()
+    s.probe(140)                                # close consumed g1 fully first
+    s.usage(150, "acme", "requests", 1, 149)
+    s.probe(410)                                # both grants now expired
+    s.check(140)
+    s.check(410)
+
+
+def test_grant_issued_after_due_moment_stays_out():
+    # Period 0 is due at 120. The tail's grant arrives at 130, before
+    # anything caused the close to be processed — it must still never
+    # participate in period 0's invoice.
+    s = Scenario()
+    s.usage(10, "acme", "requests", 130, 9)
+    s.snap()
+    s.grant(130, "acme", 1000, 5_000)
+    s.probe(140)
+    s.check(140)
+
+
+def test_grant_expiring_between_due_and_asof_still_covers():
+    # The grant is alive at the due moment but expired by as_of. An
+    # implementation that closes everything "as of as_of" drops it.
+    s = Scenario()
+    s.usage(10, "acme", "requests", 130, 9)
+    s.grant(20, "acme", 700, 200)               # dies at 200; due is 120
+    s.snap()
+    s.usage(150, "acme", "requests", 1, 149)
+    s.probe(300)
+    s.check(300)
+
+
+def test_shared_grant_rolls_across_periods_in_order():
+    s = Scenario()
+    s.usage(10, "acme", "requests", 100, 9)          # p0: 500 cents
+    s.grant(15, "acme", 560, 10_000)
+    s.grant(16, "acme", 900, 10_000)                 # equal expiry, later issue
+    s.snap()
+    s.usage(150, "acme", "requests", 20, 149)        # p1: 100 cents
+    s.usage(260, "acme", "compute_ms", 400, 255)     # p2 usage
+    s.probe(560)                                     # p0..p4 all due by now
+    s.check(560)
+
+
+# -- idle accounts keep being invoiced -----------------------------------
+
+
+def test_idle_periods_invoice_at_minimum_with_credits():
+    s = Scenario()
+    s.usage(10, "acme", "requests", 1, 9)
+    s.grant(30, "acme", 1200, 10_000)
+    s.snap()
+    # No records at all after the snapshot; as_of is far in the future.
+    # Periods 0..3 are due by 450, each at the 500-cent minimum, and the
+    # grant must have been eaten in period order.
+    s.probe(450)
+    s.check(450)
+
+
+# -- boundaries ----------------------------------------------------------
+
+
+def test_same_tick_records_after_snapshot_are_included():
+    s = Scenario()
+    s.usage(10, "acme", "requests", 40, 9)
+    s.snap()                                    # taken_at 10
+    s.usage(10, "acme", "requests", 5, 10)      # same tick, after snapshot
+    s.probe(10)                                 # as_of == taken_at
+    s.usage(25, "beta", "requests", 8, 24)
+    s.probe(25)                                 # as_of == a record's arrival
+    s.usage(90, "acme", "requests", 1, 89)
+    s.check(10)
+    s.check(25)
+
+
+# -- demand: the peak is money, and the export only kept its class -------
+
+
+def test_burst_before_snapshot_prices_the_open_period():
+    # The burst arrived before the snapshot; the export records only
+    # "burst". The exact excess (2600 - 2000) is worth 480 cents of
+    # accrual and must be recovered from the retained journal — the
+    # bucket total (3400) is not the peak, and neither is zero.
+    s = Scenario(segment_size=3)
+    s.usage(10, "acme", "compute_ms", 2600, 9)
+    s.usage(15, "acme", "compute_ms", 800, 14)
+    s.usage(20, "acme", "requests", 30, 19)
+    s.snap()
+    s.usage(40, "acme", "compute_ms", 500, 39)  # raises total, not peak
+    s.probe(50)
+    s.check(50)
+
+
+def test_burst_peak_survives_the_period_close_in_tail():
+    # Same shape, but as_of lands after the period's due moment: the
+    # recovered peak must reach the frozen statement's demand line.
+    s = Scenario(segment_size=3)
+    s.usage(10, "acme", "compute_ms", 2600, 9)
+    s.usage(15, "acme", "compute_ms", 800, 14)
+    s.snap()
+    s.usage(40, "acme", "compute_ms", 900, 39)
+    s.probe(150)
+    s.check(150)
+
+
+def test_a_sum_of_small_events_is_not_a_peak():
+    # Two calm events whose total tops the demand threshold: the
+    # export rightly says "normal", and no demand may be charged —
+    # at the probe or on the eventual invoice.
+    s = Scenario(segment_size=3)
+    s.usage(10, "acme", "compute_ms", 1200, 9)
+    s.usage(20, "acme", "compute_ms", 900, 19)
+    s.snap()
+    s.usage(40, "acme", "compute_ms", 300, 39)
+    s.probe(50)
+    s.probe(150)
+    s.check(50)
+    s.check(150)
+
+
+def test_tail_burst_needs_no_history():
+    # The burst itself is in the tail: everything needed is in plain
+    # sight, but the demand line must still be billed and accrued.
+    s = Scenario(segment_size=3)
+    s.usage(10, "acme", "compute_ms", 300, 9)
+    s.snap()
+    s.usage(30, "acme", "compute_ms", 2900, 29)
+    s.probe(60)
+    s.probe(160)
+    s.check(60)
+    s.check(160)
+
+
+# -- windowed statement detail and the carried balance -------------------
+
+
+def test_windowed_statements_and_carried_balance():
+    s = Scenario()
+    s.usage(10, "acme", "requests", 200, 9)
+    s.grant(20, "acme", 700, 100_000)
+    # March the clock through nine closed periods, then snapshot.
+    s.usage(950, "acme", "requests", 90, 949)   # closes p0..p8 on arrival
+    s.snap()
+    s.usage(970, "acme", "compute_ms", 60, 969)
+    s.probe(990)
+    s.usage(1180, "acme", "requests", 4, 1179)  # p9 closes in the tail
+    s.probe(1190)
+    s.check(990)
+    s.check(1190)
+
+
+# -- honesty: refuse when the store cannot support the moment ------------
+
+
+def test_refuses_before_first_snapshot():
+    s = Scenario()
+    s.usage(10, "acme", "requests", 40, 9)
+    s.usage(30, "acme", "requests", 10, 29)
+    s.snap()                                    # taken_at 30
+    s.usage(50, "acme", "requests", 5, 49)
+    s.probe(50)
+    with pytest.raises(RewindUnsupported):
+        rewind(s.config, s.store, 20)
+    s.check(50)                                 # supported moments still work
+
+
+def test_refuses_on_pruned_gap_but_serves_newer_snapshots():
+    s = Scenario(segment_size=2)
+    s.usage(10, "acme", "requests", 40, 9)
+    s.usage(20, "acme", "requests", 10, 19)
+    s.snap()                                    # sn @ seq 2, taken_at 20
+    s.usage(30, "acme", "requests", 5, 29)
+    s.usage(40, "acme", "requests", 5, 39)
+    s.usage(50, "acme", "requests", 5, 49)
+    s.usage(60, "acme", "requests", 5, 59)
+    s.snap()                                    # sn @ seq 6, taken_at 60
+    s.usage(70, "acme", "requests", 5, 69)
+    s.probe(75)
+    s.prune(keep=2)                             # seqs 3..4 are gone
+    # Moments needing the old snapshot's tail are no longer supported.
+    with pytest.raises(RewindUnsupported):
+        rewind(s.config, s.store, 45)
+    # Moments covered by the newer snapshot still are.
+    s.check(75)
+
+
+def test_refuses_when_burst_history_is_gone():
+    # A burst period's exact peak lives only in the journal; a store
+    # pruned below the retention floor cannot price it, and a rewind
+    # must say so instead of guessing from the class.
+    s = Scenario(segment_size=2)
+    for i in range(6):                          # seqs 1..6: old, calm periods
+        s.usage(5 + i, "acme", "requests", 10, 4 + i)
+    s.usage(210, "acme", "compute_ms", 2600, 209)   # seq 7: the burst
+    s.usage(212, "acme", "compute_ms", 100, 211)    # seq 8
+    s.snap()                                    # burst period still open
+    s.usage(220, "acme", "compute_ms", 50, 219)     # seq 9: tail
+    s.probe(230)
+    s.check(230)                                # intact store: answerable
+    s.prune(keep=1)                             # cuts below the burst events
+    with pytest.raises(RewindUnsupported):
+        rewind(s.config, s.store, 230)
+
+
+# -- read discipline -----------------------------------------------------
+
+
+class AuditedStore(BlobStore):
+    def __init__(self):
+        super().__init__()
+        self.read_names = []
+
+    def read(self, name):
+        self.read_names.append(name)
+        return super().read(name)
+
+
+def audited_scenario(segment_size):
+    s = Scenario(segment_size=segment_size)
+    audited = AuditedStore()
+    s.store = audited
+    s.engine = MeterEngine(s.config, audited, segment_size=segment_size)
+    return s, audited
+
+
+def test_reads_one_snapshot_and_only_the_tail():
+    # No burst anywhere: nothing before the snapshot is needed, and the
+    # pre-snapshot segments are off-limits bait.
+    s, audited = audited_scenario(segment_size=2)
+    for i in range(8):                          # seqs 1..8, four segments
+        s.usage(10 + 10 * i, "acme", "requests", 10, 9 + 10 * i)
+    right = s.snap()                            # covers through seq 8
+    s.usage(95, "acme", "requests", 3, 94)      # seq 9
+    s.usage(96, "beta", "compute_ms", 30, 95)   # seq 10
+    s.probe(97)
+    s.usage(120, "acme", "requests", 1, 119)    # seq 11, after the moment
+    s.snap()                                    # decoy: too new for as_of=97
+
+    audited.read_names.clear()
+    audited.reset_meters()
+    s.check(97)
+
+    snapshot_reads = [n for n in audited.read_names if n.startswith("snapshots/")]
+    segment_reads = {n for n in audited.read_names if n.startswith("journal/")}
+    assert snapshot_reads == [right], "must open exactly the covering snapshot"
+    assert all(n >= "journal/seg-00000009" for n in segment_reads), (
+        f"read pre-snapshot journal history: {sorted(segment_reads)}"
+    )
+    assert audited.writes == 0, "rewind must not write to the artifact store"
+
+
+def test_burst_backfill_reads_stay_within_the_boundary():
+    # A burst period makes some pre-snapshot journal necessary — but
+    # only back to the start of the oldest open period. Older segments
+    # remain off-limits.
+    s, audited = audited_scenario(segment_size=2)
+    for i in range(6):                          # seqs 1..6: arrivals 5..10
+        s.usage(5 + i, "acme", "requests", 10, 4 + i)   # old, closed by 210
+    s.usage(205, "acme", "compute_ms", 2600, 204)   # seq 7: burst, period 2
+    s.usage(207, "acme", "compute_ms", 300, 206)    # seq 8: same bucket
+    right = s.snap()                            # taken_at 207
+    s.usage(215, "acme", "compute_ms", 40, 214)     # seq 9: tail
+    s.probe(216)
+
+    audited.read_names.clear()
+    audited.reset_meters()
+    s.check(216)
+
+    snapshot_reads = [n for n in audited.read_names if n.startswith("snapshots/")]
+    segment_reads = {n for n in audited.read_names if n.startswith("journal/")}
+    assert snapshot_reads == [right], "must open exactly the covering snapshot"
+    # The burst period starts at 200. seg-7 (arrivals 205, 207) is real
+    # backfill; seg-5 (arrivals 9, 10) is the boundary proof that the
+    # walk-back is complete. seg-1 and seg-3 are off-limits bait.
+    assert all(n >= "journal/seg-00000005" for n in segment_reads), (
+        f"read beyond the backfill boundary: {sorted(segment_reads)}"
+    )
+    assert audited.writes == 0, "rewind must not write to the artifact store"
+
+
+# -- generality: seeded metamorphic sweeps -------------------------------
+
+
+def flat_config():
+    return MeterConfig(
+        period_length=100,
+        lateness_allowance=20,
+        min_charge_cents=0,
+        schedule={
+            "requests": [(None, 12_000)],
+            "compute_ms": [(None, 3_000)],
+        },
+    )
+
+
+def spiky_config():
+    return MeterConfig(
+        period_length=50,
+        lateness_allowance=1,
+        min_charge_cents=200,
+        schedule={
+            "requests": [(100, 50_000), (None, 10_000)],
+            "compute_ms": [(1000, 5_000), (None, 2_000)],
+        },
+        demand={"compute_ms": (100, 8_000), "requests": (350, 2_000)},
+    )
+
+
+def drive_seeded(seed, config, grant_heavy):
+    rng = random.Random(seed)
+    s = Scenario(config=config, segment_size=3)
+    now = 5
+    accounts = ["acme", "beta", "gamma"]
+    probes = []
+    max_late = 0 if config.lateness_allowance <= 1 else 29
+    for step in range(46):
+        now += rng.randrange(1, 14)
+        account = rng.choice(accounts)
+        roll = rng.random()
+        if roll < (0.35 if grant_heavy else 0.15):
+            expiry = now + rng.choice([37, 90, 90, 210, 480])
+            s.grant(now, account, rng.randrange(1, 30) * 25, expiry)
+            if grant_heavy and rng.random() < 0.4:
+                # sibling grant: same expiry, different size
+                s.grant(now, account, rng.randrange(1, 30) * 25, expiry)
+        else:
+            meter = rng.choice(["requests", "requests", "compute_ms"])
+            qty = rng.choice([1, 3, 7, 20, 45, 120, 400])
+            lateness = rng.randrange(0, max_late + 1)
+            s.usage(now, account, meter, qty, now - lateness)
+        if step in (14, 29, 41):
+            s.snap()
+        if step in (20, 34, 44):
+            probe_at = now + rng.randrange(0, 9)
+            probes.append(probe_at)
+            s.probe(probe_at)
+            now = max(now, probe_at)  # keep the arrival clock monotonic
+    return s, probes
+
+
+@pytest.mark.parametrize(
+    "seed,config,grant_heavy",
+    [
+        (11, make_config(), False),
+        (23, make_config(), True),
+        (37, flat_config(), False),
+        (53, spiky_config(), True),
+    ],
+)
+def test_seeded_histories_match_at_every_probe(seed, config, grant_heavy):
+    s, probes = drive_seeded(seed, config, grant_heavy)
+    for as_of in probes:
+        s.check(as_of)