Skip to content

feat(reporting): materialize revisions with durable fenced work - #1187

Merged
bokelley merged 9 commits into
mainfrom
conductor/1167b2-durable-managed-reporting
Sep 24, 2026
Merged

bokelley merged 9 commits into
mainfrom
conductor/1167b2-durable-managed-reporting

Conversation

@bokelley

@bokelley bokelley commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Durable materializer workers reserve one persistent external identity, resume it after process loss, and commit verified outcomes with captured status inputs and fenced acknowledgment. This is B2.1 of the reporting rollout. It does not activate Managed/Reconciled or delivery-ready advertisement.

Composition and preserved boundaries

  • Integrates original 8e18ca12b9a0c3750f80aa822058c02982ab3e52 with actual main 5487f2bdef23c5102118b305be9e868228f6ce61 through ordinary two-parent composition af549d76eac9f7b2f548d4c8bbb3b4b51868b91e, followed by sole-parent corrections. No rebase, squash, reset, or remote rewrite.
  • All 36 original feature paths remain. The integrated rc.6 schemas, waiver bindings, producer ownership guard, and service correction are retained. B464, C249 and B1 selector10 manifests are unchanged; the materializer's 187-object manifest is unchanged from the feature. No fingerprint regeneration.
  • Materializer admission remains epoch0 and its logical events/expansions remain quarantined. Retained work cannot be promoted or replayed as production-admitted work. Core advertisement also remains closed for the new materializer store; ordinary polling remains available. Activation and later receipt/feed stages remain separate.
  • Long destination I/O stays outside database transactions. Account locks, lease fences, current authorization, original external identity, and atomic finish remain load-bearing. Memory rollback applies with notifications both on and off. Public outcomes still dirty status projection even when readiness notification emission is suppressed. Caller usage errors remain distinct from closed internal-failure diagnostics.

Integration corrections

The required Postgres conformance tests (Postgres 16) rollup now depends on the existing PG matrix, status job, and materializer job. It always runs and accepts only three exact success results; failure, cancellation, skipped, or empty results fail. The new materializer job has contents: read; the gate retains permissions: {}. There are 20 expanded CI jobs. Core/process/status remain at 15 minutes, the inherited materializer job/pytest budgets remain 35/30 minutes, and Python retains its inherited 60/45-minute budgets. No ceiling was raised during this integration.

Core excludes exactly the four complete materializer files selected by the materializer job, in addition to main's existing process/status exclusions. URL-present collection accounts for 2274 cases as 1969 core + 7 process + 251 status + 47 materializer, disjoint with an exact union. Whole-file dispatch maintains the partition when cases are added.

Frozen A/B/C/B1 controls now use integrated commits 17ee407a, 0f34c666, 967b6e28, and 5487f2bd; the three earlier foundation pins are unchanged. Removed obsolete C-only fixture imports and service initialization flags. The frozen helper still requires PostgreSQL 16 and UTF8 and records its actual locale. Full installed binary provenance, readiness classification, old worker behavior, and immutable-object comparisons remain required.

A real scheduler defect surfaced under libc en_US.utf8: selecting served_at::text creates an output alias named served_at, so unqualified ORDER BY served_at sorted text instead of timestamps. Under that locale, a just-served account sorted ahead of -infinity and repeatedly displaced a never-served account. A same-server PostgreSQL 16.14 C/libc discriminator reproduces the difference. Both sampling queries now explicitly order the underlying timestamp column, preserving serialized cursor values and the timestamp seek predicate. No DDL, index, deadline, or fingerprint changes.

The populated migration fixture now requires the exact prior B464 catalog plus the ten rc.6 waiver-binding objects, and still compares every original object before/after installation. It does not permit arbitrary extra objects. Separately, 59 operational awaits were moved immediately before their assertions, including one embedded subprocess-source site. Complete inverse AST reconstruction preserves calls, arguments, expectations, and normal execution order; no conditional operand was promoted. Two SQL literal groupings and four explanatory handler comments preserve AST/constants.

Validation and limits

Exact final head 8ea0dbe46aafa356a40bb256dbb97316487c2468:

  • CPython 3.12.14 / PostgreSQL 16.14 on libc en_US.utf8: 543 passed, 0 skipped in 545.28s pytest, under the unchanged 900s local ceiling. This includes native materializer migration, transaction, process-crash, service, and ledger controls.
  • C and ICU en-US: 21/21 migration/catalog controls each (27.34s /28.15s), with 910/910 frozen entries matching. Final libc catalog read also matches 910/910; all strict validators pass, with no fingerprint regeneration.
  • Before the two-query timestamp repair, the base selection at 2fed6422 was 427 passed/116 intended PostgreSQL skips. That is retained as its own result; the final PostgreSQL selection includes the memory cases.
  • Required gate's actual shell was checked against 216 combinations of success/failure/cancelled/skipped/empty/unset results. Exactly the all-success combination passes. This is a local shell check, not a workflow run.

