Skip to content

perf(ri): make owner read paths bounded and fast - #63

Merged
tnunamak merged 34 commits into
mainfrom
perf/terminal-owner-read-paths
Jul 31, 2026
Merged

perf(ri): make owner read paths bounded and fast#63
tnunamak merged 34 commits into
mainfrom
perf/terminal-owner-read-paths

Conversation

@tnunamak

Copy link
Copy Markdown
Contributor

Summary

  • Batch connector-summary reads and scope historical aggregates to the requested page.
  • Store one run-history projection for scheduled, manual, browser, and cancelled runs, then use it for owner list reads.
  • Make Explore partition discovery index-backed and define snapshot_at as the stable first-page capture time.
  • Document Explore as an RI extension and add parity, migration, concurrency, and PostgreSQL regression coverage.

Why

The owner pages repeated fleet-wide SQL and event reconstruction. On a large PostgreSQL instance, Overview, Sources, Syncs, and Explore took several seconds. This change removes that repeated work without adding a cache or a second read authority.

Validation

  • Deployed exact SHA 8d3d563bb65a75e5364409150ce12abd3e0800b1 to the reference instance.
  • Strict fleet audit passed for all 20 connections.
  • All 12 schedules were restored exactly after deployment.
  • Authenticated 12-sample medians: Overview 210 ms, Sources 192 ms, Add Source 19 ms, Explore 56 ms, Syncs 233 ms, Grants 23 ms, Connect 7 ms, Search 6 ms.
  • Explore improved from 1,114 ms to 56 ms p50. Page 1 and cursor-resumed page 2 returned the same capture timestamp.
  • Focused real-PostgreSQL Explore tests passed 40/40. TypeScript, Biome, strict OpenSpec, test-accounting inventory, and pre-push checks passed.
  • Fable issued FINAL LAND on the terminal architecture. A separate implementation gate issued LAND on the final Explore batch.

Risk

The main migration risk is an interrupted deployment where both legacy scheduler_run_history and new run_history contain rows. The migration now reconciles that state losslessly and handles duplicate composite identities on SQLite and PostgreSQL. The live deployment exercised this migration before this final batch.

Upcoming partition fan-in is capped at four workers on the shared default 10-connection PostgreSQL pool. Live calibration showed that four workers met the latency target without increasing representative writer latency.

During calibration, one delegated worker briefly inserted uniquely prefixed synthetic run_history rows into the live database despite a read-only instruction. It deleted them immediately. Independent inspection found no remaining rows or schema changes. The process violation is recorded in the RI deployment ledger.

Assisted-by: AI

tnunamak added 30 commits July 30, 2026 07:46
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
(cherry picked from commit 9a72beb78e220e7de7683988905a47e064032187)
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
(cherry picked from commit f0070ea0b997134dd52c653bc7fa8af296df3040)
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
(cherry picked from commit 21df2ca4a268c66b1d33fd8e4f42faa0505a449c)
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
(cherry picked from commit de780e83793b02145df5c8ddfe4163051f2c67de)
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
(cherry picked from commit 4517fa9df1779c09ba6fe9c3fe235265108d4933)
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…y oracle

Documents rs.explore.timeline/record_buckets as an explicitly RI-only
operations/rs-* extension family (not Core, not /v1, not MCP-exposed),
the opaque composite cursor as a third distinct token space, the
owner-session-only auth boundary, and semantic-time ordering as
independent from rs.records.list's chronology contract. Adds a
cross-operation membership-parity test proving rs.explore.timeline and
rs.records.list return the same record-key set for the same connection,
through real owner-session and bearer-token HTTP auth. Corrects stale
task-tracking in port-explore-timeline-server-foundation/tasks.md
against directly-verified code/test state.

Squashed from independently gated branch commit 516c9dabb (LAND,
explore-ri-extension-contract-0730.md).

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…ions

Adds the terminal owner-LIST projection substrate: connector_summary_evidence
gains list_summary_projection_* columns (SQLite + Postgres) carrying one
named projection payload with a current/stale/unobserved/failed state,
fenced on canonical_evidence_revision so a stale-derived payload can never
publish as current. Adds getConnectorListSummaryTerminalProjection /
publishConnectorListSummaryTerminalProjection, the scoped browser-surface
observation seam (observeDynamicBrowserSurfaceRuntimeSurfaces, bounded to 25
surface ids; browser-surface-lease-store's readForConnectionIdentities,
bounded to 25 connection identities), and a bounded batch getter
(getConnectorListSummaryTerminalProjectionBatch) doing one IN/ANY query per
call, capped at 100 ids, never one query per id -- the single-id getter now
delegates to it.

