diff --git a/.github/workflows/deploy-strategy-switch-console.yml b/.github/workflows/deploy-strategy-switch-console.yml index e17f76c6..50514abe 100644 --- a/.github/workflows/deploy-strategy-switch-console.yml +++ b/.github/workflows/deploy-strategy-switch-console.yml @@ -1123,6 +1123,28 @@ jobs: && [ "$(git rev-parse 'HEAD^{tree}')" = "$expected_tree" ] || exit 2 exit 0 fi + # S2 Desk gap-fill release source (#1168): exact source, sole parent, tree, map and + # Worker from main history. Its tree carries the five N1 read routes with the stored + # sparkline/incomplete-run reads and the reviewed #1126 reader test; nothing is composed. + if [ "$RELEASE_SOURCE_SHA" = "2fa23318e341fd3ecdc88cb0b7fc50bba14c7c86" ]; then + [ "$GITHUB_REF" = "refs/heads/main" ] && [ "$frontend_mode" = true ] \ + && [ "$SYNC_SECRETS" = false ] && [ "$SYNC_STRATEGY_PROFILES" = false ] \ + && [ "$SKIP_BINANCE_RECEIVER_CHECKS" = false ] || exit 2 + control_sha="$(git rev-parse HEAD)" + [ "$control_sha" = "$GITHUB_SHA" ] && [ "$control_sha" != "$RELEASE_SOURCE_SHA" ] || exit 2 + git cat-file -e "${RELEASE_SOURCE_SHA}^{commit}" + git merge-base --is-ancestor "$RELEASE_SOURCE_SHA" "$control_sha" + [ "$(git rev-parse "${RELEASE_SOURCE_SHA}^@")" = "9ba46c2db5820569e985c962fbb762ff77e7404c" ] || exit 2 + expected_tree="55da6c630588aaa3f705de8c09242c4e908c61e7" + [ "$(git rev-parse "${RELEASE_SOURCE_SHA}^{tree}")" = "$expected_tree" ] || exit 2 + [ "$(git rev-parse "${RELEASE_SOURCE_SHA}:web/strategy-switch-console/v2_asset_map.js")" = "05155b33803c00805b2c51fd29d18c041ad4be20" ] || exit 2 + [ "$(git rev-parse "${RELEASE_SOURCE_SHA}:web/strategy-switch-console/worker.js")" = "bbe906c52ebbbf719b19103c7f808c5bde5f294c" ] \ + && [ "$(git rev-parse "${RELEASE_SOURCE_SHA}:tests/binance_account_facts_validation.mjs")" = "f590e43dd71274e761c505cd375b05b7da0b9756" ] || exit 2 + git -c advice.detachedHead=false checkout --detach "$RELEASE_SOURCE_SHA" + [ "$(git rev-parse HEAD)" = "$RELEASE_SOURCE_SHA" ] \ + && [ "$(git rev-parse 'HEAD^{tree}')" = "$expected_tree" ] || exit 2 + exit 0 + fi # S2 Desk N1 read-only release source (#1166): exact source, sole parent, tree, # map and Worker from main history. Its tree carries the five N1 read routes and # the reviewed #1126 Binance reader test, so nothing is composed onto it. @@ -1317,6 +1339,7 @@ jobs: s1_source="aa05d670f9a51a52c91f142beae65888f7b110fc" desk_entry_source="1618b2488698804e8bedc19d3509694e4c306a75" desk_n1_source="c0ac5848b8af983d4f857e465c6ff64009b0957f" + desk_gap_source="2fa23318e341fd3ecdc88cb0b7fc50bba14c7c86" head_sha="$(git rev-parse HEAD)" if [ "${RELEASE_SOURCE_SHA:-}" = "866388696a6bad7d1b88a76a3ab5957c7958b3bf" ]; then [ "$GITHUB_REF" = "refs/heads/main" ] && [ "$head_sha" = "$console_source" ] || exit 2 @@ -1329,9 +1352,15 @@ jobs: git diff --quiet exit 0 fi - if [ "$head_sha" = "$desk_n1_source" ] || [ "$head_sha" = "$desk_entry_source" ] || [ "$head_sha" = "$s1_source" ] || [ "$head_sha" = "$console_source" ] || [ "$head_sha" = "$reviewed_main" ] || [ "$head_sha" = "$historical_reviewed_main" ] || [ "$head_sha" = "$older_reviewed_main" ]; then + if [ "$head_sha" = "$desk_gap_source" ] || [ "$head_sha" = "$desk_n1_source" ] || [ "$head_sha" = "$desk_entry_source" ] || [ "$head_sha" = "$s1_source" ] || [ "$head_sha" = "$console_source" ] || [ "$head_sha" = "$reviewed_main" ] || [ "$head_sha" = "$historical_reviewed_main" ] || [ "$head_sha" = "$older_reviewed_main" ]; then [ "$GITHUB_REF" = "refs/heads/main" ] || exit 2 - if [ "$head_sha" = "$desk_n1_source" ]; then + if [ "$head_sha" = "$desk_gap_source" ]; then + # The S2 Desk gap-fill tree supplies the Worker and the built frontend map unchanged; + # the rebuilt map must equal the pinned blob, with no delta below. + [ "${RELEASE_SOURCE_SHA:-}" = "$desk_gap_source" ] || exit 2 + [ "$(git rev-parse 'HEAD^{tree}')" = "55da6c630588aaa3f705de8c09242c4e908c61e7" ] || exit 2 + [ "$(git hash-object web/strategy-switch-console/v2_asset_map.js)" = "05155b33803c00805b2c51fd29d18c041ad4be20" ] || exit 2 + elif [ "$head_sha" = "$desk_n1_source" ]; then # The S2 Desk N1 tree supplies the Worker and the built frontend map unchanged; # the rebuilt map must equal the pinned blob, with no delta below. [ "${RELEASE_SOURCE_SHA:-}" = "$desk_n1_source" ] || exit 2 @@ -1869,7 +1898,8 @@ jobs: || process.env.RELEASE_SOURCE_SHA === '6f9b84e6192b68bac1627337fb4e633e178fcee3' || process.env.RELEASE_SOURCE_SHA === 'aa05d670f9a51a52c91f142beae65888f7b110fc' || process.env.RELEASE_SOURCE_SHA === '1618b2488698804e8bedc19d3509694e4c306a75' - || process.env.RELEASE_SOURCE_SHA === 'c0ac5848b8af983d4f857e465c6ff64009b0957f'; + || process.env.RELEASE_SOURCE_SHA === 'c0ac5848b8af983d4f857e465c6ff64009b0957f' + || process.env.RELEASE_SOURCE_SHA === '2fa23318e341fd3ecdc88cb0b7fc50bba14c7c86'; const reviewedMain = currentReviewedMain || process.env.RELEASE_SOURCE_SHA === '084ba4281cfff3304cf226ce511a2fc0a6b67b08' || process.env.RELEASE_SOURCE_SHA === 'c1b737d6e721eb9fe71a12c613fbb49d3fa51563'; diff --git a/tests/console_deploy_safety_validation.mjs b/tests/console_deploy_safety_validation.mjs index b760bcb7..6899775d 100644 --- a/tests/console_deploy_safety_validation.mjs +++ b/tests/console_deploy_safety_validation.mjs @@ -258,6 +258,13 @@ const deskN1Parent = "af897f8b215c5e46f436888179be4be26d467679"; const deskN1Tree = "a7b327c0f41296ba2e92d08371bb84920fe3c84f"; const deskN1Blob = "281002eae61aac3d12ccdf6a00c6a084964f4604"; const deskN1WorkerBlob = "bbe906c52ebbbf719b19103c7f808c5bde5f294c"; +// S2 Desk gap-fill release source (#1168): stored sparkline and incomplete-run reads on the +// N1 routes. Its tree carries its own Worker and the reviewed #1126 reader test. +const deskGapSource = "2fa23318e341fd3ecdc88cb0b7fc50bba14c7c86"; +const deskGapParent = "9ba46c2db5820569e985c962fbb762ff77e7404c"; +const deskGapTree = "55da6c630588aaa3f705de8c09242c4e908c61e7"; +const deskGapBlob = "05155b33803c00805b2c51fd29d18c041ad4be20"; +const deskGapWorkerBlob = "bbe906c52ebbbf719b19103c7f808c5bde5f294c"; const selectionStep = steps.find(step => step.startsWith("Select reviewed historical main source\n")); assert.ok(selectionStep); assert.ok(deploy.indexOf("Select reviewed historical main source") < deploy.indexOf("Setup Python"), "reject unapproved source before running project code"); @@ -314,6 +321,14 @@ const fakeGit = `git() { 'rev-parse ${deskN1Source}:web/strategy-switch-console/worker.js') printf '%s' "$FAKE_N1_WORKER" ;; 'rev-parse ${deskN1Source}:tests/binance_account_facts_validation.mjs') printf '%s' "$FAKE_N1_TEST" ;; '-c advice.detachedHead=false checkout --detach ${deskN1Source}') touch "$SELECTED" ;; + 'cat-file -e ${deskGapSource}^{commit}') return "$FAKE_OBJECT_EXIT" ;; + 'merge-base --is-ancestor ${deskGapSource} ${control}') return "$FAKE_ANCESTRY_EXIT" ;; + 'rev-parse ${deskGapSource}^@') printf '%s' "$FAKE_PARENT" ;; + 'rev-parse ${deskGapSource}^{tree}') printf '%s' "$FAKE_TREE" ;; + 'rev-parse ${deskGapSource}:${assetPath}') printf '%s' "$FAKE_PR764_BLOB" ;; + 'rev-parse ${deskGapSource}:web/strategy-switch-console/worker.js') printf '%s' "$FAKE_GAP_WORKER" ;; + 'rev-parse ${deskGapSource}:tests/binance_account_facts_validation.mjs') printf '%s' "$FAKE_GAP_TEST" ;; + '-c advice.detachedHead=false checkout --detach ${deskGapSource}') touch "$SELECTED" ;; *) return 91 ;; esac };\n`; @@ -331,7 +346,8 @@ function sourceCase(delta, expected) { FAKE_WALLET_BASE_TEST: s1ReaderTestBlob, FAKE_BASE_WORKER: s1WorkerBlob, FAKE_BASE_TEST: s1ReaderTestBlob, FAKE_COMPOSED_WORKER: walletWorkerBlob, FAKE_COMPOSED_TEST: walletReaderTestBlob, FAKE_DIFF_CHECK_EXIT: "0", FAKE_DESK_WORKER: walletWorkerBlob, FAKE_DESK_TEST: walletReaderTestBlob, - FAKE_N1_WORKER: deskN1WorkerBlob, FAKE_N1_TEST: walletReaderTestBlob, ...delta }; + FAKE_N1_WORKER: deskN1WorkerBlob, FAKE_N1_TEST: walletReaderTestBlob, + FAKE_GAP_WORKER: deskGapWorkerBlob, FAKE_GAP_TEST: walletReaderTestBlob, ...delta }; const result = spawnSync("bash", ["-c", fakeGit + selectShell], { env, encoding: "utf8" }); assert.equal(result.status === 0, expected, result.stderr); assert.ok(!result.stdout.includes("PRIVATE_SOURCE_SENTINEL") && !result.stderr.includes("PRIVATE_SOURCE_SENTINEL")); @@ -615,6 +631,62 @@ assert.equal(sourceCase({...deskN1Env,FAKE_SELECTED_HEAD:"b".repeat(40)},false). assert.equal(sourceCase({...deskEntryEnv,FAKE_PARENT:deskN1Parent,FAKE_TREE:deskN1Tree,FAKE_PR764_BLOB:deskN1Blob},false).selected,false); assert.equal(sourceCase({...deskN1Env,FAKE_PARENT:deskEntryParent,FAKE_TREE:deskEntryTree,FAKE_PR764_BLOB:deskEntryBlob},false).selected,false); +// S2 Desk gap-fill source: five exact pins (parent, tree, map, Worker and the reviewed +// #1126 reader test) in its own tree; receiver checks retained; later distinct CONTROL. +{ + const identity = spawnSync("git", ["rev-parse", `${deskGapSource}^{commit}`, `${deskGapSource}^@`, `${deskGapSource}^{tree}`, + `${deskGapSource}:${assetPath}`, `${deskGapSource}:${walletPaths[1]}`, `${deskGapSource}:${walletPaths[0]}`], {cwd: repoRoot, encoding:"utf8"}); + assert.equal(identity.status, 0); + assert.deepEqual(identity.stdout.trim().split("\n"), [deskGapSource, deskGapParent, deskGapTree, deskGapBlob, deskGapWorkerBlob, walletReaderTestBlob], + "S2 Desk gap-fill source has one parent, the frozen tree/map/Worker and the published reader test"); + assert.equal(spawnSync("git", ["merge-base", "--is-ancestor", deskGapSource, "HEAD"], {cwd: repoRoot}).status, 0, "S2 Desk gap-fill source must be adopted in this checkout"); + assert.equal(spawnSync("git", ["merge-base", "--is-ancestor", deskN1Source, deskGapSource], {cwd: repoRoot}).status, 0, "it descends from the published S2 N1 source"); + const gapMap = mapGeneration(deskGapSource); + assert.equal(gapMap.assets, mapGeneration(deskN1Source).assets, "same asset set size as the published S2 N1 bundle"); + assert.equal(gapMap.guard, 1, "the bundle keeps the S1 desk delivery guard"); + assert.notEqual(gapMap.build, mapGeneration(deskN1Source).build, "a new frontend generation"); + assert.equal(producerEmitsReadErrorCode(deskGapSource), true, "its Worker keeps the S1 producer field"); + const decoded = [...gapMap.text.matchAll(/"\/v2\/assets\/[^"]+": \{ contentType: "[^"]+", base64: "([A-Za-z0-9+/=]*)"/g)] + .map(([, b64]) => Buffer.from(b64, "base64").toString("utf8")); + assert.ok(decoded.some(text => text.includes("qsl.desk.switch_strategies.v2")), "the bundle reads the N1 switch-strategies projection"); + const overview = spawnSync("git", ["show", `${deskGapSource}:web/strategy-switch-console/application/desk/overview.js`], {cwd: repoRoot, encoding:"utf8", maxBuffer:64*1024*1024}).stdout; + assert.ok(overview.includes("export function bookSparkline"), "its overview projection carries the stored sparkline read"); +} +const deskGapEnv = {...mergedEnv, RELEASE_SOURCE_SHA:deskGapSource, FAKE_SELECTED_HEAD:deskGapSource, + FAKE_PARENT:deskGapParent, FAKE_TREE:deskGapTree, FAKE_PR764_BLOB:deskGapBlob}; +{ + const accepted = sourceCase(deskGapEnv, true); + assert.equal(accepted.selected, true); + assert.equal(accepted.composed, false, "nothing is composed onto the S2 Desk gap-fill source"); + const ancestry = accepted.calls.indexOf(`merge-base --is-ancestor ${deskGapSource} ${control}`); + const checkout = accepted.calls.indexOf(`-c advice.detachedHead=false checkout --detach ${deskGapSource}`); + assert.ok(accepted.calls[0] === "rev-parse HEAD" && ancestry > 0 && checkout > ancestry, "CONTROL is read, then SOURCE ancestry, then checkout"); + for (const pin of [`rev-parse ${deskGapSource}^@`, `rev-parse ${deskGapSource}^{tree}`, `rev-parse ${deskGapSource}:${assetPath}`, + `rev-parse ${deskGapSource}:web/strategy-switch-console/worker.js`, `rev-parse ${deskGapSource}:tests/binance_account_facts_validation.mjs`]) + assert.ok(accepted.calls.indexOf(pin) > ancestry && accepted.calls.indexOf(pin) < checkout, pin); + assert.ok(!accepted.calls.some(call => call.startsWith("fetch") || call.startsWith("apply")), "main history only, no fetch or composition"); +} +for (const delta of [{RELEASE_FRONTEND_ASSETS:"false"},{RELEASE_FRONTEND_ASSETS:"true\n"},{SYNC_SECRETS:"true"}, + {SYNC_STRATEGY_PROFILES:"true"},{SKIP_BINANCE_RECEIVER_CHECKS:"true"}, + {GITHUB_REF:"refs/heads/other"},{GITHUB_REF:"refs/tags/main"},{IBKR_CANDIDATE_RESUME:"verify"},{IBKR_CANDIDATE_RESUME:"activate"}, + {EXTERNAL_FLOW_ENABLE:"true"},{RELEASE_SOURCE_SHA:deskGapSource.toUpperCase()},{RELEASE_SOURCE_SHA:deskGapSource.slice(0,8)}, + {RELEASE_SOURCE_SHA:deskGapSource+"\n"},{RELEASE_SOURCE_SHA:deskGapParent},{RELEASE_SOURCE_SHA:"b".repeat(40)}]) { + assert.deepEqual(sourceCase({...deskGapEnv,...delta},false).calls,[], JSON.stringify(delta)); +} +// Wrong parent/tree/map/Worker/reader test/object/ancestry/CONTROL rejects before selection; +// the published S2 N1 tree and map are rejected too. +for (const delta of [{FAKE_PARENT:"b".repeat(40)},{FAKE_PARENT:""},{FAKE_PARENT:deskGapParent+"\n"+"b".repeat(40)},{FAKE_PARENT:deskN1Parent}, + {FAKE_TREE:"b".repeat(40)},{FAKE_TREE:deskN1Tree},{FAKE_PR764_BLOB:"b".repeat(40)},{FAKE_PR764_BLOB:deskN1Blob}, + {FAKE_GAP_WORKER:walletWorkerBlob},{FAKE_GAP_WORKER:"b".repeat(40)},{FAKE_GAP_TEST:s1ReaderTestBlob},{FAKE_GAP_TEST:""}, + {FAKE_OBJECT_EXIT:"1"},{FAKE_ANCESTRY_EXIT:"1"},{FAKE_CONTROL_HEAD:"b".repeat(40)}, + {FAKE_CONTROL_HEAD:deskGapSource,GITHUB_SHA:deskGapSource}]) { + assert.equal(sourceCase({...deskGapEnv,...delta},false).selected,false, JSON.stringify(delta)); +} +assert.equal(sourceCase({...deskGapEnv,FAKE_SELECTED_HEAD:"b".repeat(40)},false).selected,true, "verify the actual selected checkout too"); +// No cross-acceptance with the published S2 N1 source. +assert.equal(sourceCase({...deskN1Env,FAKE_PARENT:deskGapParent,FAKE_TREE:deskGapTree,FAKE_PR764_BLOB:deskGapBlob},false).selected,false); +assert.equal(sourceCase({...deskGapEnv,FAKE_PARENT:deskN1Parent,FAKE_TREE:deskN1Tree,FAKE_PR764_BLOB:deskN1Blob},false).selected,false); + const validSource = sourceCase({}, true); assert.equal(validSource.selected, true); assert.ok(validSource.calls.indexOf(`merge-base --is-ancestor ${approved} ${control}`) < validSource.calls.findIndex(line => line.includes("checkout --detach"))); @@ -775,6 +847,7 @@ const fakeOverlayGit = `git() { 'diff --cached --name-only ${s1Source}') printf '%s' "$FAKE_CACHED" ;; 'diff --name-only ${deskEntrySource}') printf '%s' "$FAKE_DIFF" ;; 'diff --name-only ${deskN1Source}') printf '%s' "$FAKE_DIFF" ;; + 'diff --name-only ${deskGapSource}') printf '%s' "$FAKE_DIFF" ;; 'rev-parse :web/strategy-switch-console/worker.js') printf '%s' "$FAKE_STAGED_WORKER" ;; 'rev-parse :tests/binance_account_facts_validation.mjs') printf '%s' "$FAKE_STAGED_TEST" ;; 'diff --quiet') return "$FAKE_WORKTREE_EXIT" ;; @@ -961,6 +1034,34 @@ function overlayCase(delta = {}, expected = true, bytes = reviewedMap) { } } finally {rmSync(temp, {recursive:true,force:true});} } +// The S2 Desk gap-fill source ships its own tree and map unchanged: any staged, unstaged or +// untracked delta, the previous (S2 N1) map, or a head not selected for it must stop. +{ + const asset = spawnSync("git", ["show", `${deskGapSource}:${assetPath}`], {cwd: repoRoot, maxBuffer:64*1024*1024}).stdout; + assert.equal(spawnSync("git", ["hash-object", "--stdin"], {input:asset,encoding:"utf8",maxBuffer:64*1024*1024}).stdout.trim(), deskGapBlob); + const n1Asset = spawnSync("git", ["show", `${deskN1Source}:${assetPath}`], {cwd: repoRoot, maxBuffer:64*1024*1024}).stdout; + const temp = mkdtempSync(join(tmpdir(), "console-desk-gap-overlay-")); + try { + const dest = join(temp, assetPath); + mkdirSync(join(temp, "web/strategy-switch-console"), {recursive:true}); + const env = {PATH:process.env.PATH, GITHUB_REF:"refs/heads/main", RELEASE_SOURCE_SHA:deskGapSource, FAKE_HEAD:deskGapSource, + FAKE_TREE:deskGapTree, FAKE_DIFF:"", FAKE_UNTRACKED:""}; + for (const delta of [{}, {FAKE_DIFF:"fake-file"}, {FAKE_DIFF:walletPaths[1]}, {FAKE_UNTRACKED:"fake-file"}, + {FAKE_TREE:"a".repeat(40)}, {FAKE_TREE:deskN1Tree}, {GITHUB_REF:"refs/heads/other"}, {RELEASE_SOURCE_SHA:""}, + {RELEASE_SOURCE_SHA:deskN1Source}, {RELEASE_SOURCE_SHA:deskEntrySource}]) { + writeFileSync(dest, asset); + const result = spawnSync("bash", ["-c", fakeOverlayGit + overlayShell], {cwd:temp,env:{...env,...delta}}); + assert.equal(result.status === 0, Object.keys(delta).length === 0, `S2 Desk gap-fill overlay must validate exact pins: ${JSON.stringify(delta)}`); + overlayCases++; + assert.deepEqual(readFileSync(dest), asset, "never overlay an older asset"); + } + for (const bytes of [n1Asset, Buffer.from("fake modified asset")]) { + writeFileSync(dest, bytes); + assert.notEqual(spawnSync("bash", ["-c", fakeOverlayGit + overlayShell], {cwd:temp,env}).status, 0, "a mismatched frontend generation must stop"); + overlayCases++; + } + } finally {rmSync(temp, {recursive:true,force:true});} +} // S1-R4-01 regression on the actual composition: run the real selection and overlay // shells with real Git in a shared clone of this checkout, then execute the composed // Worker's digest reader, including the legacy (no include_valuation) response shape. @@ -1251,7 +1352,7 @@ async function releaseCase({pr764=false,ibkrDaily=false,activeOverride=null,inhe async function integratedReleaseCase({ reviewedMain=false, reviewedMainSource="c1b737d6e721eb9fe71a12c613fbb49d3fa51563", lineage=null, settings=false, daily=false, statusCandidate=false, cpu=false, active=null, latest=null, moduleContent=syntheticBundle.toString("base64"), uploadedTag=undefined, postTag=runtime.migration_tag, flow=false, oldFlag=null, drift=false, baselineItems=undefined, preUploadVersion=null, preDeployWitness=null, uploadId=null, ambiguousActive=false, expectFailure=null, testHandlers=null, deployed=false } = {}, expected=true) { const calls=[], stdout=[], stderr=[], prior=settings?"7ffe9227-686a-48ad-baa9-72665969d918":daily?"14f0c9c2-09f9-40e9-bfbe-17165f499495":statusCandidate?"175ba768-484a-4fe6-9cea-e5a101bc1618":cpu?"90a0c8e9-b5ad-447a-998f-5d585ec28063":"600ec84d-a804-4ede-8d1d-0a34819ce46a"; active ??= prior; latest ??= active; - const separateWitness = reviewedMain && (reviewedMainSource === deskSource || reviewedMainSource === inboxSource || reviewedMainSource === consoleSource || reviewedMainSource === s1Source || reviewedMainSource === deskEntrySource || reviewedMainSource === deskN1Source) && !flow; + const separateWitness = reviewedMain && (reviewedMainSource === deskSource || reviewedMainSource === inboxSource || reviewedMainSource === consoleSource || reviewedMainSource === s1Source || reviewedMainSource === deskEntrySource || reviewedMainSource === deskN1Source || reviewedMainSource === deskGapSource) && !flow; const responses=responseSequence(false,active); if (ambiguousActive) responses[0].result.deployments[0].versions.push({version_id:"00000000-0000-4000-8000-000000000024",percentage:0}); responses.splice(3,0,wrap({items:baselineItems===undefined?[{id:latest}]:baselineItems})); @@ -1405,6 +1506,14 @@ await integratedReleaseCase({...deskN1Release,latest:orphanWitness,preDeployWitn await integratedReleaseCase({...deskN1Release,latest:orphanWitness,uploadId:orphanWitness,expectFailure:{...stopped,checkpoint:"uploaded_matches_witness",upload_attempted:true,outcome:"UPLOAD_REQUIRES_READBACK_NOT_ACTIVATED"}},false); await integratedReleaseCase({...deskN1Release,moduleContent:Buffer.from("wrong").toString("base64")},false); await integratedReleaseCase({...deskN1Release,testHandlers:{active:["alpha"],uploaded:["alpha"],post:["alpha"],runtimeUploaded:["other"]},expectFailure:{...configStop,checkpoint:"configuration_hash_mismatch"}},false); +// S2 Desk gap-fill source: the same current reviewed-main publisher path and witness cases. +const deskGapRelease = {reviewedMain:true,reviewedMainSource:deskGapSource}; +await integratedReleaseCase(deskGapRelease); +await integratedReleaseCase({...deskGapRelease,latest:orphanWitness}); +await integratedReleaseCase({...deskGapRelease,latest:orphanWitness,preDeployWitness:driftedWitness,expectFailure:{...stopped,stage:"pre_deploy_lineage",checkpoint:"inherit_lineage_mismatch",upload_attempted:true,outcome:"UPLOAD_REQUIRES_READBACK_NOT_ACTIVATED"}},false); +await integratedReleaseCase({...deskGapRelease,latest:orphanWitness,uploadId:orphanWitness,expectFailure:{...stopped,checkpoint:"uploaded_matches_witness",upload_attempted:true,outcome:"UPLOAD_REQUIRES_READBACK_NOT_ACTIVATED"}},false); +await integratedReleaseCase({...deskGapRelease,moduleContent:Buffer.from("wrong").toString("base64")},false); +await integratedReleaseCase({...deskGapRelease,testHandlers:{active:["alpha"],uploaded:["alpha"],post:["alpha"],runtimeUploaded:["other"]},expectFailure:{...configStop,checkpoint:"configuration_hash_mismatch"}},false); await integratedReleaseCase(s1Release); await integratedReleaseCase({...s1Release,latest:orphanWitness}); for (const baselineItems of [[],null,[{id:"not-a-uuid"}],Array.from({length:11},(_,i)=>({id:`00000000-0000-4000-8000-${String(i).padStart(12,"0")}`}))])