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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/workflows/deploy-strategy-switch-console.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ on:
default: "off"
options: ["off", "baseline", "candidate"]
external_flow_enable:
description: "Enable the existing external-flow contract while retaining all other settings."
description: "Enable the existing external-flow contract on its pinned reviewed release. For current main set the CONSOLE_EXTERNAL_FLOW_ENABLED Environment variable instead."
required: true
type: boolean
default: false
Expand Down Expand Up @@ -671,6 +671,7 @@ jobs:
UX1_EXPECTED_EVIDENCE_HASHES: ${{ vars.UX1_EXPECTED_EVIDENCE_HASHES }}
ACCOUNT_FACTS_READ_MODEL_ENABLED: ${{ vars.ACCOUNT_FACTS_READ_MODEL_ENABLED }}
RUNTIME_DAILY_READ_MODEL_ENABLED: ${{ vars.RUNTIME_DAILY_READ_MODEL_ENABLED }}
CONSOLE_EXTERNAL_FLOW_ENABLED: ${{ vars.CONSOLE_EXTERNAL_FLOW_ENABLED }}
STRATEGY_SWITCH_SYNC_TOKEN: ${{ secrets.STRATEGY_SWITCH_SYNC_TOKEN || secrets.RUNTIME_SETTINGS_GH_TOKEN }}
STRATEGY_HEALTH_SYNC_TOKEN: ${{ secrets.STRATEGY_HEALTH_SYNC_TOKEN }}
CONTROL_PLANE_SYNC_TOKEN: ${{ secrets.CONTROL_PLANE_SYNC_TOKEN }}
Expand Down Expand Up @@ -1398,6 +1399,10 @@ jobs:
):
raise SystemExit("EXECUTION_EVIDENCE_SYNC_TOKEN must be non-empty without surrounding whitespace or HTTP control characters when runtime daily is enabled.")
runtime_daily_value = "true" if runtime_daily_flag == "true" else "false"
external_flow_flag = os.environ.get("CONSOLE_EXTERNAL_FLOW_ENABLED", "")
if external_flow_flag not in ("", "true", "false"):
raise SystemExit("CONSOLE_EXTERNAL_FLOW_ENABLED must be empty, true, or false.")
external_flow_value = "true" if external_flow_flag == "true" else "false"
text = source.read_text(encoding="utf-8")
account_id = os.environ.get("CLOUDFLARE_ACCOUNT_ID", "").strip()
if account_id and "\naccount_id" not in text:
Expand Down Expand Up @@ -1431,6 +1436,19 @@ jobs:
raise SystemExit("wrangler.toml.example must define RUNTIME_DAILY_READ_MODEL_ENABLED once under [vars].")
text = text.replace(runtime_daily_placeholder, f'RUNTIME_DAILY_READ_MODEL_ENABLED = "{runtime_daily_value}"', 1)

external_flow_line = f'CONSOLE_EXTERNAL_FLOW_ENABLED = "{external_flow_value}"'
external_flow_default = 'CONSOLE_EXTERNAL_FLOW_ENABLED = "false"'
if text.count(external_flow_default) == 1:
text = text.replace(external_flow_default, external_flow_line, 1)
elif "CONSOLE_EXTERNAL_FLOW_ENABLED" in text:
raise SystemExit("wrangler.toml.example must declare CONSOLE_EXTERNAL_FLOW_ENABLED with the closed default value.")
else:
# Reviewed historical sources predate the declaration, and a variable the
# config does not declare is erased by the next ordinary deploy, so render
# it into [vars] instead of refusing to publish those pinned sources.
# The account-facts check above already requires a [vars] section.
text = text.replace("[vars]\n", f"[vars]\n{external_flow_line}\n", 1)

mode = os.environ.get("UX1_PREVIEW_MODE", "")
if mode and mode != "github_actions":
raise SystemExit("UX1_PREVIEW_MODE must be empty or github_actions.")
Expand Down
7 changes: 7 additions & 0 deletions docs/console-performance-charts.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,13 @@ The existing external-flow switch continues to control exact-time ingress and
performance calculation; it is not enabled merely to store dates. No new
credential or storage service is used.