The bounded maintenance publisher that would populate this projection is
NOT included: it requires a bounded "last successful run per connection id"
primitive that does not yet exist in this codebase (documented as an open
blocker in this change's OpenSpec tasks.md). publishConnectorListSummaryTerminalProjection
and the batch getter have zero production callers in this slice.

Squashed from independently gated branch commits 617b2e4fb, a2021563f,
7e7b9fa5a, d45f8b447, 7985242f2 (LAND, terminal-summary-integration-0730.md
/ terminal-summary-batch-gate-0730.md).

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…egates to the requested page

listSourceInstanceHeartbeatsByConnectionIds (connector-summary LIST read,
GET /_ref/connectors) and listSourceInstanceHeartbeatsByConnector
(single-connector detail read) both joined a LEFT JOIN subquery computing
MAX(accepted_at) GROUP BY device_id, source_instance_id over the entire
device_ingest_batch_outcomes table, filtered only by status='accepted' --
carrying no identity scope of its own. Both LIST and detail read cost grew
with total historical ingest volume instead of the requested page/connector.

Pushes the requested scope (device ids under the requested connector
instance ids, or under the requested connector_id) into the subquery's own
WHERE clause, on both SQLite and PostgreSQL, for both read paths. The scope
is derived through device_source_instances.device_id rather than filtering
device_ingest_batch_outcomes.connector_instance_id directly, because legacy
rows written through recordBatchOutcome's insert path can carry an
empty-string connector_instance_id and would otherwise be silently dropped.

Proven via N=0/1/25 parity against the unscoped legacy aggregate (no
off-page/off-connector leakage from a larger unrelated fleet), and EXPLAIN
(SQLite EXPLAIN QUERY PLAN + real PostgreSQL EXPLAIN against the sanctioned
disposable instance) confirming an index-seek, not a full-table scan.

Squashed from independently gated branch commits 79724c7fa, 17382105f
(LAND, scoped-ingest-outcome-summary-0730.md /
scoped-ingest-outcome-gate-0730.md / scoped-ingest-detail-regate-0730.md).

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Renames scheduler_run_history to run_history on SQLite and PostgreSQL and
hooks the general spine-event funnel (emitSpineEvent/postgresEmitSpineEvent)
so every run kind (scheduled/manual/browser/cancelled) creates a running row
at run.started and finalizes it at the terminal event, idempotently under
retried emissions (ON CONFLICT(run_id) WHERE run_id IS NOT NULL, matching a
new partial unique index). The scheduler's own richer write now upserts onto
the same row instead of creating a duplicate, marking it scheduler_managed so
existing cadence/backoff readers (getLatestRunHistoryForConnection,
listLatestRunHistoryByConnectionIds, listRunHistory) stay scoped to exactly
the rows they saw before this change -- LIST readers and the spine fallback
are unchanged in this slice (Authority Slice A); backfill and LIST cutover
are separate follow-up lanes.

Includes the completed_at legacy-migration fix: migrateRunHistoryRename /
migratePostgresRunHistoryRename renamed the table and added columns but
never relaxed completed_at's legacy NOT NULL constraint, so on any
already-deployed database (the majority deploy path) every run.started
write of every run kind threw at the moment the run started. Postgres:
ALTER COLUMN completed_at DROP NOT NULL inside the migration transaction.
SQLite: gated table-rebuild (create with fresh-install nullable-completed_at
defs, copy rows by explicit column list, drop, rename) when the notnull
pragma is still set post-migration.

Also fixes an ON CONFLICT(run_id) partial-index-target mismatch across all
5 insert/upsert sites, and a migration-ordering race where SCHEMA's
CREATE TABLE IF NOT EXISTS run_history could create an empty placeholder
before the legacy-rename migration's own existence guard ran, stranding
real data under the old table name.

Proven via test/run-history-writer-authority.test.ts (all four run kinds,
retried-started/retried-terminal idempotency, terminal-only fallback,
scheduler-merge-not-duplicate, scheduler_managed scoping, and the legacy
migration + completed_at fix on both SQLite and real PostgreSQL, with
mutation-bite confirmation on revert), plus the full existing
scheduler/cadence/backoff test surface re-run green.

Squashed from independently gated branch commits 998028a82, e44bf3391
(LAND, unified-run-history-projection-0730.md /
unified-run-history-authority-gate-0730.md).

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…maryPage

Adds a named identity_inventory profile to the existing GET /_ref/connectors
operation (option-gating within the one existing projection, no new route or
scope): a 7-field contract (connection_id, connector_id,
connector_instance_id, display_name, connector_display_name, streams,
membership_state) with stream-membership authority read from the evidence
row's stored union (evidence.stream_records, deduped, never re-derived from
record tables), falling back to manifest-declared streams with
membership_state: pending when no evidence row exists.

Routes Explore's facet pager, exact-selection lookup, and peek-relationship
path through the new profile, replacing the full-fat connector-summary read
those three call sites previously consumed only identity/membership fields
from. Dependency matrix drops to identity page + one evidence-row batch --
zero spine/runtime/browser-surface/schedule/run-history reads -- confirmed
flat at 2-3 statements across N=0/1/25/100 via genuine driver-level
instrumentation (Database.prototype.prepare / pool.query), both SQLite and
real PostgreSQL.

OpenSpec change is purely additive: the merged normative spec
(reference-implementation-architecture) is byte-identical before/after: no
new route, no new scope introduced.

Includes the gate-fix commit closing two REVISE findings: a stale
CONNECTION_DISPLAY_HELPER_RE regex in
apps/console/.../explore/page.invariants.test.ts left over from the
RefConnectorSummary -> RefConnectorIdentitySummary parameter rename
(apps/console/.../explore full glob now 287/287), and the RI contract
test's cost-gate ceiling tightened from <=6 to the ruling's stated <=4
(real behavior already within bound on both backends).

Squashed from independently gated branch commits de30786e6, 9bb3a7041,
bf2943c57, 8329bb363, 9c57799ec (LAND, explore-identity-profile-0730.md /
explore-identity-profile-gate-0730.md). The three intermediate commits
(9bb3a7041, bf2943c57, 8329bb363) carried Assisted-by: AI but no DCO
Signed-off-by trailer -- this squash is the DCO-signed commit that carries
their accepted content to trunk, so those unsigned commits never enter
trunk history directly.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
… retained_count_summary

