-
Notifications
You must be signed in to change notification settings - Fork 5
feat(reporting): persist scheduled provisional observations #1221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
826d007
feat(reporting): persist scheduled provisional observations
bokelley 8388b48
fix(reporting): validate provisional storage and compose publishers
bokelley d0ec796
fix(reporting): use protocol stub bodies
bokelley c6de2ce
test(reporting): await late-account turn and scheduled reread
bokelley f3c094c
ci(reporting): allow core Postgres lane to finish installed cases
bokelley 69050a3
ci(reporting): allow installed PostgreSQL contract to finish
bokelley 91d0f04
ci(reporting): allow coverage matrix to finish
bokelley d276714
ci(reporting): preserve coverage timeout headroom
bokelley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| # Provisional observations and upgrade status | ||
|
|
||
| The producer records every successful scheduled provisional read as a new | ||
| immutable revision, including a read whose rows are unchanged. A durable | ||
| reservation freezes the acquisition before source work starts. Its successful | ||
| observation, revision rows, ledger change, notifications and next checkpoint | ||
| commit together. A retry of the same reserved acquisition returns the existing | ||
| revision instead of appending another observation. | ||
|
|
||
| This is a producer and persistence change. Complete service rollout qualification | ||
| is still pending, including activation of previously retired work, source-object | ||
| reuse, installed-package checks and independent integration review. Follow the | ||
| [production guide](reporting-production.md) and | ||
| [migration guide](reporting-ledger-migration.md) for the surrounding deployment gates. | ||
|
|
||
| ## Scheduling | ||
|
|
||
| - An offering without `restatement_window` uses an SDK fallback of 72 elapsed | ||
| hours after the reporting period ends. An explicit offering window continues | ||
| to apply. | ||
| - The first reserved acquisition freezes the window and cadence. Cadence is the | ||
| explicit `restatement_cadence`, or the greater of the reporting-period duration | ||
| and `fastest_safe_cadence`. Later successful reads retain that policy. | ||
| - A successful read anchors the next due time at its checked/acquired instant | ||
| plus cadence, capped at the resolved provisional boundary. After downtime, | ||
| one due read runs; missed intervals do not create a burst of catch-up reads. | ||
| - The adapter may return typed `InlineFetchResult.provisional_until` evidence to | ||
| shorten or extend the boundary. It must be timezone-aware and at least the | ||
| source observation time. Invalid evidence commits no revision or checkpoint. | ||
| If evidence is absent, the frozen offering window or SDK fallback applies. | ||
| - Snapshot-only reporting retains one final inclusive read at the boundary, | ||
| including after downtime. Valid new source evidence may extend that boundary. | ||
| Expiry does not create an official revision. | ||
| - Existing explicitly configured official-close behavior remains in force. | ||
| `official_close_lag` still requires an explicitly declared `restatement_window` | ||
| and an official offering. The SDK fallback does not opt an adapter into | ||
| automatic official publication. | ||
|
|
||
| The durable acquisition freezes the source request, predecessor revision and | ||
| observation ordinal. A retry renews only `deadline_at`, the execution budget for | ||
| that attempt. It retains the execution key, run ID, cutoff, scope and remaining | ||
| request fields even after the original deadline expires. Once reserved, a | ||
| successful retry completes that observation before a later acquisition starts. | ||
|
|
||
| ## Persistence and compatibility | ||
|
|
||
| `PgReportingLedgerStore.create_schema()` installs two additive private tables: | ||
| `reporting_provisional_acquisitions` and `reporting_provisional_observations`. | ||
| They retain immutable reservation and observation metadata. The existing | ||
| restatement checkpoint remains the progress cursor. The in-memory store has the | ||
| same atomic operations. Custom producer stores must implement | ||
| `ProvisionalObservationStore` from `adcp.reporting.ledger.provisional` together | ||
| with `RestatementCheckpointStore`. | ||
|
|
||
| The packaged `reporting/ledger/required_provisional_schema.json` describes all | ||
| 27 objects added by this extension. Bootstrap and each reservation, observation | ||
| read, and atomic observation commit validate those objects, including immutable | ||
| triggers and their function definitions. Validation also runs with notifications | ||
| disabled. Missing, disabled or changed objects fail closed with | ||
| `PROVISIONAL_SCHEMA_UNREADY`; reinstall or repair the schema before resuming | ||
| production. Notification readiness alone does not establish observation readiness: | ||
| historical notification-only schemas remain valid for their original operations. | ||
|
|
||
| Existing revision tables, exact-read row layout and pinned migration manifests | ||
| are unchanged. Each observation has its own revision ID and revision-specific | ||
| wire content hash. Unchanged source content retains its source content identity; | ||
| it does not mean that the revision ID or wire hash is reused. Historical readers | ||
| can still read rows directly from `reporting_revision_rows` for every revision. | ||
|
|
||
| This change retains per-revision row payloads. Content-addressed reuse of staged | ||
| source objects is a separate pending change; complete payload deduplication has | ||
| not been accepted. Do not interpret immutable observation support as completion | ||
| of the full no-duplicate-payload requirement. | ||
|
|
||
| Stop old producer writers before switching scheduling behavior. Old binaries do | ||
| not write the new observation metadata, even though they can still read the | ||
| existing revision rows. Historical-binary rolling tests and full service | ||
| qualification must pass before claiming an upgrade is supported. | ||
|
|
||
| ## Custom stores and publication decorators | ||
|
|
||
| Declare the checkpoint and observation methods explicitly on the store class. | ||
| Dynamic `__getattr__` forwarding does not enable these optional capabilities. | ||
| The producer checks declarations consistently across supported Python versions; | ||
| an incomplete adapter raises `RESTATEMENT_CHECKPOINTS_NOT_SUPPORTED` or | ||
| `PROVISIONAL_OBSERVATIONS_NOT_SUPPORTED` before source acquisition. It does not | ||
| fall back to separate revision and checkpoint writes. | ||
|
|
||
| A decorator that prepares revisions in `commit_revision` must also prepare them | ||
| in `commit_provisional_observation`. Explicitly compose all three observation | ||
| methods (`reserve_provisional_acquisition`, `get_provisional_observation`, | ||
| `commit_provisional_observation`) and both checkpoint methods | ||
| (`get_restatement_checkpoint`, `record_restatement_checkpoint`). Reuse the same | ||
| revision preparation, validation and fault boundaries around the delegated atomic | ||
| commit. Calling ordinary `commit_revision` first would break the observation's | ||
| transaction boundary; forwarding the new method without preparation would bypass | ||
| the decorator's publication rules. | ||
|
|
||
| An `OBSERVATION_CONFLICT` fails only the affected slice for that worker turn. Its | ||
| pending work remains eligible for a later retry while sibling slices continue. | ||
| Schema or capability failures stop the turn so the operator can repair the store. | ||
|
|
||
| ## Pending scheduling qualification | ||
|
|
||
| The production progress queue still selects bounded pending work before checking | ||
| its observation due time. During the new provisional window, future-due work can | ||
| therefore consume turn capacity that would otherwise acquire newly closed periods. | ||
| Durable due-time selection and its fairness tests remain a service activation | ||
| requirement; the default window is not a claim of completed production rollout. | ||
|
|
||
| Successful observations retain their next cadence checkpoint. Failed or not-ready | ||
| attempts retain their acquisition identity, but do not yet persist an independent | ||
| retry time. Repeated worker turns can retry that acquisition before its source's | ||
| safe cadence. Restart-safe retry scheduling remains required before full rollout; | ||
| an in-process delay alone would not complete that requirement. | ||
|
|
||
| ## Previously retired work: activation requirement remains open | ||
|
|
||
| A deployment that already processed a snapshot with no declared window may have | ||
| retired that obligation under the old one-shot policy. Installing this schema or | ||
| restarting a producer does **not** re-enroll it. Until the activation migration | ||
| below is implemented and verified, the new fallback applies to eligible pending | ||
| work; it is not a complete upgrade path for existing deployments. | ||
|
|
||
| The affected progress APIs are `ReportingProducerProgress.next_producer_obligations`, | ||
| `finish_producer_acquisition`, `commit_producer_period` and | ||
| `producer_closed_through`. PostgreSQL retains retired rows as `state='settled'` | ||
| in `reporting_production_source_work`; `reporting_production_source_progress` | ||
| retains the generation's `closed_through` and acquisition turn. The in-memory | ||
| equivalents are `_production_source_work`, `_production_closed` and | ||
| `_production_source_turns`. Selection currently considers only pending work. | ||
|
|
||
| Existing configuration enrollment installs generation/source/destination | ||
| bindings, without changing a retained work item's state. Repeated production | ||
| activation returns without re-enrollment. Creating a new configuration generation | ||
| would change the frozen reporting identity and is not a repair for an old | ||
| obligation. Rewinding `closed_through` is also unsupported. | ||
|
|
||
| The activation owner must provide an explicit disposition for each affected | ||
| deployment. A candidate bounded migration starts with an admitted account and | ||
| configuration generation, selects settled snapshot obligations in a recorded | ||
| period-end range, excludes official publications, and resumes eligible work | ||
| without changing the obligation or its retained policy evidence. It must also | ||
| define the treatment of already-expired obligations and source overrides; a | ||
| simple three-day filter alone cannot establish eligibility for every legacy | ||
| record. Missing policy evidence needs an explicit upgrade decision. | ||
|
|
||
| An indexed selection can use | ||
| `(account_id, delivery_config_id, delivery_config_version, period_end, | ||
| reporting_obligation_id) WHERE state='settled'`, a bounded period range and | ||
| keyset batches. The current pending-only index does not cover this selection. | ||
| Any index and re-enrollment operation belong in an additive migration, under the | ||
| existing account/generation lock, with idempotent progress and preserved | ||
| acquisition turns. Do not scan all history on each worker turn or change the | ||
| separate delivery queues to trigger acquisition. | ||
|
|
||
| The required regression starts with an old writer completing a no-window | ||
| snapshot, leaving its work row settled. Upgrade while the intended provisional | ||
| boundary is still open, activate/configure using the same frozen generation, | ||
| advance one cadence, and require a second immutable snapshot with the original | ||
| revision still readable. Current activation does not satisfy that regression. | ||
| The completed migration must also cover official, expired, parked, foreign-account | ||
| and interrupted/resumed batches without changing unrelated work. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.