`CONSOLE_EXTERNAL_FLOW_ENABLED` is an ordinary non-secret Worker variable. Set it
to `true` in the `runtime-strategy-switch` GitHub Environment, then deploy normally:
`release_frontend_assets=false` and no historical `source_sha`. The deploy workflow
renders the value into `wrangler.toml`, so a later release keeps it instead of
dropping an undeclared variable. A frontend-asset release removes `[vars]` from the
generated config and inherits the live bindings, so it cannot activate the switch.

`external_flow.v2` retains v1 exact-time windows and also accepts native date
windows: `time_precision: "date"`, inclusive ISO `period_start`/`period_end` of at
most 31 days, and `coverage: "reported"`. These records contain `effective_date`
Expand Down
6 changes: 5 additions & 1 deletion python/tests/test_ux1_deploy_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def run_config_generator(self, ux1_env):
worker_dir.mkdir()
(worker_dir / "wrangler.toml.example").write_text(
'[vars]\nEXISTING = "retained"\nACCOUNT_FACTS_READ_MODEL_ENABLED = "false"\n'
'RUNTIME_DAILY_READ_MODEL_ENABLED = "false"\n\n'
'RUNTIME_DAILY_READ_MODEL_ENABLED = "false"\nCONSOLE_EXTERNAL_FLOW_ENABLED = "false"\n'
'\n'
'# [[kv_namespaces]]\n'
'# binding = "STRATEGY_SWITCH_CONFIG"\n'
'# id = "replace-with-cloudflare-kv-namespace-id"\n\n',
Expand All @@ -60,6 +61,7 @@ def run_config_generator(self, ux1_env):
"WORKER_DIR": str(worker_dir),
"CLOUDFLARE_API_TOKEN": "synthetic-cloudflare-token",
"STRATEGY_SWITCH_CONFIG_KV_NAMESPACE_ID": "a" * 32,
"CONSOLE_EXTERNAL_FLOW_ENABLED": "",
**ux1_env,
}
result = subprocess.run(
Expand Down Expand Up @@ -94,6 +96,7 @@ def test_enabled_config_is_toml_serialized_without_secret(self):
self.assertEqual(variables["EXISTING"], "retained")
self.assertEqual(variables["ACCOUNT_FACTS_READ_MODEL_ENABLED"], "false")
self.assertEqual(variables["RUNTIME_DAILY_READ_MODEL_ENABLED"], "false")
self.assertEqual(variables["CONSOLE_EXTERNAL_FLOW_ENABLED"], "false")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
self.assertEqual(variables["UX1_PREVIEW_MODE"], "github_actions")
self.assertEqual(variables["UX1_UES_REVISION"], "a" * 40)
self.assertEqual(variables["UX1_RUNTIME_EPOCH"], "b" * 32)
Expand All @@ -114,6 +117,7 @@ def test_empty_mode_preserves_local_binding_config(self):
self.assertEqual(config["vars"]["EXISTING"], "retained")
self.assertEqual(config["vars"]["ACCOUNT_FACTS_READ_MODEL_ENABLED"], "false")
self.assertEqual(config["vars"]["RUNTIME_DAILY_READ_MODEL_ENABLED"], "false")
self.assertEqual(config["vars"]["CONSOLE_EXTERNAL_FLOW_ENABLED"], "false")
self.assertFalse(any(key.startswith("UX1_") for key in config["vars"]))

def test_invalid_or_incomplete_enabled_config_fails(self):
Expand Down
45 changes: 41 additions & 4 deletions tests/console_catalog_release_validation.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
import { copyFileSync, existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import { copyFileSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { tmpdir } from "node:os";
import { join } from "node:path";
Expand Down Expand Up @@ -83,7 +83,7 @@ test("the deploy workflow syncs catalogs only after an explicit manual choice",
assert.doesNotMatch(workflow, /git diff --quiet "\$BEFORE" HEAD -- platform-config\.json/);
});

test("the deploy renderer pins account-facts and runtime-daily flags and fails closed before deploy", () => {
test("the deploy renderer pins account-facts, runtime-daily and external-flow flags and fails closed before deploy", () => {
const workflow = readFileSync(new URL("../.github/workflows/deploy-strategy-switch-console.yml", import.meta.url), "utf8");
const template = new URL("../web/strategy-switch-console/wrangler.toml.example", import.meta.url);
const heredoc = workflow.match(/ python3 - <<'PY'\n([\s\S]*?)\n PY\n/);
Expand All @@ -95,13 +95,15 @@ test("the deploy renderer pins account-facts and runtime-daily flags and fails c
assert.match(workflow, /Sync optional IBKR account-facts ingress token/);
assert.match(workflow, /secret put IBKR_ACCOUNT_FACTS_SYNC_TOKEN/);
assert.match(workflow, /RUNTIME_DAILY_READ_MODEL_ENABLED: \$\{\{ vars\.RUNTIME_DAILY_READ_MODEL_ENABLED \}\}/);
assert.match(workflow, /CONSOLE_EXTERNAL_FLOW_ENABLED: \$\{\{ vars\.CONSOLE_EXTERNAL_FLOW_ENABLED \}\}/);
assert.match(workflow, /EXECUTION_EVIDENCE_SYNC_TOKEN: \$\{\{ secrets\.EXECUTION_EVIDENCE_SYNC_TOKEN \}\}/);
assert.match(workflow, /node tests\/console_catalog_release_validation\.mjs/);

function render({ flag, token, ibkrToken, runtimeDailyFlag, executionEvidenceToken } = {}) {
function render({ flag, token, ibkrToken, runtimeDailyFlag, executionEvidenceToken, externalFlowFlag, example } = {}) {
const directory = mkdtempSync(join(tmpdir(), "qsl-config-render-"));
try {
copyFileSync(template, join(directory, "wrangler.toml.example"));
if (example === undefined) copyFileSync(template, join(directory, "wrangler.toml.example"));
else writeFileSync(join(directory, "wrangler.toml.example"), example, "utf8");
const env = { ...process.env, WORKER_DIR: directory, STRATEGY_SWITCH_CONFIG_KV_NAMESPACE_ID: "synthetic-kv-id", CLOUDFLARE_ACCOUNT_ID: "synthetic-account-id" };
for (const key of ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_WRANGLER_CONFIG_TOML", "UX1_RESEARCH_JOB_TOKEN"]) delete env[key];
env.UX1_PREVIEW_MODE = "";
Expand All @@ -118,6 +120,8 @@ test("the deploy renderer pins account-facts and runtime-daily flags and fails c
else env.RUNTIME_DAILY_READ_MODEL_ENABLED = runtimeDailyFlag;
if (executionEvidenceToken === undefined) delete env.EXECUTION_EVIDENCE_SYNC_TOKEN;
else env.EXECUTION_EVIDENCE_SYNC_TOKEN = executionEvidenceToken;
if (externalFlowFlag === undefined) delete env.CONSOLE_EXTERNAL_FLOW_ENABLED;
else env.CONSOLE_EXTERNAL_FLOW_ENABLED = externalFlowFlag;
const result = spawnSync("python3", ["-c", renderer], { encoding: "utf8", env });
const target = join(directory, "wrangler.toml");
const exists = existsSync(target);
Expand Down Expand Up @@ -217,4 +221,37 @@ test("the deploy renderer pins account-facts and runtime-daily flags and fails c
assert.equal(toml, "");
assert.doesNotMatch(result.stdout + result.stderr, /synthetic-token/);
}

// Without a [vars] entry only the pinned release path can set this flag, and the
// next ordinary wrangler deploy erases it. Rendering it from the Environment keeps
// enabling external flows a configuration change instead of a stale-source release.
for (const [externalFlowFlag, expected] of [["true", "true"], ["false", "false"], ["", "false"], [undefined, "false"]]) {
const { result, toml } = render({ externalFlowFlag });
assert.equal(result.status, 0, result.stderr);
assert.equal((toml.match(/^CONSOLE_EXTERNAL_FLOW_ENABLED\s*=/gm) || []).length, 1);
assert.equal(parsedConfig(toml).vars.CONSOLE_EXTERNAL_FLOW_ENABLED, expected);
}

for (const externalFlowFlag of ["TRUE", " true", "true ", "enabled"]) {
const { result, toml, targetExists } = render({ externalFlowFlag });
assert.notEqual(result.status, 0, `accepted invalid external-flow flag ${JSON.stringify(externalFlowFlag)}`);
assert.match(result.stderr, /CONSOLE_EXTERNAL_FLOW_ENABLED/);
assert.equal(targetExists, false, "an invalid external-flow flag must fail before creating a deploy config");
assert.equal(toml, "");
}

// Reviewed pinned sources predate the declaration, so requiring it would stop every
// historical release path from publishing; the value has to be injected instead.
const historicalExample = '[vars]\nACCOUNT_FACTS_READ_MODEL_ENABLED = "false"\nRUNTIME_DAILY_READ_MODEL_ENABLED = "false"\n';
const injected = render({ externalFlowFlag: "true", example: historicalExample });
assert.equal(injected.result.status, 0, injected.result.stderr);
assert.equal((injected.toml.match(/^CONSOLE_EXTERNAL_FLOW_ENABLED\s*=/gm) || []).length, 1);
assert.equal(parsedConfig(injected.toml).vars.CONSOLE_EXTERNAL_FLOW_ENABLED, "true");

// A template that already states the switch must state it closed, or the checked-in
// default stops being the fail-closed value the deploy contract assumes.
const openDefault = render({ externalFlowFlag: "false", example: historicalExample + 'CONSOLE_EXTERNAL_FLOW_ENABLED = "true"\n' });
assert.notEqual(openDefault.result.status, 0, "accepted a template declaring the switch open");
assert.match(openDefault.result.stderr, /CONSOLE_EXTERNAL_FLOW_ENABLED/);
assert.equal(openDefault.targetExists, false);
});
10 changes: 8 additions & 2 deletions tests/console_deploy_safety_validation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -543,11 +543,17 @@ for (const frontendMode of ["false", "true"]) {
const text = readFileSync(join(temp, "wrangler.toml"), "utf8");
const parsed = spawnSync("python3", ["-c", "import json,sys,tomllib;print(json.dumps(tomllib.loads(sys.stdin.read())))"], { input: text, encoding: "utf8" });
assert.equal(parsed.status, 0, parsed.stderr);
renderedConfigs.push(JSON.parse(parsed.stdout));
const rendered = JSON.parse(parsed.stdout);
renderedConfigs.push(rendered);
if (frontendMode === "true") {
assert.match(text, /\[vars\]\n\n\[triggers\]/);
assert.doesNotMatch(text, /ACCOUNT_FACTS_READ_MODEL_ENABLED =/);
} else assert.match(text, /ACCOUNT_FACTS_READ_MODEL_ENABLED = "true"/);
} else {
assert.match(text, /ACCOUNT_FACTS_READ_MODEL_ENABLED = "true"/);
// A normal wrangler deploy uploads only what wrangler.toml declares, so the
// external-flow switch has to be in the rendered config to survive one.
assert.equal(rendered.vars.CONSOLE_EXTERNAL_FLOW_ENABLED, "false", "an ordinary deploy must carry the external-flow switch");
}
assert.ok(!result.stdout.includes("SYNTHETIC_CONFIG_SENTINEL") && !result.stderr.includes("SYNTHETIC_CONFIG_SENTINEL"));
configCases++;
} finally { rmSync(temp, { recursive: true, force: true }); }
Expand Down
3 changes: 3 additions & 0 deletions web/strategy-switch-console/wrangler.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ ACCOUNT_FACTS_READ_MODEL_ENABLED = "false"
# The deploy workflow renders this from the runtime-strategy-switch Environment value.
# Keep daily runtime read access disabled unless explicitly enabled for deployment.
RUNTIME_DAILY_READ_MODEL_ENABLED = "false"
# The deploy workflow renders this from the runtime-strategy-switch Environment value.
# Keep the checked-in default closed; use the Environment value as the deployment source of truth.
CONSOLE_EXTERNAL_FLOW_ENABLED = "false"
# Website visibility only; does not disable trading or remove platform support.
# Comma-separated platform IDs. Set to an empty string to show all platforms.
STRATEGY_SWITCH_HIDDEN_PLATFORMS = "qmt"
Expand Down
Loading