The original composition's two obsolete-helper collection errors, the first libc run's 3 failed/540 passed result, the C discriminator's 2 failed/1 passed result, and the isolated failing backfill probe are retained. They are not relabeled or replaced by later passes.

Normal commit hooks passed. Complete installed VCS/sdist and frozen-wheel controls were not run locally: the available filesystem cannot accommodate the full historical source archives and isolated builds. Fresh exact-head CI must provide those controls and whole-job lifecycle evidence. No earlier PR's local tests, CI, source approval, or scanner acceptance transfers here.

Secret scanning is separate from source approval. A fresh exact-head full-content/history Gitleaks scan is retained with its complete coverage and individual match adjudications; it is not an accepted GitGuardian substitute for this PR. Fresh GitGuardian and genuine CodeQL output must be assessed independently of check conclusions.

Rolling qualification excludes pre-17ee407a A, pre-0f34c666 B, pre-967b6e28 C, and pre-5487f2bd B1 binaries. These limits must accompany release notes. A's aggregate readiness after C remains intentionally closed while supported ordinary operations remain covered. Historical per-capability catalog cost (9rLB, no #1191 credit), inherited fail-closed snapshot invalidation, the #1199 execution hold, and the TS diagnostic hold remain unchanged. Guard #1201 stays draft/LAST and the legacy publishing workflow remains disabled. Source integration does not qualify artifacts, release publication, later rollout stages, or cross-runtime behavior.

B2.1 of 4 within B2 of B1/B2. Preserve the coherent reserve, verified I/O, captured finish and logical enqueue transaction unit; keep production activation gated on the remaining seller slices.

Refs #1167
Guard writer metadata and custom-store reservation failures, keep component reprs private, and retain bounded artifact setup and cleanup headroom. Refs #1167.

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve.

Reviewed PR #1187 (+7103/−109), an additive, isolated durable reporting-materializer unit adding materializer/{pg,memory,service,work,capture,schema,_errors}.py, a 420-line isolated SQL migration, and additive delivery/store refactors. The reviewer read the largest files (pg.py 955L, memory.py 670L, the SQL, service/verification/capture) and verified fenced ACK atomicity, DB-level work-guard invariants, deadline-bounded I/O, notification quarantine, the process-local verification seal, and the correct additive feat(reporting) semver signal.

No Critical/High/Medium findings; no gate-weakening; no public-surface breaks. high_risk is false, gated_paths is false, no no-auto-approve team match, and no prior decision. Decision table rows 1–8 do not fire → row 9 approve.

bokelley and others added 2 commits September 17, 2026 05:54
The materializer stores suppress `materialization_event` for every
`ReportingMaterializationRecord` so only a fenced verified finish can enqueue a
readiness intent. That single `notify` flag also gated `delivery_dirty`, so an
ordinary public `commit_materialization` through `InMemoryReportingMaterializerStore`
or `PgReportingMaterializerStore` stopped marking the status scope dirty. The
projector then never saw the outcome and the projected status stayed stale
indefinitely, losing the public persistence compatibility the parent kept.

Split the connection-bound and memory commit primitives into independent
`notify` and `dirty` flags. Reservation still writes its delivery record and
attempt without either, and the finish transaction still owns its own captured
dirty work, so the atomic finish boundary is unchanged.

`test_public_terminal_outcome_keeps_projection_dirty_work_without_readiness`
asserts the restored `materialization` dirty evidence with no readiness in the
materializer or the ordinary queue, and
`test_reserved_attempt_keeps_the_finish_transaction_as_the_only_dirty_work`
pins reservation as the only path that stays clean. Both run on memory and real
PostgreSQL and fail on the previous behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ects

`materializer_errors` closes every non-writer exception into
`ReportingWriterError("RESOURCE_UNAVAILABLE", "same_identity", "unknown")` so a
driver, hook or provider message can never become a diagnostic. It also caught
the SDK's own argument validators, so `claim_materialization(lease_seconds=1)`
and `read_materializer_boundaries(after=-1)` reported an unknown external
effect: the adopter is told a destination write may have started, and loses the
message naming the bound they violated.

