diff --git a/.github/review-media-sql-successor.v1.json b/.github/review-media-sql-successor.v1.json index 557b276779..d60b3b3dcb 100644 --- a/.github/review-media-sql-successor.v1.json +++ b/.github/review-media-sql-successor.v1.json @@ -309,7 +309,7 @@ "toolchain": [ { "path": "apps/web/scripts/review-media-sql-successor.mjs", - "sha256": "5d61808b45661d412937fbfdfd3bfb75b1cf92c7c285e4d8ad061a2f667c3c0b" + "sha256": "4f1b2798e0cfc13db4f11cd6eb4a8653320aa8516eccd71d410fcff6d06a7f24" }, { "path": "apps/web/scripts/review-media-sql-rehearsal.mjs", diff --git a/apps/web/performance/review-db-rehearsal-20261012/README.md b/apps/web/performance/review-db-rehearsal-20261012/README.md index 35a4bb422e..781d456487 100644 --- a/apps/web/performance/review-db-rehearsal-20261012/README.md +++ b/apps/web/performance/review-db-rehearsal-20261012/README.md @@ -113,3 +113,17 @@ SUCCESSOR_ADMISSION_EXPIRED and confirmed exact baseline restoration from a sepa read-only connection. This explicitly labelled fault injection proves the final guard/rollback path; it does not measure an actual fifteen-minute operating lease. The original migration statements and statement vectors remain unchanged. +# Review-fix verification — 2026-10-14 + +The current selector now points to `pg17-review-fixes-20261014-v1.json`. +It binds the changed successor manifest and the unchanged rollback harness. +Twelve stage readbacks, exact rollback, forced failure, lost acknowledgement +with one mutation attempt, real contended expiry, and apply-final deadline +rejection passed on isolated PostgreSQL 17.6. The owned container was removed. +No hosted rows or operating changes were involved. + +The first PR3202 Ubuntu npm full suite correctly rejected stale dependency +hashes in this directory's complete artifact map. Its bounded failure receipt +and the old map, pin and selector are retained. The current map binds current +source bytes while historical proofs remain historical; the new selector is +backed by a fresh replay, not by relabelling an older proof. diff --git a/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS b/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS index f12e0be4cc..a70e1ddfd0 100644 --- a/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS +++ b/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS @@ -1,28 +1,36 @@ -23bf7e7d7e9e5d7962021dd3ec860e87b47d07e119ba0e39de2e423a0a601f4e .github/review-media-sql-successor.v1.json -ac14e363f38d6cb6acba0e24a5ad6c6943c8e8c15152cff49fcd86a2d014bd9d apps/web/performance/review-db-rehearsal-20261012/README.md +295b2354c562ed87102c5e256497a2705f90941587fefa7e9ad8a4ba8e40787f .github/review-media-sql-successor.v1.json +ef4c24b489343213733ca719b1fca4cb456d4cdd8510e21e6f5c038d5b968b63 apps/web/performance/review-db-rehearsal-20261012/README.md ea529addcf42cdb766f622786ccfffb13fe337f70faae6ade6da83736be58f57 apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-0833ed81-historical +d8ad511edf7d3832aa432e499b27f33e2c9ad423530ffdff06dea42869eaff85 apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-4ba9c6d2-historical +756ead633ae6d2a7805043203c6781d0726cdd3042980031ebc508028f0d3621 apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-4ba9c6d2-historical.sha256 6d9cbca375e6790ee1051aa9d9bf1c45beeeece8d919651281885b4c04962981 apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-4d4ecf60-historical 5df2ade9cb3618b774eb4d3796df06ade6ba069409de49d0bdd542b34b6ec4ba apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-7d111349-historical 7d395c9246782322ccc326dd99ab0d5e94e0d82e203eb519a6baf91a4a62ece2 apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-7d111349-historical.sha256 6c474d8e78c0eba3f15c077dbdd8634f073524c3b5f842213372bf0f1fed84fc apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-pre-hardening-historical +88a814a1fc0d9b8a8fa7bc6df299d9374bef5f97595736e72579deeb2c475456 apps/web/performance/review-db-rehearsal-20261012/ci-npm-failure-20261014-v1.json 75b801b80a2d23a7c415085e8fb9b187b03469d2c2d861ba3279d52b2ba7545c apps/web/performance/review-db-rehearsal-20261012/fixture-url-unit-verification-20261013.json a12f8880f2a0b8b5493e37ea4b2619962d42643581c14c889f0c6f6e8d528129 apps/web/performance/review-db-rehearsal-20261012/interrupted-attempt-20261013.json +23fb9b471a06dbd36aafab3a678152ca897893b3cdc968f2d623035d217b1e38 apps/web/performance/review-db-rehearsal-20261012/pg17-review-fixes-20261014-v1.json +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 apps/web/performance/review-db-rehearsal-20261012/pg17-review-fixes-20261014-v1.stderr.log +97682860c19c701a9062a409160bd0e9f2f692f7e296118c48cfba75ad2cc5f9 apps/web/performance/review-db-rehearsal-20261012/pg17-review-fixes-20261014-v1.stdout.log 9c73b47c0841f2585d7ceee77423d27410a488e61080004cf227d4329dd88076 apps/web/performance/review-db-rehearsal-20261012/pg17-reviewed-boundaries-20261013.json b279a6b900dd3be10b9084a6362d169e87435bae955b2a1419858709645a6883 apps/web/performance/review-db-rehearsal-20261012/pg17-rollback-rehearsal-current-20261013.json 7f74c175be3eee72b5e93eb75dac07bfefc0ef88d7d9957c26d877ee125ff590 apps/web/performance/review-db-rehearsal-20261012/pg17-rollback-rehearsal-final-20261013.json cb766e279baad0315e9d7055bc326b41b78a7d50a4972dac3214bc071aee0e49 apps/web/performance/review-db-rehearsal-20261012/pg17-rollback-rehearsal.json 0cfc9c10ff7b70c606f6eeef33fff8dbd14636dc15b7c963925097454ee454a2 apps/web/performance/review-db-rehearsal-20261012/pg17-terminal-boundaries-20261013.json 128773e5dabbbb120889716ffbba41e498a37a437aa6799311a81af729703880 apps/web/performance/review-db-rehearsal-20261012/retained-current-proof-0833ed81-historical.json -78f3e0ad5dbc4e3c53cf8dce3b2a9cb2f484f8f72683cbf1a5c659c8f142acb6 apps/web/performance/review-db-rehearsal-20261012/retained-current-proof.json +71ff7b8dde2ff6ef936a012b84453fcc0990bd9724d89b046bb1ebaa7bfddf1e apps/web/performance/review-db-rehearsal-20261012/retained-current-proof.json +78f3e0ad5dbc4e3c53cf8dce3b2a9cb2f484f8f72683cbf1a5c659c8f142acb6 apps/web/performance/review-db-rehearsal-20261012/retained-current-proof.json-4ba9c6d2-historical +20aa7a87b82f034eac71b843b6d5ab4547c4b6e0f6bba950b3e0b7484b2952ff apps/web/performance/review-db-rehearsal-20261012/review-fix-index-units-20261014-v1.log 7bdf3cd525427da824674ce50cb3ba7c5d54bf6cb0ef8ebe85b21bb2372eae0a apps/web/performance/review-db-rehearsal-20261012/reviewed-boundaries-unit-verification-20261013.json c53a467eaf9b929647bc1d66248308ba1ff73eba89fc9221dbf7db4711eb8562 apps/web/performance/review-db-rehearsal-20261012/scanner-readback.json fad17a393e5fc74d06dec2be58c41265d21f355d5ddce92a3947bb67a823e73a apps/web/performance/review-db-rehearsal-20261012/terminal-boundary-unit-verification-20261013.json f7321f607a5bf3a24ba099d51abc67254db52fcc385945692426d1b2dfa6e431 apps/web/performance/review-db-rehearsal-20261012/verify-review-media-sql-rehearsal.mjs -768b8887a30c2b8f496a73a21d373b85a0b64fadaed3a1485034a729ee704d39 apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs +7e750e9d0126f6f10afe9056d747a2ea854d462161ea4e8f0168e2751d554c0a apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs c893c294719776bf5b064c3355866ab97e0fa9412082d9b47d7de5266094c65f apps/web/scripts/review-media-sql-rehearsal.mjs -5d61808b45661d412937fbfdfd3bfb75b1cf92c7c285e4d8ad061a2f667c3c0b apps/web/scripts/review-media-sql-successor.mjs +4f1b2798e0cfc13db4f11cd6eb4a8653320aa8516eccd71d410fcff6d06a7f24 apps/web/scripts/review-media-sql-successor.mjs fcebc20dc4846606dc14bdbe658a07daa32430ad632e52383151f5c22d22efb9 apps/web/scripts/supabase-migration-bundle.mjs dc1f2594e629d5af16541db93e2e51f9b98d8282dd7fbeb802dd240fa6f522c0 apps/web/tests-unit/review-media-migration-bundle.test.ts 54a9560b77a3afb9eb70631224ba4f481caf82eb8b5d2cdc1a076d8831c1ec7e apps/web/tests-unit/review-media-sql-rehearsal-verifier.test.ts -983a62a3f81973e18914835b2d483a7a05fef3fa9f7d129ffd7a60ed5cd4e551 apps/web/tests-unit/review-media-sql-successor-harness.test.ts -f74f30aa6f1237a757c7fd18693fa839cb4c199409a87d870260ec574bfc5d33 apps/web/tests-unit/review-media-sql-successor.test.ts +99c6bc0221188f93317640d4d65affdfeb91fea233bb0f0cc0f409407e419d84 apps/web/tests-unit/review-media-sql-successor-harness.test.ts +f1ce764e0bffea5c05cbca0017574b305c353f9498623df928a177fee3b780ff apps/web/tests-unit/review-media-sql-successor.test.ts diff --git a/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-4ba9c6d2-historical b/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-4ba9c6d2-historical new file mode 100644 index 0000000000..f12e0be4cc --- /dev/null +++ b/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-4ba9c6d2-historical @@ -0,0 +1,28 @@ +23bf7e7d7e9e5d7962021dd3ec860e87b47d07e119ba0e39de2e423a0a601f4e .github/review-media-sql-successor.v1.json +ac14e363f38d6cb6acba0e24a5ad6c6943c8e8c15152cff49fcd86a2d014bd9d apps/web/performance/review-db-rehearsal-20261012/README.md +ea529addcf42cdb766f622786ccfffb13fe337f70faae6ade6da83736be58f57 apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-0833ed81-historical +6d9cbca375e6790ee1051aa9d9bf1c45beeeece8d919651281885b4c04962981 apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-4d4ecf60-historical +5df2ade9cb3618b774eb4d3796df06ade6ba069409de49d0bdd542b34b6ec4ba apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-7d111349-historical +7d395c9246782322ccc326dd99ab0d5e94e0d82e203eb519a6baf91a4a62ece2 apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-7d111349-historical.sha256 +6c474d8e78c0eba3f15c077dbdd8634f073524c3b5f842213372bf0f1fed84fc apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-pre-hardening-historical +75b801b80a2d23a7c415085e8fb9b187b03469d2c2d861ba3279d52b2ba7545c apps/web/performance/review-db-rehearsal-20261012/fixture-url-unit-verification-20261013.json +a12f8880f2a0b8b5493e37ea4b2619962d42643581c14c889f0c6f6e8d528129 apps/web/performance/review-db-rehearsal-20261012/interrupted-attempt-20261013.json +9c73b47c0841f2585d7ceee77423d27410a488e61080004cf227d4329dd88076 apps/web/performance/review-db-rehearsal-20261012/pg17-reviewed-boundaries-20261013.json +b279a6b900dd3be10b9084a6362d169e87435bae955b2a1419858709645a6883 apps/web/performance/review-db-rehearsal-20261012/pg17-rollback-rehearsal-current-20261013.json +7f74c175be3eee72b5e93eb75dac07bfefc0ef88d7d9957c26d877ee125ff590 apps/web/performance/review-db-rehearsal-20261012/pg17-rollback-rehearsal-final-20261013.json +cb766e279baad0315e9d7055bc326b41b78a7d50a4972dac3214bc071aee0e49 apps/web/performance/review-db-rehearsal-20261012/pg17-rollback-rehearsal.json +0cfc9c10ff7b70c606f6eeef33fff8dbd14636dc15b7c963925097454ee454a2 apps/web/performance/review-db-rehearsal-20261012/pg17-terminal-boundaries-20261013.json +128773e5dabbbb120889716ffbba41e498a37a437aa6799311a81af729703880 apps/web/performance/review-db-rehearsal-20261012/retained-current-proof-0833ed81-historical.json +78f3e0ad5dbc4e3c53cf8dce3b2a9cb2f484f8f72683cbf1a5c659c8f142acb6 apps/web/performance/review-db-rehearsal-20261012/retained-current-proof.json +7bdf3cd525427da824674ce50cb3ba7c5d54bf6cb0ef8ebe85b21bb2372eae0a apps/web/performance/review-db-rehearsal-20261012/reviewed-boundaries-unit-verification-20261013.json +c53a467eaf9b929647bc1d66248308ba1ff73eba89fc9221dbf7db4711eb8562 apps/web/performance/review-db-rehearsal-20261012/scanner-readback.json +fad17a393e5fc74d06dec2be58c41265d21f355d5ddce92a3947bb67a823e73a apps/web/performance/review-db-rehearsal-20261012/terminal-boundary-unit-verification-20261013.json +f7321f607a5bf3a24ba099d51abc67254db52fcc385945692426d1b2dfa6e431 apps/web/performance/review-db-rehearsal-20261012/verify-review-media-sql-rehearsal.mjs +768b8887a30c2b8f496a73a21d373b85a0b64fadaed3a1485034a729ee704d39 apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs +c893c294719776bf5b064c3355866ab97e0fa9412082d9b47d7de5266094c65f apps/web/scripts/review-media-sql-rehearsal.mjs +5d61808b45661d412937fbfdfd3bfb75b1cf92c7c285e4d8ad061a2f667c3c0b apps/web/scripts/review-media-sql-successor.mjs +fcebc20dc4846606dc14bdbe658a07daa32430ad632e52383151f5c22d22efb9 apps/web/scripts/supabase-migration-bundle.mjs +dc1f2594e629d5af16541db93e2e51f9b98d8282dd7fbeb802dd240fa6f522c0 apps/web/tests-unit/review-media-migration-bundle.test.ts +54a9560b77a3afb9eb70631224ba4f481caf82eb8b5d2cdc1a076d8831c1ec7e apps/web/tests-unit/review-media-sql-rehearsal-verifier.test.ts +983a62a3f81973e18914835b2d483a7a05fef3fa9f7d129ffd7a60ed5cd4e551 apps/web/tests-unit/review-media-sql-successor-harness.test.ts +f74f30aa6f1237a757c7fd18693fa839cb4c199409a87d870260ec574bfc5d33 apps/web/tests-unit/review-media-sql-successor.test.ts diff --git a/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-4ba9c6d2-historical.sha256 b/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-4ba9c6d2-historical.sha256 new file mode 100644 index 0000000000..98e05b4991 --- /dev/null +++ b/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS-4ba9c6d2-historical.sha256 @@ -0,0 +1 @@ +d8ad511edf7d3832aa432e499b27f33e2c9ad423530ffdff06dea42869eaff85 diff --git a/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS.sha256 b/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS.sha256 index 98e05b4991..290cf460a0 100644 --- a/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS.sha256 +++ b/apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS.sha256 @@ -1 +1 @@ -d8ad511edf7d3832aa432e499b27f33e2c9ad423530ffdff06dea42869eaff85 +c1af1afd832bd2b0bbdcd4419589de778525e1b18a4dff196e457b82381c97fb diff --git a/apps/web/performance/review-db-rehearsal-20261012/ci-npm-failure-20261014-v1.json b/apps/web/performance/review-db-rehearsal-20261012/ci-npm-failure-20261014-v1.json new file mode 100644 index 0000000000..970d468726 --- /dev/null +++ b/apps/web/performance/review-db-rehearsal-20261012/ci-npm-failure-20261014-v1.json @@ -0,0 +1,19 @@ +{ + "observedAtUTC": "2026-10-13T20:31:32.330565+00:00", + "sourceRevision": "71dcfc98ab03a016d05449780574a4b9edd81e36", + "runId": 38461911693, + "jobId": 115518972088, + "job": "Ubuntu npm", + "boundedFailure": "LOCAL_REHEARSAL_ARTIFACT_SHA256_MISMATCH", + "failedTest": "default retained proof uses a stable current-proof selector", + "summary": [ + "(pass) verified review count pagination > thrown page 2 failure rejects instead of returning partial counts [0.01ms]", + " 3318 pass", + " 21 skip", + " 1 fail", + "Ran 3340 tests across 420 files. [164.92s]" + ], + "cause": "Rollback evidence map retained hashes for the changed successor controller, manifest and fixture helper. The full-map validator correctly rejected stale dependencies.", + "fullProviderLogPersistedInRepository": false, + "operatingEvidence": false +} diff --git a/apps/web/performance/review-db-rehearsal-20261012/pg17-review-fixes-20261014-v1.json b/apps/web/performance/review-db-rehearsal-20261012/pg17-review-fixes-20261014-v1.json new file mode 100644 index 0000000000..fac22e271d --- /dev/null +++ b/apps/web/performance/review-db-rehearsal-20261012/pg17-review-fixes-20261014-v1.json @@ -0,0 +1,72 @@ +{ + "schemaVersion": 1, + "status": "passed", + "environment": { + "engine": "17.6", + "image": "supabase/postgres@sha256:fbe6c858a2ea9616aead3c39e132afcc0660decc5cb7dc656208ed145f5331ab", + "emptyRowChecks": [ + "public.profiles", + "public.reviews" + ] + }, + "manifestSha256": "295b2354c562ed87102c5e256497a2705f90941587fefa7e9ad8a4ba8e40787f", + "manifestToolchainRoot": "6409387ff1da0bb5e499dab76e4b0b7326a9a81aa71fd21f2d51682a48df0a2f", + "rehearsalHarnessSha256": "f7321f607a5bf3a24ba099d51abc67254db52fcc385945692426d1b2dfa6e431", + "sourceRoot": "61f9d7637c0714a7b0d9108c6f7959ea820175a7870dba681498b570296a1544", + "fixtureDumpSha256": "b7777306a43cc84408434e9835973604b2eb520cf9e9e8d0ee4bba6dcb41257f", + "predecessorSources": [ + { + "path": "backend/supabase/migrations/20261004190259_admin_record_guarded_actions.sql", + "sha256": "b373b7ea472c0352a33a4d4043cf8d6aa8474c04cc1ca5778805edfa77ac9c95" + }, + { + "path": "backend/supabase/migrations/20261004192657_admin_evaluation_raw_warning_groups.sql", + "sha256": "66eace1d6fb0dd55c1d780776bab855cc37690335ad40b0fdc1294c610e07d3a" + }, + { + "path": "backend/supabase/migrations/20261004194715_admin_evaluation_raw_warning_invoker_contract.sql", + "sha256": "e1c105df82c4f814d3e6adad807ff9d072b8cd42ae770b4b78f75b89a9387020" + }, + { + "path": "backend/supabase/migrations/20261009022915_restaurant_review_manual_preview_eligibility.sql", + "sha256": "8acf6d1428764260ed57dac5fe09711868a82896f0a6d631d502128004c168a3" + }, + { + "path": "backend/supabase/migrations/20261009091342_admin_record_private_verification_cleanup.sql", + "sha256": "b596b200e52c6813a4cfa1b0a2818625f067864549e8854f3497afdcdab706da" + }, + { + "path": "backend/supabase/migrations/20261009101645_admin_user_management_rpc_forward.sql", + "sha256": "2067538f89c9f90d28e784672c7a1288306ba22d5ae92b087c8692503da9b1ae" + } + ], + "migrationCount": 11, + "statementCount": 112, + "baselineStateSha256": "3802888324e1651ad7b2f71e7cf15fac78ab77aa57637d0c60431f8da67e6298", + "stageStatesSha256": "bd25ecbd82759ca75e994ca0f1da38b49acc848d5dd556e702e4e43024edaa65", + "terminalStateSha256": "9cf59a5d983e78f6f7d9a594267c6feda3276611bf3539289b8a3de3295f700a", + "compiledSqlBytes": 2510560, + "compiledSqlSha256": "285d0c859117006646389dbd2a22de9aef1c0b97bc796a9e4d4448a8a232b093", + "applyCompiledSqlSha256": "91e378c58d58d7d7f818464c9c92aff02c7b01ca528e9db858e2b1d2d00bd92f", + "applyExpiryFaultedSqlSha256": "727ff0f6be2dbf8ee06551d4efd6ebe665f7187189f7a0a1159d5281ae90635b", + "results": { + "rollbackStageCount": 12, + "rollbackExact": true, + "forcedErrorRejected": true, + "forcedErrorRollbackExact": true, + "lostResponseMutationAttempts": 1, + "lostResponseRestorationExact": true, + "contendedExpiryRejected": true, + "contendedExpiryRollbackExact": true, + "applyFinalGuardDeadlineFaultInjected": true, + "applyTerminalReadbackObserved": true, + "applyExpiryRejectedBeforeCommit": true, + "applyExpiryMutationAttempts": 1, + "applyExpiryRollbackExact": true + }, + "hostedWrites": false, + "operatingRowsRead": false, + "rawSqlInProof": false, + "sourceMutation": false, + "temporaryContainerRemoved": true +} diff --git a/apps/web/performance/review-db-rehearsal-20261012/pg17-review-fixes-20261014-v1.stderr.log b/apps/web/performance/review-db-rehearsal-20261012/pg17-review-fixes-20261014-v1.stderr.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/web/performance/review-db-rehearsal-20261012/pg17-review-fixes-20261014-v1.stdout.log b/apps/web/performance/review-db-rehearsal-20261012/pg17-review-fixes-20261014-v1.stdout.log new file mode 100644 index 0000000000..429c11d1fc --- /dev/null +++ b/apps/web/performance/review-db-rehearsal-20261012/pg17-review-fixes-20261014-v1.stdout.log @@ -0,0 +1 @@ +{"kind":"review-media-sql-local-rehearsal","retainedProof":{"present":true,"classification":"retained-historical-evidence","repositoryPath":"apps/web/performance/review-db-rehearsal-20261012/pg17-reviewed-boundaries-20261013.json","selector":{"repositoryPath":"apps/web/performance/review-db-rehearsal-20261012/retained-current-proof.json","sha256":"78f3e0ad5dbc4e3c53cf8dce3b2a9cb2f484f8f72683cbf1a5c659c8f142acb6","proofPath":"pg17-reviewed-boundaries-20261013.json","proofSha256":"9c73b47c0841f2585d7ceee77423d27410a488e61080004cf227d4329dd88076"},"expectedSha256":"9c73b47c0841f2585d7ceee77423d27410a488e61080004cf227d4329dd88076","actualSha256":"9c73b47c0841f2585d7ceee77423d27410a488e61080004cf227d4329dd88076","currentSourceMatch":false,"usedAsCurrentProof":false,"bindingDifferences":[{"field":"manifestSha256","current":"295b2354c562ed87102c5e256497a2705f90941587fefa7e9ad8a4ba8e40787f","retained":"23bf7e7d7e9e5d7962021dd3ec860e87b47d07e119ba0e39de2e423a0a601f4e"}]},"currentProof":{"schemaVersion":1,"status":"passed","environment":{"engine":"17.6","image":"supabase/postgres@sha256:fbe6c858a2ea9616aead3c39e132afcc0660decc5cb7dc656208ed145f5331ab","emptyRowChecks":["public.profiles","public.reviews"]},"manifestSha256":"295b2354c562ed87102c5e256497a2705f90941587fefa7e9ad8a4ba8e40787f","manifestToolchainRoot":"6409387ff1da0bb5e499dab76e4b0b7326a9a81aa71fd21f2d51682a48df0a2f","rehearsalHarnessSha256":"f7321f607a5bf3a24ba099d51abc67254db52fcc385945692426d1b2dfa6e431","sourceRoot":"61f9d7637c0714a7b0d9108c6f7959ea820175a7870dba681498b570296a1544","fixtureDumpSha256":"b7777306a43cc84408434e9835973604b2eb520cf9e9e8d0ee4bba6dcb41257f","predecessorSources":[{"path":"backend/supabase/migrations/20261004190259_admin_record_guarded_actions.sql","sha256":"b373b7ea472c0352a33a4d4043cf8d6aa8474c04cc1ca5778805edfa77ac9c95"},{"path":"backend/supabase/migrations/20261004192657_admin_evaluation_raw_warning_groups.sql","sha256":"66eace1d6fb0dd55c1d780776bab855cc37690335ad40b0fdc1294c610e07d3a"},{"path":"backend/supabase/migrations/20261004194715_admin_evaluation_raw_warning_invoker_contract.sql","sha256":"e1c105df82c4f814d3e6adad807ff9d072b8cd42ae770b4b78f75b89a9387020"},{"path":"backend/supabase/migrations/20261009022915_restaurant_review_manual_preview_eligibility.sql","sha256":"8acf6d1428764260ed57dac5fe09711868a82896f0a6d631d502128004c168a3"},{"path":"backend/supabase/migrations/20261009091342_admin_record_private_verification_cleanup.sql","sha256":"b596b200e52c6813a4cfa1b0a2818625f067864549e8854f3497afdcdab706da"},{"path":"backend/supabase/migrations/20261009101645_admin_user_management_rpc_forward.sql","sha256":"2067538f89c9f90d28e784672c7a1288306ba22d5ae92b087c8692503da9b1ae"}],"migrationCount":11,"statementCount":112,"baselineStateSha256":"3802888324e1651ad7b2f71e7cf15fac78ab77aa57637d0c60431f8da67e6298","stageStatesSha256":"bd25ecbd82759ca75e994ca0f1da38b49acc848d5dd556e702e4e43024edaa65","terminalStateSha256":"9cf59a5d983e78f6f7d9a594267c6feda3276611bf3539289b8a3de3295f700a","compiledSqlBytes":2510560,"compiledSqlSha256":"285d0c859117006646389dbd2a22de9aef1c0b97bc796a9e4d4448a8a232b093","applyCompiledSqlSha256":"91e378c58d58d7d7f818464c9c92aff02c7b01ca528e9db858e2b1d2d00bd92f","applyExpiryFaultedSqlSha256":"727ff0f6be2dbf8ee06551d4efd6ebe665f7187189f7a0a1159d5281ae90635b","results":{"rollbackStageCount":12,"rollbackExact":true,"forcedErrorRejected":true,"forcedErrorRollbackExact":true,"lostResponseMutationAttempts":1,"lostResponseRestorationExact":true,"contendedExpiryRejected":true,"contendedExpiryRollbackExact":true,"applyFinalGuardDeadlineFaultInjected":true,"applyTerminalReadbackObserved":true,"applyExpiryRejectedBeforeCommit":true,"applyExpiryMutationAttempts":1,"applyExpiryRollbackExact":true},"hostedWrites":false,"operatingRowsRead":false,"rawSqlInProof":false,"sourceMutation":false,"temporaryContainerRemoved":true},"currentOutputPath":"/Users/twoimo/.codex/worktrees/mobile-nav-read-20261012/tzudong/apps/web/performance/review-db-rehearsal-20261012/pg17-review-fixes-20261014-v1.json"} diff --git a/apps/web/performance/review-db-rehearsal-20261012/retained-current-proof.json b/apps/web/performance/review-db-rehearsal-20261012/retained-current-proof.json index dc2a656581..9f1af55bac 100644 --- a/apps/web/performance/review-db-rehearsal-20261012/retained-current-proof.json +++ b/apps/web/performance/review-db-rehearsal-20261012/retained-current-proof.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, "kind": "review-media-sql-retained-current-proof", - "proofPath": "pg17-reviewed-boundaries-20261013.json", - "proofSha256": "9c73b47c0841f2585d7ceee77423d27410a488e61080004cf227d4329dd88076" + "proofPath": "pg17-review-fixes-20261014-v1.json", + "proofSha256": "23fb9b471a06dbd36aafab3a678152ca897893b3cdc968f2d623035d217b1e38" } diff --git a/apps/web/performance/review-db-rehearsal-20261012/retained-current-proof.json-4ba9c6d2-historical b/apps/web/performance/review-db-rehearsal-20261012/retained-current-proof.json-4ba9c6d2-historical new file mode 100644 index 0000000000..dc2a656581 --- /dev/null +++ b/apps/web/performance/review-db-rehearsal-20261012/retained-current-proof.json-4ba9c6d2-historical @@ -0,0 +1,6 @@ +{ + "schemaVersion": 1, + "kind": "review-media-sql-retained-current-proof", + "proofPath": "pg17-reviewed-boundaries-20261013.json", + "proofSha256": "9c73b47c0841f2585d7ceee77423d27410a488e61080004cf227d4329dd88076" +} diff --git a/apps/web/performance/review-db-rehearsal-20261012/review-fix-index-units-20261014-v1.log b/apps/web/performance/review-db-rehearsal-20261012/review-fix-index-units-20261014-v1.log new file mode 100644 index 0000000000..79f44e6288 --- /dev/null +++ b/apps/web/performance/review-db-rehearsal-20261012/review-fix-index-units-20261014-v1.log @@ -0,0 +1,44 @@ +bun test v1.4.0 (34cbb9a40) + +apps/web/tests-unit/review-media-sql-successor-harness.test.ts: +(pass) fixture observes delayed advisory contention before expiry without the old 700ms cutoff [0.12ms] +(pass) fixture rejects contention returned by a slow observation after expiry [0.09ms] +(pass) successor acknowledgement loss reconciles a committed terminal state without SQL replay [0.18ms] +(pass) successor failure at the baseline is classified without diagnostic SQL replay [0.05ms] +(pass) retained proof is checksum-verified and distinguished from the current harness source [1.16ms] +(pass) retained proof with bytes not authorized by SHA256SUMS is rejected [1476.90ms] +(pass) jointly altered retained proof and checksum map are rejected by the independent digest [2.89ms] +(pass) owned database is cleaned after predecessor SQL fails [0.75ms] +(pass) owned database is cleaned after fixture SQL fails [0.12ms] +(pass) pre-existing database is never acquired or dropped [0.08ms] +(pass) database CREATE acknowledgement loss is reconciled without replay and cleaned by exact ownership [0.23ms] +(pass) cleanup re-reads and removes the exact owned database after the first CREATE readback fails [0.09ms] +(pass) cleanup refuses a database whose exact owner metadata does not match [0.18ms] +(pass) fixture role creation acknowledgement loss is reconciled and exact owned roles are cleaned [0.60ms] +(pass) cleanup re-reads exact owned roles when the first post-create readback fails [0.20ms] +(pass) fixture role creation failure without owned readback never authorizes cleanup [0.11ms] +(pass) pre-existing fixture role collision is preserved and never acquired for cleanup [0.10ms] +(pass) uncertain create collision with another owner is not guessed or removed [0.09ms] +(pass) cleanup refuses roles whose ownership marker changed after creation [0.08ms] +(pass) cleanup refuses to remove owned roles after an out-of-scope membership is added [0.06ms] + +apps/web/tests-unit/review-media-sql-rehearsal-verifier.test.ts: +(pass) default retained proof uses a stable current-proof selector [2.44ms] +(pass) selector rejects unauthenticated bytes, path escape, and proof-SHA relabelling [3523.67ms] +(pass) retained proof verifies the complete non-circular artifact map from one byte snapshot [1.82ms] +(pass) retained rollback proof rejects bytes not authorized by SHA256SUMS [0.76ms] +(pass) explicit current output is exclusive and separate from retained proof [0.10ms] +(pass) apply expiry fault changes only the final wrapper guard after the exported marker [0.10ms] +(pass) container create acknowledgement loss is reconciled by unique name and owner label [0.23ms] +(pass) first inspect failure after uncertain create is reconciled and cleaned by exact owned ID [0.09ms] +(pass) same-process rehearsal runs derive distinct container names from their owner tokens [0.03ms] +(pass) create failure reconciles proven absence without issuing a removal [0.03ms] +(pass) owner-label mismatch is rejected and the unowned container is not removed [0.10ms] +(pass) acknowledgement mismatch retains exact owned ID for safe finally cleanup [0.10ms] +(pass) cleanup failure leaves ownership live and cannot be misreported as removed [0.05ms] +(pass) cleanup acknowledgement loss succeeds only after exact-ID absence readback [0.08ms] + + 34 pass + 0 fail + 153 expect() calls +Ran 34 tests across 2 files. [5.12s] diff --git a/apps/web/performance/review-db-successor-20261012/README.md b/apps/web/performance/review-db-successor-20261012/README.md index f32f250e81..323d2519b2 100644 --- a/apps/web/performance/review-db-successor-20261012/README.md +++ b/apps/web/performance/review-db-successor-20261012/README.md @@ -81,3 +81,20 @@ is reconciled only by an exact read-only baseline/terminal-state read; it never performs a diagnostic mutation replay. Two injected outcome cases cover terminal commit acknowledgement loss and a rolled-back baseline. This change has local fault tests; no fresh forward-harness replay is claimed. + +## Current review-fix replay — 2026-10-14 + +`pg17-current-review-fixes-20261014-v2.json` binds the current manifest and harness. +It passed the 11-stage PG17.6 atomic commit, exact rollback, wrong preimage, +view/column ACL/membership drift, and real advisory-wait expiry rejection. The +per-run database and fixture roles were removed. The template still records +1,963 manifest rows and zero profiles/reviews; no hosted state was accessed. + +The v1 attempt failed to observe contention in its 700ms observation window. +Its stdout and stderr are retained. The local fixture now confirms the holder +is ready before starting the same 1.5-second expiry window, observes an actual +wait before expiry, and releases the lock just after expiry. Production timeouts +are unchanged; this is a fixture-order correction, not a performance result. +The historical proof and old checksum indexes remain available. The current +retained-proof reader verifies the independently pinned checksum map before +accepting any entry and still classifies old source bindings as historical. diff --git a/apps/web/performance/review-db-successor-20261012/SHA256SUMS b/apps/web/performance/review-db-successor-20261012/SHA256SUMS index 0e1233e64c..d57f554868 100644 --- a/apps/web/performance/review-db-successor-20261012/SHA256SUMS +++ b/apps/web/performance/review-db-successor-20261012/SHA256SUMS @@ -1,17 +1,25 @@ -23bf7e7d7e9e5d7962021dd3ec860e87b47d07e119ba0e39de2e423a0a601f4e .github/review-media-sql-successor.v1.json +295b2354c562ed87102c5e256497a2705f90941587fefa7e9ad8a4ba8e40787f .github/review-media-sql-successor.v1.json f7321f607a5bf3a24ba099d51abc67254db52fcc385945692426d1b2dfa6e431 apps/web/performance/review-db-rehearsal-20261012/verify-review-media-sql-rehearsal.mjs -830e2f3d6baaf95ee42201b46ad703640b0caea8ae31d5db395adf74d9067f09 apps/web/performance/review-db-successor-20261012/README.md +c592db68bb0da972e91035d42543b0023571d378f14ce6da0771a21624212512 apps/web/performance/review-db-successor-20261012/README.md 1a3aa2b9a3e1faef9916cce3d7d3b11246683dadce15da97b3a0e3a75fbb9048 apps/web/performance/review-db-successor-20261012/SHA256SUMS-0833ed81-historical +d19614c3478ddbe90c38b856ebcbdba7a3a4dc9599ce259c2eabd60dcb0337a6 apps/web/performance/review-db-successor-20261012/SHA256SUMS-4ba9c6d2-historical +fe70a536d2fde48ad6332c76b8e64ee8e03f3784398022f190e45827e1c58983 apps/web/performance/review-db-successor-20261012/SHA256SUMS-4ba9c6d2-historical.sha256 b9f3fca7e506e195312c2ca3e4da97f04c7767e42b2a0f980aab68dfc6395e09 apps/web/performance/review-db-successor-20261012/SHA256SUMS-4d4ecf60-historical dc27a75b296716c612e9e3b9cb35f0fd113129dad5215f72a76f3ea94747ee95 apps/web/performance/review-db-successor-20261012/fixture-ownership-failure-interpretation-20261013.json d9cdeba56f0c38a0c1c46f50b48645d827b47df2ba09f3101ff01dcc828cf118 apps/web/performance/review-db-successor-20261012/fixture-ownership-first-attempt-20261013.json 0a3b893331ff16732dd79c6bfca9f88689f7be3065777e62b0dcf3528cea05df apps/web/performance/review-db-successor-20261012/fixture-ownership-protocol-failure-20261013.json +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v1.json +701edcd5b4d76bfef560aa280ec80b812274058fb358375106f80ba90c526849 apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v1.stderr.log +5a1aa0fb9f45ad4510377455b82b6940f18fa43b25123a4fbafcdb8fcf235ee6 apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v2.json +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v2.stderr.log ef0af48a51ee8e3b9a57725eb7504c88401d2b59c3470dec0da11b5257187fbc apps/web/performance/review-db-successor-20261012/pg17-fixture-ownership-20261013.json 0e7f96493ba9ad63959fa25c775fb6e95d0616d47fa4bdae973e248f5eab0349 apps/web/performance/review-db-successor-20261012/pg17-fixture-ownership-current-20261013.json ad6917e416ff20e074eaedd523baa3f0366e3c865f62849a7df6793c2fbd758d apps/web/performance/review-db-successor-20261012/pg17-fixture-ownership-reviewed-20261013.json 6f5dc5c5603c8a4d8b601d3edb28009e454e955292a65707747e66f274557b3f apps/web/performance/review-db-successor-20261012/pg17-local-replay.json +7815d90d73d6b3508a355aee06743c22ff802e78ffca46172cda18ca83ed51b4 apps/web/performance/review-db-successor-20261012/review-fix-harness-units-20261014-v2.log 41972cf6c4ac2b819b42cf9f06b762d54fb9d161f4594192492fccf79cca7418 apps/web/performance/review-db-successor-20261012/verify-fixture-ownership.mjs -768b8887a30c2b8f496a73a21d373b85a0b64fadaed3a1485034a729ee704d39 apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs -5d61808b45661d412937fbfdfd3bfb75b1cf92c7c285e4d8ad061a2f667c3c0b apps/web/scripts/review-media-sql-successor.mjs +768b8887a30c2b8f496a73a21d373b85a0b64fadaed3a1485034a729ee704d39 apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor-4ba9c6d2-historical.source.txt +7e750e9d0126f6f10afe9056d747a2ea854d462161ea4e8f0168e2751d554c0a apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs +4f1b2798e0cfc13db4f11cd6eb4a8653320aa8516eccd71d410fcff6d06a7f24 apps/web/scripts/review-media-sql-successor.mjs fcebc20dc4846606dc14bdbe658a07daa32430ad632e52383151f5c22d22efb9 apps/web/scripts/supabase-migration-bundle.mjs 92651102154fd4d573ca841137bd31c319ef545b2527e7da17398bd2e6b97d47 apps/web/tests-unit/review-media-fixture-ownership-verifier.test.ts diff --git a/apps/web/performance/review-db-successor-20261012/SHA256SUMS-4ba9c6d2-historical b/apps/web/performance/review-db-successor-20261012/SHA256SUMS-4ba9c6d2-historical new file mode 100644 index 0000000000..0e1233e64c --- /dev/null +++ b/apps/web/performance/review-db-successor-20261012/SHA256SUMS-4ba9c6d2-historical @@ -0,0 +1,17 @@ +23bf7e7d7e9e5d7962021dd3ec860e87b47d07e119ba0e39de2e423a0a601f4e .github/review-media-sql-successor.v1.json +f7321f607a5bf3a24ba099d51abc67254db52fcc385945692426d1b2dfa6e431 apps/web/performance/review-db-rehearsal-20261012/verify-review-media-sql-rehearsal.mjs +830e2f3d6baaf95ee42201b46ad703640b0caea8ae31d5db395adf74d9067f09 apps/web/performance/review-db-successor-20261012/README.md +1a3aa2b9a3e1faef9916cce3d7d3b11246683dadce15da97b3a0e3a75fbb9048 apps/web/performance/review-db-successor-20261012/SHA256SUMS-0833ed81-historical +b9f3fca7e506e195312c2ca3e4da97f04c7767e42b2a0f980aab68dfc6395e09 apps/web/performance/review-db-successor-20261012/SHA256SUMS-4d4ecf60-historical +dc27a75b296716c612e9e3b9cb35f0fd113129dad5215f72a76f3ea94747ee95 apps/web/performance/review-db-successor-20261012/fixture-ownership-failure-interpretation-20261013.json +d9cdeba56f0c38a0c1c46f50b48645d827b47df2ba09f3101ff01dcc828cf118 apps/web/performance/review-db-successor-20261012/fixture-ownership-first-attempt-20261013.json +0a3b893331ff16732dd79c6bfca9f88689f7be3065777e62b0dcf3528cea05df apps/web/performance/review-db-successor-20261012/fixture-ownership-protocol-failure-20261013.json +ef0af48a51ee8e3b9a57725eb7504c88401d2b59c3470dec0da11b5257187fbc apps/web/performance/review-db-successor-20261012/pg17-fixture-ownership-20261013.json +0e7f96493ba9ad63959fa25c775fb6e95d0616d47fa4bdae973e248f5eab0349 apps/web/performance/review-db-successor-20261012/pg17-fixture-ownership-current-20261013.json +ad6917e416ff20e074eaedd523baa3f0366e3c865f62849a7df6793c2fbd758d apps/web/performance/review-db-successor-20261012/pg17-fixture-ownership-reviewed-20261013.json +6f5dc5c5603c8a4d8b601d3edb28009e454e955292a65707747e66f274557b3f apps/web/performance/review-db-successor-20261012/pg17-local-replay.json +41972cf6c4ac2b819b42cf9f06b762d54fb9d161f4594192492fccf79cca7418 apps/web/performance/review-db-successor-20261012/verify-fixture-ownership.mjs +768b8887a30c2b8f496a73a21d373b85a0b64fadaed3a1485034a729ee704d39 apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs +5d61808b45661d412937fbfdfd3bfb75b1cf92c7c285e4d8ad061a2f667c3c0b apps/web/scripts/review-media-sql-successor.mjs +fcebc20dc4846606dc14bdbe658a07daa32430ad632e52383151f5c22d22efb9 apps/web/scripts/supabase-migration-bundle.mjs +92651102154fd4d573ca841137bd31c319ef545b2527e7da17398bd2e6b97d47 apps/web/tests-unit/review-media-fixture-ownership-verifier.test.ts diff --git a/apps/web/performance/review-db-successor-20261012/SHA256SUMS-4ba9c6d2-historical.sha256 b/apps/web/performance/review-db-successor-20261012/SHA256SUMS-4ba9c6d2-historical.sha256 new file mode 100644 index 0000000000..c0ba176f4b --- /dev/null +++ b/apps/web/performance/review-db-successor-20261012/SHA256SUMS-4ba9c6d2-historical.sha256 @@ -0,0 +1 @@ +d19614c3478ddbe90c38b856ebcbdba7a3a4dc9599ce259c2eabd60dcb0337a6 diff --git a/apps/web/performance/review-db-successor-20261012/SHA256SUMS.sha256 b/apps/web/performance/review-db-successor-20261012/SHA256SUMS.sha256 index c0ba176f4b..28f2abcf82 100644 --- a/apps/web/performance/review-db-successor-20261012/SHA256SUMS.sha256 +++ b/apps/web/performance/review-db-successor-20261012/SHA256SUMS.sha256 @@ -1 +1 @@ -d19614c3478ddbe90c38b856ebcbdba7a3a4dc9599ce259c2eabd60dcb0337a6 +aff9374a0e6a6964b2d32f6cb5c6069296c5155b94c2ec1015460d2386fb4e88 diff --git a/apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v1.json b/apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v1.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v1.stderr.log b/apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v1.stderr.log new file mode 100644 index 0000000000..dba3aafb39 --- /dev/null +++ b/apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v1.stderr.log @@ -0,0 +1,12 @@ +file:///Users/twoimo/.codex/worktrees/mobile-nav-read-20261012/tzudong/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs:55 + throw new Error(detail ? `${code}:${detail}` : code); + ^ + +Error: LOCAL_REHEARSAL_ADVISORY_CONTENTION_NOT_OBSERVED + at fail (file:///Users/twoimo/.codex/worktrees/mobile-nav-read-20261012/tzudong/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs:55:9) + at waitForAdvisoryContention (file:///Users/twoimo/.codex/worktrees/mobile-nav-read-20261012/tzudong/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs:823:3) + at async assertContendedExpiryRejected (file:///Users/twoimo/.codex/worktrees/mobile-nav-read-20261012/tzudong/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs:842:34) + at async run (file:///Users/twoimo/.codex/worktrees/mobile-nav-read-20261012/tzudong/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs:993:46) + at async file:///Users/twoimo/.codex/worktrees/mobile-nav-read-20261012/tzudong/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs:1057:85 + +Node.js v24.21.0 diff --git a/apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v2.json b/apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v2.json new file mode 100644 index 0000000000..b7085ca571 --- /dev/null +++ b/apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v2.json @@ -0,0 +1 @@ +{"current":{"classification":"current-source-verification","proofSha256":"9a4985689d121979e3d7559c32b8f27c88ae3d1bb82d559fb9bb8ada6e706ac2","bindings":{"manifestSha256":"295b2354c562ed87102c5e256497a2705f90941587fefa7e9ad8a4ba8e40787f","rehearsalHarnessSha256":"7e750e9d0126f6f10afe9056d747a2ea854d462161ea4e8f0168e2751d554c0a","sourceRoot":"61f9d7637c0714a7b0d9108c6f7959ea820175a7870dba681498b570296a1544"},"proof":{"schemaVersion":1,"status":"passed","environment":{"dockerContext":"colima-tzudong-catalog-20261007","dockerContainer":"tzudong-ranking-clone-20261008","engine":"17.6","templateDatabase":"ranking_clone","temporaryDatabase":"review_successor_local_437a74bbfd2f4629d996b901cf050c29"},"input":{"engine":"17.6","ledger":0,"manifest":1963,"profiles":0,"reviews":0},"manifestSha256":"295b2354c562ed87102c5e256497a2705f90941587fefa7e9ad8a4ba8e40787f","rehearsalHarnessSha256":"7e750e9d0126f6f10afe9056d747a2ea854d462161ea4e8f0168e2751d554c0a","sourceRoot":"61f9d7637c0714a7b0d9108c6f7959ea820175a7870dba681498b570296a1544","predecessorSources":[{"path":"backend/supabase/migrations/20261004190259_admin_record_guarded_actions.sql","sha256":"b373b7ea472c0352a33a4d4043cf8d6aa8474c04cc1ca5778805edfa77ac9c95"},{"path":"backend/supabase/migrations/20261004192657_admin_evaluation_raw_warning_groups.sql","sha256":"66eace1d6fb0dd55c1d780776bab855cc37690335ad40b0fdc1294c610e07d3a"},{"path":"backend/supabase/migrations/20261004194715_admin_evaluation_raw_warning_invoker_contract.sql","sha256":"e1c105df82c4f814d3e6adad807ff9d072b8cd42ae770b4b78f75b89a9387020"},{"path":"backend/supabase/migrations/20261009022915_restaurant_review_manual_preview_eligibility.sql","sha256":"8acf6d1428764260ed57dac5fe09711868a82896f0a6d631d502128004c168a3"},{"path":"backend/supabase/migrations/20261009091342_admin_record_private_verification_cleanup.sql","sha256":"b596b200e52c6813a4cfa1b0a2818625f067864549e8854f3497afdcdab706da"},{"path":"backend/supabase/migrations/20261009101645_admin_user_management_rpc_forward.sql","sha256":"2067538f89c9f90d28e784672c7a1288306ba22d5ae92b087c8692503da9b1ae"}],"migrationCount":11,"statementCount":112,"stageStatesSha256":"e62c3e811bceec08cecb1785950e53cb1cd5a22ed7e80df2fdd79b290962ec04","stages":[{"index":0,"ledgerCount":86,"targetCount":0,"prefixRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","suffixRoot":null,"schemaRoot":"70ef24069d2f6ceeec28db7f715b2bc1e1830e51007df5f97476a64f7583c2be","ledgerRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","g014Root":"4f3886411c6329ec0a6a61743e7e89de21cf519848bc145be2f3e96f1e685149","storageRoot":"2e4b000d1012f1df1e6e6fd3e19cc66e8718a5be1d8cdc3695677bd2cc3bbda8","finishCleanupBodySha256":null,"ownerAuthorityReady":true,"terminalContractReady":false},{"index":1,"ledgerCount":87,"targetCount":1,"prefixRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","suffixRoot":null,"schemaRoot":"6660c658b956479a86b85dc5f6c257bdfde7b94d1e6e7e675b989303bc776de4","ledgerRoot":"9dff2943b2fb957d82b7ac325a999fef829c077e5738f178e5bc19da67609701","g014Root":"4f3886411c6329ec0a6a61743e7e89de21cf519848bc145be2f3e96f1e685149","storageRoot":"145e14b4467ccabbbd0d1009df236b3cc734174a9b287811a55a29058257111f","finishCleanupBodySha256":"1a0eb803e2f2ee4098960ad2fbe2b99b363405536c03a09c0465b8a8ffddecc7","ownerAuthorityReady":true,"terminalContractReady":false},{"index":2,"ledgerCount":88,"targetCount":2,"prefixRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","suffixRoot":null,"schemaRoot":"7574cc0bd42e93dd1112e9068b6538fc2401b5de8d7de3e7ce40762405abbe35","ledgerRoot":"5f3dcac3e6820c2571b2f04221cbdf34ee18085363c434605c445a76ea59061a","g014Root":"4f3886411c6329ec0a6a61743e7e89de21cf519848bc145be2f3e96f1e685149","storageRoot":"98ad8115175e671fce1f853cb48b2e1bb7e430892107fd333bc74df24799cdea","finishCleanupBodySha256":"f7e925a25dedb987a3166c5ca641ba7db1568a7583bdeca3cee04f27d9189f20","ownerAuthorityReady":true,"terminalContractReady":false},{"index":3,"ledgerCount":89,"targetCount":3,"prefixRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","suffixRoot":null,"schemaRoot":"fe8ca8917b834397b49253154bed7074372bc403ffc2a0e06037a650c7cadfb2","ledgerRoot":"928e46ee03f4f2ed492be89617cf8b455f2931f79fdf3c218adbc92c5fa3d21e","g014Root":"810c5888b257c16ea07881e99914a718eede2e2339cbd805381fe7bf346aef34","storageRoot":"4c1c8a63f3cb8553acfcac1c62c5452ab53568724f662e43291d63037910e34f","finishCleanupBodySha256":"f6c4e71988ed2b789b887484af5fdf2e482bde98f1d1c5b5d4e7ad124c314bd1","ownerAuthorityReady":true,"terminalContractReady":false},{"index":4,"ledgerCount":90,"targetCount":4,"prefixRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","suffixRoot":null,"schemaRoot":"2085388afbef4445f615ee40e5e00837c526e2f29a2e1c3e0786afe8a569fb22","ledgerRoot":"73b30ad98e4cbace156815cd214091312364302e9403065b24a2d1e86227a2c0","g014Root":"68e78dd7263864584a6b1b4778ef1aa13942b4d54825910fab1101a989025c00","storageRoot":"4c1c8a63f3cb8553acfcac1c62c5452ab53568724f662e43291d63037910e34f","finishCleanupBodySha256":"f6c4e71988ed2b789b887484af5fdf2e482bde98f1d1c5b5d4e7ad124c314bd1","ownerAuthorityReady":true,"terminalContractReady":false},{"index":5,"ledgerCount":91,"targetCount":5,"prefixRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","suffixRoot":null,"schemaRoot":"0f3f4ed4b7547cc154e5beaeb2621db1c05ff0d4dc7325c47246dcfd4be6fbc5","ledgerRoot":"150bb9f1c5a4b447dd50a86097923fb1c78e522fb5f2bf8ca16b9f08ca6d2b58","g014Root":"68e78dd7263864584a6b1b4778ef1aa13942b4d54825910fab1101a989025c00","storageRoot":"4c1c8a63f3cb8553acfcac1c62c5452ab53568724f662e43291d63037910e34f","finishCleanupBodySha256":"f6c4e71988ed2b789b887484af5fdf2e482bde98f1d1c5b5d4e7ad124c314bd1","ownerAuthorityReady":true,"terminalContractReady":false},{"index":6,"ledgerCount":92,"targetCount":6,"prefixRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","suffixRoot":null,"schemaRoot":"8aa69a0aa43e60ab1b6680ebb7b3fb4e4f53b07aa8231847883a181375554690","ledgerRoot":"f57304a9d3122d643535464cf72565aa04c450dbc1d8fe0a3a64add17b5c7c97","g014Root":"68e78dd7263864584a6b1b4778ef1aa13942b4d54825910fab1101a989025c00","storageRoot":"4c1c8a63f3cb8553acfcac1c62c5452ab53568724f662e43291d63037910e34f","finishCleanupBodySha256":"f6c4e71988ed2b789b887484af5fdf2e482bde98f1d1c5b5d4e7ad124c314bd1","ownerAuthorityReady":true,"terminalContractReady":false},{"index":7,"ledgerCount":93,"targetCount":7,"prefixRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","suffixRoot":null,"schemaRoot":"cee1ac952c3bd3aa8fff26a625a4140c5ce2028a08a8510eecdc1e760aaf5166","ledgerRoot":"3da3b76460e0ad7249d50a1f4ddf0cb433edd0ca7b3b6b7a8f6e334974888065","g014Root":"68e78dd7263864584a6b1b4778ef1aa13942b4d54825910fab1101a989025c00","storageRoot":"4c1c8a63f3cb8553acfcac1c62c5452ab53568724f662e43291d63037910e34f","finishCleanupBodySha256":"f6c4e71988ed2b789b887484af5fdf2e482bde98f1d1c5b5d4e7ad124c314bd1","ownerAuthorityReady":true,"terminalContractReady":false},{"index":8,"ledgerCount":94,"targetCount":8,"prefixRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","suffixRoot":null,"schemaRoot":"ab7f4f4d84c2d759d570669641131f149db2a1a023765fb468e04d7f162348be","ledgerRoot":"c8920bba0db232a705ddf012ee5da809df71d72e3ddfb3f2fbc91d156b87cca2","g014Root":"68e78dd7263864584a6b1b4778ef1aa13942b4d54825910fab1101a989025c00","storageRoot":"4c1c8a63f3cb8553acfcac1c62c5452ab53568724f662e43291d63037910e34f","finishCleanupBodySha256":"f6c4e71988ed2b789b887484af5fdf2e482bde98f1d1c5b5d4e7ad124c314bd1","ownerAuthorityReady":true,"terminalContractReady":false},{"index":9,"ledgerCount":95,"targetCount":9,"prefixRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","suffixRoot":null,"schemaRoot":"7409744a943050706edaf080c59b069c10bb3b471296728991c589b2ce3d18bb","ledgerRoot":"b6f0aa150d0a5177828767ca5ad31796f6ae4cc034fcd5c943e15cea3d876a9b","g014Root":"68e78dd7263864584a6b1b4778ef1aa13942b4d54825910fab1101a989025c00","storageRoot":"4c1c8a63f3cb8553acfcac1c62c5452ab53568724f662e43291d63037910e34f","finishCleanupBodySha256":"f6c4e71988ed2b789b887484af5fdf2e482bde98f1d1c5b5d4e7ad124c314bd1","ownerAuthorityReady":true,"terminalContractReady":false},{"index":10,"ledgerCount":96,"targetCount":10,"prefixRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","suffixRoot":null,"schemaRoot":"284d8a28560707a02167e6f9131c2ca4e0658808ace9061b5251d8ff12a236cd","ledgerRoot":"e56731bbe2c7bbdfa826e594d468082c9f16354350f66325e7c58cfdc549fe1a","g014Root":"68e78dd7263864584a6b1b4778ef1aa13942b4d54825910fab1101a989025c00","storageRoot":"6bf06a15d9ef5cc3abbe210657b6a21b3b50f18e0dde58bea9af5ba958b46a60","finishCleanupBodySha256":"f6c4e71988ed2b789b887484af5fdf2e482bde98f1d1c5b5d4e7ad124c314bd1","ownerAuthorityReady":true,"terminalContractReady":false},{"index":11,"ledgerCount":97,"targetCount":11,"prefixRoot":"c975c881e8eb3af88c4b0fc87f1109ac4e1507258f29363de88760f20713dba2","suffixRoot":"1ecd299cb2525a1d66738d2c0adf01a482e47c44f881f6d09ddc2141d469d32b","schemaRoot":"a255fb9f415627e139762649c4e4230c4f6c56e541be3a09d9f986120287ea7e","ledgerRoot":"fa142da48d834923b4702a302d44ea0f9818d7713da76a316d5959b8852065f2","g014Root":"68e78dd7263864584a6b1b4778ef1aa13942b4d54825910fab1101a989025c00","storageRoot":"6bf06a15d9ef5cc3abbe210657b6a21b3b50f18e0dde58bea9af5ba958b46a60","finishCleanupBodySha256":"a6b4211b2d8486d482f0758ce249eec157249e1a5176be286b76af7e7a775ee9","ownerAuthorityReady":true,"terminalContractReady":true}],"results":{"advisoryContentionObserved":true,"atomicCommit":true,"atomicCommitOutcome":"acknowledged","atomicRollbackExact":true,"catalogDriftStatePreserved":true,"columnAclDriftRejected":true,"compiledSqlBytes":6495623,"compiledSqlSha256":"267a09fdcb24ce8183425dfb847281a2fea9e04226aa94de01b13f3544b353fd","contendedAdmissionExpiryRejected":true,"contendedAdmissionExpiryStatePreserved":true,"membershipOptionsBaseline":{"inherit":true,"set":true},"membershipInheritOptionDriftRejected":true,"membershipSetOptionDriftRejected":true,"securityInvokerBaseline":true,"securityInvokerDriftRejected":true,"wrongFinishPreimageRejected":true,"wrongFinishPreimageRollbackExact":true,"terminalLedgerCount":97,"terminalContractReady":true},"sourceMutation":false,"hostedWrites":false,"operatingWrites":false,"temporaryDatabaseDropped":true,"fixtureRolesDropped":true}},"retained":{"present":true,"classification":"retained-historical-evidence","repositoryPath":"apps/web/performance/review-db-successor-20261012/pg17-local-replay.json","expectedSha256":"6f5dc5c5603c8a4d8b601d3edb28009e454e955292a65707747e66f274557b3f","actualSha256":"6f5dc5c5603c8a4d8b601d3edb28009e454e955292a65707747e66f274557b3f","checksumMapSha256":"d19614c3478ddbe90c38b856ebcbdba7a3a4dc9599ce259c2eabd60dcb0337a6","bindings":{"manifestSha256":"9fac81d6e3ec81207b9c8985ccfdaf9e300bb7d44a7617d95579d4e8f96af19b","rehearsalHarnessSha256":"03e999caeda7b57925126ba8d6bcec4ac5a671fedb2c69e07e988924ccb9f895","sourceRoot":"61f9d7637c0714a7b0d9108c6f7959ea820175a7870dba681498b570296a1544"},"currentSourceMatch":false,"bindingDifferences":[{"field":"manifestSha256","retained":"9fac81d6e3ec81207b9c8985ccfdaf9e300bb7d44a7617d95579d4e8f96af19b","current":"295b2354c562ed87102c5e256497a2705f90941587fefa7e9ad8a4ba8e40787f"},{"field":"rehearsalHarnessSha256","retained":"03e999caeda7b57925126ba8d6bcec4ac5a671fedb2c69e07e988924ccb9f895","current":"7e750e9d0126f6f10afe9056d747a2ea854d462161ea4e8f0168e2751d554c0a"}],"usedAsCurrentProof":false}} diff --git a/apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v2.stderr.log b/apps/web/performance/review-db-successor-20261012/pg17-current-review-fixes-20261014-v2.stderr.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/web/performance/review-db-successor-20261012/review-fix-harness-units-20261014-v2.log b/apps/web/performance/review-db-successor-20261012/review-fix-harness-units-20261014-v2.log new file mode 100644 index 0000000000..30a3f294b0 --- /dev/null +++ b/apps/web/performance/review-db-successor-20261012/review-fix-harness-units-20261014-v2.log @@ -0,0 +1,28 @@ +bun test v1.4.0 (34cbb9a40) + +tests-unit/review-media-sql-successor-harness.test.ts: +(pass) fixture observes delayed advisory contention before expiry without the old 700ms cutoff [0.18ms] +(pass) fixture rejects contention returned by a slow observation after expiry [0.22ms] +(pass) successor acknowledgement loss reconciles a committed terminal state without SQL replay [0.24ms] +(pass) successor failure at the baseline is classified without diagnostic SQL replay [0.06ms] +(pass) retained proof is checksum-verified and distinguished from the current harness source [1.73ms] +(pass) retained proof with bytes not authorized by SHA256SUMS is rejected [1.26ms] +(pass) jointly altered retained proof and checksum map are rejected by the independent digest [1.29ms] +(pass) owned database is cleaned after predecessor SQL fails [0.55ms] +(pass) owned database is cleaned after fixture SQL fails [0.05ms] +(pass) pre-existing database is never acquired or dropped [0.06ms] +(pass) database CREATE acknowledgement loss is reconciled without replay and cleaned by exact ownership [0.16ms] +(pass) cleanup re-reads and removes the exact owned database after the first CREATE readback fails [0.10ms] +(pass) cleanup refuses a database whose exact owner metadata does not match [0.12ms] +(pass) fixture role creation acknowledgement loss is reconciled and exact owned roles are cleaned [0.38ms] +(pass) cleanup re-reads exact owned roles when the first post-create readback fails [0.10ms] +(pass) fixture role creation failure without owned readback never authorizes cleanup [0.05ms] +(pass) pre-existing fixture role collision is preserved and never acquired for cleanup [0.06ms] +(pass) uncertain create collision with another owner is not guessed or removed [0.05ms] +(pass) cleanup refuses roles whose ownership marker changed after creation [0.26ms] +(pass) cleanup refuses to remove owned roles after an out-of-scope membership is added [0.07ms] + + 20 pass + 0 fail + 98 expect() calls +Ran 20 tests across 1 file. [161.00ms] diff --git a/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor-4ba9c6d2-historical.source.txt b/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor-4ba9c6d2-historical.source.txt new file mode 100644 index 0000000000..779cf90ec7 --- /dev/null +++ b/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor-4ba9c6d2-historical.source.txt @@ -0,0 +1,1050 @@ +#!/usr/bin/env node + +/** + * Local-only PostgreSQL 17.6 rehearsal for the held eleven-stage successor. + * + * The harness clones the existing schema-only ranking_clone template into one + * per-process, owned database name. It refuses a reused target, never connects + * to a hosted endpoint, and drops the owned database before emitting its + * current-source result. + */ +import { createHash, randomBytes } from 'node:crypto'; +import { existsSync, readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { spawn, spawnSync } from 'node:child_process'; + +import { + buildSuccessorStateQuery, + compileSuccessorPlan, + loadSuccessorManifest, +} from '../../scripts/review-media-sql-successor.mjs'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const REPOSITORY_ROOT = resolve(HERE, '../../../..'); +const OUTPUT_PATH = resolve(HERE, 'pg17-local-replay.json'); +const CHECKSUMS_PATH = resolve(HERE, 'SHA256SUMS'); +const RETAINED_PROOF_REPOSITORY_PATH = 'apps/web/performance/review-db-successor-20261012/pg17-local-replay.json'; +const DOCKER_CONTEXT = process.env.TZUDONG_REHEARSAL_DOCKER_CONTEXT || 'colima-tzudong-catalog-20261007'; +const DOCKER_CONTAINER = process.env.TZUDONG_REHEARSAL_DOCKER_CONTAINER || 'tzudong-ranking-clone-20261008'; +const TEMPLATE_DATABASE = 'ranking_clone'; +const DATABASE = `review_successor_local_${randomBytes(16).toString('hex')}`; +const FIXTURE_PASSWORD = 'fixture-only'; +const FIXTURE_PARENT_ROLE = 'review_successor_fixture_parent_20261012'; +const FIXTURE_MEMBER_ROLE = 'review_successor_fixture_member_20261012'; +const FIXTURE_OWNER_PREFIX = 'tzudong-review-successor-fixture'; +const EXPIRY_APPLICATION_NAME = 'tzudong-review-successor-expiry-fixture'; +const PREDECESSOR_PATHS = Object.freeze([ + 'backend/supabase/migrations/20261004190259_admin_record_guarded_actions.sql', + 'backend/supabase/migrations/20261004192657_admin_evaluation_raw_warning_groups.sql', + 'backend/supabase/migrations/20261004194715_admin_evaluation_raw_warning_invoker_contract.sql', + 'backend/supabase/migrations/20261009022915_restaurant_review_manual_preview_eligibility.sql', + 'backend/supabase/migrations/20261009091342_admin_record_private_verification_cleanup.sql', + 'backend/supabase/migrations/20261009101645_admin_user_management_rpc_forward.sql', +]); +const sha256 = value => createHash('sha256').update(value).digest('hex'); +const quote = value => `'${String(value).replaceAll("'", "''")}'`; +const canonical = value => Array.isArray(value) + ? value.map(canonical) + : value && typeof value === 'object' + ? Object.fromEntries(Object.keys(value).sort().map(key => [key, canonical(value[key])])) + : value; +const equal = (left, right) => JSON.stringify(canonical(left)) === JSON.stringify(canonical(right)); + +function fail(code, detail = '') { + throw new Error(detail ? `${code}:${detail}` : code); +} + +function docker(args, { input, allowFailure = false } = {}) { + const result = spawnSync('docker', ['--context', DOCKER_CONTEXT, ...args], { + encoding: 'utf8', + input, + maxBuffer: 64 * 1024 * 1024, + }); + if (!allowFailure && (result.error || result.status !== 0)) { + const sqlstates = [...(result.stderr || '').matchAll(/(?:ERROR|FATAL):\s+([0-9A-Z]{5}):/g)] + .map(match => match[1]); + const diagnostic = (result.stderr || '').split(/\r?\n/) + .find(line => /(?:ERROR|FATAL):/.test(line)) + ?.replace(/\s+/g, ' ').slice(0, 240) || ''; + fail('LOCAL_REHEARSAL_COMMAND_FAILED', [sqlstates.join(',') || String(result.status), diagnostic] + .filter(Boolean).join(':')); + } + return result; +} + +function psql(database, sql, { actor = 'postgres', singleTransaction = false, allowFailure = false } = {}) { + const options = [ + 'exec', '-i', '-e', `PGPASSWORD=${FIXTURE_PASSWORD}`, DOCKER_CONTAINER, + 'psql', '-XAtq', '-v', 'ON_ERROR_STOP=1', '-v', 'VERBOSITY=terse', + ]; + if (singleTransaction) options.push('--single-transaction'); + options.push('-h', '127.0.0.1', '-U', actor, '-d', database, '--file=-'); + return docker(options, { input: sql, allowFailure }); +} + +function scalar(database, sql, actor = 'postgres') { + return psql(database, `${sql}\n`, { actor }).stdout.trim(); +} + +function readState(database, stateQuery) { + const output = scalar(database, `${stateQuery};`); + const lines = output.split(/\r?\n/).filter(Boolean); + if (lines.length !== 1) fail('LOCAL_REHEARSAL_STATE_SHAPE_INVALID', String(lines.length)); + try { + return JSON.parse(lines[0]); + } catch { + fail('LOCAL_REHEARSAL_STATE_JSON_INVALID'); + } +} + +export function applySuccessorPlanOnce({ + database, + sql, + stateQuery, + baselineState, + terminalState, + psqlImpl = psql, + readStateImpl = readState, +}) { + try { + return Object.freeze({ + outcome: 'acknowledged', + result: psqlImpl(database, sql, { singleTransaction: true }), + }); + } catch { + let observedState; + try { + observedState = readStateImpl(database, stateQuery); + } catch { + fail('LOCAL_REHEARSAL_APPLICATION_OUTCOME_UNCERTAIN'); + } + if (equal(observedState, terminalState)) { + return Object.freeze({ + outcome: 'committed-after-acknowledgement-loss', + result: Object.freeze({ stdout: `${JSON.stringify(observedState)}\n` }), + }); + } + if (equal(observedState, baselineState)) { + fail('LOCAL_REHEARSAL_APPLICATION_ROLLED_BACK'); + } + fail('LOCAL_REHEARSAL_APPLICATION_STATE_AMBIGUOUS'); + } +} + +function ledgerInsert(migration) { + const vector = JSON.stringify(migration.originalStatementVector); + const tag = `$vector_${sha256(vector).slice(0, 20)}$`; + return `INSERT INTO supabase_migrations.schema_migrations(version,statements,name) +SELECT ${quote(migration.version)},ARRAY(SELECT jsonb_array_elements_text(${tag}${vector}${tag}::jsonb)),${quote(migration.name)};`; +} + +function fixtureSql() { + return ` +DO $fixture$ +BEGIN + IF (SELECT count(*) FROM supabase_migrations.schema_migrations)<>0 THEN + RAISE EXCEPTION 'LOCAL_REHEARSAL_LEDGER_NOT_EMPTY'; + END IF; + IF (SELECT count(*) FROM public.profiles)<>0 OR (SELECT count(*) FROM public.reviews)<>0 THEN + RAISE EXCEPTION 'LOCAL_REHEARSAL_PERSONAL_ROWS_PRESENT'; + END IF; +END $fixture$; +INSERT INTO supabase_migrations.schema_migrations(version,statements,name) +SELECT lpad(value::text,14,'0'),ARRAY['local schema-only predecessor fixture'],format('local_predecessor_%s',value) +FROM generate_series(1,86) value; +INSERT INTO pipeline_control.restaurant_review_policy(singleton,enabled) +VALUES(true,false) +ON CONFLICT(singleton) DO UPDATE SET enabled=false,operator_id=NULL,last_run_at=NULL; +INSERT INTO pipeline_control.admin_evaluation_catalog_revision(singleton,revision) +VALUES(true,1) +ON CONFLICT(singleton) DO NOTHING; +`; +} + +function databaseMetadataSql(databaseName) { + const projection = alias => `jsonb_build_object( + 'name',${alias}.datname, + 'owner',pg_get_userbyid(${alias}.datdba), + 'encoding',pg_encoding_to_char(${alias}.encoding), + 'collate',${alias}.datcollate, + 'ctype',${alias}.datctype, + 'localeProvider',${alias}.datlocprovider::text, + 'allowConnections',${alias}.datallowconn, + 'isTemplate',${alias}.datistemplate + )`; + return `SELECT jsonb_build_object( + 'target',(SELECT ${projection('target_database')} FROM pg_database target_database + WHERE target_database.datname=${quote(databaseName)}), + 'template',(SELECT ${projection('template_database')} FROM pg_database template_database + WHERE template_database.datname=${quote(TEMPLATE_DATABASE)}) +)::text;`; +} + +function inspectDatabaseOwnership(databaseName, { scalarImpl = scalar } = {}) { + let state; + try { + state = JSON.parse(scalarImpl('postgres', databaseMetadataSql(databaseName), 'supabase_admin')); + } catch { + fail('LOCAL_REHEARSAL_DATABASE_STATE_INVALID'); + } + if (!state + || typeof state !== 'object' + || !Object.hasOwn(state, 'target') + || !databaseMetadataValid(state.template) + || state.template.name !== TEMPLATE_DATABASE + || (state.target !== null && !databaseMetadataValid(state.target))) { + fail('LOCAL_REHEARSAL_DATABASE_STATE_INVALID'); + } + return state; +} + +function databaseMetadataValid(metadata) { + return metadata + && typeof metadata === 'object' + && ['name', 'owner', 'encoding', 'collate', 'ctype', 'localeProvider'] + .every(field => typeof metadata[field] === 'string' && metadata[field].length > 0) + && typeof metadata.allowConnections === 'boolean' + && typeof metadata.isTemplate === 'boolean'; +} + +function databaseTargetAbsent(state, databaseName) { + return state?.target === null && state?.template?.name === TEMPLATE_DATABASE && databaseName === DATABASE; +} + +function databaseTargetOwned(state, baseline, databaseName) { + const target = state?.target; + const template = state?.template; + return databaseTargetAbsent(baseline, databaseName) + && target?.name === databaseName + && target.owner === 'postgres' + && target.allowConnections === true + && target.isTemplate === false + && template?.name === TEMPLATE_DATABASE + && equal(template, baseline.template) + && target.encoding === template.encoding + && target.collate === template.collate + && target.ctype === template.ctype + && target.localeProvider === template.localeProvider; +} + +export function createOwnedDatabase(ownership, { + scalarImpl = scalar, + psqlImpl = psql, + readFileImpl = readFileSync, +} = {}) { + if (!ownership + || ownership.database !== false + || ownership.databaseAttempted + || ownership.databaseName + || ownership.databaseBaseline) { + fail('LOCAL_REHEARSAL_OWNERSHIP_STATE_INVALID'); + } + const baseline = inspectDatabaseOwnership(DATABASE, { scalarImpl }); + if (!databaseTargetAbsent(baseline, DATABASE)) fail('LOCAL_REHEARSAL_TARGET_ALREADY_EXISTS'); + ownership.databaseName = DATABASE; + ownership.databaseBaseline = baseline; + ownership.databaseAttempted = true; + let creationAcknowledged = false; + try { + psqlImpl('postgres', `CREATE DATABASE ${DATABASE} TEMPLATE ${TEMPLATE_DATABASE} OWNER postgres;`, { actor: 'supabase_admin' }); + creationAcknowledged = true; + } catch { + // CREATE is intentionally never replayed. The exact catalog readback below + // distinguishes a committed CREATE from a failure before mutation. + } + let observed; + try { + observed = inspectDatabaseOwnership(DATABASE, { scalarImpl }); + } catch { + fail('LOCAL_REHEARSAL_DATABASE_READBACK_FAILED'); + } + if (!databaseTargetOwned(observed, baseline, DATABASE)) { + if (databaseTargetAbsent(observed, DATABASE)) { + ownership.databaseAttempted = false; + ownership.databaseName = null; + ownership.databaseBaseline = null; + fail('LOCAL_REHEARSAL_DATABASE_CREATION_UNCONFIRMED'); + } + fail('LOCAL_REHEARSAL_DATABASE_OWNERSHIP_UNPROVEN'); + } + ownership.database = true; + ownership.databaseCreationAcknowledged = creationAcknowledged; + ownership.databaseCreationReconciled = !creationAcknowledged; + PREDECESSOR_PATHS.forEach((path, index) => { + try { + psqlImpl(DATABASE, readFileImpl(resolve(REPOSITORY_ROOT, path))); + } catch (error) { + fail('LOCAL_REHEARSAL_PREDECESSOR_FAILED', `${index + 1}:${error.message}`); + } + }); + psqlImpl(DATABASE, fixtureSql()); + return true; +} + +export function dropOwnedDatabase(ownership, { scalarImpl = scalar, psqlImpl = psql } = {}) { + if (!ownership?.database && !ownership?.databaseAttempted) return false; + if (ownership.databaseName !== DATABASE + || !ownership.databaseBaseline + || !databaseTargetAbsent(ownership.databaseBaseline, DATABASE)) { + fail('LOCAL_REHEARSAL_DATABASE_OWNERSHIP_STATE_INVALID'); + } + let before; + try { + before = inspectDatabaseOwnership(DATABASE, { scalarImpl }); + } catch { + fail('LOCAL_REHEARSAL_DATABASE_CLEANUP_READBACK_FAILED'); + } + if (databaseTargetAbsent(before, DATABASE)) { + const wasOwned = ownership.database === true; + ownership.database = false; + ownership.databaseAttempted = false; + ownership.databaseDropped = wasOwned; + ownership.databaseCleanupReconciled = true; + ownership.databaseName = null; + ownership.databaseBaseline = null; + return false; + } + if (!databaseTargetOwned(before, ownership.databaseBaseline, DATABASE)) { + fail('LOCAL_REHEARSAL_DATABASE_OWNERSHIP_UNPROVEN'); + } + ownership.database = true; + let removalAcknowledged = false; + try { + psqlImpl('postgres', `DROP DATABASE ${DATABASE} WITH (FORCE);`, { actor: 'supabase_admin' }); + removalAcknowledged = true; + } catch { + // A lost DROP acknowledgement is reconciled once; DROP is not replayed. + } + let after; + try { + after = inspectDatabaseOwnership(DATABASE, { scalarImpl }); + } catch { + fail('LOCAL_REHEARSAL_DATABASE_CLEANUP_READBACK_FAILED'); + } + if (!databaseTargetAbsent(after, DATABASE)) { + if (!databaseTargetOwned(after, ownership.databaseBaseline, DATABASE)) { + fail('LOCAL_REHEARSAL_DATABASE_OWNERSHIP_UNPROVEN'); + } + fail('LOCAL_REHEARSAL_DATABASE_DROP_UNCONFIRMED'); + } + ownership.database = false; + ownership.databaseAttempted = false; + ownership.databaseDropped = true; + ownership.databaseRemovalAcknowledged = removalAcknowledged; + ownership.databaseCleanupReconciled = !removalAcknowledged; + ownership.databaseName = null; + ownership.databaseBaseline = null; + return true; +} + +function resetOwnedDatabase(ownership) { + dropOwnedDatabase(ownership); + createOwnedDatabase(ownership); +} + +function fixtureRoleStateQuery() { + return `WITH fixture_roles AS ( + SELECT fixture_role.rolname, + fixture_role.rolcanlogin, + fixture_role.rolsuper, + fixture_role.rolcreatedb, + fixture_role.rolcreaterole, + fixture_role.rolinherit, + fixture_role.rolreplication, + fixture_role.rolbypassrls, + fixture_role.rolconnlimit, + fixture_role.rolvaliduntil, + fixture_role.rolconfig, + coalesce(shobj_description(fixture_role.oid,'pg_authid'),'') AS owner_marker + FROM pg_roles fixture_role + WHERE fixture_role.rolname IN(${quote(FIXTURE_PARENT_ROLE)},${quote(FIXTURE_MEMBER_ROLE)}) +), fixture_memberships AS ( + SELECT parent.rolname AS parent, + member.rolname AS member, + grantor.rolname AS grantor, + membership.admin_option, + membership.inherit_option, + membership.set_option + FROM pg_auth_members membership + JOIN pg_roles parent ON parent.oid=membership.roleid + JOIN pg_roles member ON member.oid=membership.member + JOIN pg_roles grantor ON grantor.oid=membership.grantor + WHERE parent.rolname IN(${quote(FIXTURE_PARENT_ROLE)},${quote(FIXTURE_MEMBER_ROLE)}) + OR member.rolname IN(${quote(FIXTURE_PARENT_ROLE)},${quote(FIXTURE_MEMBER_ROLE)}) +) +SELECT jsonb_build_object( + 'roles',coalesce((SELECT jsonb_agg(to_jsonb(fixture_role) ORDER BY fixture_role.rolname) FROM fixture_roles fixture_role),'[]'::jsonb), + 'memberships',coalesce((SELECT jsonb_agg(to_jsonb(fixture_membership) ORDER BY fixture_membership.parent,fixture_membership.member,fixture_membership.grantor) FROM fixture_memberships fixture_membership),'[]'::jsonb) +)::text;`; +} + +function inspectFixtureMembership({ scalarImpl = scalar } = {}) { + let output; + try { + output = scalarImpl('postgres', fixtureRoleStateQuery(), 'supabase_admin'); + } catch { + fail('LOCAL_REHEARSAL_FIXTURE_ROLE_READBACK_FAILED'); + } + let state; + try { + state = JSON.parse(output); + } catch { + fail('LOCAL_REHEARSAL_FIXTURE_ROLE_READBACK_INVALID'); + } + if (!Array.isArray(state?.roles) || !Array.isArray(state?.memberships)) { + fail('LOCAL_REHEARSAL_FIXTURE_ROLE_READBACK_INVALID'); + } + return Object.freeze({ + roles: Object.freeze(state.roles.map(role => Object.freeze({ ...role }))), + memberships: Object.freeze(state.memberships.map(membership => Object.freeze({ ...membership }))), + }); +} + +function fixtureMembershipAbsent(state) { + return state.roles.length === 0 && state.memberships.length === 0; +} + +function fixtureMembershipOwned(state, token) { + const roles = new Map(state.roles.map(role => [role.rolname, role])); + const parent = roles.get(FIXTURE_PARENT_ROLE); + const member = roles.get(FIXTURE_MEMBER_ROLE); + const membership = state.memberships[0]; + const sharedRoleShape = role => role + && role.rolcanlogin === false + && role.rolsuper === false + && role.rolcreatedb === false + && role.rolcreaterole === false + && role.rolreplication === false + && role.rolbypassrls === false + && role.rolconnlimit === -1 + && role.rolvaliduntil === null + && role.rolconfig === null; + return state.roles.length === 2 + && sharedRoleShape(parent) + && parent.rolinherit === false + && parent.owner_marker === `${FIXTURE_OWNER_PREFIX}:${token}:parent` + && sharedRoleShape(member) + && member.rolinherit === true + && member.owner_marker === `${FIXTURE_OWNER_PREFIX}:${token}:member` + && state.memberships.length === 1 + && membership.parent === FIXTURE_PARENT_ROLE + && membership.member === FIXTURE_MEMBER_ROLE + && membership.grantor === 'supabase_admin' + && membership.admin_option === false + && membership.inherit_option === true + && membership.set_option === true; +} + +export function createFixtureMembership(ownership, { + scalarImpl = scalar, + psqlImpl = psql, + generateToken = () => randomBytes(16).toString('hex'), +} = {}) { + if (!ownership + || ownership.fixtureMembership !== false + || ownership.fixtureMembershipAttempted + || ownership.fixtureMembershipToken + || ownership.fixtureMembershipBaseline) { + fail('LOCAL_REHEARSAL_FIXTURE_ROLE_OWNERSHIP_STATE_INVALID'); + } + const baseline = inspectFixtureMembership({ scalarImpl }); + ownership.fixtureMembershipBaseline = baseline; + if (!fixtureMembershipAbsent(baseline)) fail('LOCAL_REHEARSAL_FIXTURE_ROLE_EXISTS'); + const token = generateToken(); + if (!/^[0-9a-f]{32}$/.test(token)) fail('LOCAL_REHEARSAL_FIXTURE_ROLE_TOKEN_INVALID'); + ownership.fixtureMembershipToken = token; + ownership.fixtureMembershipAttempted = true; + let creationAcknowledged = false; + try { + psqlImpl('postgres', ` +BEGIN; +CREATE ROLE ${FIXTURE_PARENT_ROLE} NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOREPLICATION NOBYPASSRLS; +CREATE ROLE ${FIXTURE_MEMBER_ROLE} NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOREPLICATION NOBYPASSRLS; +COMMENT ON ROLE ${FIXTURE_PARENT_ROLE} IS ${quote(`${FIXTURE_OWNER_PREFIX}:${token}:parent`)}; +COMMENT ON ROLE ${FIXTURE_MEMBER_ROLE} IS ${quote(`${FIXTURE_OWNER_PREFIX}:${token}:member`)}; +GRANT ${FIXTURE_PARENT_ROLE} TO ${FIXTURE_MEMBER_ROLE} + WITH ADMIN FALSE, INHERIT TRUE, SET TRUE GRANTED BY supabase_admin; +COMMIT; +`, { actor: 'supabase_admin' }); + creationAcknowledged = true; + } catch {} + const observed = inspectFixtureMembership({ scalarImpl }); + if (!fixtureMembershipOwned(observed, token)) { + ownership.fixtureMembershipAttempted = false; + if (fixtureMembershipAbsent(observed)) fail('LOCAL_REHEARSAL_FIXTURE_ROLE_CREATION_UNCONFIRMED'); + fail('LOCAL_REHEARSAL_FIXTURE_ROLE_OWNERSHIP_UNPROVEN'); + } + ownership.fixtureMembership = true; + ownership.fixtureMembershipCreationAcknowledged = creationAcknowledged; + ownership.fixtureMembershipCreationReconciled = !creationAcknowledged; + return true; +} + +export function dropFixtureMembership(ownership, { + scalarImpl = scalar, + psqlImpl = psql, +} = {}) { + if (!ownership?.fixtureMembership && !ownership?.fixtureMembershipAttempted) return false; + const token = ownership.fixtureMembershipToken; + if (!/^[0-9a-f]{32}$/.test(token || '') + || !ownership.fixtureMembershipBaseline + || !fixtureMembershipAbsent(ownership.fixtureMembershipBaseline)) { + fail('LOCAL_REHEARSAL_FIXTURE_ROLE_OWNERSHIP_STATE_INVALID'); + } + const before = inspectFixtureMembership({ scalarImpl }); + if (fixtureMembershipAbsent(before)) { + const wasOwned = ownership.fixtureMembership === true; + ownership.fixtureMembership = false; + ownership.fixtureMembershipAttempted = false; + ownership.fixtureMembershipDropped = wasOwned; + ownership.fixtureMembershipCleanupReconciled = true; + return wasOwned; + } + if (!fixtureMembershipOwned(before, token)) { + fail('LOCAL_REHEARSAL_FIXTURE_ROLE_OWNERSHIP_UNPROVEN'); + } + // An earlier creation readback can fail after COMMIT. Exact token, shape, + // membership, and empty preimage establish this run's cleanup authority. + ownership.fixtureMembership = true; + let removalAcknowledged = false; + try { + psqlImpl('postgres', ` +BEGIN; +REVOKE ${FIXTURE_PARENT_ROLE} FROM ${FIXTURE_MEMBER_ROLE} GRANTED BY supabase_admin; +DROP ROLE ${FIXTURE_MEMBER_ROLE}; +DROP ROLE ${FIXTURE_PARENT_ROLE}; +COMMIT; +`, { actor: 'supabase_admin' }); + removalAcknowledged = true; + } catch {} + const after = inspectFixtureMembership({ scalarImpl }); + if (!fixtureMembershipAbsent(after)) { + if (!fixtureMembershipOwned(after, token)) { + fail('LOCAL_REHEARSAL_FIXTURE_ROLE_OWNERSHIP_UNPROVEN'); + } + fail('LOCAL_REHEARSAL_FIXTURE_ROLE_REMOVAL_UNCONFIRMED'); + } + ownership.fixtureMembership = false; + ownership.fixtureMembershipAttempted = false; + ownership.fixtureMembershipDropped = true; + ownership.fixtureMembershipRemovalAcknowledged = removalAcknowledged; + return true; +} + +function cleanupOwnedFixtures(ownership) { + const failures = []; + try { + dropOwnedDatabase(ownership); + } catch (error) { + failures.push(`database:${error.message}`); + } + try { + dropFixtureMembership(ownership); + } catch (error) { + failures.push(`roles:${error.message}`); + } + if (failures.length > 0) fail('LOCAL_REHEARSAL_CLEANUP_FAILED', failures.join(',')); +} + +export function inspectRetainedProof({ + outputPath = OUTPUT_PATH, + checksumsPath = CHECKSUMS_PATH, + currentBindings, + existsImpl = existsSync, + readFileImpl = readFileSync, +} = {}) { + if (!existsImpl(outputPath)) return Object.freeze({ present: false }); + if (!currentBindings || typeof currentBindings !== 'object') { + fail('LOCAL_REHEARSAL_CURRENT_BINDINGS_INVALID'); + } + const checksumLines = readFileImpl(checksumsPath, 'utf8').split(/\r?\n/).filter(Boolean); + const matchingLines = checksumLines.filter(line => line.endsWith(` ${RETAINED_PROOF_REPOSITORY_PATH}`)); + if (matchingLines.length !== 1) fail('LOCAL_REHEARSAL_RETAINED_CHECKSUM_MISSING'); + const checksumMatch = matchingLines[0].match(/^([0-9a-f]{64}) (.+)$/); + if (!checksumMatch || checksumMatch[2] !== RETAINED_PROOF_REPOSITORY_PATH) { + fail('LOCAL_REHEARSAL_RETAINED_CHECKSUM_INVALID'); + } + const bytes = readFileImpl(outputPath); + const actualSha256 = sha256(bytes); + if (actualSha256 !== checksumMatch[1]) fail('LOCAL_REHEARSAL_RETAINED_SHA256_MISMATCH'); + let retained; + try { + retained = JSON.parse(bytes.toString('utf8')); + } catch { + fail('LOCAL_REHEARSAL_RETAINED_JSON_INVALID'); + } + const retainedBindings = { + manifestSha256: retained.manifestSha256, + rehearsalHarnessSha256: retained.rehearsalHarnessSha256, + sourceRoot: retained.sourceRoot, + }; + if (retained.status !== 'passed' + || retained.temporaryDatabaseDropped !== true + || retained.hostedWrites !== false + || retained.operatingWrites !== false + || Object.values(retainedBindings).some(value => !/^[0-9a-f]{64}$/.test(value))) { + fail('LOCAL_REHEARSAL_RETAINED_SHAPE_INVALID'); + } + const bindingDifferences = Object.keys(retainedBindings) + .filter(key => retainedBindings[key] !== currentBindings[key]) + .map(key => ({ + field: key, + retained: retainedBindings[key], + current: currentBindings[key], + })); + return Object.freeze({ + present: true, + classification: 'retained-historical-evidence', + repositoryPath: RETAINED_PROOF_REPOSITORY_PATH, + expectedSha256: checksumMatch[1], + actualSha256, + bindings: Object.freeze(retainedBindings), + currentSourceMatch: bindingDifferences.length === 0, + bindingDifferences: Object.freeze(bindingDifferences), + usedAsCurrentProof: false, + }); +} + +function applyRawMigration(record, index) { + const migration = record.manifest.migrations[index]; + const material = record.materials[index]; + try { + psql(DATABASE, material.bytes); + psql(DATABASE, `${ledgerInsert(migration)}\n`); + } catch (error) { + fail('LOCAL_REHEARSAL_MIGRATION_FAILED', `${index + 1}:${error.message}`); + } +} + +function collectStageStates(record, stateQuery) { + const states = [readState(DATABASE, stateQuery)]; + for (let index = 0; index < record.manifest.migrations.length; index += 1) { + applyRawMigration(record, index); + states.push(readState(DATABASE, stateQuery)); + } + return states; +} + +function summarizeStage(stage, index) { + return { + index, + ledgerCount: stage.bundle.ledgerCount, + targetCount: stage.bundle.targetCount, + prefixRoot: stage.bundle.prefixRoot, + suffixRoot: stage.bundle.suffixRoot, + schemaRoot: stage.state.schemaRoot, + ledgerRoot: stage.state.ledgerRoot, + g014Root: stage.state.g014Root, + storageRoot: stage.state.storageRoot, + finishCleanupBodySha256: stage.state.finishCleanupBodySha256, + ownerAuthorityReady: stage.state.ownerAuthorityReady, + terminalContractReady: stage.state.terminalContractReady, + }; +} + +function assertRehearsalStages(record, stages) { + const { manifest } = record; + if (stages.length !== 12) fail('LOCAL_REHEARSAL_STAGE_COUNT_INVALID'); + const prefixRoot = stages[0].bundle.prefixRoot; + stages.forEach((stage, index) => { + if (stage.bundle.ledgerCount !== 86 + index + || stage.bundle.priorCount !== 86 + || stage.bundle.prefixRoot !== prefixRoot + || stage.bundle.targetCount !== index + || stage.state.activeReviewItems !== 0 + || stage.state.reviewItems !== 0 + || stage.state.reviewRuns !== 0 + || stage.state.reviewPolicyEnabled !== false + || stage.state.currentUser !== 'postgres' + || stage.state.sessionUser !== 'postgres' + || stage.state.databaseName !== DATABASE + || stage.state.serverMajor !== 17 + || stage.state.ownerAuthorityReady !== true + || stage.state.terminalContractReady !== (index === 11)) { + fail('LOCAL_REHEARSAL_STAGE_INVALID', String(index)); + } + }); + if (stages[10].state.finishCleanupBodySha256 !== manifest.terminalContract.finishPreimageSha256 + || stages[11].state.finishCleanupBodySha256 !== manifest.terminalContract.finishPostimageSha256) { + fail('LOCAL_REHEARSAL_FINISH_PREIMAGE_INVALID'); + } + if (!equal(stages.slice(0, 3).map(stage => stage.state.g014Root), Array(3).fill(stages[0].state.g014Root)) + || !equal(stages.slice(4).map(stage => stage.state.g014Root), Array(8).fill(stages[4].state.g014Root))) { + fail('LOCAL_REHEARSAL_G014_TRANSITION_INVALID'); + } +} + +function tamperFinishCleanupPreimage() { + psql(DATABASE, ` +BEGIN; +DO $guard$ BEGIN + IF EXISTS(SELECT 1 FROM pg_auth_members + WHERE roleid='privacy_workflow_owner'::regrole AND member='postgres'::regrole AND grantor='postgres'::regrole) + THEN RAISE EXCEPTION 'LOCAL_REHEARSAL_SELF_GRANT_EXISTS'; END IF; +END $guard$; +GRANT privacy_workflow_owner TO postgres WITH ADMIN FALSE,INHERIT FALSE,SET TRUE GRANTED BY postgres; +SET LOCAL ROLE privacy_workflow_owner; +DO $tamper$ +DECLARE definition text; source text; +BEGIN + SELECT pg_get_functiondef(oid),prosrc INTO definition,source + FROM pg_proc WHERE oid='public.finish_review_media_cleanup()'::regprocedure; + EXECUTE replace(definition,source,source || E'\\n-- local-only preimage drift'); +END $tamper$; +RESET ROLE; +REVOKE privacy_workflow_owner FROM postgres GRANTED BY postgres; +COMMIT; +`); +} + +function assertCatalogDriftRejected({ label, ownership, plan, stages, stateQuery, tamperSql, restoreSql = null }) { + resetOwnedDatabase(ownership); + const baseline = readState(DATABASE, stateQuery); + if (!equal(baseline, stages[0])) fail('LOCAL_REHEARSAL_DRIFT_BASELINE_INVALID', label); + psql(DATABASE, tamperSql, { actor: 'supabase_admin' }); + const driftedBefore = readState(DATABASE, stateQuery); + if (driftedBefore.state.schemaRoot === baseline.state.schemaRoot + || driftedBefore.state.g014Root !== baseline.state.g014Root + || driftedBefore.state.storageRoot !== baseline.state.storageRoot + || driftedBefore.state.ledgerRoot !== baseline.state.ledgerRoot) { + fail('LOCAL_REHEARSAL_DRIFT_PROJECTION_INVALID', label); + } + const rejected = psql(DATABASE, plan.sql, { singleTransaction: true, allowFailure: true }); + if (rejected.status === 0 || !/MIGRATION_PRIOR_STATE_MISMATCH/.test(rejected.stderr)) { + fail('LOCAL_REHEARSAL_CATALOG_DRIFT_NOT_REJECTED', label); + } + const driftedAfter = readState(DATABASE, stateQuery); + if (!equal(driftedBefore, driftedAfter) + || driftedAfter.bundle.targetCount !== 0 + || driftedAfter.bundle.ledgerCount !== 86) { + fail('LOCAL_REHEARSAL_CATALOG_DRIFT_STATE_CHANGED', label); + } + if (restoreSql) { + psql(DATABASE, restoreSql, { actor: 'supabase_admin' }); + if (!equal(readState(DATABASE, stateQuery), baseline)) { + fail('LOCAL_REHEARSAL_CATALOG_DRIFT_RESTORE_FAILED', label); + } + } + return true; +} + +function startAdvisoryLockHolder() { + const args = [ + '--context', DOCKER_CONTEXT, + 'exec', '-i', '-e', `PGPASSWORD=${FIXTURE_PASSWORD}`, DOCKER_CONTAINER, + 'psql', '-XAtq', '-v', 'ON_ERROR_STOP=1', '-h', '127.0.0.1', '-U', 'postgres', '-d', DATABASE, + ]; + const child = spawn('docker', args, { stdio: ['pipe', 'pipe', 'pipe'] }); + let stdout = ''; + let stderr = ''; + let settled = false; + const ready = new Promise((resolveReady, rejectReady) => { + const reject = detail => { + if (!settled) { + settled = true; + rejectReady(new Error(`LOCAL_REHEARSAL_LOCK_HOLDER_FAILED:${detail}`)); + } + }; + child.on('error', error => reject(error.code || 'SPAWN')); + child.stdout.on('data', chunk => { + stdout += chunk.toString(); + if (!settled && stdout.includes('LOCK_HELD')) { + settled = true; + resolveReady(); + } + }); + child.stderr.on('data', chunk => { stderr += chunk.toString(); }); + child.on('exit', code => { + if (!settled) reject(`${code}:${stderr.replace(/\s+/g, ' ').slice(0, 120)}`); + }); + }); + const completion = new Promise((resolveCompletion, rejectCompletion) => { + child.on('error', rejectCompletion); + child.on('close', code => { + if (code === 0) resolveCompletion(); + else rejectCompletion(new Error(`LOCAL_REHEARSAL_LOCK_HOLDER_FAILED:${code}:${stderr.replace(/\s+/g, ' ').slice(0, 120)}`)); + }); + }); + child.stdin.end(`BEGIN;\nSELECT pg_advisory_xact_lock(7311754282260211::bigint);\nSELECT 'LOCK_HELD';\nSELECT pg_sleep(1.55);\nCOMMIT;\n`); + return { child, completion, ready }; +} + +function startPsqlProcess(database, sql, { applicationName, singleTransaction = false } = {}) { + const args = [ + '--context', DOCKER_CONTEXT, + 'exec', '-i', '-e', `PGPASSWORD=${FIXTURE_PASSWORD}`, + ]; + if (applicationName) args.push('-e', `PGAPPNAME=${applicationName}`); + args.push( + DOCKER_CONTAINER, + 'psql', '-XAtq', '-v', 'ON_ERROR_STOP=1', '-v', 'VERBOSITY=terse', + ); + if (singleTransaction) args.push('--single-transaction'); + args.push('-h', '127.0.0.1', '-U', 'postgres', '-d', database, '--file=-'); + const child = spawn('docker', args, { stdio: ['pipe', 'pipe', 'pipe'] }); + let stdout = ''; + let stderr = ''; + let inputError = null; + child.stdout.on('data', chunk => { stdout += chunk.toString(); }); + child.stderr.on('data', chunk => { stderr += chunk.toString(); }); + child.stdin.on('error', error => { + if (error.code !== 'EPIPE') inputError = error; + }); + const completion = new Promise((resolveCompletion, rejectCompletion) => { + child.on('error', rejectCompletion); + child.on('close', status => { + if (inputError) rejectCompletion(inputError); + else resolveCompletion({ status, stderr, stdout }); + }); + }); + child.stdin.end(sql); + return { child, completion }; +} + +async function waitForAdvisoryContention(expiresAt) { + const deadline = Math.min(expiresAt - 25, Date.now() + 700); + while (Date.now() < deadline) { + const waiting = scalar(DATABASE, `SELECT count(*) FROM pg_stat_activity a + JOIN pg_locks l ON l.pid=a.pid + WHERE a.application_name=${quote(EXPIRY_APPLICATION_NAME)} + AND l.locktype='advisory' AND NOT l.granted;`, 'supabase_admin'); + if (waiting === '1') return Date.now(); + if (waiting !== '0') fail('LOCAL_REHEARSAL_CONTENTION_CARDINALITY_INVALID'); + await new Promise(resolveDelay => setTimeout(resolveDelay, 20)); + } + fail('LOCAL_REHEARSAL_ADVISORY_CONTENTION_NOT_OBSERVED'); +} + +async function assertContendedExpiryRejected(record, stages, stateQuery, ownership) { + resetOwnedDatabase(ownership); + const baseline = readState(DATABASE, stateQuery); + if (!equal(baseline, stages[0])) fail('LOCAL_REHEARSAL_EXPIRY_BASELINE_INVALID'); + const expiresAt = Date.now() + 1500; + const expiry = new Date(expiresAt).toISOString(); + const expiringPlan = compileSuccessorPlan(record, { expiresAt: expiry, stageStates: stages }); + const holder = startAdvisoryLockHolder(); + let apply = null; + try { + await holder.ready; + if (Date.now() >= expiresAt) fail('LOCAL_REHEARSAL_EXPIRY_WINDOW_TOO_SHORT'); + apply = startPsqlProcess(DATABASE, expiringPlan.sql, { + applicationName: EXPIRY_APPLICATION_NAME, + singleTransaction: true, + }); + const contentionObservedAt = await waitForAdvisoryContention(expiresAt); + if (contentionObservedAt >= expiresAt) fail('LOCAL_REHEARSAL_CONTENTION_OBSERVED_AFTER_EXPIRY'); + const [rejected] = await Promise.all([apply.completion, holder.completion]); + if (rejected.status === 0 + || !/SUCCESSOR_ADMISSION_EXPIRED/.test(rejected.stderr) + || /lock timeout/.test(rejected.stderr)) { + fail('LOCAL_REHEARSAL_CONTENDED_EXPIRY_NOT_REJECTED'); + } + } finally { + if (apply?.child.exitCode === null) apply.child.kill('SIGTERM'); + if (holder.child.exitCode === null) holder.child.kill('SIGTERM'); + } + const after = readState(DATABASE, stateQuery); + if (!equal(after, baseline) || after.bundle.targetCount !== 0 || after.bundle.ledgerCount !== 86) { + fail('LOCAL_REHEARSAL_CONTENDED_EXPIRY_STATE_CHANGED'); + } + return true; +} + +export async function run() { + const manifestBytes = readFileSync(resolve(REPOSITORY_ROOT, '.github/review-media-sql-successor.v1.json')); + const record = loadSuccessorManifest(); + const currentBindings = Object.freeze({ + manifestSha256: sha256(manifestBytes), + rehearsalHarnessSha256: sha256(readFileSync(fileURLToPath(import.meta.url))), + sourceRoot: record.manifest.sourceRoot, + }); + const retainedProof = inspectRetainedProof({ currentBindings }); + const stateQuery = buildSuccessorStateQuery(record.manifest); + const template = JSON.parse(scalar(TEMPLATE_DATABASE, `SELECT json_build_object( + 'engine',current_setting('server_version'), + 'ledger',(SELECT count(*) FROM supabase_migrations.schema_migrations), + 'manifest',(SELECT count(*) FROM privacy_retention.g014_catalog_contract_manifest), + 'profiles',(SELECT count(*) FROM public.profiles), + 'reviews',(SELECT count(*) FROM public.reviews) + )::text;`, 'supabase_admin')); + if (!String(template.engine).startsWith('17.6') + || template.ledger !== 0 + || template.profiles !== 0 + || template.reviews !== 0) fail('LOCAL_REHEARSAL_TEMPLATE_INVALID'); + + const ownership = { + database: false, + databaseAttempted: false, + databaseName: null, + databaseBaseline: null, + fixtureMembership: false, + fixtureMembershipAttempted: false, + fixtureMembershipToken: null, + fixtureMembershipBaseline: null, + }; + let proof; + try { + createFixtureMembership(ownership); + createOwnedDatabase(ownership); + if (scalar(DATABASE, `SELECT coalesce('security_invoker=true'=ANY(c.reloptions),false) + FROM pg_class c WHERE c.oid='public.admin_evaluation_related_rows'::regclass;`) !== 't') { + fail('LOCAL_REHEARSAL_SECURITY_INVOKER_BASELINE_INVALID'); + } + if (scalar(DATABASE, `SELECT m.inherit_option::text || ':' || m.set_option::text + FROM pg_auth_members m + WHERE m.roleid=${quote(FIXTURE_PARENT_ROLE)}::regrole + AND m.member=${quote(FIXTURE_MEMBER_ROLE)}::regrole + AND m.grantor='supabase_admin'::regrole;`, 'supabase_admin') !== 'true:true') { + fail('LOCAL_REHEARSAL_MEMBERSHIP_OPTIONS_BASELINE_INVALID'); + } + const stages = collectStageStates(record, stateQuery); + assertRehearsalStages(record, stages); + const plan = compileSuccessorPlan(record, { + expiresAt: '9999-12-31T23:59:59.999Z', + stageStates: stages, + }); + + resetOwnedDatabase(ownership); + const application = applySuccessorPlanOnce({ + database: DATABASE, + sql: plan.sql, + stateQuery, + baselineState: stages[0], + terminalState: stages[11], + }); + const success = application.result; + const successLines = success.stdout.trim().split(/\r?\n/).filter(Boolean); + const successReadback = JSON.parse(successLines.at(-1)); + const committed = readState(DATABASE, stateQuery); + if (!equal(successReadback, stages[11]) || !equal(committed, stages[11])) { + fail('LOCAL_REHEARSAL_COMMIT_READBACK_INVALID'); + } + + resetOwnedDatabase(ownership); + const rollback = psql(DATABASE, `${plan.sql}\nDO $forced$ BEGIN RAISE EXCEPTION 'LOCAL_FORCED_ROLLBACK'; END $forced$;\n`, { + singleTransaction: true, + allowFailure: true, + }); + if (rollback.status === 0 || !/LOCAL_FORCED_ROLLBACK/.test(rollback.stderr)) { + fail('LOCAL_REHEARSAL_FORCED_ROLLBACK_MISSING'); + } + const rolledBack = readState(DATABASE, stateQuery); + if (!equal(rolledBack, stages[0])) fail('LOCAL_REHEARSAL_ROLLBACK_DRIFT'); + + resetOwnedDatabase(ownership); + for (let index = 0; index < 10; index += 1) applyRawMigration(record, index); + const cleanPreimage = readState(DATABASE, stateQuery); + if (!equal(cleanPreimage, stages[10])) fail('LOCAL_REHEARSAL_STAGE10_REPLAY_DRIFT'); + tamperFinishCleanupPreimage(); + const tamperedBefore = readState(DATABASE, stateQuery); + const wrongPreimage = psql(DATABASE, record.materials[10].bytes, { allowFailure: true }); + if (wrongPreimage.status === 0 || !/REVIEW_SHARED_SOURCE_DRIFT/.test(wrongPreimage.stderr)) { + fail('LOCAL_REHEARSAL_WRONG_PREIMAGE_NOT_REJECTED'); + } + const tamperedAfter = readState(DATABASE, stateQuery); + if (!equal(tamperedBefore, tamperedAfter) + || tamperedAfter.bundle.targetCount !== 10 + || tamperedAfter.bundle.ledgerCount !== 96) { + fail('LOCAL_REHEARSAL_WRONG_PREIMAGE_ROLLBACK_DRIFT'); + } + + const securityInvokerDriftRejected = assertCatalogDriftRejected({ + label: 'security-invoker', + ownership, + plan, + stages, + stateQuery, + tamperSql: `ALTER VIEW public.admin_evaluation_related_rows SET (security_invoker=false);`, + }); + const columnAclDriftRejected = assertCatalogDriftRejected({ + label: 'column-acl', + ownership, + plan, + stages, + stateQuery, + tamperSql: `GRANT UPDATE(approved_name) ON TABLE public.restaurants TO anon;`, + }); + const membershipInheritOptionDriftRejected = assertCatalogDriftRejected({ + label: 'membership-inherit-option', + ownership, + plan, + stages, + stateQuery, + tamperSql: `GRANT ${FIXTURE_PARENT_ROLE} TO ${FIXTURE_MEMBER_ROLE} WITH ADMIN FALSE, INHERIT FALSE, SET TRUE GRANTED BY supabase_admin;`, + restoreSql: `GRANT ${FIXTURE_PARENT_ROLE} TO ${FIXTURE_MEMBER_ROLE} WITH ADMIN FALSE, INHERIT TRUE, SET TRUE GRANTED BY supabase_admin;`, + }); + const membershipSetOptionDriftRejected = assertCatalogDriftRejected({ + label: 'membership-set-option', + ownership, + plan, + stages, + stateQuery, + tamperSql: `GRANT ${FIXTURE_PARENT_ROLE} TO ${FIXTURE_MEMBER_ROLE} WITH ADMIN FALSE, INHERIT TRUE, SET FALSE GRANTED BY supabase_admin;`, + restoreSql: `GRANT ${FIXTURE_PARENT_ROLE} TO ${FIXTURE_MEMBER_ROLE} WITH ADMIN FALSE, INHERIT TRUE, SET TRUE GRANTED BY supabase_admin;`, + }); + const contendedAdmissionExpiryRejected = await assertContendedExpiryRejected(record, stages, stateQuery, ownership); + + proof = { + schemaVersion: 1, + status: 'passed', + environment: { + dockerContext: DOCKER_CONTEXT, + dockerContainer: DOCKER_CONTAINER, + engine: template.engine, + templateDatabase: TEMPLATE_DATABASE, + temporaryDatabase: DATABASE, + }, + input: template, + ...currentBindings, + predecessorSources: PREDECESSOR_PATHS.map(path => ({ + path, + sha256: sha256(readFileSync(resolve(REPOSITORY_ROOT, path))), + })), + migrationCount: record.manifest.migrations.length, + statementCount: record.manifest.migrations.reduce((sum, migration) => sum + migration.statementCount, 0), + stageStatesSha256: sha256(JSON.stringify(canonical(stages))), + stages: stages.map(summarizeStage), + results: { + advisoryContentionObserved: true, + atomicCommit: true, + atomicCommitOutcome: application.outcome, + atomicRollbackExact: true, + catalogDriftStatePreserved: true, + columnAclDriftRejected, + compiledSqlBytes: Buffer.byteLength(plan.sql), + compiledSqlSha256: sha256(plan.sql), + contendedAdmissionExpiryRejected, + contendedAdmissionExpiryStatePreserved: true, + membershipOptionsBaseline: { inherit: true, set: true }, + membershipInheritOptionDriftRejected, + membershipSetOptionDriftRejected, + securityInvokerBaseline: true, + securityInvokerDriftRejected, + wrongFinishPreimageRejected: true, + wrongFinishPreimageRollbackExact: true, + terminalLedgerCount: committed.bundle.ledgerCount, + terminalContractReady: committed.state.terminalContractReady, + }, + sourceMutation: false, + hostedWrites: false, + operatingWrites: false, + }; + } finally { + cleanupOwnedFixtures(ownership); + } + proof.temporaryDatabaseDropped = true; + proof.fixtureRolesDropped = true; + const currentProofBytes = Buffer.from(`${JSON.stringify(canonical(proof))}\n`); + process.stdout.write(`${JSON.stringify({ + current: { + classification: 'current-source-verification', + proofSha256: sha256(currentProofBytes), + bindings: currentBindings, + proof, + }, + retained: retainedProof, + })}\n`); +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await run(); diff --git a/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs b/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs index 779cf90ec7..6f7c1560ba 100644 --- a/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs +++ b/apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs @@ -550,6 +550,7 @@ function cleanupOwnedFixtures(ownership) { export function inspectRetainedProof({ outputPath = OUTPUT_PATH, checksumsPath = CHECKSUMS_PATH, + checksumsDigestPath = `${checksumsPath}.sha256`, currentBindings, existsImpl = existsSync, readFileImpl = readFileSync, @@ -558,7 +559,12 @@ export function inspectRetainedProof({ if (!currentBindings || typeof currentBindings !== 'object') { fail('LOCAL_REHEARSAL_CURRENT_BINDINGS_INVALID'); } - const checksumLines = readFileImpl(checksumsPath, 'utf8').split(/\r?\n/).filter(Boolean); + if (!existsImpl(checksumsDigestPath)) fail('LOCAL_REHEARSAL_CHECKSUMS_DIGEST_MISSING'); + const checksumBytes = readFileImpl(checksumsPath); + const checksumDigest = readFileImpl(checksumsDigestPath, 'utf8').trim(); + if (!/^[a-f0-9]{64}$/.test(checksumDigest)) fail('LOCAL_REHEARSAL_CHECKSUMS_DIGEST_INVALID'); + if (sha256(checksumBytes) !== checksumDigest) fail('LOCAL_REHEARSAL_CHECKSUMS_SHA256_MISMATCH'); + const checksumLines = checksumBytes.toString('utf8').split(/\r?\n/).filter(Boolean); const matchingLines = checksumLines.filter(line => line.endsWith(` ${RETAINED_PROOF_REPOSITORY_PATH}`)); if (matchingLines.length !== 1) fail('LOCAL_REHEARSAL_RETAINED_CHECKSUM_MISSING'); const checksumMatch = matchingLines[0].match(/^([0-9a-f]{64}) (.+)$/); @@ -599,6 +605,7 @@ export function inspectRetainedProof({ repositoryPath: RETAINED_PROOF_REPOSITORY_PATH, expectedSha256: checksumMatch[1], actualSha256, + checksumMapSha256: checksumDigest, bindings: Object.freeze(retainedBindings), currentSourceMatch: bindingDifferences.length === 0, bindingDifferences: Object.freeze(bindingDifferences), @@ -766,8 +773,15 @@ function startAdvisoryLockHolder() { else rejectCompletion(new Error(`LOCAL_REHEARSAL_LOCK_HOLDER_FAILED:${code}:${stderr.replace(/\s+/g, ' ').slice(0, 120)}`)); }); }); - child.stdin.end(`BEGIN;\nSELECT pg_advisory_xact_lock(7311754282260211::bigint);\nSELECT 'LOCK_HELD';\nSELECT pg_sleep(1.55);\nCOMMIT;\n`); - return { child, completion, ready }; + child.stdin.write(`BEGIN;\nSET LOCAL statement_timeout='10s';\nSET LOCAL idle_in_transaction_session_timeout='10s';\nSELECT pg_advisory_xact_lock(7311754282260211::bigint);\nSELECT 'LOCK_HELD';\n`); + let released = false; + const release = () => { + if (!released) { + released = true; + child.stdin.end('COMMIT;\n'); + } + }; + return { child, completion, ready, release }; } function startPsqlProcess(database, sql, { applicationName, singleTransaction = false } = {}) { @@ -802,16 +816,23 @@ function startPsqlProcess(database, sql, { applicationName, singleTransaction = return { child, completion }; } -async function waitForAdvisoryContention(expiresAt) { - const deadline = Math.min(expiresAt - 25, Date.now() + 700); - while (Date.now() < deadline) { - const waiting = scalar(DATABASE, `SELECT count(*) FROM pg_stat_activity a +export async function waitForAdvisoryContention(expiresAt, { + nowMs = Date.now, + readWaiting = () => scalar(DATABASE, `SELECT count(*) FROM pg_stat_activity a JOIN pg_locks l ON l.pid=a.pid WHERE a.application_name=${quote(EXPIRY_APPLICATION_NAME)} - AND l.locktype='advisory' AND NOT l.granted;`, 'supabase_admin'); - if (waiting === '1') return Date.now(); + AND l.locktype='advisory' AND NOT l.granted;`, 'supabase_admin'), + delayImpl = ms => new Promise(resolveDelay => setTimeout(resolveDelay, ms)), +} = {}) { + const deadline = Math.min(expiresAt - 25, nowMs() + 2000); + while (nowMs() < deadline) { + const waiting = readWaiting(); + if (waiting === '1') { + if (nowMs() >= expiresAt) fail('LOCAL_REHEARSAL_CONTENTION_OBSERVED_AFTER_EXPIRY'); + return nowMs(); + } if (waiting !== '0') fail('LOCAL_REHEARSAL_CONTENTION_CARDINALITY_INVALID'); - await new Promise(resolveDelay => setTimeout(resolveDelay, 20)); + await delayImpl(20); } fail('LOCAL_REHEARSAL_ADVISORY_CONTENTION_NOT_OBSERVED'); } @@ -820,13 +841,14 @@ async function assertContendedExpiryRejected(record, stages, stateQuery, ownersh resetOwnedDatabase(ownership); const baseline = readState(DATABASE, stateQuery); if (!equal(baseline, stages[0])) fail('LOCAL_REHEARSAL_EXPIRY_BASELINE_INVALID'); - const expiresAt = Date.now() + 1500; - const expiry = new Date(expiresAt).toISOString(); - const expiringPlan = compileSuccessorPlan(record, { expiresAt: expiry, stageStates: stages }); const holder = startAdvisoryLockHolder(); let apply = null; try { await holder.ready; + // Fixture setup and Docker startup must not consume the admitted lock-wait window. + const expiresAt = Date.now() + 1500; + const expiry = new Date(expiresAt).toISOString(); + const expiringPlan = compileSuccessorPlan(record, { expiresAt: expiry, stageStates: stages }); if (Date.now() >= expiresAt) fail('LOCAL_REHEARSAL_EXPIRY_WINDOW_TOO_SHORT'); apply = startPsqlProcess(DATABASE, expiringPlan.sql, { applicationName: EXPIRY_APPLICATION_NAME, @@ -834,6 +856,8 @@ async function assertContendedExpiryRejected(record, stages, stateQuery, ownersh }); const contentionObservedAt = await waitForAdvisoryContention(expiresAt); if (contentionObservedAt >= expiresAt) fail('LOCAL_REHEARSAL_CONTENTION_OBSERVED_AFTER_EXPIRY'); + await new Promise(resolveDelay => setTimeout(resolveDelay, Math.max(0, expiresAt - Date.now() + 25))); + holder.release(); const [rejected] = await Promise.all([apply.completion, holder.completion]); if (rejected.status === 0 || !/SUCCESSOR_ADMISSION_EXPIRED/.test(rejected.stderr) diff --git a/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS b/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS index 9fdb091e51..cce48e79e1 100644 --- a/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS +++ b/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS @@ -1,6 +1,13 @@ 31e775a669d4a1295fc8b391653f26d0559695937f4cc357818ef53faa7c8e67 apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS-0833ed81-historical +5a41205af7546221083956ddac62488f51254b253de1d738f39ab714d4835026 apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS-4ba9c6d2-historical +0d7c993375a3015143cc03b84fd7b369e68713cd49926e87205c023a773def76 apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS-4ba9c6d2-historical.sha256 +ad9406090dea1f2c1f8a146f5e241aae2651449f0c58af50201116e39ccfa2b6 apps/web/performance/review-media-operating-admission-20261013/controller-reverification-v2.json d29f5fa8ddc27573b3ea2d2a144c9d9a1c24a6c5ac4d7aeef30e3bda514fc575 apps/web/performance/review-media-operating-admission-20261013/controller-reverification.json +66634350db141610eca229f7fa4a45ca2b9733541a25c9bcbdff0a492d746225 apps/web/performance/review-media-operating-admission-20261013/current-evidence.json +af2b42d1ef05343b00bf58c699aff20bb6d80194de40b7f128d3fcf823f5f9a3 apps/web/performance/review-media-operating-admission-20261013/review-fix-parity-20261014-v1.log +af2b42d1ef05343b00bf58c699aff20bb6d80194de40b7f128d3fcf823f5f9a3 apps/web/performance/review-media-operating-admission-20261013/review-fix-parity-20261014-v2.log +6ccafd88858b8613ab199e22f14eac97cdadb2f9a69e73d2edd6afb1fd1fefd3 apps/web/performance/review-media-operating-admission-20261013/review-fix-units-20261014-v1.log ad4d439f9609db73d5879e655380d640b3b52274dbfe80654f6d715bd7a68ab7 apps/web/performance/review-media-operating-admission-20261013/verification.json -586847d5030cedc6575328cbe78b5eb9f1d9ff47d91e8d8f1aef40076b6d2277 apps/web/scripts/review-media-operating-admission.mjs -184368546c3668da99449f3d7ec0f08b95e1c06f44ca85100a6d2dfa533bc84a apps/web/tests-unit/review-media-operating-admission.test.ts -b4fbd520413f59e82743c545f521df5a8adde04970f610d4d06b432955f07518 docs/operations/review-media-operating-admission-20261013.md +b30f841c858edfc4865dd704ae7f5463d5570117bc3543fd43a63208b2b34ea9 apps/web/scripts/review-media-operating-admission.mjs +dfccda6dc30761a7ce0be2c3c15344cf66d9fbca9781907c4d8ac48a96024d9b apps/web/tests-unit/review-media-operating-admission.test.ts +36242344c78d292a5818ab5b28bec5ce9af9895b9e84cc900cc17a4c56f813b1 docs/operations/review-media-operating-admission-20261013.md diff --git a/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS-4ba9c6d2-historical b/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS-4ba9c6d2-historical new file mode 100644 index 0000000000..9fdb091e51 --- /dev/null +++ b/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS-4ba9c6d2-historical @@ -0,0 +1,6 @@ +31e775a669d4a1295fc8b391653f26d0559695937f4cc357818ef53faa7c8e67 apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS-0833ed81-historical +d29f5fa8ddc27573b3ea2d2a144c9d9a1c24a6c5ac4d7aeef30e3bda514fc575 apps/web/performance/review-media-operating-admission-20261013/controller-reverification.json +ad4d439f9609db73d5879e655380d640b3b52274dbfe80654f6d715bd7a68ab7 apps/web/performance/review-media-operating-admission-20261013/verification.json +586847d5030cedc6575328cbe78b5eb9f1d9ff47d91e8d8f1aef40076b6d2277 apps/web/scripts/review-media-operating-admission.mjs +184368546c3668da99449f3d7ec0f08b95e1c06f44ca85100a6d2dfa533bc84a apps/web/tests-unit/review-media-operating-admission.test.ts +b4fbd520413f59e82743c545f521df5a8adde04970f610d4d06b432955f07518 docs/operations/review-media-operating-admission-20261013.md diff --git a/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS-4ba9c6d2-historical.sha256 b/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS-4ba9c6d2-historical.sha256 new file mode 100644 index 0000000000..c7d4a5cec2 --- /dev/null +++ b/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS-4ba9c6d2-historical.sha256 @@ -0,0 +1 @@ +5a41205af7546221083956ddac62488f51254b253de1d738f39ab714d4835026 diff --git a/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS.sha256 b/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS.sha256 index c7d4a5cec2..3cb06f2b4c 100644 --- a/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS.sha256 +++ b/apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS.sha256 @@ -1 +1 @@ -5a41205af7546221083956ddac62488f51254b253de1d738f39ab714d4835026 +f7345de87e22378811fb0bfef0f57461702a4178ec92cd13e82812dcb921138e diff --git a/apps/web/performance/review-media-operating-admission-20261013/controller-reverification-v2.json b/apps/web/performance/review-media-operating-admission-20261013/controller-reverification-v2.json new file mode 100644 index 0000000000..ae1b1028f6 --- /dev/null +++ b/apps/web/performance/review-media-operating-admission-20261013/controller-reverification-v2.json @@ -0,0 +1,53 @@ +{ + "schemaVersion": 1, + "kind": "local-unit-reverification", + "recordedAtUTC": "2026-10-13T20:18:10.822481+00:00", + "sourceBaseRevision": "4ba9c6d2f54b6e0c8458b2518977665f606eb4ff", + "checks": { + "passed": 98, + "failed": 0, + "assertions": 808, + "command": "bun test tests-unit/review-media-operating-admission.test.ts tests-unit/review-media-sql-successor.test.ts tests-unit/review-media-sql-successor-harness.test.ts tests-unit/supabase-migration-bundle.test.ts", + "cwd": "apps/web", + "log": "apps/web/performance/review-media-operating-admission-20261013/review-fix-units-20261014-v1.log", + "logSha256": "6ccafd88858b8613ab199e22f14eac97cdadb2f9a69e73d2edd6afb1fd1fefd3" + }, + "sources": [ + { + "path": "apps/web/scripts/review-media-operating-admission.mjs", + "sha256": "b30f841c858edfc4865dd704ae7f5463d5570117bc3543fd43a63208b2b34ea9" + }, + { + "path": "apps/web/tests-unit/review-media-operating-admission.test.ts", + "sha256": "dfccda6dc30761a7ce0be2c3c15344cf66d9fbca9781907c4d8ac48a96024d9b" + }, + { + "path": "apps/web/scripts/review-media-sql-successor.mjs", + "sha256": "4f1b2798e0cfc13db4f11cd6eb4a8653320aa8516eccd71d410fcff6d06a7f24" + }, + { + "path": ".github/review-media-sql-successor.v1.json", + "sha256": "295b2354c562ed87102c5e256497a2705f90941587fefa7e9ad8a4ba8e40787f" + } + ], + "supplementalHarnessVerification": { + "passed": 20, + "failed": 0, + "assertions": 98, + "log": "apps/web/performance/review-db-successor-20261012/review-fix-harness-units-20261014-v2.log", + "logSha256": "7815d90d73d6b3508a355aee06743c22ff802e78ffca46172cda18ca83ed51b4", + "sources": [ + { + "path": "apps/web/performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs", + "sha256": "7e750e9d0126f6f10afe9056d747a2ea854d462161ea4e8f0168e2751d554c0a" + }, + { + "path": "apps/web/tests-unit/review-media-sql-successor-harness.test.ts", + "sha256": "99c6bc0221188f93317640d4d65affdfeb91fea233bb0f0cc0f409407e419d84" + } + ] + }, + "reason": "Bound stored rehearsal admission, finite preflight and transport timeouts; verify detached checksum pin. The 98-test run covers the four primary source hashes. The later 20-test run replaces its 18 harness tests after the contention fixture change; these runs are not 118 independent tests.", + "operatingEvidence": false, + "performanceEvidence": false +} diff --git a/apps/web/performance/review-media-operating-admission-20261013/current-evidence.json b/apps/web/performance/review-media-operating-admission-20261013/current-evidence.json new file mode 100644 index 0000000000..f81b23fdd1 --- /dev/null +++ b/apps/web/performance/review-media-operating-admission-20261013/current-evidence.json @@ -0,0 +1,12 @@ +{ + "schemaVersion": 1, + "current": "controller-reverification-v2.json", + "sha256": "ad9406090dea1f2c1f8a146f5e241aae2651449f0c58af50201116e39ccfa2b6", + "historical": [ + "verification.json", + "controller-reverification.json" + ], + "historicalSourceMatch": false, + "operatingEvidence": false, + "performanceEvidence": false +} diff --git a/apps/web/performance/review-media-operating-admission-20261013/review-fix-parity-20261014-v1.log b/apps/web/performance/review-media-operating-admission-20261013/review-fix-parity-20261014-v1.log new file mode 100644 index 0000000000..1bfe59481e --- /dev/null +++ b/apps/web/performance/review-media-operating-admission-20261013/review-fix-parity-20261014-v1.log @@ -0,0 +1,5 @@ + +> web@1.2.4 typecheck:parity +> node scripts/run-typecheck.mjs --compiler parity + +{"status":"passed","diagnostics":0,"logicalInputs":3148,"logicalInputNamesSha256":"4f11b9a9b98661e4b0b52aade8d12be721b72ad229a7bb851ed39c0d94520158","nonLibraryContentSha256":"047401fde3b7a83508e1f431b0321cdf14d0a8d722ec5f126d460b8876799ccd","standardLibraryContentSha256":{"native":"d328ef5dcbac7d4c183cd37a26eb383e8e6921ab2147b20e5d73f3c149223a64","compat":"82afdd90154e9b315a83eb66f2d4ee59972c492094bbf66d7f0791f584e41eb0"}} diff --git a/apps/web/performance/review-media-operating-admission-20261013/review-fix-parity-20261014-v2.log b/apps/web/performance/review-media-operating-admission-20261013/review-fix-parity-20261014-v2.log new file mode 100644 index 0000000000..1bfe59481e --- /dev/null +++ b/apps/web/performance/review-media-operating-admission-20261013/review-fix-parity-20261014-v2.log @@ -0,0 +1,5 @@ + +> web@1.2.4 typecheck:parity +> node scripts/run-typecheck.mjs --compiler parity + +{"status":"passed","diagnostics":0,"logicalInputs":3148,"logicalInputNamesSha256":"4f11b9a9b98661e4b0b52aade8d12be721b72ad229a7bb851ed39c0d94520158","nonLibraryContentSha256":"047401fde3b7a83508e1f431b0321cdf14d0a8d722ec5f126d460b8876799ccd","standardLibraryContentSha256":{"native":"d328ef5dcbac7d4c183cd37a26eb383e8e6921ab2147b20e5d73f3c149223a64","compat":"82afdd90154e9b315a83eb66f2d4ee59972c492094bbf66d7f0791f584e41eb0"}} diff --git a/apps/web/performance/review-media-operating-admission-20261013/review-fix-units-20261014-v1.log b/apps/web/performance/review-media-operating-admission-20261013/review-fix-units-20261014-v1.log new file mode 100644 index 0000000000..a1ff521480 --- /dev/null +++ b/apps/web/performance/review-media-operating-admission-20261013/review-fix-units-20261014-v1.log @@ -0,0 +1,112 @@ +bun test v1.4.0 (34cbb9a40) + +tests-unit/review-media-operating-admission.test.ts: +(pass) CLI contract accepts only the explicit rehearsal or apply producer modes [0.27ms] +(pass) rehearsal producer preserves proven receipts and creates a controller-valid private admission [10.67ms] +(pass) unknown, missing, stale, and mismatched evidence is rejected before custody creation [4.01ms] +(pass) rehearsal producer never overwrites an existing custody directory [6.03ms] +(pass) rehearsal producer fails closed on parent, file, and custody fsync failures [5.23ms] +(pass) rehearsal producer rejects symlink and oversized inputs before custody creation [3.71ms] +(pass) apply producer binds the verified rehearsal artifacts without extending their time window [55.83ms] +(pass) apply producer rejects a relabeled or tampered rehearsal before writing an admission [16.40ms] +(pass) apply producer rejects a fully rehashed receipt and journal relabeled to another rehearsal attempt [8.22ms] +(pass) apply producer treats a dangling reserved journal symlink as an existing output [8.53ms] +(pass) apply producer reads rehearsal journals through the no-follow bounded reader [22.06ms] + +tests-unit/supabase-migration-bundle.test.ts: +(pass) actual four source files compile in exact identity/hash/vector order with one output SELECT [158.11ms] +(pass) order, source identity, source hash, vector and suffix drift reject before an executor exists [665.11ms] +(pass) five-stage compilation is explicit while the historical four-stage API stays exact [141.48ms] +(pass) fresh prior N and prefix/suffix roots are mandatory and partial target ledger is rejected by the first transaction guard [239.72ms] +(pass) reconciliation classifies only exact prior/final objects; all intermediate stages conflict [130.57ms] +(pass) ambiguous transport performs one read-only reconciliation without claiming or resending a commit [131.87ms] +(pass) server terminal guard rollback cannot become success after a matching external commit [126.23ms] +(pass) exit-zero malformed or mismatched readback is confirmed once by exact final state [125.48ms] +(pass) not-applied preserves failure while partial and unknown are fixed fail-closed outcomes [140.83ms] +(pass) transport and malformed-output diagnostics never escape through errors or causes [133.84ms] +(pass) successful output must be exactly one JSON object matching the final bundle readback [137.83ms] + +tests-unit/review-media-sql-successor-harness.test.ts: +(pass) successor acknowledgement loss reconciles a committed terminal state without SQL replay [0.23ms] +(pass) successor failure at the baseline is classified without diagnostic SQL replay [0.20ms] +(pass) retained proof is checksum-verified and distinguished from the current harness source [1.04ms] +(pass) retained proof with bytes not authorized by SHA256SUMS is rejected [10.52ms] +(pass) jointly altered retained proof and checksum map are rejected by the independent digest [1.59ms] +(pass) owned database is cleaned after predecessor SQL fails [1.18ms] +(pass) owned database is cleaned after fixture SQL fails [0.08ms] +(pass) pre-existing database is never acquired or dropped [0.07ms] +(pass) database CREATE acknowledgement loss is reconciled without replay and cleaned by exact ownership [0.28ms] +(pass) cleanup re-reads and removes the exact owned database after the first CREATE readback fails [0.23ms] +(pass) cleanup refuses a database whose exact owner metadata does not match [0.18ms] +(pass) fixture role creation acknowledgement loss is reconciled and exact owned roles are cleaned [0.86ms] +(pass) cleanup re-reads exact owned roles when the first post-create readback fails [0.34ms] +(pass) fixture role creation failure without owned readback never authorizes cleanup [0.17ms] +(pass) pre-existing fixture role collision is preserved and never acquired for cleanup [0.05ms] +(pass) uncertain create collision with another owner is not guessed or removed [0.13ms] +(pass) cleanup refuses roles whose ownership marker changed after creation [0.12ms] +(pass) cleanup refuses to remove owned roles after an out-of-scope membership is added [0.07ms] + +tests-unit/review-media-sql-successor.test.ts: +(pass) manifest binds the exact eleven sources and remains held without fresh admission [275.17ms] +(pass) rehearsal CLI requires one admission, journal and receipt path [0.16ms] +(pass) compiled plan has one advisory lock, one outer transaction and eleven ledger writes [1502.58ms] +(pass) apply accepts rehearsal evidence completed after thirty seconds within its original lease [11.98ms] +(pass) apply rejects rehearsal evidence outside the original fifteen-minute lease [10.41ms] +(pass) actual psql runner with a spy transport preserves the post-lock expiry code [0.63ms] +(pass) rollback-only rehearsal plan owns one transaction and captures twelve safe stages [10.28ms] +(pass) rehearsal admission is distinct and rejects stale source or any wrong writer fence [39.32ms] +(pass) rehearsal rolls back, separately confirms restoration and emits an apply-bound receipt [21.46ms] +(pass) lost rehearsal response performs restoration readback once and never resends [13.33ms] +(pass) post-lock rehearsal expiry keeps its exact bounded code after restoration readback [21.21ms] +(pass) incomplete rehearsal output cannot create an apply admission receipt [14.22ms] +(pass) verification line write-success then fsync-failure never publishes the canonical receipt [23.71ms] +(pass) pending receipt full-write then fsync-failure records failure without canonical publication [24.98ms] +(pass) pending receipt storage delay cannot verify after the admission expires [15.38ms] +(pass) expiry terminal audit failure preserves the bounded rehearsal outcome [21.93ms] +(pass) verification journal storage delay cannot publish after the admission expires [16.64ms] +(pass) canonical publication storage delay cannot record an expired passed result [23.34ms] +(pass) publication journal completion cannot return passed after the admission expires [22.64ms] +(pass) prepublication interruption leaves verified journal and pending receipt apply-invalid [21.36ms] +(pass) failed canonical publication is audited as failure, never as passed [15.72ms] +(pass) pre-existing exact canonical bytes do not make an unperformed link durable [24.08ms] +(pass) unconfirmed directory durability cannot be reconciled from visible canonical bytes [36.73ms] +(pass) directory fsync failure plus failure-audit loss leaves visible receipt apply-invalid [19.83ms] +(pass) publication audit fsync failure requires proven durability before reconciliation [17.45ms] +(pass) publication audit close failure requires proven durability before reconciliation [14.76ms] +(pass) publication audit failure before any bytes leaves durable receipt apply-invalid [31.55ms] +(pass) atomic publication acknowledgement loss reconciles exact receipt without retry [18.86ms] +(pass) apply rejects orphan and semantically tampered rehearsal journals even when rehashed [36.19ms] +(pass) apply pins the stored admission against a valid fully rehashed rehearsal from another attempt [27.42ms] +(pass) apply rejects a rehashed rehearsal with mismatched attempt binding [21.98ms] +(pass) apply rejects a rehashed rehearsal with mismatched rollback binding [35.08ms] +(pass) apply rejects a rehashed rehearsal with mismatched writerFence binding [12.37ms] +(pass) apply requires the pinned private canonical rehearsal admission before any external read [85.43ms] +(pass) admission rejects wrong preimage and every wrong writer fence [50.10ms] +(pass) success path performs preflight, rechecks rollback and commits all eleven once [2270.70ms] +(pass) bounded preflight clientStall with 600000ms left cannot journal or write [2157.13ms] +(pass) bounded preflight statementTimeout with 600000ms left cannot journal or write [2206.43ms] +(pass) bounded preflight lockTimeout with 600000ms left cannot journal or write [2367.47ms] +(pass) bounded preflight expiry with 600000ms left cannot journal or write [2590.32ms] +(pass) bounded preflight clientStall with 5000ms left cannot journal or write [2403.03ms] +(pass) bounded preflight statementTimeout with 5000ms left cannot journal or write [2490.51ms] +(pass) bounded preflight lockTimeout with 5000ms left cannot journal or write [2588.69ms] +(pass) bounded preflight expiry with 5000ms left cannot journal or write [2337.19ms] +(pass) psql runner keeps a finite mutation/reconciliation cap and rejects unbounded overrides [0.27ms] +(pass) committed terminal audit fsync failure preserves the committed outcome [2451.85ms] +(pass) committed terminal audit close failure preserves the committed outcome [2423.49ms] +(pass) committed terminal audit readback requires both durability and exact bytes [2375.40ms] +(pass) concurrent rollback deployment change rejects before journal or mutation [2325.17ms] +(pass) prior readback mismatch fails before journal and mutating transport [2315.87ms] +(pass) lost apply acknowledgement reconciles once and never resends eleven mutations [2978.11ms] +(pass) apply fsync failure at custody/start boundary 1 prevents database mutation [2518.75ms] +(pass) apply fsync failure at custody/start boundary 2 prevents database mutation [2217.47ms] +(pass) apply fsync failure at custody/start boundary 3 prevents database mutation [2145.64ms] +(pass) apply fsync failure at custody/start boundary 4 prevents database mutation [2141.04ms] +(pass) apply fsync failure at custody/start boundary 5 prevents database mutation [2253.09ms] +(pass) apply fsync failure at custody/start boundary 6 prevents database mutation [2234.35ms] +(pass) apply preserves expiry and audit failure as separate bounded outcomes [2260.59ms] + + 98 pass + 0 fail + 808 expect() calls +Ran 98 tests across 4 files. [57.48s] diff --git a/apps/web/scripts/review-media-operating-admission.mjs b/apps/web/scripts/review-media-operating-admission.mjs index 037b9e52a0..40da2080ce 100644 --- a/apps/web/scripts/review-media-operating-admission.mjs +++ b/apps/web/scripts/review-media-operating-admission.mjs @@ -640,6 +640,7 @@ export function prepareReviewMediaApplyAdmission({ projectRef: rehearsal.projectRef, protectedMainReceiptSha256: rehearsal.protectedMainReceiptSha256, purpose: rehearsal.purpose, + rehearsalAdmissionSha256: sha256(rehearsalAdmissionRecord.bytes), rehearsalJournalSha256: inputRecord.value.rehearsalJournalSha256, rehearsalReceiptSha256: inputRecord.value.rehearsalReceiptSha256, rollback: structuredClone(rehearsal.rollback), diff --git a/apps/web/scripts/review-media-sql-successor.mjs b/apps/web/scripts/review-media-sql-successor.mjs index 4e86b241e2..1d3d78f632 100644 --- a/apps/web/scripts/review-media-sql-successor.mjs +++ b/apps/web/scripts/review-media-sql-successor.mjs @@ -53,10 +53,13 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a- const PROJECT_REF = 'aqlcofblfxdrjhhdmarw'; const PURPOSE = 'review-media-current-state-eleven-stage'; const ADMISSION_MAX_AGE_MS = 15 * 60 * 1000; +const PREFLIGHT_TIMEOUT_MS = 30_000; +const PSQL_MAX_TIMEOUT_MS = ADMISSION_MAX_AGE_MS + 30_000; const GIT_REMOTE_READ_TIMEOUT_MS = 15_000; const PRIVATE_RECEIPT_FILES = Object.freeze({ protectedMain: 'protected-main-receipt.json', rehearsal: 'rehearsal-receipt.json', + rehearsalAdmission: 'rehearsal-admission.json', rehearsalJournal: 'rehearsal-journal.jsonl', rollback: 'rollback-readback-receipt.json', source: 'source-receipt.json', @@ -702,6 +705,27 @@ function validatePrivateReceipts(admission, manifestRecord, { assertReceiptWindow(protectedMain, admission, createdAt, expiresAt, nowMs); if (includeRehearsal) { + const rehearsalAdmission = readPrivateCanonicalReceipt( + receiptDirectory, + PRIVATE_RECEIPT_FILES.rehearsalAdmission, + admission.rehearsalAdmissionSha256, + ); + const rehearsalWindow = validateSuccessorRehearsalAdmission(rehearsalAdmission, manifestRecord, { + now: new Date(nowMs), + receiptDirectory, + }); + if (rehearsalWindow.createdAt > createdAt + || rehearsalWindow.expiresAt < expiresAt + || rehearsalAdmission.sourceReceiptSha256 !== admission.sourceReceiptSha256 + || rehearsalAdmission.protectedMainReceiptSha256 !== admission.protectedMainReceiptSha256 + || !equal(rehearsalAdmission.rollback, admission.rollback) + || !equal(rehearsalAdmission.writerFences, admission.writerFences) + || rehearsalAdmission.journalPathSha256 !== sha256(Buffer.from(resolve( + receiptDirectory, PRIVATE_RECEIPT_FILES.rehearsalJournal, + ))) + || rehearsalAdmission.receiptPathSha256 !== sha256(Buffer.from(resolve( + receiptDirectory, PRIVATE_RECEIPT_FILES.rehearsal, + )))) fail('SUCCESSOR_ADMISSION_INVALID'); const rehearsal = readPrivateCanonicalReceipt( receiptDirectory, PRIVATE_RECEIPT_FILES.rehearsal, @@ -719,8 +743,9 @@ function validatePrivateReceipts(admission, manifestRecord, { if (!common(rehearsal) || rehearsal.kind !== 'successor-rehearsal-receipt' || rehearsal.passed !== true - || !UUID.test(rehearsal.attemptId) - || !HEX_SHA256.test(rehearsal.admissionSha256) + || rehearsal.attemptId !== rehearsalAdmission.attemptId + || rehearsal.admissionSha256 !== admission.rehearsalAdmissionSha256 + || rehearsal.expiresAt !== rehearsalAdmission.expiresAt || rehearsal.journalPathSha256 !== sha256(Buffer.from(resolve( receiptDirectory, PRIVATE_RECEIPT_FILES.rehearsalJournal, @@ -759,7 +784,7 @@ function validatePrivateReceipts(admission, manifestRecord, { const verifiedAt = Date.parse(verification.verifiedAt); const publishedAt = Date.parse(publication.publishedAt); const observedAt = Date.parse(rehearsal.observedAt); - const rehearsalLeaseStartedAt = Date.parse(rehearsal.expiresAt) - ADMISSION_MAX_AGE_MS; + const rehearsalLeaseStartedAt = rehearsalWindow.createdAt; if (started.event !== 'started' || verification.event !== 'verification' || verification.code !== 'SUCCESSOR_REHEARSAL_VERIFIED' @@ -929,6 +954,7 @@ export function validateSuccessorAdmission(admission, manifestRecord, { 'projectRef', 'protectedMainReceiptSha256', 'purpose', + 'rehearsalAdmissionSha256', 'rehearsalJournalSha256', 'rehearsalReceiptSha256', 'rollback', @@ -945,6 +971,7 @@ export function validateSuccessorAdmission(admission, manifestRecord, { receiptDirectory, requiredReceiptHashes: [ 'protectedMainReceiptSha256', + 'rehearsalAdmissionSha256', 'rehearsalJournalSha256', 'rehearsalReceiptSha256', 'sourceReceiptSha256', @@ -1052,10 +1079,13 @@ export function assertLaunchReady(manifest) { if (manifest.launchPolicy.state !== 'ready') fail('SUCCESSOR_LAUNCH_HELD'); } -function successorExpirySql(admission, suffix, { armTransactionTimeout = false } = {}) { +function successorExpirySql(admission, suffix, { + armTransactionTimeout = false, + maxTransactionTimeoutMs = ADMISSION_MAX_AGE_MS, +} = {}) { const tag = `$successor_expiry_${sha256(`${admission.expiresAt}:${suffix}`).slice(0, 18)}$`; const armTimeout = armTransactionTimeout - ? `\n PERFORM set_config('transaction_timeout',least(${ADMISSION_MAX_AGE_MS},greatest(1,ceil(extract(epoch FROM (${quote(admission.expiresAt)}::timestamptz-transaction_timestamp()))*1000)::bigint))::text,true);` + ? `\n PERFORM set_config('transaction_timeout',least(${maxTransactionTimeoutMs},greatest(1,ceil(extract(epoch FROM (${quote(admission.expiresAt)}::timestamptz-transaction_timestamp()))*1000)::bigint))::text,true);` : ''; return `DO ${tag} BEGIN IF clock_timestamp() >= ${quote(admission.expiresAt)}::timestamptz THEN RAISE EXCEPTION 'SUCCESSOR_ADMISSION_EXPIRED'; END IF;${armTimeout} END ${tag};`; } @@ -1280,6 +1310,7 @@ function assertDurableRehearsalArtifacts(directory, admission, fsyncImpl) { fail('SUCCESSOR_ADMISSION_INVALID'); } for (const [fileName, expected] of [ + [PRIVATE_RECEIPT_FILES.rehearsalAdmission, admission.rehearsalAdmissionSha256], [PRIVATE_RECEIPT_FILES.rehearsal, admission.rehearsalReceiptSha256], [PRIVATE_RECEIPT_FILES.rehearsalJournal, admission.rehearsalJournalSha256], ]) { @@ -1418,7 +1449,10 @@ export function createSuccessorPsqlRunner(databaseUrl, { psql = environment.TZUDONG_SUCCESSOR_PSQL || 'psql', spawnImpl = spawnSync, } = {}) { - return (_databaseUrl, sql, singleTransaction) => { + return (_databaseUrl, sql, singleTransaction, { timeoutMs = PSQL_MAX_TIMEOUT_MS } = {}) => { + if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > PSQL_MAX_TIMEOUT_MS) { + fail('SUCCESSOR_ARGUMENT_INVALID'); + } const args = ['--no-psqlrc', '--set=ON_ERROR_STOP=1', '--quiet', '--tuples-only', '--no-align']; if (singleTransaction) args.splice(1, 0, '--single-transaction'); args.push('--file=-'); @@ -1427,6 +1461,9 @@ export function createSuccessorPsqlRunner(databaseUrl, { env: psqlConnectionEnvironment(databaseUrl, environment), input: `\\set VERBOSITY verbose\n${sql}`, maxBuffer: 16 * 1024 * 1024, + timeout: timeoutMs, + // A stalled client must not keep spawnSync waiting after SIGTERM. + killSignal: 'SIGKILL', }); if (result.error || result.status !== 0) throw transportError(result); return result.stdout || ''; @@ -2006,10 +2043,27 @@ export function runReviewMediaSuccessor({ let observed; try { + const preflightAt = now(); + assertAdmissionFresh(admissionWindow.expiresAt, preflightAt); + const timeoutMs = Math.min(PREFLIGHT_TIMEOUT_MS, admissionWindow.expiresAt - preflightAt.getTime()); + const preflightSql = [ + 'SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY;', + 'SET LOCAL standard_conforming_strings=on;', + "SET LOCAL lock_timeout='2s';", + "SET LOCAL statement_timeout='15s';", + "SET LOCAL idle_in_transaction_session_timeout='30s';", + successorExpirySql(admission, 'preflight', { + armTransactionTimeout: true, + maxTransactionTimeoutMs: PREFLIGHT_TIMEOUT_MS, + }), + `${plan.stateQuery};`, + successorExpirySql(admission, 'post-preflight'), + ].join('\n'); observed = parseReadback(transport( databaseUrl, - `SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY;\n${plan.stateQuery};`, + preflightSql, true, + { timeoutMs }, )); } catch (error) { throw boundedSuccessorError(error); diff --git a/apps/web/tests-unit/review-media-operating-admission.test.ts b/apps/web/tests-unit/review-media-operating-admission.test.ts index b20c191380..413ef82cbc 100644 --- a/apps/web/tests-unit/review-media-operating-admission.test.ts +++ b/apps/web/tests-unit/review-media-operating-admission.test.ts @@ -445,6 +445,10 @@ test('apply producer binds the verified rehearsal artifacts without extending th expect(published.receipt.attemptId).toBe(published.admission.attemptId); expect(admission.attemptId).toBe(applyInputDocument.attemptId); expect(admission.attemptId).not.toBe(published.admission.attemptId); + expect(admission.rehearsalAdmissionSha256).toBe(sha256(readFileSync( + join(prepared.custodyDirectory, 'rehearsal-admission.json'), + ))); + expect(admission.rehearsalAdmissionSha256).toBe(published.receipt.admissionSha256); expect(admission.createdAt).toBe(createdAt); expect(admission.expiresAt).toBe(expiresAt); expect(admission.stageStates).toEqual(published.receipt.stageStates); diff --git a/apps/web/tests-unit/review-media-sql-successor-harness.test.ts b/apps/web/tests-unit/review-media-sql-successor-harness.test.ts index 12c5d61b85..c8c87aa837 100644 --- a/apps/web/tests-unit/review-media-sql-successor-harness.test.ts +++ b/apps/web/tests-unit/review-media-sql-successor-harness.test.ts @@ -11,10 +11,31 @@ import { dropFixtureMembership, dropOwnedDatabase, inspectRetainedProof, + waitForAdvisoryContention, } from '../performance/review-db-successor-20261012/verify-review-media-sql-successor.mjs'; const retainedProofRepositoryPath = 'apps/web/performance/review-db-successor-20261012/pg17-local-replay.json'; const sha256 = (value: string | Buffer) => createHash('sha256').update(value).digest('hex'); + +test('fixture observes delayed advisory contention before expiry without the old 700ms cutoff', async () => { + let time = 0; + const observed = await waitForAdvisoryContention(1500, { + nowMs: () => time, + readWaiting: () => time >= 950 ? '1' : '0', + delayImpl: async ms => { time += ms; }, + }); + expect(observed).toBe(960); + expect(observed).toBeLessThan(1500); +}); + +test('fixture rejects contention returned by a slow observation after expiry', async () => { + let time = 0; + await expect(waitForAdvisoryContention(1500, { + nowMs: () => time, + readWaiting: () => { time = 1600; return '1'; }, + delayImpl: async () => {}, + })).rejects.toThrow('LOCAL_REHEARSAL_CONTENTION_OBSERVED_AFTER_EXPIRY'); +}); const fixtureParentRole = 'review_successor_fixture_parent_20261012'; const fixtureMemberRole = 'review_successor_fixture_member_20261012'; const fixtureOwnerPrefix = 'tzudong-review-successor-fixture'; @@ -235,7 +256,9 @@ test('retained proof with bytes not authorized by SHA256SUMS is rejected', () => sourceRoot: '3'.repeat(64), })}\n`); writeFileSync(outputPath, proof); - writeFileSync(checksumsPath, `${sha256(proof)} ${retainedProofRepositoryPath}\n`); + const checksumBytes = `${sha256(proof)} ${retainedProofRepositoryPath}\n`; + writeFileSync(checksumsPath, checksumBytes); + writeFileSync(`${checksumsPath}.sha256`, `${sha256(checksumBytes)}\n`); writeFileSync(outputPath, Buffer.concat([proof, Buffer.from(' ')])); expect(() => inspectRetainedProof({ @@ -252,6 +275,28 @@ test('retained proof with bytes not authorized by SHA256SUMS is rejected', () => } }); +test('jointly altered retained proof and checksum map are rejected by the independent digest', () => { + const directory = mkdtempSync(join(tmpdir(), 'tzudong-retained-map-tamper-')); + try { + const outputPath = join(directory, 'pg17-local-replay.json'); + const checksumsPath = join(directory, 'SHA256SUMS'); + const proof = { status: 'passed', temporaryDatabaseDropped: true, hostedWrites: false, + operatingWrites: false, manifestSha256: '1'.repeat(64), + rehearsalHarnessSha256: '2'.repeat(64), sourceRoot: '3'.repeat(64) }; + const original = Buffer.from(`${JSON.stringify(proof)}\n`); + const originalMap = `${sha256(original)} ${retainedProofRepositoryPath}\n`; + writeFileSync(`${checksumsPath}.sha256`, `${sha256(originalMap)}\n`); + const tampered = Buffer.from(`${JSON.stringify({ ...proof, sourceRoot: '4'.repeat(64) })}\n`); + writeFileSync(outputPath, tampered); + writeFileSync(checksumsPath, `${sha256(tampered)} ${retainedProofRepositoryPath}\n`); + expect(() => inspectRetainedProof({ outputPath, checksumsPath, currentBindings: { + manifestSha256: '1'.repeat(64), rehearsalHarnessSha256: '2'.repeat(64), sourceRoot: '4'.repeat(64), + } })).toThrow('LOCAL_REHEARSAL_CHECKSUMS_SHA256_MISMATCH'); + } finally { + rmSync(directory, { recursive: true, force: true }); + } +}); + for (const [label, failingDatabaseCall, expectedCode] of [ ['predecessor', 1, 'LOCAL_REHEARSAL_PREDECESSOR_FAILED'], ['fixture', 7, 'fixture failed'], diff --git a/apps/web/tests-unit/review-media-sql-successor.test.ts b/apps/web/tests-unit/review-media-sql-successor.test.ts index a6491bc25b..16a1647038 100644 --- a/apps/web/tests-unit/review-media-sql-successor.test.ts +++ b/apps/web/tests-unit/review-media-sql-successor.test.ts @@ -141,6 +141,7 @@ function admission() { projectRef: manifest.projectRef, protectedMainReceiptSha256: '1'.repeat(64), purpose: manifest.purpose, + rehearsalAdmissionSha256: '6'.repeat(64), rehearsalJournalSha256: '5'.repeat(64), rehearsalReceiptSha256: '2'.repeat(64), rollback: { ...structuredClone(rollback), readbackSha256: '3'.repeat(64) }, @@ -172,7 +173,7 @@ function custodyFiles(document = admission()) { expiresAt: document.expiresAt, }; const rehearsalAttemptId = '123e4567-e89b-42d3-a456-426614174001'; - const rehearsalAdmissionSha256 = '5'.repeat(64); + let rehearsalAdmissionSha256 = ''; const receipts: Record = { 'source-receipt.json': { ...common, kind: 'successor-source-receipt', sourceRoot: document.sourceRoot }, 'protected-main-receipt.json': { @@ -261,14 +262,40 @@ function custodyFiles(document = admission()) { receipts[name] = { ...common, fence: key, kind: 'writer-fence-readback-receipt', state }; document.writerFences[key].evidenceSha256 = sha256(canonicalBytes(receipts[name])); } + const storedRehearsalAdmission = { + ...rehearsalAdmission(), + attemptId: rehearsalAttemptId, + createdAt: new Date(Math.max( + Date.parse(document.createdAt) - 120_000, Date.parse(document.expiresAt) - 15 * 60_000, + )).toISOString(), + expiresAt: document.expiresAt, + journalPathSha256: sha256(rehearsalJournalPath), + receiptPathSha256: sha256(join(directory, 'rehearsal-receipt.json')), + protectedMainReceiptSha256: document.protectedMainReceiptSha256, + rollback: structuredClone(document.rollback), + sourceReceiptSha256: document.sourceReceiptSha256, + writerFences: structuredClone(document.writerFences), + }; + const storedRehearsalBytes = canonicalBytes(storedRehearsalAdmission); + rehearsalAdmissionSha256 = sha256(storedRehearsalBytes); + document.rehearsalAdmissionSha256 = rehearsalAdmissionSha256; + receipts['rehearsal-receipt.json'].admissionSha256 = rehearsalAdmissionSha256; + document.rehearsalReceiptSha256 = sha256(canonicalBytes(receipts['rehearsal-receipt.json'])); + for (const entry of rehearsalJournal) { + entry.admissionSha256 = rehearsalAdmissionSha256; + if ('receiptSha256' in entry) entry.receiptSha256 = document.rehearsalReceiptSha256; + } + const boundJournalBytes = Buffer.from(`${rehearsalJournal.map(entry => JSON.stringify(canonical(entry))).join('\n')}\n`); + document.rehearsalJournalSha256 = sha256(boundJournalBytes); + writeFileSync(join(directory, 'rehearsal-admission.json'), storedRehearsalBytes, { mode: 0o600 }); for (const [name, receipt] of Object.entries(receipts)) { const path = join(directory, name); writeFileSync(path, canonicalBytes(receipt), { mode: 0o600 }); chmodSync(path, 0o600); } - writeFileSync(rehearsalJournalPath, rehearsalJournalBytes, { mode: 0o600 }); + writeFileSync(rehearsalJournalPath, boundJournalBytes, { mode: 0o600 }); chmodSync(rehearsalJournalPath, 0o600); - writeFileSync(admissionPath, `${JSON.stringify(document)}\n`, { mode: 0o600 }); + writeFileSync(admissionPath, canonicalBytes(document), { mode: 0o600 }); chmodSync(admissionPath, 0o600); return { admissionPath, directory, document, journalPath, rehearsalJournalPath }; } @@ -305,6 +332,28 @@ function rewriteRehearsalTimeline(files: ReturnType, { files.document.rehearsalJournalSha256 = sha256(journalBytes); } +function rebindRehearsalEvidence(files: ReturnType, stored: any) { + const admissionBytes = canonicalBytes(stored); + writeFileSync(join(files.directory, 'rehearsal-admission.json'), admissionBytes, { mode: 0o600 }); + const digest = sha256(admissionBytes); + const receiptPath = join(files.directory, 'rehearsal-receipt.json'); + const receipt = JSON.parse(readFileSync(receiptPath, 'utf8')); + Object.assign(receipt, { admissionSha256: digest, attemptId: stored.attemptId }); + const receiptBytes = canonicalBytes(receipt); + writeFileSync(receiptPath, receiptBytes, { mode: 0o600 }); + files.document.rehearsalReceiptSha256 = sha256(receiptBytes); + const journal = readFileSync(files.rehearsalJournalPath, 'utf8').trim().split('\n').map(JSON.parse); + for (const entry of journal) { + entry.admissionSha256 = digest; + entry.attemptId = stored.attemptId; + if ('receiptSha256' in entry) entry.receiptSha256 = files.document.rehearsalReceiptSha256; + } + const journalBytes = Buffer.from(`${journal.map(entry => JSON.stringify(canonical(entry))).join('\n')}\n`); + writeFileSync(files.rehearsalJournalPath, journalBytes, { mode: 0o600 }); + files.document.rehearsalJournalSha256 = sha256(journalBytes); + return digest; +} + function rehearsalAdmission() { const writerFences = Object.fromEntries(Object.entries(manifest.requiredWriterFences).map( ([key, state], index) => [key, { evidenceSha256: (index + 8).toString(16).repeat(64), state }], @@ -389,7 +438,7 @@ function rehearsalCustodyFiles(document = rehearsalAdmission()) { writeFileSync(path, canonicalBytes(receipt), { mode: 0o600 }); chmodSync(path, 0o600); } - writeFileSync(admissionPath, `${JSON.stringify(document)}\n`, { mode: 0o600 }); + writeFileSync(admissionPath, canonicalBytes(document), { mode: 0o600 }); chmodSync(admissionPath, 0o600); return { admissionPath, directory, document, journalPath, pendingReceiptPath, receiptPath }; } @@ -1411,6 +1460,107 @@ test('apply rejects orphan and semantically tampered rehearsal journals even whe } }); +test('apply pins the stored admission against a valid fully rehashed rehearsal from another attempt', () => { + const files = custodyFiles(); + expect(validateSuccessorAdmission(files.document, executableRecord, { + now: fixedNow, receiptDirectory: files.directory, + }).expiresAt).toBe(Date.parse(files.document.expiresAt)); + const storedPath = join(files.directory, 'rehearsal-admission.json'); + const originalBytes = readFileSync(storedPath); + const other = JSON.parse(originalBytes.toString('utf8')); + other.attemptId = '123e4567-e89b-42d3-a456-426614174002'; + const otherDigest = rebindRehearsalEvidence(files, other); + expect(otherDigest).not.toBe(files.document.rehearsalAdmissionSha256); + expect(validateSuccessorRehearsalAdmission(other, record, { + now: fixedNow, receiptDirectory: files.directory, + }).expiresAt).toBe(Date.parse(other.expiresAt)); + expect(() => validateSuccessorAdmission(files.document, executableRecord, { + now: fixedNow, receiptDirectory: files.directory, + })).toThrow('SUCCESSOR_ADMISSION_INVALID'); + // Restore the pinned file, leaving the internally consistent other receipt/journal. + writeFileSync(storedPath, originalBytes, { mode: 0o600 }); + expect(() => validateSuccessorAdmission(files.document, executableRecord, { + now: fixedNow, receiptDirectory: files.directory, + })).toThrow('SUCCESSOR_ADMISSION_INVALID'); +}); + +for (const changed of ['attempt', 'rollback', 'writerFence'] as const) { + test(`apply rejects a rehashed rehearsal with mismatched ${changed} binding`, () => { + const files = custodyFiles(); + const stored = JSON.parse(readFileSync(join(files.directory, 'rehearsal-admission.json'), 'utf8')); + if (changed === 'attempt') { + // Receipt and all journal entries agree on B, but the actual admission is A. + const other = { ...stored, attemptId: '123e4567-e89b-42d3-a456-426614174002' }; + rebindRehearsalEvidence(files, other); + const receiptPath = join(files.directory, 'rehearsal-receipt.json'); + const receipt = JSON.parse(readFileSync(receiptPath, 'utf8')); + receipt.admissionSha256 = files.document.rehearsalAdmissionSha256; + writeFileSync(receiptPath, canonicalBytes(receipt), { mode: 0o600 }); + files.document.rehearsalReceiptSha256 = sha256(readFileSync(receiptPath)); + const journal = readFileSync(files.rehearsalJournalPath, 'utf8').trim().split('\n').map(JSON.parse); + for (const entry of journal) { + entry.admissionSha256 = files.document.rehearsalAdmissionSha256; + if ('receiptSha256' in entry) entry.receiptSha256 = files.document.rehearsalReceiptSha256; + } + const bytes = Buffer.from(`${journal.map(entry => JSON.stringify(canonical(entry))).join('\n')}\n`); + writeFileSync(files.rehearsalJournalPath, bytes, { mode: 0o600 }); + files.document.rehearsalJournalSha256 = sha256(bytes); + writeFileSync(join(files.directory, 'rehearsal-admission.json'), canonicalBytes(stored), { mode: 0o600 }); + } else { + stored.attemptId = '123e4567-e89b-42d3-a456-426614174002'; + const fileName = changed === 'rollback' ? 'rollback-readback-receipt.json' : 'writer-fence-directSql.json'; + const receiptPath = join(files.directory, fileName); + const receipt = JSON.parse(readFileSync(receiptPath, 'utf8')); + if (changed === 'rollback') { + receipt.deploymentSha = '2'.repeat(40); + stored.rollback.deploymentSha = receipt.deploymentSha; + } else receipt.observedAt = fixedNow.toISOString(); + const bytes = canonicalBytes(receipt); + writeFileSync(receiptPath, bytes, { mode: 0o600 }); + if (changed === 'rollback') stored.rollback.readbackSha256 = sha256(bytes); + else stored.writerFences.directSql.evidenceSha256 = sha256(bytes); + // Even rebinding the apply digest cannot mix B's rollback/fence with A's. + files.document.rehearsalAdmissionSha256 = rebindRehearsalEvidence(files, stored); + } + expect(validateSuccessorRehearsalAdmission(stored, record, { + now: fixedNow, receiptDirectory: files.directory, + }).expiresAt).toBe(Date.parse(stored.expiresAt)); + expect(() => validateSuccessorAdmission(files.document, executableRecord, { + now: fixedNow, receiptDirectory: files.directory, + })).toThrow('SUCCESSOR_ADMISSION_INVALID'); + }); +} + +test('apply requires the pinned private canonical rehearsal admission before any external read', () => { + for (const invalid of ['missing', 'public', 'noncanonical', 'wrongDigest'] as const) { + const files = custodyFiles(); + const path = join(files.directory, 'rehearsal-admission.json'); + if (invalid === 'missing') rmSync(path); + if (invalid === 'public') chmodSync(path, 0o644); + if (invalid === 'noncanonical') { + const stored = JSON.parse(readFileSync(path, 'utf8')); + writeFileSync(path, JSON.stringify(stored, null, 2), { mode: 0o600 }); + files.document.rehearsalAdmissionSha256 = sha256(readFileSync(path)); + } + if (invalid === 'wrongDigest') files.document.rehearsalAdmissionSha256 = '0'.repeat(64); + writeFileSync(files.admissionPath, canonicalBytes(files.document), { mode: 0o600 }); + let externalCalls = 0; + const unexpected = () => { externalCalls += 1; throw new Error('unexpected external read'); }; + expect(() => runReviewMediaSuccessor({ + admissionPath: files.admissionPath, journalPath: files.journalPath, environment: {}, + }, { + now: () => fixedNow, + loadManifestImpl: () => executableRecord, + gitFactsImpl: unexpected, + protectedMainReadbackImpl: unexpected, + rollbackReadbackImpl: unexpected, + runPsqlImpl: unexpected, + })).toThrow('SUCCESSOR_ADMISSION_INVALID'); + expect(externalCalls).toBe(0); + expect(existsSync(files.journalPath)).toBe(false); + } +}); + test('admission rejects wrong preimage and every wrong writer fence', () => { { const files = custodyFiles(); @@ -1467,6 +1617,83 @@ test('success path performs preflight, rechecks rollback and commits all eleven expect(readFileSync(files.journalPath, 'utf8')).toContain('SUCCESSOR_COMMITTED'); }); +for (const remainingMs of [600_000, 5_000]) { + for (const failure of ['clientStall', 'statementTimeout', 'lockTimeout', 'expiry'] as const) { + test(`bounded preflight ${failure} with ${remainingMs}ms left cannot journal or write`, () => { + const document = admission(); + document.expiresAt = new Date(fixedNow.getTime() + remainingMs).toISOString(); + const files = custodyFiles(document); + const plan = compileSuccessorPlan(executableRecord, files.document); + let spawnCalls = 0; + let rollbackReads = 0; + const runner = createSuccessorPsqlRunner(databaseUrl, { + environment: {}, psql: '/fixture/psql', + spawnImpl: (_command: string, args: string[], options: any) => { + spawnCalls += 1; + expect(args).toContain('--single-transaction'); + expect(options.timeout).toBe(Math.min(30_000, remainingMs)); + expect(options.killSignal).toBe('SIGKILL'); + const sql = options.input; + const queryAt = sql.indexOf(plan.stateQuery); + for (const setting of [ + 'REPEATABLE READ READ ONLY', 'standard_conforming_strings=on', + "lock_timeout='2s'", "statement_timeout='15s'", + "idle_in_transaction_session_timeout='30s'", "set_config('transaction_timeout',least(30000", + ]) { + expect(sql.indexOf(setting)).toBeGreaterThanOrEqual(0); + expect(sql.indexOf(setting)).toBeLessThan(queryAt); + } + expect(sql).toContain('transaction_timestamp()'); + expect(sql).toContain(`clock_timestamp() >= '${document.expiresAt}'::timestamptz`); + expect(sql.match(/SUCCESSOR_ADMISSION_EXPIRED/g)).toHaveLength(2); + expect(sql).not.toMatch(/INSERT INTO supabase_migrations\.schema_migrations/); + if (failure === 'clientStall') return { + error: Object.assign(new Error('private transport diagnostic'), { code: 'ETIMEDOUT' }), + status: null, stdout: '', stderr: 'private transport diagnostic', + }; + const sqlstate = failure === 'statementTimeout' ? '57014' : failure === 'lockTimeout' ? '55P03' : 'P0001'; + return { status: 1, stdout: '', stderr: `ERROR: ${sqlstate}: ${failure === 'expiry' ? 'SUCCESSOR_ADMISSION_EXPIRED' : 'private diagnostic'}\n` }; + }, + }); + const expectedCode = failure === 'clientStall' ? 'MIGRATION_PSQL_EXECUTION_FAILED' + : failure === 'statementTimeout' ? 'MIGRATION_PSQL_FAILED_57014' + : failure === 'lockTimeout' ? 'MIGRATION_PSQL_FAILED_55P03' : 'SUCCESSOR_ADMISSION_EXPIRED'; + expect(() => runReviewMediaSuccessor({ + admissionPath: files.admissionPath, journalPath: files.journalPath, + environment: { SUPABASE_DB_URL: databaseUrl }, + }, { + now: () => fixedNow, + gitFactsImpl: () => ({ clean: true, detached: true, revision }), + loadManifestImpl: () => executableRecord, + protectedMainReadbackImpl: () => protectedSourceReadbackBinding(manifest, revision), + rollbackReadbackImpl: () => { rollbackReads += 1; return rollbackReadback(); }, + runPsqlImpl: runner, + })).toThrow(expectedCode); + expect(spawnCalls).toBe(1); + expect(rollbackReads).toBe(1); + expect(existsSync(files.journalPath)).toBe(false); + }); + } +} + +test('psql runner keeps a finite mutation/reconciliation cap and rejects unbounded overrides', () => { + let calls = 0; + const runner = createSuccessorPsqlRunner(databaseUrl, { + environment: {}, + spawnImpl: (_command: string, _args: string[], options: any) => { + calls += 1; + expect(options.timeout).toBe(930_000); + expect(options.killSignal).toBe('SIGKILL'); + return { status: 0, stdout: 'fixture', stderr: '' }; + }, + }); + expect(runner(databaseUrl, 'SELECT 1;', true)).toBe('fixture'); + for (const timeoutMs of [0, -1, Infinity, NaN, 930_001]) { + expect(() => runner(databaseUrl, 'SELECT 1;', true, { timeoutMs })).toThrow('SUCCESSOR_ARGUMENT_INVALID'); + } + expect(calls).toBe(1); +}); + for (const failureAt of ['fsync', 'close'] as const) { test(`committed terminal audit ${failureAt} failure preserves the committed outcome`, () => { const files = custodyFiles(); @@ -1642,7 +1869,7 @@ test('lost apply acknowledgement reconciles once and never resends eleven mutati expect(readFileSync(files.journalPath, 'utf8')).toContain('MIGRATION_BUNDLE_OUTCOME_UNCONFIRMED'); }); -for (const failureAt of [1, 2, 3, 4, 5]) { +for (const failureAt of [1, 2, 3, 4, 5, 6]) { test(`apply fsync failure at custody/start boundary ${failureAt} prevents database mutation`, () => { const files = custodyFiles(); const plan = compileSuccessorPlan(executableRecord, files.document); @@ -1668,7 +1895,7 @@ for (const failureAt of [1, 2, 3, 4, 5]) { expect(sql).toStartWith('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY;'); return JSON.stringify(plan.priorReadback.expected); }, - })).toThrow(failureAt <= 3 ? 'SUCCESSOR_ADMISSION_INVALID' : 'SUCCESSOR_JOURNAL_INVALID'); + })).toThrow(failureAt <= 4 ? 'SUCCESSOR_ADMISSION_INVALID' : 'SUCCESSOR_JOURNAL_INVALID'); expect(transports).toBe(1); expect(syncs).toBe(failureAt); }); diff --git a/docs/evidence/review-admission-fixes-20261014-validation-v1.json b/docs/evidence/review-admission-fixes-20261014-validation-v1.json new file mode 100644 index 0000000000..9c490991d2 --- /dev/null +++ b/docs/evidence/review-admission-fixes-20261014-validation-v1.json @@ -0,0 +1,26 @@ +{ + "schemaVersion": 1, + "observedAtUTC": "2026-10-13T20:18:46.314544+00:00", + "verified": true, + "checksumMaps": [ + { + "path": "apps/web/performance/review-db-successor-20261012/SHA256SUMS", + "sha256": "aff9374a0e6a6964b2d32f6cb5c6069296c5155b94c2ec1015460d2386fb4e88", + "files": 25 + }, + { + "path": "apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS", + "sha256": "f7345de87e22378811fb0bfef0f57461702a4178ec92cd13e82812dcb921138e", + "files": 13 + } + ], + "primaryUnitTests": 98, + "subsequentHarnessTests": 20, + "overlapNotSummed": true, + "pg17ProofSha256": "9a4985689d121979e3d7559c32b8f27c88ae3d1bb82d559fb9bb8ada6e706ac2", + "originalProofPreserved": true, + "heldLaunchPolicy": true, + "hostedWrites": false, + "operatingWrites": false, + "performanceEvidence": false +} diff --git a/docs/evidence/review-admission-fixes-20261014-validation-v2.json b/docs/evidence/review-admission-fixes-20261014-validation-v2.json new file mode 100644 index 0000000000..c8096a8c88 --- /dev/null +++ b/docs/evidence/review-admission-fixes-20261014-validation-v2.json @@ -0,0 +1,28 @@ +{ + "observedAtUTC": "2026-10-13T20:34:16.074395+00:00", + "verified": true, + "checksumMaps": [ + { + "path": "apps/web/performance/review-db-rehearsal-20261012/SHA256SUMS", + "sha256": "c1af1afd832bd2b0bbdcd4419589de778525e1b18a4dff196e457b82381c97fb", + "files": 36 + }, + { + "path": "apps/web/performance/review-db-successor-20261012/SHA256SUMS", + "sha256": "aff9374a0e6a6964b2d32f6cb5c6069296c5155b94c2ec1015460d2386fb4e88", + "files": 25 + }, + { + "path": "apps/web/performance/review-media-operating-admission-20261013/SHA256SUMS", + "sha256": "f7345de87e22378811fb0bfef0f57461702a4178ec92cd13e82812dcb921138e", + "files": 13 + } + ], + "relatedIndexTests": 34, + "assertions": 153, + "currentRollbackProofSha256": "23fb9b471a06dbd36aafab3a678152ca897893b3cdc968f2d623035d217b1e38", + "fullSuiteFailedPriorHead": "71dcfc98ab03a016d05449780574a4b9edd81e36", + "fullSuiteNewHeadPending": true, + "operatingEvidence": false, + "performanceEvidence": false +} diff --git a/docs/operations/review-media-operating-admission-20261013.md b/docs/operations/review-media-operating-admission-20261013.md index fd7d42c60d..ce7c69760c 100644 --- a/docs/operations/review-media-operating-admission-20261013.md +++ b/docs/operations/review-media-operating-admission-20261013.md @@ -9,7 +9,7 @@ 1. 보호된 `main`의 깨끗한 detached checkout, 현재 production rollback 대상, manifest의 writer fence 7개를 실제로 확인한다. 실행 창과 원래 `observedAt`을 포함한 canonical JSON 입력을 저장소 밖 private 디렉터리에 저장한다. 프로바이더 원문·인증 정보는 입력에 넣지 않는다. 2. `--mode rehearsal --input --custody-dir `로 리허설 admission을 만든다. 입력은 schema, source revision/root, manifest hash, checkout readback, source/protected-main/rollback receipt와 writer-fence receipt 전체를 포함해야 한다. 3. 준비된 admission을 기존 `review-media-sql-rehearsal.mjs`에 전달한다. 기존 적용기는 source와 rollback을 다시 읽고 DB 대상·중지 조건을 확인한 뒤 rollback-only 리허설을 수행한다. 이 생성기만 실행해서는 리허설 receipt가 생기지 않는다. -4. 성공한 리허설의 admission·receipt·3행 journal SHA와 원래 만료 시각을 결속한 apply 입력을 준비한다. `--mode apply --input --custody-dir `로 apply admission을 만든다. +4. 성공한 리허설의 admission·receipt·3행 journal SHA와 원래 만료 시각을 결속한 apply 입력을 준비한다. `--mode apply --input --custody-dir `로 apply admission을 만든다. 적용 admission의 `rehearsalAdmissionSha256`는 저장된 `rehearsal-admission.json`의 실제 바이트이며 receipt·journal의 원래 실행과 source·rollback·writer fence가 같아야 한다. 5. 기존 적용기의 launch policy, 보호 source, 실제 rollback과 writer-fence 근거가 충족된 경우에만 `review-media-sql-successor.mjs`를 실행한다. 출력 존재·실패·응답 유실은 기존 산출물을 확인한 뒤 처리하며, 파일을 지워 같은 작업을 자동 재시도하지 않는다. 두 mode 모두 Node 24로 `apps/web/scripts/review-media-operating-admission.mjs`를 실행한다. 성공 출력은 준비한 경로·SHA·만료 시각이며, 운영 적용 완료 영수증이 아니다. @@ -22,3 +22,7 @@ - 입력을 수집하는 운영 adapter와 live 인증은 별도다. 저장소에 Vercel 토큰이 없다는 사실만으로 새 토큰 생성·저장 권한을 추정하지 않는다. 현재 manifest의 `held` 상태를 이 도구가 변경하지 않는다. 현재 통과한 PG17.6 proof는 controller와 SQL의 격리 재생이다. 이 생성기의 검증은 별도 단위 검사로 기록하며 hosted DB 적용·Storage 물리 삭제·운영 성능 증거로 합산하지 않는다. + +최신 단위 검사 기록은 `apps/web/performance/review-media-operating-admission-20261013/current-evidence.json`이 선택한다. 이전 `verification.json`과 `controller-reverification.json`은 해당 시점의 기록이며 현재 소스 증명으로 사용하지 않는다. 새 기록은 검사 로그와 source 바이트를 결속한다. + +적용 전 state read는 lock 2초, statement 15초, transaction 최대 30초와 남은 admission 시간, client 최대 30초로 제한한다. 실패·만료는 apply journal 및 mutation 전에 거부한다. 다른 psql 작업도 유한 client 제한을 사용하지만 이 로컬 검사만으로 운영 timeout 동작을 입증하지 않는다.