also looking at this
fix: qualify registration expiry across native commit
#75
2 files
+35
-8
| @@ -193,9 +193,10 @@ try { | ||
| 193 | 193 | } |
| 194 | 194 | finally { restoreAudit() } |
| 195 | 195 | |
| 196 | // The native actual COMMIT has completed before its hook delays response release. | |
| 197 | // Refusal after this boundary cannot undo SQL or its content-free refresh hint. | |
| 198 | for (const kind of ['result', 'token', 'incident'] as const) { | |
| 196 | // Expiry can occur during native COMMIT or while its response is held. In both | |
| 197 | // cases the expired result must be refused without pretending SQL rolled back. | |
| 198 | let heldResponseCases = 0 | |
| 199 | for (const kind of ['result', 'token', 'incident', 'commit-crossing'] as const) { | |
| 199 | 200 | const committed = await create(`registration-commit-${kind}`) |
| 200 | 201 | let token = f.senderToken, deadline = 0 |
| 201 | 202 | if (kind === 'token') { |
| @@ -210,20 +211,42 @@ try { | ||
| 210 | 211 | writer.query('UPDATE public_safety_incidents SET config_json = ?, retain_until = ? WHERE incident_key = ?').run(publicSafetyRecordingData(committed.config), committed.config.retention.expiresAt, committed.config.id) |
| 211 | 212 | } |
| 212 | 213 | const before = await f.snapshot(committed.config.id), hintCount = hints.length |
| 213 | let actualCommits = 0 | |
| 214 | f.database.db.primary.setTransactionDefaults({ afterCommit: async () => { | |
| 214 | let actualCommits = 0, crossingCallbacks = 0, nativeReturnMarginMs: number | null = null, refusalObserved = false | |
| 215 | f.database.db.primary.setTransactionDefaults({ logger: event => { | |
| 216 | // QB's "commit" logger is inside the transaction callback, before native | |
| 217 | // begin() resolves. This real elapsed pause exercises expiry across COMMIT. | |
| 218 | if (kind !== 'commit-crossing' || event.type !== 'commit') return | |
| 219 | crossingCallbacks++ | |
| 220 | const waitMs = Math.max(1, deadline - Date.now() + 20) | |
| 221 | assert(waitMs <= 600, 'Controlled commit crossing retains its bounded 500 ms lifetime') | |
| 222 | Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, waitMs) | |
| 223 | assert(Date.now() >= deadline, 'Controlled callback pause actually crosses expiry') | |
| 224 | }, afterCommit: async () => { | |
| 215 | 225 | actualCommits++ |
| 216 | assert(Date.now() < deadline, 'Actual SQL COMMIT precedes the scoped deadline') | |
| 226 | // A separate native SQLite connection proves this writer's change is | |
| 227 | // committed. Hook receipt time is an upper bound, not a SQL commit clock. | |
| 228 | const persisted = writer.query('SELECT version, (SELECT COUNT(*) FROM public_safety_sources WHERE incident_id = public_safety_incidents.id) AS source_count FROM public_safety_incidents WHERE incident_key = ?').get(committed.config.id) as { version: number, source_count: number } | |
| 229 | assert.deepEqual(persisted, { version: 2, source_count: 1 }) | |
| 230 | nativeReturnMarginMs = deadline - Date.now() | |
| 231 | if (nativeReturnMarginMs > 0) heldResponseCases++ | |
| 232 | if (kind === 'commit-crossing') assert(nativeReturnMarginMs <= 0, 'Explicit crossing arrives after expiry') | |
| 217 | 233 | await Bun.sleep(Math.max(1, deadline - Date.now() + 20)) |
| 234 | assert(Date.now() >= deadline, 'Response release actually follows the scoped expiry') | |
| 218 | 235 | } }) |
| 219 | 236 | try { |
| 220 | 237 | await assert.rejects(() => f.as(token, () => f.records.registerPublicSafetySourceChecked(committed.config.id, { expectedVersion: 1, recording: committed.run }, async () => { |
| 221 | if (kind === 'result') { deadline = Date.now() + 500; return deadline } | |
| 238 | if (kind === 'result' || kind === 'commit-crossing') { deadline = Date.now() + 500; return deadline } | |
| 222 | 239 | return Date.now() + 30000 |
| 223 | 240 | })), f.status(404)) |
| 241 | refusalObserved = true | |
| 242 | } | |
| 243 | finally { | |
| 244 | f.database.db.primary.setTransactionDefaults({ afterCommit: undefined, logger: undefined }) | |
| 245 | const phase = nativeReturnMarginMs === null ? 'native-return-unobserved' : nativeReturnMarginMs > 0 ? 'held-after-native-return' : 'expired-at-native-return' | |
| 246 | console.error(`REGISTRATION_COMMIT_BOUNDARY ${JSON.stringify({ kind, phase, actualCommits, crossingCallbacks, nativeReturnMarginMs, refusalObserved, refusalMarginMs: deadline - Date.now() })}`) | |
| 224 | 247 | } |
| 225 | finally { f.database.db.primary.setTransactionDefaults({ afterCommit: undefined }) } | |
| 226 | 248 | assert.equal(actualCommits, 1) |
| 249 | assert.equal(crossingCallbacks, kind === 'commit-crossing' ? 1 : 0) | |
| 227 | 250 | const after = await f.snapshot(committed.config.id) |
| 228 | 251 | assert.equal(after.incident!.version, 2) |
| 229 | 252 | assert.equal(after.audits.length, before.audits.length + 1) |
| @@ -233,6 +256,7 @@ try { | ||
| 233 | 256 | assert.equal((writer.query('SELECT COUNT(*) AS count FROM public_safety_sources WHERE incident_id = ?').get(after.incident!.id as number) as { count: number }).count, 1) |
| 234 | 257 | if (kind !== 'incident') assert.equal((await sources(committed.config.id)).entries.length, 1) |
| 235 | 258 | } |
| 259 | assert(heldResponseCases > 0, 'At least one actual native return precedes expiry and the held response crosses it') | |
| 236 | 260 | console.log('PASS native guarded source registration compatibility, authority, rollback and COMMIT deadlines') |
| 237 | 261 | } |
| 238 | 262 | finally { unhookHints(); setServer(null); writer.close(); await f.database.closeDatabaseConnection() } |