Raise those two fixed-message validations as `ReportingMaterializerUsageError`,
a `ValueError` subclass, and re-raise it from the guard. Existing `except
ValueError` handling is unchanged, arbitrary internal failures stay closed, and
`_LeaseHeartbeat` still treats any renewal failure as a lost lease.

`test_invalid_caller_arguments_stay_actionable_and_claim_no_external_effect`
covers both bounds on memory and real PostgreSQL, asserting the actionable
message, the absence of a writer failure record, an unchanged store image and
no write effect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve.

Clean pass on the delta. No critical/high/medium findings reported. The change correctly decouples projection-dirty work from the readiness-notify event so ordinary public materialization outcomes keep marking the status scope dirty (fixing a silent projector stall), while claim/finish paths pass dirty=False to avoid double-dirty. The new ReportingMaterializerUsageError keeps caller-argument rejections as plain ValueErrors rather than phantom external-effect failures. Both changes land with conformance tests on both backends.

Gate checks:

  • No no-auto-approve team match.
  • gated_paths: false; review_decision: APPROVED (gate satisfied regardless).
  • high_risk: false.
  • Prior decision was approve with no findings — this run remains clean.

No rows 1–8 fire → row 9 approve.

`advertised_notifications` matches the ledger by exact `type(...)`, so a
materializer store is refused outright rather than only being denied the
Managed/Reconciled claims. A deployment advertising Core
`reporting.ledger_changed` today through `PgReportingReconciliationStore` gets a
closed `notification_chain_unready` after switching to
`PgReportingMaterializerStore`. That is correct and fail closed — B2.4 admits
these stores only once it proves projection, component and mount readiness — but
the rollout doc said only that the helpers "retain C's Managed/Reconciled veto",
which reads as if Core advertisement survives.

Say what actually happens, give adopters the two supported options, and note
that polling is unaffected because this helper gates notification capabilities
only. No production identity set is widened.

`test_materializer_store_closes_core_advertisement_until_b24_admits_it` pins
both halves on memory and real PostgreSQL: the reviewed store still advertises
`reporting.ledger_changed`, and the materializer store raises. B2.4 has to
change this deliberately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Sep 17, 2026

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — clean pass, no blocking or medium findings.

Checked: no critical/high/medium findings surfaced. gated_paths is false, review_decision is APPROVED, high_risk is false, and no no-auto-approve team match. Prior decision was also approve, so this is a net-neutral iteration.

Delta since prior approve is two defensive additions: a docs clarification (adopting a materializer store closes notification advertisement entirely, not only Managed/Reconciled) and a new conformance test pinning that both materializer stores lose Core reporting.ledger_changed. Reviewer verified the mechanism against _capabilities.py exact-type gating and confirmed the test raises correctly on both backends — fail-closed and accurate.

Decision table: rows 1–8 do not fire (no critical/high, gate satisfied, no deletions, no medium findings, no team gate, prior was approve not escalate). Falls through to row 9 → approve.

Base automatically changed from conductor/1167b1-materializer-contracts to main September 24, 2026 14:20
@bokelley
bokelley dismissed aao-secretariat[bot]’s stale review September 24, 2026 14:20

The base branch was changed.

Comment thread src/adcp/reporting/materializer/work.py
Comment thread src/adcp/reporting/materializer/work.py
Comment thread src/adcp/reporting/materializer/work.py
Comment thread src/adcp/reporting/materializer/work.py
Comment thread tests/conformance/reporting/_materializer_process.py

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — subsequent pass on PR #1187 (prior outcome: clean approve at 8e18ca1).

The delta since the prior approve is three commits (compose with integrated main, qualify against integrated baselines, order accounts by native timestamps). The reviewer verified: the ORDER BY served_at::text alias fix in pg.py is correct, the CI required-gate/lane matrix in ci.yml is fail-closed and partitions cleanly, and the C-collation pin removal converges to already-reviewed main.

No Critical/High/Medium findings surfaced. Decision-table walkthrough: no critical/high (row 1 no); gated_paths false (row 2 no); high_risk false so no deleted/modified sensitive-path triggers (rows 3/5 no); no medium findings at all (rows 4/8 no); prior decision was approve, not escalate (row 6 no); no no-auto-approve team match (row 7 no). Falls through to row 9 → approve.

Note: review_decision is REVIEW_REQUIRED, but with gated_paths false this does not force escalation — the row 2 gate only fires when gated_paths is true.

@bokelley
bokelley merged commit 3fd6212 into main Sep 24, 2026
31 checks passed
@bokelley
bokelley deleted the conductor/1167b2-durable-managed-reporting branch September 24, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant