feat(server): storage v2 P3 — TypeScript delphi storage twin (dual backends + jest conformance)#2599
feat(server): storage v2 P3 — TypeScript delphi storage twin (dual backends + jest conformance)#2599jucor wants to merge 1 commit into
Conversation
… PG migration 000019)
Implements Stack 1 / P4 of docs/STORAGE_V2_DESIGN.md: the deployment
artifacts for the V2 schema, pinned to the backend definitions.
- create_dynamodb_tables.py: DELPHI2_TABLE_SCHEMAS + create_delphi2_tables()
wired into create_tables() (always created, like the job queue). The
schemas are an INLINED copy because the script runs standalone in the
dynamodb-init container (bare python + boto3, cannot import
delphi_storage); tests/test_delphi_storage_ddl.py fails if the copy drifts
from delphi_storage.backends.dynamodb.table_schemas("Delphi2_").
- server/postgres/migrations/000019_create_delphi_storage.sql: schema
`delphi` with runs / latest / run_inputs / artifacts / topic_moderation /
collective_statements, mirroring
delphi_storage.backends.postgres.schema_ddl("delphi") statement for
statement (drift-tested the same way).
- tests/test_delphi_storage_ddl.py proves the DEPLOYED artifacts (not the
ensure_* test helpers) yield conformant stores: the migration applied by
raw SQL to a scratch database passes the full conformance suite with
ensure_schema off, and script-created Delphi2_* tables pass it with
ensure_tables off. Service-gated tests follow the skip-locally /
fail-in-GitHub-Actions convention.
Tests: 406 passed, 30 skipped, 58 xfailed (5 pre-existing
DynamoDB-unavailable errors unchanged); with DynamoDB local + dockerized PG
all 6 DDL tests pass.
Stack: P1 #2597, P2 #2598, P3 #2599, P4 this commit. Completes Stack 1
(Foundation); Stack 2 (provenance plumbing, P5-P8) follows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
57be28e to
9ee292b
Compare
d87422d to
328f1de
Compare
|
Post-push self-review (Claude subagent) found one real divergence from the Python twin, now fixed and re-pushed across the stack: Unvalidated
All suites re-verified: Python 43/43 storage tests across 4 backends; TS 53/53 (unit + integration); tsc + eslint clean. |
… PG migration 000019)
Implements Stack 1 / P4 of docs/STORAGE_V2_DESIGN.md: the deployment
artifacts for the V2 schema, pinned to the backend definitions.
- create_dynamodb_tables.py: DELPHI2_TABLE_SCHEMAS + create_delphi2_tables()
wired into create_tables() (always created, like the job queue). The
schemas are an INLINED copy because the script runs standalone in the
dynamodb-init container (bare python + boto3, cannot import
delphi_storage); tests/test_delphi_storage_ddl.py fails if the copy drifts
from delphi_storage.backends.dynamodb.table_schemas("Delphi2_").
- server/postgres/migrations/000019_create_delphi_storage.sql: schema
`delphi` with runs / latest / run_inputs / artifacts / topic_moderation /
collective_statements, mirroring
delphi_storage.backends.postgres.schema_ddl("delphi") statement for
statement (drift-tested the same way).
- tests/test_delphi_storage_ddl.py proves the DEPLOYED artifacts (not the
ensure_* test helpers) yield conformant stores: the migration applied by
raw SQL to a scratch database passes the full conformance suite with
ensure_schema off, and script-created Delphi2_* tables pass it with
ensure_tables off. Service-gated tests follow the skip-locally /
fail-in-GitHub-Actions convention.
Tests: 406 passed, 30 skipped, 58 xfailed (5 pre-existing
DynamoDB-unavailable errors unchanged); with DynamoDB local + dockerized PG
all 6 DDL tests pass.
Stack: P1 #2597, P2 #2598, P3 #2599, P4 this commit. Completes Stack 1
(Foundation); Stack 2 (provenance plumbing, P5-P8) follows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… pipeline entry points Implements Stack 2 / P5 of docs/STORAGE_V2_DESIGN.md (§4.4): the pipeline job id travels ON THE COMMAND LINE instead of the env-var-only propagation that reached 2 of 18 tables (§3.2). - delphi_storage/job_id.py — resolve_job_id: explicit CLI value > DELPHI_JOB_ID env (transition fallback, removed in a later phase) > auto local-<uuid4> for dev runs. - run_delphi.py — gains --job-id, resolves once, exports DELPHI_JOB_ID for un-migrated env readers (same id as the command lines), and appends --job-id=<id> to all six stage subprocesses (reset, math, umap 500s, 501, 502, 700 per layer). - scripts/job_poller.py — command construction extracted into module-level build_job_command() (now unit-testable); FULL_PIPELINE and CREATE_NARRATIVE_BATCH commands carry --job-id=<queue job_id>; the DELPHI_JOB_ID env export stays for the transition window. 803's existing --job-id (the batch-tracking id, a distinct concept) is unchanged. - Stage entry points all accept --job-id: reset_conversation (CLI moved into cli(), main() stays the worker), run_math_pipeline (arg + log only — no math-logic change), run_pipeline (threaded into process_conversation, replacing the buried env read; auto prefix changes pipeline_run_<uuid> → local-<uuid> for bare dev runs), 501, 502 (arg + log, used from P7), 700 (threaded down to the S3-key builder; 'unknown' fallback preserved), 801 (arg > env, NO auto-generation — narrative section keys embed the id, so a missing one must keep failing loudly downstream). Tests (TDD; tests/test_job_id_threading.py, RED first): - resolve_job_id precedence; run_delphi threads one shared id to all six stage commands (explicit, auto, and env-fallback variants); - every stage entry point's --help advertises --job-id; - process_conversation exposes the job_id parameter; - poller commands for all three job types (FULL_PIPELINE + 801 carry the queue job id; 803 keeps batch_job_id semantics). Full suite: 424 passed, 30 skipped, 58 xfailed (5 pre-existing DynamoDB-unavailable errors unchanged); golden snapshots ran and passed — math outputs untouched. Stack: P1 #2597, P2 #2598, P3 #2599, P4 #2600, P5 this commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… + purge tool
Implements the capture half of Stack 2 / P6 (docs/STORAGE_V2_DESIGN.md §4.2
entity 2, §4.4): reproducibility begins with recording EXACTLY what the
pipeline read. The read seam (--input-source, P6b) follows.
- delphi_storage/inputs.py — capture_run_inputs(store, job_id, zid, rid):
writes the six snapshot kinds as run_inputs items (codec envelopes,
zstd-compressed votes) and returns per-kind fingerprints (sha256 of the
canonical uncompressed payload, row_count, max vote created — the P7
manifest inputs). Key fidelity decisions, pinned by tests:
- votes = the RAW stream stage 1 consumes: ORDER BY created, raw PG signs
(the production math path never flips — fetch_votes()'s flip is dead
code), superseded votes included, order preserved (KMeans init depends
on vote-encounter order, INVESTIGATION_K_DIVERGENCE.md).
- The vote SQL is shared BY CONSTRUCTION: run_math_pipeline.py now imports
VOTES_COUNT_SQL / VOTES_BATCH_SQL from delphi_storage.inputs (strings
unchanged; golden suite + the e2e SQL pins stay green).
- comments/participants captured in FULL, including moderated-out rows
(mod = -1) and banned participants — filtering is pipeline behavior,
not snapshot behavior.
- clojure_math_main captured for ALL math_env rows (501 reads env-less
latest, 801 filters MATH_ENV with a different default — snapshotting
every env keeps both replayable).
- derive_votes_latest_unique() reproduces PG's RULE-maintained
votes_latest_unique from the stream (last vote per (pid, tid) in stream
order) — tested against a seeded PG table mimicking the RULE.
- delphi_storage/purge.py — purge_job(): deletes every generic-entity
partition for a job (the §9 GDPR path for snapshot copies).
- run_delphi.py — --snapshot-inputs flag (or DELPHI_SNAPSHOT_INPUTS=1):
captures before any stage runs, aborts the run on capture failure
(a run without recorded inputs defeats the point when enabled).
Off by default until P7's DELPHI_WRITE_MODE subsumes it.
Tests (TDD; tests/test_input_snapshots.py, RED first): 16 tests — raw
stream order/signs, compression, fingerprints, derivation-vs-PG-RULE,
full-row comments/participants, all-env math_main, SQL-parity-by-import,
purge, and the run_delphi hook (flag, env, off-by-default). PG-backed tests
use a seeded scratch database (skip-locally / fail-in-CI convention).
Full suite: 429 passed, 41 skipped, 58 xfailed (5 pre-existing
DynamoDB-unavailable errors unchanged); golden snapshots ran and passed —
math outputs untouched.
Stack: P1 #2597, P2 #2598, P3 #2599, P4 #2600, P5 #2601, P6a this commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
328f1de to
fd5037e
Compare
… + purge tool
Implements the capture half of Stack 2 / P6 (docs/STORAGE_V2_DESIGN.md §4.2
entity 2, §4.4): reproducibility begins with recording EXACTLY what the
pipeline read. The read seam (--input-source, P6b) follows.
- delphi_storage/inputs.py — capture_run_inputs(store, job_id, zid, rid):
writes the six snapshot kinds as run_inputs items (codec envelopes,
zstd-compressed votes) and returns per-kind fingerprints (sha256 of the
canonical uncompressed payload, row_count, max vote created — the P7
manifest inputs). Key fidelity decisions, pinned by tests:
- votes = the RAW stream stage 1 consumes: ORDER BY created, raw PG signs
(the production math path never flips — fetch_votes()'s flip is dead
code), superseded votes included, order preserved (KMeans init depends
on vote-encounter order, INVESTIGATION_K_DIVERGENCE.md).
- The vote SQL is shared BY CONSTRUCTION: run_math_pipeline.py now imports
VOTES_COUNT_SQL / VOTES_BATCH_SQL from delphi_storage.inputs (strings
unchanged; golden suite + the e2e SQL pins stay green).
- comments/participants captured in FULL, including moderated-out rows
(mod = -1) and banned participants — filtering is pipeline behavior,
not snapshot behavior.
- clojure_math_main captured for ALL math_env rows (501 reads env-less
latest, 801 filters MATH_ENV with a different default — snapshotting
every env keeps both replayable).
- derive_votes_latest_unique() reproduces PG's RULE-maintained
votes_latest_unique from the stream (last vote per (pid, tid) in stream
order) — tested against a seeded PG table mimicking the RULE.
- delphi_storage/purge.py — purge_job(): deletes every generic-entity
partition for a job (the §9 GDPR path for snapshot copies).
- run_delphi.py — --snapshot-inputs flag (or DELPHI_SNAPSHOT_INPUTS=1):
captures before any stage runs, aborts the run on capture failure
(a run without recorded inputs defeats the point when enabled).
Off by default until P7's DELPHI_WRITE_MODE subsumes it.
Tests (TDD; tests/test_input_snapshots.py, RED first): 16 tests — raw
stream order/signs, compression, fingerprints, derivation-vs-PG-RULE,
full-row comments/participants, all-env math_main, SQL-parity-by-import,
purge, and the run_delphi hook (flag, env, off-by-default). PG-backed tests
use a seeded scratch database (skip-locally / fail-in-CI convention).
Full suite: 429 passed, 41 skipped, 58 xfailed (5 pre-existing
DynamoDB-unavailable errors unchanged); golden snapshots ran and passed —
math outputs untouched.
Stack: P1 #2597, P2 #2598, P3 #2599, P4 #2600, P5 #2601, P6a this commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9ee292b to
5edc451
Compare
… + purge tool
Implements the capture half of Stack 2 / P6 (docs/STORAGE_V2_DESIGN.md §4.2
entity 2, §4.4): reproducibility begins with recording EXACTLY what the
pipeline read. The read seam (--input-source, P6b) follows.
- delphi_storage/inputs.py — capture_run_inputs(store, job_id, zid, rid):
writes the six snapshot kinds as run_inputs items (codec envelopes,
zstd-compressed votes) and returns per-kind fingerprints (sha256 of the
canonical uncompressed payload, row_count, max vote created — the P7
manifest inputs). Key fidelity decisions, pinned by tests:
- votes = the RAW stream stage 1 consumes: ORDER BY created, raw PG signs
(the production math path never flips — fetch_votes()'s flip is dead
code), superseded votes included, order preserved (KMeans init depends
on vote-encounter order, INVESTIGATION_K_DIVERGENCE.md).
- The vote SQL is shared BY CONSTRUCTION: run_math_pipeline.py now imports
VOTES_COUNT_SQL / VOTES_BATCH_SQL from delphi_storage.inputs (strings
unchanged; golden suite + the e2e SQL pins stay green).
- comments/participants captured in FULL, including moderated-out rows
(mod = -1) and banned participants — filtering is pipeline behavior,
not snapshot behavior.
- clojure_math_main captured for ALL math_env rows (501 reads env-less
latest, 801 filters MATH_ENV with a different default — snapshotting
every env keeps both replayable).
- derive_votes_latest_unique() reproduces PG's RULE-maintained
votes_latest_unique from the stream (last vote per (pid, tid) in stream
order) — tested against a seeded PG table mimicking the RULE.
- delphi_storage/purge.py — purge_job(): deletes every generic-entity
partition for a job (the §9 GDPR path for snapshot copies).
- run_delphi.py — --snapshot-inputs flag (or DELPHI_SNAPSHOT_INPUTS=1):
captures before any stage runs, aborts the run on capture failure
(a run without recorded inputs defeats the point when enabled).
Off by default until P7's DELPHI_WRITE_MODE subsumes it.
Tests (TDD; tests/test_input_snapshots.py, RED first): 16 tests — raw
stream order/signs, compression, fingerprints, derivation-vs-PG-RULE,
full-row comments/participants, all-env math_main, SQL-parity-by-import,
purge, and the run_delphi hook (flag, env, off-by-default). PG-backed tests
use a seeded scratch database (skip-locally / fail-in-CI convention).
Full suite: 429 passed, 41 skipped, 58 xfailed (5 pre-existing
DynamoDB-unavailable errors unchanged); golden snapshots ran and passed —
math outputs untouched.
Stack: P1 #2597, P2 #2598, P3 #2599, P4 #2600, P5 #2601, P6a this commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds the TypeScript “twin” implementation of Delphi Storage V2 under server/src/storage/delphi/, matching the Python repository abstraction and validating cross-language behavior by executing the shared JSON conformance case files in Jest.
Changes:
- Implemented Delphi Storage V2 repository interface + shared semantics (
completeRun,appendLog) in TypeScript. - Added DynamoDB, PostgreSQL, and in-memory backends with queue/lease/latest-pointer semantics and KV entity support.
- Added Jest unit + integration coverage that runs the shared conformance case scripts (and codec fixtures) against the TS backends.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| server/src/storage/delphi/postgresStore.ts | PostgreSQL backend implementation and schema DDL helpers. |
| server/src/storage/delphi/memoryStore.ts | In-memory reference backend used by unit conformance tests. |
| server/src/storage/delphi/keys.ts | Shared key/scope construction and UTF-8 byte-order compare utilities. |
| server/src/storage/delphi/interface.ts | Backend-neutral interface, manifest normalization/validation, and shared semantics (complete/log). |
| server/src/storage/delphi/factory.ts | Backend selection wiring via server config/env. |
| server/src/storage/delphi/errors.ts | Cross-language error classes and code mapping for conformance cases. |
| server/src/storage/delphi/dynamoStore.ts | DynamoDB backend with optimistic locking, claim-index querying, and chunked blob storage. |
| server/src/storage/delphi/codec.ts | Canonical JSON + f64 packing + zstd envelope codec (TS twin of Python). |
| server/src/config.ts | Adds Delphi storage env/config fields (backend selection + connection settings). |
| server/tests/unit/delphiStorage.memory.test.ts | Runs shared conformance cases + codec fixtures against memory backend. |
| server/tests/unit/delphiStorage.codec.test.ts | Unit tests for codec + key semantics. |
| server/tests/setup/delphi-storage-conformance.ts | Jest runner executing shared JSON operation-script case files. |
| server/tests/integration/delphiStorage.backends.test.ts | Conformance + race tests against real DynamoDB local and PostgreSQL. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async ensureSchema(): Promise<void> { | ||
| const client = await this.pool.connect(); | ||
| try { | ||
| for (const statement of schemaDdl(this.schema)) { | ||
| await client.query(statement); | ||
| } | ||
| } finally { | ||
| client.release(); | ||
| } | ||
| } |
| } catch (error) { | ||
| await client.query("ROLLBACK"); | ||
| throw error; | ||
| } finally { | ||
| client.release(); | ||
| } |
5edc451 to
a8ff600
Compare
fd5037e to
3c07e95
Compare
…ckends + jest conformance) Implements Stack 1 / P3 of delphi/docs/STORAGE_V2_DESIGN.md: the TypeScript side of the two-sided storage abstraction (§3.6 — both the Python pipeline AND the server touch the store), kept honest by executing the SAME JSON conformance case files as the pytest suite (delphi/delphi_storage/conformance/cases/). - server/src/storage/delphi/: errors, keys, codec (canonical JSON with UTF-8-byte-order key sort, little-endian packed f64, zstd via Node's built-in zlib — Node >= 22.15), interface (snake_case wire fields shared with Python, normalizeRunManifest validation, BaseDelphiStore with the shared completeRun/appendLog semantics), memoryStore, dynamoStore, postgresStore, factory. - dynamoStore: SDK v3 DocumentClient WITHOUT convertEmptyValues (empty strings must round-trip), conditional writes on version, sparse claim-index GSI, generation-tagged chunk rows for blobs >300KB with the main item as commit point (crash-safe, matching the Python backend). - postgresStore: pg Pool, COLLATE "C" byte-order keys, FOR UPDATE SKIP LOCKED claims, JSONB manifests — same DDL as the Python backend and the upcoming migration 000019 (P4). - config.ts: delphiStorageBackend / delphiStorageTablePrefix / delphiStoragePgSchema / delphiStoragePgUrl (env read stays confined to config.ts per the lint rule). - __tests__/setup/delphi-storage-conformance.ts: the jest case runner (twin of runner.py). Unit: codec/keys pins + all 8 shared cases on the memory backend + Python-encoded zstd fixture decoding. Integration: all cases + a Promise.all two-claimants race against real DynamoDB and PostgreSQL. Tests: 37 unit + 16 integration pass (DynamoDB local :8002, dockerized PG 16); tsc build clean; eslint clean. The 12 pre-existing JWT-env unit failures reproduce identically on the base commit (missing local JWT keys, unrelated). Part of the storage-v2 stack: P1 #2597 (design), P2 #2598 (Python package), P4 (DDL) next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3c07e95 to
afbe110
Compare
a8ff600 to
9ff55da
Compare
Stack 1 / P3 of the Storage V2 effort — the TypeScript twin of #2598, per
delphi/docs/STORAGE_V2_DESIGN.md§3.6: the abstraction must be two-sided, because both the Python pipeline and the server touch the store directly.Stacked on #2598 (
jc/delphi-storage-v2-p2) — it needs the conformance case files from that PR at test time (same repo checkout, no build coupling).What this adds
server/src/storage/delphi/— the TypeScript implementation of the same backend-neutral repository, kept honest by executing the same JSON conformance case files (delphi/delphi_storage/conformance/cases/) as the pytest suite:errors.ts,keys.ts,codec.ts— same wire format: canonical JSON (keys sorted by UTF-8 byte order, matching Python'ssort_keys), little-endian packed float64, zstd via Node's built-inzlibzstd support (Node ≥ 22.15; no new npm dependency). The jest suite decodes zstd blobs that were encoded by Python and committed as fixtures — the cross-language pin.interface.ts— method names camelCase, data fields snake_case (manifests/pointers are shared wire objects written by Python and read here, and vice versa);normalizeRunManifestmirrors the Pydantic validation;BaseDelphiStorecarries the sharedcompleteRun(scopes derived before the status flip; pointer written last as the commit point; idempotent + crash-healing) andappendLogsemantics.dynamoStore.ts— SDK v3DynamoDBDocumentClientwithoutconvertEmptyValues(deliberate deviation from the older house pattern: empty strings must round-trip); optimistic-lock conditional writes onversion; sparseclaim-indexGSI; generation-tagged chunk rows for blobs >300KB with the main item as the atomic commit point — the same crash-safe protocol as the Python backend (see the review fixes on feat(delphi): storage v2 P2 — backend-neutral delphi_storage repository (dual backends + conformance) #2598).postgresStore.ts—pgPool,COLLATE "C"sort keys,FOR UPDATE SKIP LOCKEDclaims, JSONB manifests; DDL identical to the Python backend's (migration000019in P4 materializes it).memoryStore.ts— reference implementation (internally synchronous, so concurrent async callers can't interleave inside an op).factory.ts+config.ts—DELPHI_STORAGE_BACKEND/DELPHI_STORAGE_TABLE_PREFIX/DELPHI_STORAGE_PG_SCHEMA/DELPHI_STORAGE_PG_URL, env access confined toconfig.tsper the lint rule.Tests
__tests__/setup/delphi-storage-conformance.ts— the jest case runner (twin ofrunner.py).Promise.alltwo-claimants-one-wins race against real DynamoDB and PostgreSQL. 16 tests.Testing
:8002, dockerized PG 16).tscbuild clean;eslintclean (0 errors).Stack
delphi_storage/)create_dynamodb_tables.pyadditions + PG migration000019(next)🤖 Generated with Claude Code