From ceb249d812090ba80cd74d53759e545c76d68852 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Sat, 7 Nov 2026 04:54:01 +0800 Subject: [PATCH 1/2] fix: Accept the reviewed main frontend release source --- .../deploy-strategy-switch-console.yml | 26 +++++++++ docs/console-execution-evidence.md | 15 ++++++ tests/console_deploy_safety_validation.mjs | 54 ++++++++++++++++--- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-strategy-switch-console.yml b/.github/workflows/deploy-strategy-switch-console.yml index 62626e99..96d84946 100644 --- a/.github/workflows/deploy-strategy-switch-console.yml +++ b/.github/workflows/deploy-strategy-switch-console.yml @@ -1040,6 +1040,23 @@ jobs: if [ "$(git rev-parse HEAD)" != "$RELEASE_SOURCE_SHA" ] || [ "$(git rev-parse 'HEAD^{tree}')" != "$expected_tree" ]; then exit 2; fi exit 0 fi + # Reviewed merged main release: keep all receiver checks and live configuration. + if [ "$RELEASE_SOURCE_SHA" = "c1b737d6e721eb9fe71a12c613fbb49d3fa51563" ]; 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" ] || exit 2 + git cat-file -e "${RELEASE_SOURCE_SHA}^{commit}" + git merge-base --is-ancestor "$RELEASE_SOURCE_SHA" "$control_sha" + expected_tree="5dcdab977ec72e32f99cd3ef4956cebff90ba107" + [ "$(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")" = "b4ef57a6830011f78d0aa0270729e17d05a4d230" ] || 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 if [ "$frontend_mode" = "true" ]; then if [ "$GITHUB_REF" != "refs/heads/main" ] || [ "$RELEASE_SOURCE_SHA" != "9722414a99298cd7079a88eb8e3e703d50e986de" ] \ || [ "$SYNC_SECRETS" != "false" ] || [ "$SYNC_STRATEGY_PROFILES" != "false" ] \ @@ -1127,6 +1144,15 @@ jobs: if: inputs.release_frontend_assets && inputs.source_sha != '20dad166be317dd0d57e492ed06e56dfb82b7aef' && !inputs.render_and_compare_only run: | set -euo pipefail + reviewed_main="c1b737d6e721eb9fe71a12c613fbb49d3fa51563" + if [ "$(git rev-parse HEAD)" = "$reviewed_main" ]; then + [ "$GITHUB_REF" = "refs/heads/main" ] \ + && [ "$(git rev-parse 'HEAD^{tree}')" = "5dcdab977ec72e32f99cd3ef4956cebff90ba107" ] || exit 2 + [ "$(git hash-object web/strategy-switch-console/v2_asset_map.js)" = "b4ef57a6830011f78d0aa0270729e17d05a4d230" ] || exit 2 + [ -z "$(git diff --name-only "$reviewed_main")" ] \ + && [ -z "$(git ls-files --others --exclude-standard)" ] || exit 2 + exit 0 + fi integrated="0dd2c0b3e73e890bc2c39ab0bae0eeb48c58470a" cpu_candidate="974354b98ebe0cd55931baa03034d887d4fac560" status_candidate="68e3d4b69e706e2100f860c473ec657e9baf05ba" diff --git a/docs/console-execution-evidence.md b/docs/console-execution-evidence.md index 55a5464b..b053c54d 100644 --- a/docs/console-execution-evidence.md +++ b/docs/console-execution-evidence.md @@ -25,3 +25,18 @@ The account and overview cards display the reason for unknown, broker environmen 页面展示未知原因、券商环境、配置来源与检查时间。确认配置仅表示已读到明确配置,不能证明当前生效、真实运行或成交。此变更不授予执行权限。 未获准的会话读取 `/api/config`,可能得到 HTTP 200 和 `accountOptions=null`;这是账户资料不可见,不能当作账户目录为空。 + +## Reviewed main frontend release source + +The manual console deployment entry accepts the pinned, reviewed merged main +frontend release with receiver checks retained. The selector verifies the exact +commit, its ancestry in the workflow control revision, its tree and its embedded +asset map before checking out the source. The build must reproduce that map and +leave no tracked or untracked changes; the legacy frontend overlay is not applied. + +This mode requires `release_frontend_assets=true`, `sync_secrets=false`, +`sync_strategy_profiles=false`, `skip_binance_receiver_checks=false`, +`external_flow_enable=false` and `ibkr_candidate_resume=off`. Existing job gates +exclude backend-only, rollback, private-release and read-only modes. An arbitrary +main commit is not an accepted release source. Deployment still requires separate +scoped authorization; accepting a source does not grant deployment authority. diff --git a/tests/console_deploy_safety_validation.mjs b/tests/console_deploy_safety_validation.mjs index d0ecd195..c1fc47d8 100644 --- a/tests/console_deploy_safety_validation.mjs +++ b/tests/console_deploy_safety_validation.mjs @@ -245,17 +245,17 @@ const fakeGit = `git() { 'fetch --no-tags origin refs/heads/fix/runtime-daily-multi-release-20261030'|'fetch --no-tags origin refs/heads/fix/qrs-runtime-daily-serving-20261030'|'fetch --no-tags origin refs/heads/codex/website-account-settings-reviewed-20261029'|'fetch --no-tags origin refs/heads/codex/website-native-daily-fixed-clock-20261029'|'fetch --no-tags origin refs/heads/codex/website-integrated-candidate-20261029'|'fetch --no-tags origin refs/heads/codex/website-history-cpu-candidate-20261029'|'fetch --no-tags origin refs/heads/codex/website-incomplete-release-candidate-20261029') return 0 ;; 'rev-parse FETCH_HEAD') printf '%s' "$FAKE_FETCH_HEAD" ;; 'rev-parse HEAD') if [ -f "$SELECTED" ]; then printf '%s' "$FAKE_SELECTED_HEAD"; else printf '%s' "$FAKE_CONTROL_HEAD"; fi ;; - 'cat-file -e ${approved}^{commit}'|'cat-file -e ${pr764Source}^{commit}'|'cat-file -e ${ibkrSource}^{commit}') return "$FAKE_OBJECT_EXIT" ;; + 'cat-file -e c1b737d6e721eb9fe71a12c613fbb49d3fa51563^{commit}'|'cat-file -e ${approved}^{commit}'|'cat-file -e ${pr764Source}^{commit}'|'cat-file -e ${ibkrSource}^{commit}') return "$FAKE_OBJECT_EXIT" ;; 'cat-file -e ${frontend}^{commit}') return "$FAKE_FRONTEND_OBJECT_EXIT" ;; - 'merge-base --is-ancestor ${approved} ${control}'|'merge-base --is-ancestor ${pr764Source} ${control}') return "$FAKE_ANCESTRY_EXIT" ;; + 'merge-base --is-ancestor c1b737d6e721eb9fe71a12c613fbb49d3fa51563 ${control}'|'merge-base --is-ancestor ${approved} ${control}'|'merge-base --is-ancestor ${pr764Source} ${control}') return "$FAKE_ANCESTRY_EXIT" ;; 'merge-base --is-ancestor ${integratedSource} ${cpuSource}') return "$FAKE_ANCESTRY_EXIT" ;; 'merge-base --is-ancestor ${frontend} ${control}') return "$FAKE_FRONTEND_ANCESTRY_EXIT" ;; - 'rev-parse ${runtimeDailyMultiSource}^{tree}'|'rev-parse ${runtimeDailySource}^{tree}'|'rev-parse ${approved}^{tree}'|'rev-parse ${pr764Source}^{tree}'|'rev-parse ${ibkrSource}^{tree}'|'rev-parse ${integratedSource}^{tree}'|'rev-parse ${cpuSource}^{tree}'|'rev-parse ${settingsSource}^{tree}'|'rev-parse ${dailySource}^{tree}'|'rev-parse ${statusSource}^{tree}'|'rev-parse HEAD^{tree}') printf '%s' "$FAKE_TREE" ;; + 'rev-parse c1b737d6e721eb9fe71a12c613fbb49d3fa51563^{tree}'|'rev-parse ${runtimeDailyMultiSource}^{tree}'|'rev-parse ${runtimeDailySource}^{tree}'|'rev-parse ${approved}^{tree}'|'rev-parse ${pr764Source}^{tree}'|'rev-parse ${ibkrSource}^{tree}'|'rev-parse ${integratedSource}^{tree}'|'rev-parse ${cpuSource}^{tree}'|'rev-parse ${settingsSource}^{tree}'|'rev-parse ${dailySource}^{tree}'|'rev-parse ${statusSource}^{tree}'|'rev-parse HEAD^{tree}') printf '%s' "$FAKE_TREE" ;; 'rev-parse ${runtimeDailyMultiSource}^'|'rev-parse ${runtimeDailySource}^'|'rev-parse ${ibkrSource}^'|'rev-parse ${integratedSource}^'|'rev-parse ${settingsSource}^'|'rev-parse ${dailySource}^'|'rev-parse ${statusSource}^') printf '%s' "$FAKE_PARENT" ;; 'rev-parse ${frontend}^{tree}') printf '%s' "$FAKE_FRONTEND_TREE" ;; - 'rev-parse ${runtimeDailyMultiSource}:${assetPath}'|'rev-parse ${runtimeDailySource}:${assetPath}'|'rev-parse ${pr764Source}:${assetPath}'|'rev-parse ${ibkrSource}:${assetPath}'|'rev-parse ${integratedSource}:${assetPath}'|'rev-parse ${cpuSource}:${assetPath}'|'rev-parse ${settingsSource}:${assetPath}'|'rev-parse ${dailySource}:${assetPath}'|'rev-parse ${statusSource}:${assetPath}') printf '%s' "$FAKE_PR764_BLOB" ;; + 'rev-parse c1b737d6e721eb9fe71a12c613fbb49d3fa51563:${assetPath}'|'rev-parse ${runtimeDailyMultiSource}:${assetPath}'|'rev-parse ${runtimeDailySource}:${assetPath}'|'rev-parse ${pr764Source}:${assetPath}'|'rev-parse ${ibkrSource}:${assetPath}'|'rev-parse ${integratedSource}:${assetPath}'|'rev-parse ${cpuSource}:${assetPath}'|'rev-parse ${settingsSource}:${assetPath}'|'rev-parse ${dailySource}:${assetPath}'|'rev-parse ${statusSource}:${assetPath}') printf '%s' "$FAKE_PR764_BLOB" ;; 'rev-parse ${frontend}:${assetPath}') printf '%s' "$FAKE_FRONTEND_BLOB" ;; - '-c advice.detachedHead=false checkout --detach ${runtimeDailyMultiSource}'|'-c advice.detachedHead=false checkout --detach ${runtimeDailySource}'|'-c advice.detachedHead=false checkout --detach ${approved}'|'-c advice.detachedHead=false checkout --detach ${pr764Source}'|'-c advice.detachedHead=false checkout --detach ${ibkrSource}'|'-c advice.detachedHead=false checkout --detach ${integratedSource}'|'-c advice.detachedHead=false checkout --detach ${cpuSource}'|'-c advice.detachedHead=false checkout --detach ${statusSource}'|'-c advice.detachedHead=false checkout --detach ${settingsSource}'|'-c advice.detachedHead=false checkout --detach ${dailySource}') touch "$SELECTED" ;; + '-c advice.detachedHead=false checkout --detach c1b737d6e721eb9fe71a12c613fbb49d3fa51563'|'-c advice.detachedHead=false checkout --detach ${runtimeDailyMultiSource}'|'-c advice.detachedHead=false checkout --detach ${runtimeDailySource}'|'-c advice.detachedHead=false checkout --detach ${approved}'|'-c advice.detachedHead=false checkout --detach ${pr764Source}'|'-c advice.detachedHead=false checkout --detach ${ibkrSource}'|'-c advice.detachedHead=false checkout --detach ${integratedSource}'|'-c advice.detachedHead=false checkout --detach ${cpuSource}'|'-c advice.detachedHead=false checkout --detach ${statusSource}'|'-c advice.detachedHead=false checkout --detach ${settingsSource}'|'-c advice.detachedHead=false checkout --detach ${dailySource}') touch "$SELECTED" ;; *) return 91 ;; esac };\n`; @@ -277,6 +277,25 @@ function sourceCase(delta, expected) { return { calls, selected: existsSync(selected) }; } finally { rmSync(temp, { recursive: true, force: true }); } } +// The merged reviewed main release retains receiver checks and never syncs secrets. +const mergedSource = "c1b737d6e721eb9fe71a12c613fbb49d3fa51563"; +const mergedTree = "5dcdab977ec72e32f99cd3ef4956cebff90ba107"; +const mergedBlob = "b4ef57a6830011f78d0aa0270729e17d05a4d230"; +const mergedEnv = { RELEASE_SOURCE_SHA: mergedSource, RELEASE_FRONTEND_ASSETS: "true", + SKIP_BINANCE_RECEIVER_CHECKS: "false", FAKE_SELECTED_HEAD: mergedSource, + FAKE_TREE: mergedTree, FAKE_PR764_BLOB: mergedBlob }; +assert.equal(sourceCase(mergedEnv, true).selected, true); +for (const delta of [{RELEASE_FRONTEND_ASSETS:"false"}, {SYNC_SECRETS:"true"}, + {SYNC_STRATEGY_PROFILES:"true"}, {SKIP_BINANCE_RECEIVER_CHECKS:"true"}, + {GITHUB_REF:"refs/heads/other"}, {IBKR_CANDIDATE_RESUME:"verify"}, {EXTERNAL_FLOW_ENABLE:"true"}]) { + assert.deepEqual(sourceCase({...mergedEnv,...delta}, false).calls, []); +} +for (const delta of [{FAKE_CONTROL_HEAD:"b".repeat(40)}, {FAKE_OBJECT_EXIT:"1"}, + {FAKE_ANCESTRY_EXIT:"1"}, {FAKE_TREE:"b".repeat(40)}, {FAKE_PR764_BLOB:"b".repeat(40)}]) { + assert.equal(sourceCase({...mergedEnv,...delta}, false).selected, false); +} +assert.equal(sourceCase({...mergedEnv,FAKE_SELECTED_HEAD:"b".repeat(40)}, false).selected, true); + 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"))); @@ -432,7 +451,7 @@ const fakeOverlayGit = `git() { 'show ${ibkrSource}:${assetPath}') cat "$FROZEN_MAP" ;; hash-object*) command git "$@" ;; 'diff --name-only ${approved}') printf '%s' "$FAKE_DIFF" ;; - 'diff --name-only ${ibkrSource}'|'diff --name-only ${cpuSource}'|'diff --name-only ${settingsSource}'|'diff --name-only ${dailySource}'|'diff --name-only ${statusSource}') printf '%s' "$FAKE_DIFF" ;; + 'diff --name-only ${mergedSource}'|'diff --name-only ${ibkrSource}'|'diff --name-only ${cpuSource}'|'diff --name-only ${settingsSource}'|'diff --name-only ${dailySource}'|'diff --name-only ${statusSource}') printf '%s' "$FAKE_DIFF" ;; 'ls-files --others --exclude-standard') printf '%s' "$FAKE_UNTRACKED" ;; *) return 91 ;; esac @@ -452,6 +471,29 @@ function overlayCase(delta = {}, expected = true, bytes = reviewedMap) { overlayCases++; } finally { rmSync(temp, { recursive: true, force: true }); } } +// The merged release must preserve its own map, reject tampering and dirty output. +{ + const assetResult = spawnSync("git", ["show", `${mergedSource}:${assetPath}`], {maxBuffer:16*1024*1024}); + assert.equal(assetResult.status, 0); + const asset = assetResult.stdout; + assert.equal(spawnSync("git", ["hash-object", "--stdin"], {input:asset,encoding:"utf8",maxBuffer:16*1024*1024}).stdout.trim(), mergedBlob); + const temp = mkdtempSync(join(tmpdir(), "console-merged-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", FAKE_HEAD:mergedSource, + FAKE_TREE:mergedTree, FAKE_DIFF:"", FAKE_UNTRACKED:""}; + for (const delta of [{}, {FAKE_DIFF:"fake-file"}, {FAKE_UNTRACKED:"fake-file"}, + {FAKE_TREE:"a".repeat(40)}, {GITHUB_REF:"refs/heads/other"}]) { + 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); + assert.deepEqual(readFileSync(dest), asset, "never overlay an older asset"); + } + writeFileSync(dest, "fake modified asset"); + assert.notEqual(spawnSync("bash", ["-c", fakeOverlayGit + overlayShell], {cwd:temp,env}).status, 0); + } finally {rmSync(temp, {recursive:true,force:true});} +} // The CPU candidate carries its own exact generated map, never the older overlay. { const fixedAsset = spawnSync("git", ["show", `${cpuSource}:${assetPath}`], { cwd: new URL("..", import.meta.url) }); From b755e21620de843c0019d74224201c1c64cd11b6 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Sat, 7 Nov 2026 07:26:57 +0800 Subject: [PATCH 2/2] fix(console): pin merged release binding inheritance --- .../workflows/deploy-strategy-switch-console.yml | 15 ++++++++------- docs/console-execution-evidence.md | 4 ++++ tests/console_deploy_safety_validation.mjs | 14 +++++++++++--- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deploy-strategy-switch-console.yml b/.github/workflows/deploy-strategy-switch-console.yml index 96d84946..fd9371c7 100644 --- a/.github/workflows/deploy-strategy-switch-console.yml +++ b/.github/workflows/deploy-strategy-switch-console.yml @@ -1629,7 +1629,8 @@ jobs: const statusCandidate = process.env.RELEASE_SOURCE_SHA === '68e3d4b69e706e2100f860c473ec657e9baf05ba'; const cpuCandidate = process.env.RELEASE_SOURCE_SHA === '974354b98ebe0cd55931baa03034d887d4fac560'; const integrated = controlRuntimeCandidate || controlAdminCandidate || historyCandidate || declarationCandidate || returnsCandidate || settingsCandidate || dailyCandidate || statusCandidate || cpuCandidate || process.env.RELEASE_SOURCE_SHA === '0dd2c0b3e73e890bc2c39ab0bae0eeb48c58470a'; - const betaRelease = ibkrDaily || integrated; + const reviewedMain = process.env.RELEASE_SOURCE_SHA === 'c1b737d6e721eb9fe71a12c613fbb49d3fa51563'; + const betaRelease = ibkrDaily || integrated || reviewedMain; const flowMode = process.env.EXTERNAL_FLOW_ENABLE === 'true'; if (flowMode && process.env.RELEASE_SOURCE_SHA !== '1174db8e143f2a3689ca8e86cd547dc08461ff29') fail('external_flow_source_invalid'); const resume = process.env.IBKR_CANDIDATE_RESUME ?? 'off'; @@ -1836,7 +1837,7 @@ jobs: // version; the older script-version API only supports latest. const metadata = { main_module: 'worker.js', bindings: old.bindings.map(b => ({ type: 'inherit', name: b.name, ...(betaRelease ? {version_id: prior} : {}) })), compatibility_date: old.script_runtime.compatibility_date, usage_model: old.script_runtime.usage_model, - annotations: { 'workers/message': controlRuntimeCandidate ? 'Reviewed runtime control candidate; retained configuration' : controlAdminCandidate ? 'Reviewed runtime control admin candidate 5db5153e; retained configuration' : historyCandidate ? 'Reviewed history return candidate 21829dda; retained configuration' : declarationCandidate ? 'Reviewed account declaration candidate 411356d3; retained configuration' : returnsCandidate ? 'Reviewed account returns candidate b051154d; retained configuration' : settingsCandidate ? 'Reviewed settings candidate 3782a3e8; retained configuration' : dailyCandidate ? 'Reviewed daily candidate 4b6cbedc; retained configuration' : statusCandidate ? 'Reviewed status candidate 68e3d4b6; retained configuration' : cpuCandidate ? 'Reviewed CPU candidate 974354b9; retained configuration' : integrated ? 'Reviewed integrated candidate 0dd2c0b3; retained configuration' : pr764 ? 'Reviewed PR764 20dad166 full source; retained configuration' : ibkrDaily ? 'Reviewed IBKR daily candidate aaa46a81; retained configuration' : 'Reviewed website backend 9722414a with fixed frontend 96363633; retained bindings' } }; + annotations: { 'workers/message': reviewedMain ? 'Reviewed merged main frontend; retained configuration' : controlRuntimeCandidate ? 'Reviewed runtime control candidate; retained configuration' : controlAdminCandidate ? 'Reviewed runtime control admin candidate 5db5153e; retained configuration' : historyCandidate ? 'Reviewed history return candidate 21829dda; retained configuration' : declarationCandidate ? 'Reviewed account declaration candidate 411356d3; retained configuration' : returnsCandidate ? 'Reviewed account returns candidate b051154d; retained configuration' : settingsCandidate ? 'Reviewed settings candidate 3782a3e8; retained configuration' : dailyCandidate ? 'Reviewed daily candidate 4b6cbedc; retained configuration' : statusCandidate ? 'Reviewed status candidate 68e3d4b6; retained configuration' : cpuCandidate ? 'Reviewed CPU candidate 974354b9; retained configuration' : integrated ? 'Reviewed integrated candidate 0dd2c0b3; retained configuration' : pr764 ? 'Reviewed PR764 20dad166 full source; retained configuration' : ibkrDaily ? 'Reviewed IBKR daily candidate aaa46a81; retained configuration' : 'Reviewed website backend 9722414a with fixed frontend 96363633; retained bindings' } }; if (flowMode) { metadata.bindings = metadata.bindings.filter(binding => binding.name !== 'CONSOLE_EXTERNAL_FLOW_ENABLED'); metadata.bindings.push({ name: 'CONSOLE_EXTERNAL_FLOW_ENABLED', type: 'plain_text', text: 'true' }); @@ -1861,7 +1862,7 @@ jobs: } else { uploaded = '2b097d2e-ffb5-4f08-af3b-9cc1d4b9e9ee'; } - if (resume !== 'off' || integrated) { + if (resume !== 'off' || integrated || reviewedMain) { stage = 'candidate_module'; const candidate = await request(`/versions/${uploaded}?include=modules`, 'GET', undefined, true); if (candidate?.id !== uploaded) fail('candidate_id_mismatch'); @@ -1888,7 +1889,7 @@ jobs: desired.bindings.push({ name: 'CONSOLE_EXTERNAL_FLOW_ENABLED', type: 'plain_text', text: 'true' }); } const configBefore = configHash(desired), configUploaded = configHash(next); - if (resume !== 'off' || integrated) { + if (resume !== 'off' || integrated || reviewedMain) { const oldTag = old.script_runtime.migration_tag, newTag = next.script_runtime.migration_tag; if (typeof oldTag !== 'string' || !oldTag || (newTag !== undefined && newTag !== oldTag)) fail('migration_tag_mismatch'); const normalized = structuredClone(next); normalized.script_runtime.migration_tag = oldTag; @@ -1917,7 +1918,7 @@ jobs: } else { stage = 'activate'; deployAttempted = true; const deployment = await request('/deployments?force=false', 'POST', JSON.stringify({ strategy: 'percentage', - versions: [{ version_id: uploaded, percentage: 100 }], annotations: { 'workers/message': controlRuntimeCandidate ? 'Reviewed runtime control candidate; retained configuration' : controlAdminCandidate ? 'Reviewed runtime control admin candidate 5db5153e; retained configuration' : historyCandidate ? 'Reviewed history return candidate 21829dda; retained configuration' : declarationCandidate ? 'Reviewed account declaration candidate 411356d3; retained configuration' : returnsCandidate ? 'Reviewed account returns candidate b051154d; retained configuration' : settingsCandidate ? 'Reviewed settings candidate 3782a3e8; retained configuration' : dailyCandidate ? 'Reviewed daily candidate 4b6cbedc; retained configuration' : statusCandidate ? 'Reviewed status candidate 68e3d4b6; retained configuration' : cpuCandidate ? 'Reviewed CPU candidate 974354b9; retained configuration' : integrated ? 'Reviewed integrated candidate 0dd2c0b3; retained configuration' : pr764 ? 'Reviewed PR764 full source; retained configuration' : ibkrDaily ? 'Reviewed IBKR daily candidate aaa46a81; retained configuration' : 'Reviewed website backend 9722414a and frontend 96363633; retained configuration' } })); + versions: [{ version_id: uploaded, percentage: 100 }], annotations: { 'workers/message': reviewedMain ? 'Reviewed merged main frontend; retained configuration' : controlRuntimeCandidate ? 'Reviewed runtime control candidate; retained configuration' : controlAdminCandidate ? 'Reviewed runtime control admin candidate 5db5153e; retained configuration' : historyCandidate ? 'Reviewed history return candidate 21829dda; retained configuration' : declarationCandidate ? 'Reviewed account declaration candidate 411356d3; retained configuration' : returnsCandidate ? 'Reviewed account returns candidate b051154d; retained configuration' : settingsCandidate ? 'Reviewed settings candidate 3782a3e8; retained configuration' : dailyCandidate ? 'Reviewed daily candidate 4b6cbedc; retained configuration' : statusCandidate ? 'Reviewed status candidate 68e3d4b6; retained configuration' : cpuCandidate ? 'Reviewed CPU candidate 974354b9; retained configuration' : integrated ? 'Reviewed integrated candidate 0dd2c0b3; retained configuration' : pr764 ? 'Reviewed PR764 full source; retained configuration' : ibkrDaily ? 'Reviewed IBKR daily candidate aaa46a81; retained configuration' : 'Reviewed website backend 9722414a and frontend 96363633; retained configuration' } })); if (!object(deployment) || !Object.hasOwn(deployment, 'id')) fail('missing_id'); if (!isUuid(deployment.id)) fail('invalid_id'); const deploymentId = deployment.id; @@ -1932,14 +1933,14 @@ jobs: if (deployment.versions[0]?.percentage !== 100) fail('percentage_mismatch'); } stage = 'readback'; await active(deploymentId, uploaded); - if (pr764 || integrated || resume !== 'off') { + if (pr764 || integrated || reviewedMain || resume !== 'off') { stage = 'post_release_configuration'; const configAfter = configHash(resources(await request(`/versions/${uploaded}`), uploaded)); if (configAfter !== configBefore) fail('post_release_configuration_hash_mismatch'); // Version metadata hides secret values. Hash all comparable bindings/runtime/ // handlers/placement, never code etag or provider bookkeeping. No KV writes. if (controlRuntimeCandidate) console.log(JSON.stringify({ status: 'integrated_configuration_verified', bindings_equal: true, runtime_equal: true, secret_values_read: false })); - else console.log(JSON.stringify({ status: integrated ? 'integrated_configuration_verified' : resume !== 'off' ? 'ibkr_candidate_configuration_verified' : 'pr764_configuration_verified', config_before_sha256: configBefore, + else console.log(JSON.stringify({ status: reviewedMain ? 'reviewed_main_configuration_verified' : integrated ? 'integrated_configuration_verified' : resume !== 'off' ? 'ibkr_candidate_configuration_verified' : 'pr764_configuration_verified', config_before_sha256: configBefore, config_uploaded_sha256: configUploaded, config_after_sha256: configAfter, secret_values_read: false })); } if (controlRuntimeCandidate) console.log(JSON.stringify({ status: 'frontend_release_verified', matched: true, diff --git a/docs/console-execution-evidence.md b/docs/console-execution-evidence.md index b053c54d..69140214 100644 --- a/docs/console-execution-evidence.md +++ b/docs/console-execution-evidence.md @@ -34,6 +34,10 @@ commit, its ancestry in the workflow control revision, its tree and its embedded asset map before checking out the source. The build must reproduce that map and leave no tracked or untracked changes; the legacy frontend overlay is not applied. +Publication uses version-pinned inheritance from the active Worker version for +every binding, including secrets. Latest-version lineage checks reject concurrent +uploads; module and configuration readbacks must match before activation. + This mode requires `release_frontend_assets=true`, `sync_secrets=false`, `sync_strategy_profiles=false`, `skip_binance_receiver_checks=false`, `external_flow_enable=false` and `ibkr_candidate_resume=off`. Existing job gates diff --git a/tests/console_deploy_safety_validation.mjs b/tests/console_deploy_safety_validation.mjs index c1fc47d8..c9bc63cd 100644 --- a/tests/console_deploy_safety_validation.mjs +++ b/tests/console_deploy_safety_validation.mjs @@ -705,13 +705,13 @@ async function releaseCase({pr764=false,ibkrDaily=false,activeOverride=null,inhe releaseCases++;return {calls,stdout,stderr}; } // Exact integrated candidate: explicit inheritance, verified module, one non-forced activation. -async function integratedReleaseCase({ 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 } = {}, expected=true) { +async function integratedReleaseCase({ reviewedMain=false, 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 } = {}, 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 responses=responseSequence(false,active); responses.splice(3,0,wrap({items:[{id:latest}]})); responses.splice(6,0,wrap({id:newVersion,modules:[{name:"worker.js",content_type:"application/javascript+module",content_base64:moduleContent}]})); - responses.splice(8,0,wrap({items:[{id:newVersion},{id:latest}]})); + responses.splice(8,0,wrap({items:[{id:newVersion},{id:lineage ?? latest}]})); if (flow) { if (oldFlag) responses[1].result.resources.bindings.push(oldFlag); for (const i of [7, 10]) responses[i].result.resources.bindings.push({name:"CONSOLE_EXTERNAL_FLOW_ENABLED",type:"plain_text",text:"true"}); @@ -726,7 +726,7 @@ async function integratedReleaseCase({ settings=false, daily=false, statusCandid else postResources.script_runtime.migration_tag=postTag; responses.push(wrap({id:newVersion,resources:postResources})); const process={env:{GITHUB_REF:"refs/heads/main",CLOUDFLARE_ACCOUNT_ID:"a".repeat(32),CLOUDFLARE_API_TOKEN:privateRelease, - RUNNER_TEMP:"/synthetic",WORKER_DIR:"web/strategy-switch-console",RELEASE_SOURCE_SHA:flow?"1174db8e143f2a3689ca8e86cd547dc08461ff29":settings?settingsSource:daily?dailySource:statusCandidate?statusSource:cpu?cpuSource:integratedSource,...(flow?{EXTERNAL_FLOW_ENABLE:"true",PLATFORM_CONTROL_EXPECTED_VERSION:prior}:{})},exitCode:0}; + RUNNER_TEMP:"/synthetic",WORKER_DIR:"web/strategy-switch-console",RELEASE_SOURCE_SHA:reviewedMain?"c1b737d6e721eb9fe71a12c613fbb49d3fa51563":flow?"1174db8e143f2a3689ca8e86cd547dc08461ff29":settings?settingsSource:daily?dailySource:statusCandidate?statusSource:cpu?cpuSource:integratedSource,...(flow?{EXTERNAL_FLOW_ENABLE:"true",PLATFORM_CONTROL_EXPECTED_VERSION:prior}:{})},exitCode:0}; await runRelease(process,async(url,options)=>{calls.push({url,options}); const response=responses[calls.length-1]; assert.ok(response,"no retry"); return response instanceof Response ? response : new Response(JSON.stringify(response),{headers:{"content-type":"application/json"}});}, {log:x=>stdout.push(x),error:x=>stderr.push(x)},async path=>path.endsWith("v2_asset_map.js")?syntheticAssetMap:syntheticBundle, @@ -734,11 +734,19 @@ async function integratedReleaseCase({ settings=false, daily=false, statusCandid assert.equal(process.exitCode===0,expected,stderr.join("\n")); const posts=calls.filter(x=>x.options.method==="POST"); if(expected){assert.equal(posts.length,2);assert.ok(posts[0].url.endsWith("/versions?deploy=false")); + assert.ok(!calls.some(c=>c.url.includes("bindings_inherit=strict")), "never use latest-version secret inheritance"); const upload=JSON.parse(posts[0].options.body);assert.deepEqual(upload.bindings,[...remoteBindings.map(b=>({type:"inherit",name:b.name,version_id:prior})),...(flow?[{name:"CONSOLE_EXTERNAL_FLOW_ENABLED",type:"plain_text",text:"true"}]:[])]); assert.ok(posts[1].url.endsWith("/deployments?force=false"));if (flow) assert.equal(JSON.parse(stdout.at(-1)).external_flow_enabled,true); else assert.equal(JSON.parse(stdout.at(-1)).version_id,newVersion); } else {if (postTag !== null) assert.equal(posts.filter(x=>x.url.includes("/deployments?")).length,0); else { assert.equal(posts.filter(x=>x.url.includes("/deployments?")).length,1); assert.equal(JSON.parse(stderr.at(-1)).outcome,"UNKNOWN_REQUIRES_READBACK"); } assert.equal(JSON.parse(stderr.at(-1)).retry,false);} assert.ok(!JSON.stringify({stdout,stderr}).includes(privateRelease)); } +// Execute the actual publisher for the exact merged source, with fake provider responses. +await integratedReleaseCase({reviewedMain:true}); +await integratedReleaseCase({reviewedMain:true,latest:priorVersion},false); +await integratedReleaseCase({reviewedMain:true,lineage:priorVersion},false); +await integratedReleaseCase({reviewedMain:true,moduleContent:Buffer.from("wrong").toString("base64")},false); +await integratedReleaseCase({reviewedMain:true,uploadedTag:"wrong-tag"},false); +await integratedReleaseCase({reviewedMain:true,postTag:null},false); await integratedReleaseCase(); await integratedReleaseCase({flow:true}); await integratedReleaseCase({flow:true,oldFlag:{name:"CONSOLE_EXTERNAL_FLOW_ENABLED",type:"plain_text",text:"false"}});