Extends GET /_ref/connectors with two additive capabilities (no new route,
scope, cache, or hydrator): a bounded repeated connector_id SET scope
(1-100 distinct canonical ids per request, typed rejection on empty/
over-bound/duplicate-after-canonicalization sets; cursor bound to the
set's canonical fingerprint) and a retained_count_summary named profile
(identity + total_records/total_records_state/acquisition_coverage.latest_batch
only -- the exact Add Source field set, zero spine/runtime/browser-surface/
schedule/run-history reads, proven flat per page via driver-level
instrumentation).

Migrates apps/console's Add Source existing-connection discovery
(existing-sources-by-connector.ts) off both the prior 33-call catalog
inventory (one listConnectionsByConnector call per registered connector
type) and the per-live-connection N+1 scoped-summary backfill, replacing
them with partitionConnectorIds -> traverseConnectorIdScope ->
fetchRetainedCountSummaries, shared by both existingSourcesForConnector
and existingSourcesByConnectorCatalog. Cross-partition fan-out is bounded
via a copy of the reference server's own mapWithConcurrency primitive
(apps/console/.../lib/concurrency.ts, fixed at
EXISTING_SOURCES_PARTITION_CONCURRENCY = 8) rather than an unbounded
Promise.all, closing a gate finding on the first version of this change.
ExistingSourceSetupLink's output contract and the
status !== "revoked" && !revoked_at` filter are unchanged.

Proven via dual-backend (SQLite + real PostgreSQL) tests: SQL parity and
EXPLAIN index-seek proof for the SET-scope templates, zero-writes/
zero-spine-reads cost gate for the new profile, exact field parity against
the full detail profile as a live oracle, revoked-row pagination
preservation, and a bounded-concurrency oracle (>10,000-item max-in-flight
proof, exact failure propagation without fabricated completeness, order
preservation despite concurrency).

Squashed from independently gated branch commits 26706ffff, 1e0b4bd14
(LAND, add-source-batched-profile-0730.md /
add-source-batched-profile-gate-0730.md). These commits were built on top
of the explore identity_inventory profile's pre-gate-fix state
(8329bb363); this squash carries their accepted content forward onto the
DCO-signed identity_inventory commit already on this branch, merging the
retained_count_summary profile's third-branch additions alongside it.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
… over

Adds the bounded, resumable spine-to-run_history backfill stage
(run-history-backfill-stage.ts) that discovers historical spine-only runs
by event_seq cursor (fenced by connector_maintenance_cursor, widened to
admit a run_history_backfill row alongside the existing
connector_summary_evidence one), folds each candidate's terminal data
(collection_facts/known_gaps/recovery_only/collection_rate, which the
summary fold does not carry), attributes legacy connector-wide singleton
runs via the same throws-on-ambiguous rule resolveActiveByConnector
already establishes, and inserts each row with ON CONFLICT(run_id) DO
NOTHING so a concurrent live terminal write always wins over a backfilled
one. A bounded multi-round startup accelerator drains the backlog on boot;
the existing maintenance-sweep chassis carries it the rest of the way.

Cuts the five gated product-read routes (listConnectorSummaryPage,
getConnectorSummaryForRoute, getConnectorDetail,
getOwnerConnectionDiagnostics, listConnectorSummaries) over from the old
unbounded spine-fold synthesis to reading run_history directly, composing
the live active-run lease overlay with the existing vocabulary
(running+lease -> in_progress, running+no-lease -> failed, terminal ->
stored status, no new enum). The old spine-fold/toConnectorRunSummary path
and its six now-unreachable helper functions are deleted; the fold
survives only as the backfill's own oracle and as a test oracle for
equivalence.

Includes both REVISE fixes from the gate's second pass:

- G1 closure: readLatestCollectionRateForRun's spine_events fallback SELECT
  (fired on every route render for a currently-running connection) is
  deleted. The general executor's run.progress_reported event now merges
  collection_rate into the still-running row's facts_json via one atomic
  UPDATE fenced on status='running' (json_patch on SQLite,
  jsonb `||` on Postgres, both inside the same transaction as the spine
  write) so a concurrent/later terminal write always wins and a stale
  progress merge silently no-ops rather than resurrecting a finalized row.
  Proven via active-run-summary-zero-spine.test.ts (6 tests, dual-backend,
  zero skips): zero spine_events statements survive for in-progress,
  terminal, and no-run GETs on all five routes' shared call graph, on both
  SQLite and real PostgreSQL.

- Fleet-migration reachability fix: the completed_at NOT NULL repair
  e44bf3391 added lived inside migrateRunHistoryRename's/
  migratePostgresRunHistoryRename's legacyExists-gated branch, which
  returns immediately once scheduler_run_history no longer exists — so a
  database whose rename already executed under an earlier deploy of this
  migration (before e44bf3391 shipped) was permanently stuck on the legacy
  NOT NULL constraint, with every run.started write throwing forever. This
  is exactly the state the sanctioned gate Postgres instance was found in.
  Extracted into two new unconditional, idempotent repair functions
  (migrateRunHistoryCompletedAtNullable / migratePostgresRunHistoryCompletedAtNullable)
  called right after the rename migration regardless of legacyExists,
  gated only on the column's own current nullability — a no-op on fresh
  installs and already-repaired databases. The SQLite table-rebuild path
  explicitly recreates both indexes (idx_run_history_connector_completed,
  uniq_run_history_run_id) afterward, closing a sub-gap the extraction
  could otherwise have introduced. Proven via
  run-history-completed-at-fleet-migration.test.ts (4 tests, dual-backend,
  zero skips): reproduces the exact stuck shape, repairs it, confirms
  rows/ids/both indexes survive, confirms idempotency across a second
  bootstrap, and confirms a real run.started INSERT succeeds post-repair.

Merged against the already-integrated Authority Slice A
(d37036f) and terminal-summary-projection substrate (20ee6d1): this
chain is rooted directly at Authority Slice A's own head (e44bf3391), so
every db.ts/postgres-storage.ts/mass-justifications.json conflict was a
coherent supersession of Authority Slice A's original inline completed_at
repair by this slice's extracted, unconditionally-reachable fix — verified
hunk-by-hunk that no content from either slice was dropped (terminal-
summary's list_summary_projection_* schema columns and this slice's
run_history_backfill cursor/index/repair machinery both independently
confirmed present in the merged files). run-history-writer.ts's add/add
conflict resolved by taking the new chain's version wholesale (Authority
Slice A's own committed copy of that file was byte-identical to the shared
base, so nothing was lost).

Squashed from independently gated branch commits a0487c89b, c2a591bca,
6e5469900 (LAND on second pass, run-history-backfill-cutover-gate-0730.md
— first pass REVISE on one blocking G1 finding, closed by c2a591bca; a
second REVISE finding on migration reachability, closed by 6e5469900).

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…tgreSQL skips

The G1 spine-read closure work (c2a591bcabc) added three PostgreSQL-only
tests to test/active-run-summary-zero-spine.test.ts using the bare-boolean
skip: !POSTGRES_URL shape (in-progress, terminal, and no-run cases). None
of their exact names were present in POSTGRES_UNNAMED_SKIP_TEST_NAME_ROWS,
so memory-default accounting rejected them as unexplained skips. The
accounting parser aborts on the first unexplained skip per run, so all
three names are added together to avoid a serial one-at-a-time failure
sequence across repeated fix attempts.

Adds the three exact test names to the existing exact named-skip mapping
table in scripts/test-accounting/receipt.ts, in alphabetical order, with
no change to test logic, skip conditions, or product code. Adds a narrow
regression in inventory.test.ts asserting all three names are present in
the mapping, following the existing precedent test's pattern.

Verified via a real memory-default run: structuredNodeSummary resolves
all three skips cleanly (skip_reasons: {"PDPP_TEST_POSTGRES_URL unset": 3},
all three consumed_mapping_identities present, no throw). Verified via a
real PostgreSQL-enabled run that all three tests still execute and pass
live (test:pass events), confirming the mapping only explains the skip
under memory-default and does not affect PostgreSQL execution.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…manifest count

The browser-surface.test.ts test "Postgres scoped browser-surface reads
match filtered global rows for 0, 1, and 25 identities" uses the
bare-boolean skip: !POSTGRES_URL pattern and was not in the manifest
skip mapping, causing memory-default to reject it as an unexplained skip.

Updates:
- Add the exact browser-surface skip name to POSTGRES_UNNAMED_SKIP_TEST_NAME_ROWS
  in scripts/test-accounting/receipt.ts (alphabetical order after "Postgres browser")
- Update the memory-default skip baseline from 129 to 133 (original 129 +
  3 active-run-summary skips + 1 browser-surface skip)
- Add focused regression test in inventory.test.ts asserting the
  browser-surface skip name is present in the mapping

No product code changes. Test-accounting metadata only.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Updated manifest after mapping the fourth PostgreSQL skip (browser-surface),
bringing the total from 129 to 133 (3 active-run-summary + 1 browser-surface).

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
… authority run

Transcript 70bfe0b9-bf35-45d6-9415-4e5bd5e71f61 revealed four more PostgreSQL-only
tests using bare-boolean skip: !POSTGRES_URL that were not in
POSTGRES_UNNAMED_SKIP_TEST_NAME_ROWS, causing the owner authority to fail
with unexplained skips. All 73 emitted bare-boolean skip identities now
fully mapped.

Added four exact test names to POSTGRES_UNNAMED_SKIP_TEST_NAME_ROWS:
- "PostgreSQL: a fresh install is unaffected by the fleet-migration repair"
- "PostgreSQL: a pre-renamed-stuck database is repaired on the next boot, idempotently, with row/id/index preservation"
- "PostgreSQL: a run.started write succeeds against a database migrated from legacy scheduler_run_history"
- "Postgres terminal LIST projection rejects late canonical snapshots"

Manifest count remains 133 (emitted total == 133, accounting complete).

Added two focused regressions in inventory.test.ts:
- "keeps every fleet-migration and scheduler-upgrade PostgreSQL skip..."
- "keeps every terminal-LIST PostgreSQL skip..."

No product code changes. Test-accounting metadata only.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
The Postgres Upcoming projection fetched every partition serially
(2xN sequential roundtrips), which is the entire remaining server-
stream tail on /explore under live PostgreSQL (p50 1189ms vs 7ms
TTFB, isolated to this one function). Replace the serial for-loop
with mapWithConcurrency (existing primitive, cap 8) so partitions
fetch concurrently but pool pressure under ingest stays bounded.
Reduction remains in original partition-index order, so totals,
row ordering, and overflow flags are unchanged.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…ctor_instance_id)

LIVE CANARY REVISE: candidate 1a4d329 was rolled back to 1392a38
after the Postgres migration could not create
uniq_pg_run_history_run_id (error 42P10), then every
ON CONFLICT(run_id) writer/backfill insert failed the same way.
Read-only live proof: exactly 2 duplicate run_ids, each representing
TWO DISTINCT connection histories, not duplicate rows —
run_1782401113918 = a failed run on cin_11deac... and a succeeded run
on a different connection cin_b110...; run_1782865411684 = a failed
run on cin_11deac... and a succeeded run on cin_c858....

Root cause: run_id is minted independently by several call sites
(runtime/scheduler/run-executor.ts, runtime/controller.ts,
runtime/index.ts) using Date.now()-based generators with no
connection-scoped entropy, so two different connections can
legitimately produce the identical run_id string. run_id alone was
never a safe uniqueness/conflict/identity key. No historical rows
were deleted, collapsed, or relabeled; no live data was patched —
this is a schema/write-path fix only.

The real identity is the pair (run_id, connector_instance_id):

- Unique index widened and renamed on both backends
  (uniq_run_history_run_id_instance / uniq_pg_run_history_run_id_instance),
  ON run_history(run_id, connector_instance_id) WHERE run_id IS NOT NULL.
  No compatibility read path, no swallowed index-creation error: the
  migration sites that used to fail-open (try/catch, log, continue) on
  duplicate run_id data now create the composite index
  unconditionally.
- Every ON CONFLICT(run_id) writer retargeted to
  ON CONFLICT(run_id, connector_instance_id): start-run-history.sql,
  insert-finalized-run-history.sql, insert-run-history.sql, plus their
  Postgres inline mirrors in run-history-writer.ts and
  scheduler-store.ts, and the backfill stage's own insert.
- Every UPDATE ... WHERE run_id = ? (finalize, progress-merge) now
  also fences on AND connector_instance_id = ? on both backends — the
  actual live-corruption vector: without this fence, a terminal or
  progress write for one connection's run could match and corrupt a
  DIFFERENT connection's still-running row sharing the same run_id.
- Backfill stage candidate discovery now GROUP BY run_id,
  connector_instance_id (sourced from the real, indexed
  spine_events.connector_instance_id column); event-window fold
  filters each candidate's run_id-fetched window down to its own
  connection before folding with the genuinely unmodified
  summarizeEvents/summarizeRows fold (the latter now exported from
  lib/postgres-spine.ts, alongside connectionIdFromEventData from both
  spine modules and fetchRowsForSummaries).

New test/run-history-duplicate-run-id-identity.test.ts (4 tests,
dual-backend) proves: two connections sharing a run_id each get their
own row across started/progress/terminal/scheduler-upsert; the
backfill stage discovers and folds them as two separate candidates,
never blending event windows; and the Postgres migration builds the
composite index successfully over data reproducing the exact live
42P10 failure shape. Full RI suite diffed against unmodified HEAD:
identical 10 pre-existing failures (unrelated to this fix, confirmed
by running the exact same suite on both), zero new regressions.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…3->135

67780d9 added two new PostgreSQL-only skip: !POSTGRES_URL tests
(run-history-duplicate-run-id-identity.test.ts) to
POSTGRES_UNNAMED_SKIP_TEST_NAME_ROWS via receipt.ts, but did not
bump the memory-default "PDPP_TEST_POSTGRES_URL unset" numeric
ledger in test-accounting.manifest.json or its mirrored expectation/
comment in inventory.test.ts, leaving the count stale at 133 against
an actual emitted total of 135.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…ory migration losslessly

SECOND LIVE CANARY REVISE: candidate f2b1ebe failed startup because
migratePostgresRunHistoryRename found BOTH scheduler_run_history and a
non-empty run_history and threw ("refusing to guess which is
authoritative"). Root cause: a real interrupted migration — the first
rejected canary (candidate 1a4d329) had already renamed
scheduler_run_history -> run_history and backfilled/live-wrote into it
before being rolled back to old revision 1392a38, which predates the
entire run_history generalization (commit 998028a82) and has zero
knowledge of run_history/the rename — it recreates scheduler_run_history
fresh via its own CREATE TABLE IF NOT EXISTS and resumes writing to it.

Live read-only measurement (2026-07-30 19:23 CDT): scheduler_run_history
=22 rows (actively growing since rollback), run_history=11,415 rows
(frozen since the interrupted candidate stopped), composite-identity
overlap=0 (a pure disjoint union on the actual live data) — but numeric
id values DO overlap between the two tables, so legacy ids are never
reused/preserved blindly. No live data was edited; the live instance
stayed on old revision 1392a38 throughout this fix.

migratePostgresRunHistoryRename / migrateRunHistoryRename no longer
throw on this state. reconcilePostgresLegacySchedulerRunHistory (Postgres)
/ reconcileLegacySchedulerRunHistory (SQLite) merge scheduler_run_history's
rows into run_history losslessly:

- Composite-identity (run_id IS NOT NULL) rows merge via the SAME
  ON CONFLICT(run_id, connector_instance_id) DO UPDATE SET field =
  excluded.field upsert contract insert-run-history.sql already
  establishes for "a scheduler row meets an existing run_history row" —
  scheduler-owned fields win; facts_json/trigger_kind (fields
  scheduler_run_history never carried) are left untouched.
- run_id IS NULL rows (never conflict under the partial unique index)
  insert unconditionally, exactly once, since the whole merge runs
  inside ONE all-or-nothing transaction — table existence is the only
  idempotency marker needed. No persisted provenance marker on
  run_history rows (a synthetic facts_json marker was considered and
  rejected — unnecessary given the atomic-transaction guarantee, and
  would contaminate product facts with migration metadata).
- A genuine count-based invariant (every legacy row traceable in
  run_history) is verified BEFORE DROP TABLE scheduler_run_history; a
  mismatch throws loudly rather than dropping unreconciled data.
- Legacy numeric id values are never reused — every merged row gets a
  fresh run_history id (confirmed necessary live: the two tables' ids
  overlap).

New test/run-history-interrupted-migration-reconciliation.test.ts (7
tests, dual-backend) proves: fresh install unaffected; legacy-only
migration (no interruption) still a pure rename; interrupted-migration
reconciliation merges losslessly (overlap via the established upsert
contract, disjoint rows from both tables preserved, run_id-IS-NULL rows
preserved, duplicate run_id across two connections survives without
collapsing, no legacy id reused); idempotent on a second boot; a
simulated crash mid-reconciliation leaves scheduler_run_history fully
intact for a clean retry. Every scenario proven on both SQLite and real
Postgres.

Combined regression sweep (this file + 5 related run-history test
files): 38/38. Full RI suite diffed byte-for-byte against unmodified
HEAD (378cac600): identical set of 10 pre-existing failures on both
runs, zero new regressions.

Test-accounting: 2 new PostgreSQL bare-boolean-skip names added to the
exact named-skip mapping plus a matching inventory.test.ts regression;
manifest.json's memory-default baseline count updated 129 -> 136
(129 original + 3 + 2 + 2 from three prior deferred deltas this
session, now applied together) — an arithmetic derivation from known
deltas, not a live-verified full authority-run count.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…conciliation merge

FOURTH-PASS GATE REVISE: the adversarial gate found that
scheduler_run_history can itself contain multiple rows sharing the
identical (run_id, connector_instance_id) pair — reachable because the
pre-generalization scheduler writer at the rolled-back revision
(1392a38) performs a plain INSERT with no ON CONFLICT clause at all,
so a retried/duplicate scheduled-run completion under that
currently-live writer can produce exactly this shape.

On Postgres, reconcilePostgresLegacySchedulerRunHistory's
composite-identity merge (INSERT ... SELECT ... ON CONFLICT DO UPDATE)
threw "ON CONFLICT DO UPDATE command cannot affect row a second time"
whenever two source rows target the same conflict key — a hard
Postgres restriction independent of ORDER BY. This is on the exact
backend and exact code path the live incident is about: if the real
production scheduler_run_history contains even one such duplicate
pair, the migration would throw on every boot attempt, permanently
blocking that database until manual deduplication — the exact outcome
this reconciliation effort exists to avoid.

Fix: the composite-identity merge's source is now pre-deduplicated by
(run_id, connector_instance_id), keeping only the highest id (the
latest write) per pair — extending the same "scheduler's newer write
wins" semantics the merge already establishes for the cross-table
overlap case to the intra-table case.

- Postgres: SELECT DISTINCT ON (run_id, connector_instance_id) ...
  ORDER BY run_id, connector_instance_id, id DESC as the merge's
  source, in place of the bare SELECT.
- SQLite: confirmed NOT to have this bug (INSERT ... SELECT ... ON
  CONFLICT DO UPDATE applies duplicate source rows in order, last one
  wins, no error) — the equivalent ROW_NUMBER() OVER (PARTITION BY
  run_id, connector_instance_id ORDER BY id DESC) dedup was still added
  for defense-in-depth and cross-backend consistency, rather than
  relying on that undocumented backend-specific tolerance.

New dual-backend regression in
test/run-history-interrupted-migration-reconciliation.test.ts
reproduces the exact gate-probe fixture (two scheduler_run_history rows,
same run_id, same connector_instance_id, different attempt/status) and
proves: the migration no longer throws, exactly one row survives for
that composite key, and it reflects the highest-id source row's
fields. Independently verified the Postgres test reproduces the exact
gate-reported error on pre-fix code (via a temporary git stash of the
two migration files), confirming it is a genuine discriminating
regression test.

All previously-gated behavior re-verified unchanged in the same run:
fresh install, legacy-only migration, cross-table overlap merge,
disjoint-row preservation, run_id-IS-NULL handling, cross-connection
duplicate run_id, idempotency, and crash-before-commit safety — 9/9
tests pass dual-backend, zero skips against the sanctioned Postgres
instance. Per instruction, only focused discriminating tests plus
OpenSpec/typecheck/lint/mass-ratchet gates were run — no broad suite
re-run.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…ression test

fd0d41e76 (fourth-pass gate fix: deduplicate scheduler_run_history
source rows before the reconciliation merge) added a third
bare-boolean PostgreSQL test to
test/run-history-interrupted-migration-reconciliation.test.ts but did
not add its exact name to the named-skip mapping — an oversight caught
while re-verifying the report, confirmed real by running the actual
accounting parser against the file's reporter output
(structuredNodeSummary threw "unexplained skip" on the new test name).

Adds the new test's exact name to
POSTGRES_UNNAMED_SKIP_TEST_NAME_ROWS, extends the existing
inventory.test.ts regression for this file to cover all 3 Postgres
test names (not just the original 2), and bumps
test-accounting.manifest.json's memory-default baseline 136 -> 137.
Verified via a real memory-default run through structuredNodeSummary:
all 3 skips explained, zero "unexplained skip" throws.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
… 137

FIFTH-PASS GATE (narrow bookkeeping defect): "the memory-default profile
declares the exact current skip baseline" hardcoded 129 in the test file
itself, never bumped alongside test-accounting.manifest.json's own
updates at 3c9fae551 (129->136) or 12c48b85f (136->137) — pre-existing
since 1392a38, surfaced only when this test is run for real rather
than cherry-picked.

Updates the literal to 137, matching the manifest's current value, with
a comment documenting the exact delta breakdown.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…ng on touched set

Aggregate integration gate found two mechanical closure defects at
HEAD 2c1aeda:

- 3 TS2532 "Object is possibly 'undefined'" errors in
  run-history-interrupted-migration-reconciliation.test.ts
  (lines 674, 714, 729), introduced by 43b0e0a: .rows[0].n accessed
  directly on a query result under noUncheckedIndexedAccess, without
  the optional chaining the same file already uses at adjacent sites
  (e.g. .rows[0]?.exists). Fixed by adding ?. at all three sites,
  matching the file's own established pattern.
- 1 Biome formatting violation in inventory.test.ts (a multi-line
  single-element array Biome wants collapsed) — resolved via
  `biome check --write`, which also collapsed one adjacent
  identically-shaped array in the same file.

`npm run typecheck` (RI) and `biome check` on the touched file set both
confirmed clean after the fix. Runtime/broad test suites intentionally
not run, per instruction — this is a narrow, mechanical closure.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
postgresFetchSnapshotAnchor ran MAX(id), MAX(emitted_at) FROM records
WHERE deleted = FALSE with no leading-indexed-column filter, forcing a
~570ms parallel sequential scan over the full live table (proven via
EXPLAIN ANALYZE against the live pdpp database: 4.29M rows, 153
partitions). This runs on every first-page /explore request and was
roughly half of the observed post-fa04be6c7 tail (1113ms p50).

snapshotAt is documented as display-only (never used for cursor
membership/ordering), so it can be read from the same highest-id row
instead of a separate global MAX(emitted_at) aggregate. `ORDER BY id
DESC LIMIT 1` is a backward records_pkey index scan that stops at the
first live row: 0.04ms against the live database, a ~13,000x
reduction on this query, with no behavior change to snapshotSeq and
no change to membership/ordering semantics.

The second unscoped full-scan query in this path,
postgresListPartitions's `SELECT DISTINCT ... WHERE deleted = FALSE`
(~597ms, same live database), is NOT fixed here — no equally narrow,
>95%-confidence correction was available in this pass across all
scope-filter combinations. It remains the dominant residual cost;
see the updated /home/tnunamak/.tmp/explore-live-terminal-tail-0730.md
for the live before/after evidence and REVISE disposition.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…shot anchor"

This reverts commit c5b8091.

The committed fix silently changed snapshot_at's observable value, not just
its cost. It read emitted_at from the highest-id row instead of a true
MAX(emitted_at) aggregate, reasoning the field was "display-only, never used
for membership." That reasoning under-weighted that snapshot_at IS returned
in the API response (OpenSpec: "an ISO-8601 timestamp corresponding to the
ingest-sequence anchor") and is therefore user-visible, observable behavior.

Verified live against the production database (read-only) that the two
values genuinely diverge: emitted_at is documented and confirmed to be
non-monotonic with id in this domain (future-dated records, e.g. YNAB future
budget months, are a first-class case — 975 rows currently have an id lower
than the current max-id row but a LATER emitted_at). No fast index-backed
path to a true MAX(emitted_at) exists without adding a new index, which is
out of scope for this narrow fix. Reverting to the known-correct, known-cost
original query; the anchor-query cost remains open for a future pass with a
provably safe approach.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…l-table DISTINCT

postgresListPartitions ran `SELECT DISTINCT connector_instance_id,
connector_id, stream FROM records WHERE deleted = FALSE` (plus optional
appendPostgresScope filters). Unscoped, this has no leading-indexed-column
filter to seek on, forcing a parallel sequential scan: proven live against
the production database, ~597-627ms over 4.29M rows for 153 distinct
partitions. This runs on every /explore request (main-feed partition
enumeration) and was the largest remaining component of the tail after
fa04be6's Upcoming fan-in fix.

Replaced with a loose index scan (a standard Postgres pattern for
DISTINCT-over-a-key-prefix, since Postgres has no native skip scan): a
recursive CTE seeds with the first live row in (connector_instance_id,
stream) order, then repeatedly seeks the next row strictly greater than the
current pair via idx_pg_records_stream_cursor. connector_id is read from the
same winning row rather than aggregated separately, since a stream belongs
to exactly one connector_id per instance (verified: zero (instance, stream)
pairs have more than one distinct connector_id on live data). The seed step
and the lateral seek step reuse the identical unqualified scope-filter WHERE
clause against their own `records` scan, so appendPostgresScope's four
filters (connectionIds/streams include and exclude) compose exactly as
before with no special-casing.

Live before/after (EXPLAIN ANALYZE, same production database, read-only):
597-627ms (Parallel Seq Scan, ~440K buffer reads) -> 2.3-18ms (index scans
only), a >30x reduction.

Correctness proof (this pass): a focused parity test file,
test/rs-explore-list-partitions-loose-scan.test.ts, covers unscoped,
connectionIds include/exclude, streams include/exclude, mixed filters,
empty-result, deleted-only partitions (must be absent), mixed deleted/live
partitions (must appear once), special string values (empty string, quotes,
backslash, unicode) on both SQLite and Postgres, plus a same-shape
cross-backend agreement test. Additionally verified directly against the
live production database (read-only SELECT comparisons, no data touched):
plain DISTINCT and the loose-scan CTE produce byte-identical result sets
across 6 scope combinations on the real 153-partition dataset. Mutation-
tested: dropping the scope filter from the lateral seek step, and dropping
the deleted=FALSE filter entirely, are both caught by the existing
assertions (exact-set comparisons, not just non-empty checks); a full
revert to plain DISTINCT passes the same suite unchanged, confirming the
tests validate behavior, not implementation shape.

Scope note: postgresFetchSnapshotAnchor (the OTHER full-scan query in this
path, ~560-600ms) is intentionally UNCHANGED here — a prior attempt in this
session (c5b8091) to fast-path it by reading emitted_at from the highest-
id row instead of a true MAX(emitted_at) aggregate was reverted (c45fd9d)
after live verification showed the two values genuinely diverge (emitted_at
is non-monotonic with id by design — future-dated records, e.g. YNAB future
budget months, are a first-class case). No fast index-backed path to a true
MAX(emitted_at) exists without a new index, which is out of scope for "no
new general machinery." See the updated
/home/tnunamak/.tmp/explore-live-terminal-tail-0730.md for why this means
the <300ms p50 acceptance target is not yet met by this change alone.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
tnunamak added 2 commits July 30, 2026 20:57
…irically

POSTGRES_UPCOMING_PARTITION_CONCURRENCY was set to 8 in fa04be6 by copying
DEFAULT_POSTGRES_SEARCH_FANOUT_CONCURRENCY's pre-existing value, not by
measurement against this call site's own load. The main pg pool has no
explicit `max`, defaulting to node-postgres's 10 — 8 concurrent Upcoming
workers leaves only 2 slots of headroom for concurrent run_history/ingest
writers on the same pool.

Benchmarked live against the production database (153 real partitions) at
caps 1 (serial)/4/6/8, with a concurrent representative write load (3
writers on a ~150ms cadence, using isolated throwaway run_history rows,
cleaned up after each round) running the entire time:

  cap=1 (serial): upcoming-fetch p50 46-47ms, max 54-56ms; write latency max 13.4ms
  cap=4:          upcoming-fetch p50 15ms,    max 16ms;    write latency max 11.5ms
  cap=6:          upcoming-fetch p50 11ms,    max 13ms;    write latency max 13.2ms
  cap=8:          upcoming-fetch p50 9-10ms,  max 12-17ms; write latency max 11.3ms

No writer-admission regression at any cap (write latency does not increase
with lower Explore concurrency; if anything it correlates with SLOWER reads,
since the read benchmark itself runs longer and overlaps more writes at
cap=1). Given phase 2 (3a003d7) already removed Upcoming's status as the
tail's dominant cost — the snapshot-anchor query alone is ~560-600ms,
dwarfing any of these four options — there is no case for holding more pool
headroom than necessary: 4 already cuts serial's 47ms to 15ms with the same
zero-regression profile; 6 and 8 buy under 6ms more for no measured benefit
against a competing writer.

Not preserved by precedent: chose the lowest cap meeting the target with no
regression, per the explicit calibration requirement, not the pre-existing
search-fanout convention.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
RI owner contract decision: snapshot_at SHALL mean the wall-clock time the
first-page ingest-sequence snapshot is captured, not any record's
emitted_at. This resolves the ambiguity in the OpenSpec text ("an ISO-8601
timestamp corresponding to the ingest-sequence anchor captured at first-page
time") explicitly, in the direction that avoids future/backfilled
event-time distortion: the domain has documented future-dated records
(YNAB future budget months) and backfills as first-class cases, and
MAX(emitted_at) inherits exactly the distortion snapshotSeq was already
fixed to avoid when membership moved off emitted_at.

Concept-clean implementation: one operation-clock value is captured on
first-page load (deps.now(), injectable for tests) and reused for BOTH
snapshotAt and nowCeiling, since their meanings are intentionally
identical -- the wall clock at first-page capture time. There is exactly
one capture moment, not two independently-timed reads.

fetchSnapshotAnchor's dependency contract narrows to { snapshotSeq } only.
postgresFetchSnapshotAnchor/sqliteFetchSnapshotAnchor drop the
MAX(emitted_at) aggregate entirely -- a single-column MAX(id) with no
companion aggregate lets Postgres use its built-in MIN/MAX index
optimization instead of a full table scan: proven live against the
production database, 0.05-0.07ms (index scan backward on records_pkey, no
buffered heap reads beyond the winning row) versus the ~560-600ms the
combined aggregate previously forced. This was the last unscoped
full-table-scan query on the /explore first-page path.

Empty corpus now reports the actual captured instant for snapshot_at
(matching nowCeiling), not the prior "1970-01-01T00:00:00.000Z" sentinel --
no documented contract required that placeholder, and the real capture
moment is equally well-defined whether or not any records exist yet.

Cursor decoding is unchanged: snapshot_at's TYPE (ISO-8601 string) and
position in the composite cursor payload are unchanged, so existing
encoded cursors still decode correctly -- only which value the OPERATION
writes into it, on first-page capture, changed. Resumed and rewound pages
already carried snapshotAt/nowCeiling/snapshotSeq verbatim from the
decoded cursor (unchanged code path), so they retain the original
capture instant with no new logic.

Documented explicitly, not silently: CompositeCursorPayload's version-
history comment now documents the (previously undocumented) v4 nowCeiling
addition plus this snapshotAt meaning fix; fetchSnapshotAnchor's JSDoc
states the narrowed contract; a new OpenSpec change
(clarify-explore-snapshot-at-capture-time) rewrites the snapshot_at
scenario to the capture-time contract with 3 explicit new/tightened
scenarios (future/backfilled-immune, resume/rewind retention, empty-
corpus actual-capture-time).

New regression file test/rs-explore-snapshot-at-capture-time.test.ts (12
tests, both backends): future-dated emitted_at never changes snapshot_at;
backfilled emitted_at never changes snapshot_at; pagination membership
stays snapshotSeq-based (unaffected, confirmed alongside); resumed/rewound
pages retain the original capture instant even when the resume request
injects a DIFFERENT clock value; snapshot_at equals nowCeiling on a first
page; empty corpus reports the actual capture time, not the epoch
sentinel. Mutation-tested: a full revert to the MAX(emitted_at) aggregate
(scratch copy, discarded) kills 10/12 of these tests cleanly, with the
2 orthogonal membership tests correctly surviving unchanged -- confirming
the suite targets the value contract precisely, not over-broadly.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pdpp Ready Ready Preview Jul 31, 2026 4:00am

Request Review

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
@tnunamak
tnunamak merged commit 8d918eb into main Jul 31, 2026
12 checks passed
@tnunamak
tnunamak deleted the perf/terminal-owner-read-paths branch July 31, 2026 04:13
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