Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/tls-and-transaction-refs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
---

fix(storage): negotiate TLS, and keep an enqueued id addressable inside its transaction

`sqlx` was selected with `tls-none`, so a database that requires `sslmode=require`
could not be reached at all (#911). The workspace dependency now enables
`tls-rustls-ring-webpki` — ring with webpki roots, no native-tls — and the TLS
path now has a committed regression test: the parity target requires a
`sslmode=require` connection to be attempted rather than refused by the build,
and completes `SELECT 1` when the server offers TLS.

The parity harness's `run_transaction` handed every nested op a fresh refs map,
so an `outbox.enqueue` followed by `outbox.mark_delivered` in one group lost the
id and came back as a failed transaction (#912). The caller's map is threaded
through the nested calls, the capture generator does the same, and a new
transaction group in the fixture enqueues and then marks the row delivered, so
the TypeScript capture and the Rust replay agree on the id.
181 changes: 171 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
sha2 = "0.11.1"
sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "postgres", "migrate", "macros", "chrono", "json", "tls-none"] }
sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "postgres", "migrate", "macros", "chrono", "json", "tls-rustls-ring-webpki"] }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 TLS support lacks regression coverage

The TLS fix has no committed regression test. The database-backed CI tests use PostgreSQL without TLS, so switching sqlx back to tls-none could pass those tests while breaking TLS-required connections again. Keep a test that connects to a TLS-enabled PostgreSQL server with sslmode=require and runs a query.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

tempfile = "3"
thiserror = "2"
tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "signal", "sync", "time"] }
Expand Down
17 changes: 14 additions & 3 deletions crates/nostream-storage/tests/capture/gen-storage-parity.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,17 @@ const script = [
},
},
{ op: 'outbox.claim_batch', request: { limit: 10 } },
// One group enqueues and then marks the same row delivered: the enqueue's id has to be
// addressable by the next op, inside the transaction.
{
op: 'transaction',
request: {
ops: [
{ op: 'outbox.enqueue', request: { event_type: 'transaction.enqueue.mark', payload: { probe: 'marked' } } },
{ op: 'outbox.mark_delivered', request: { enqueue_event_type: 'transaction.enqueue.mark' } },
],
},
},
]

// ---------------------------------------------------------------------------------------
Expand Down Expand Up @@ -1278,8 +1289,8 @@ const resolveId = (request) => {
return value
}

const runOp = async (op, client) => {
const result = await run(op, client)
const runOp = async (op, client, inTransaction = false) => {
const result = await run(op, client, inTransaction)
if (op.op === 'outbox.enqueue') {
const message = result.compare.message
// The id is not part of the comparison; it is needed to address the row later.
Expand All @@ -1300,7 +1311,7 @@ const runTransaction = async (op) => {
await db.transaction(async (trx) => {
for (const [index, nested] of op.request.ops.entries()) {
try {
executed.push(await run(nested, trx, true))
executed.push(await runOp(nested, trx, true))
} catch (error) {
failedAt = index
throw error
Expand Down
Loading
Loading