From 75ed6f3a897649eff5a3ba40681571fda061015d Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Sun, 17 May 2026 22:53:53 -0400 Subject: [PATCH] add a fast deploy mode for quicker routine rollouts --- .beads/issues.jsonl | 1 + deployment/docker/README.md | 2 + deployment/native/README.md | 2 + .../2026-05-17-add-fast-deploy-mode.html | 137 ++++++++++++++++++ scripts/deploy.ts | 69 ++++++--- 5 files changed, 190 insertions(+), 21 deletions(-) create mode 100644 docs/turns/2026-05-17-add-fast-deploy-mode.html diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 6a801ba..a7b04c0 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -13,6 +13,7 @@ {"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"islandflow-xod","title":"Add --fast mode to deploy helper","description":"Why: full main deploys rebuild all images and run full verification, which is slow for routine rollouts. What: add a --fast flag to scripts/deploy.ts with explicit behavior that short-circuits slow steps while preserving basic safety checks; update help text/docs for discoverability.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T02:50:47Z","created_by":"dirtydishes","updated_at":"2026-05-18T02:53:41Z","started_at":"2026-05-18T02:50:50Z","closed_at":"2026-05-18T02:53:41Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-cif","title":"hydrate alert evidence context from clickhouse","description":"Implement alert detail hydration from ClickHouse with a new context endpoint and frontend drawer evidence resolution. Includes storage lookup by alert trace_id/evidence refs, unresolved refs diagnostics, API route GET /flow/alerts/:trace_id/context, terminal evidence hydration + loading states/copy updates, and tests across storage/api/web.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T00:15:55Z","created_by":"dirtydishes","updated_at":"2026-05-18T00:17:38Z","started_at":"2026-05-18T00:16:00Z","closed_at":"2026-05-18T00:17:38Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-4e9","title":"Polish terminal view","description":"Improve the Islandflow web terminal view with a focused UI polish pass aligned to the product design system.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T15:18:18Z","created_by":"dirtydishes","updated_at":"2026-05-17T15:25:02Z","started_at":"2026-05-17T15:18:21Z","closed_at":"2026-05-17T15:25:02Z","close_reason":"Polished terminal shell styling, responsive Tape actions, and documented the turn.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-lyt","title":"Summarize 2026-05-16 git activity for standup","description":"Create a grounded standup summary for yesterday's git activity, anchored to commits, changed files, and any linked PR context if present. Produce the required HTML document in docs/general and complete the beads + git handoff workflow.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T14:02:57Z","created_by":"dirtydishes","updated_at":"2026-05-17T14:05:37Z","started_at":"2026-05-17T14:03:09Z","closed_at":"2026-05-17T14:05:37Z","close_reason":"Created docs/general standup summary for 2026-05-16 git activity, grounded to commits and changed files, and prepared the repo handoff workflow.","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/deployment/docker/README.md b/deployment/docker/README.md index 0f5c886..2b167da 100644 --- a/deployment/docker/README.md +++ b/deployment/docker/README.md @@ -271,6 +271,7 @@ Examples: ./deploy main --runtime docker --web-only ./deploy main --runtime docker --api-only ./deploy current-branch --runtime docker --services-only +./deploy main --runtime docker --fast ./deploy main --runtime docker --web-only --no-build ``` @@ -279,6 +280,7 @@ Scoped Docker deploys now build only the selected image set and then restart onl - `--web-only`: `docker compose build web`, then `docker compose up -d web` - `--api-only`: `docker compose build api`, then `docker compose up -d api` - `--services-only`: builds and restarts `api`, `compute`, `candles`, `ingest-options`, and `ingest-equities` +- `--fast`: when no explicit scope flag is given, treats the deploy as `--services-only` and skips the public API route suite for quicker completion. It still runs remote service health checks. Use `--no-build` only when the image is already correct and you need Compose to recreate or restart containers, such as after changing server-side environment values that do not affect a Next.js build-time variable. Do not use `--no-build` for dependency changes, application source changes, or `NEXT_PUBLIC_*` changes. diff --git a/deployment/native/README.md b/deployment/native/README.md index 03c5bf7..a9903cc 100644 --- a/deployment/native/README.md +++ b/deployment/native/README.md @@ -75,6 +75,7 @@ Examples: ./deploy main --runtime native --web-only ./deploy main --runtime native --api-only ./deploy current-branch --runtime native --services-only +./deploy main --runtime native --fast ./deploy main --runtime native --web-only --no-build ``` @@ -84,6 +85,7 @@ Scope behavior: - `--web-only`: rebuild/restart only the web unit - `--api-only`: restart only the API unit - `--services-only`: restart API + backend units without touching the web unit +- `--fast`: when no explicit scope flag is provided, uses the same `--services-only` scope and trims verbose verification output for quicker completion - `--no-build`: skip `bun install --frozen-lockfile` and skip the web build step ## Current status diff --git a/docs/turns/2026-05-17-add-fast-deploy-mode.html b/docs/turns/2026-05-17-add-fast-deploy-mode.html new file mode 100644 index 0000000..94493cd --- /dev/null +++ b/docs/turns/2026-05-17-add-fast-deploy-mode.html @@ -0,0 +1,137 @@ + + + + + + Turn Report: Add --fast Deploy Mode + + + +
+
+

Added --fast mode to deploy helper

+

Date: 2026-05-17 · Repo: islandflow · Task: make ./deploy main faster for routine rollouts

+ +

Summary

+

+ Added a new --fast flag to ./deploy so operators can run a quicker deploy profile without manually combining multiple flags. In fast mode, default full-scope deploys switch to backend-services scope and skip expensive public route-suite checks. +

+ +

Changes Made

+
    +
  • Updated scripts/deploy.ts to parse and advertise --fast.
  • +
  • Added effective-scope logic so --fast + default scope behaves like --services-only.
  • +
  • Adjusted verification behavior in fast mode:
  • +
  • Skipped Docker log tail dump during remote verification.
  • +
  • Skipped verbose native systemctl status / journalctl output.
  • +
  • Skipped public API route suite (scripts/check-public-api-routes.ts) in fast mode.
  • +
  • Documented fast mode in deployment/docker/README.md and deployment/native/README.md.
  • +
+ +

Context

+

+ The default ./deploy main path is intentionally thorough and safe, but it can be slow because it rebuilds multiple service images and runs full verification. Fast mode provides an explicit, opt-in speed profile for routine operations. +

+ +

Important Implementation Details

+

+ Fast mode does not silently alter explicitly requested scopes. It only remaps scope when the caller leaves scope at default full-stack. +

+
function effectiveScope(scope: DeployScope, fast: boolean): DeployScope {
+  if (fast && scope === "full") {
+    return "services";
+  }
+  return scope;
+}
+

+ Public verification now keeps behavior explicit. In fast mode, it logs why API route checks were skipped and points operators to DEPLOY_PUBLIC_API_HEALTH_URL if they want a public API probe. +

+ +

Expected Impact for End-Users

+

+ Internal operators should see noticeably faster deploy completion in common backend-first rollouts. End-user-facing impact is indirect: faster operational iteration and quicker server refreshes when web changes are not required. +

+ +

Validation

+
    +
  • Ran bun run scripts/deploy.ts --help to validate CLI parsing/help output for the new flag.
  • +
  • Ran full test suite with bun test (pass, 232 passing tests).
  • +
+ +

Issues, Limitations, and Mitigations

+
    +
  • --fast intentionally reduces verification depth; it is not equivalent to the full rollout safety envelope.
  • +
  • Fast mode defaults away from web rollout on full scope, so web changes should use explicit web/full scope deploys.
  • +
  • Mitigation: behavior is opt-in, surfaced in help text, and documented in deployment READMEs.
  • +
+ +

Follow-up Work

+
    +
  • No immediate follow-up required for this change.
  • +
  • Optional future work: add an automatic changed-path-to-scope mapper to choose the smallest safe build set without operator guesswork.
  • +
  • Beads issue: islandflow-xod (this task).
  • +
+
+
+ + diff --git a/scripts/deploy.ts b/scripts/deploy.ts index d78db01..70e54e1 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -13,6 +13,7 @@ type DeployOptions = { mode: DeployMode; runtime: DeployRuntime; scope: DeployScope; + fast: boolean; forceRecreate: boolean; noBuild: boolean; }; @@ -69,9 +70,9 @@ const repoRoot = path.resolve(path.dirname(scriptPath), ".."); function usage(exitCode = 1): never { console.error(`Usage: - ./deploy main [--runtime docker|native] [--web-only|--api-only|--services-only] [--no-build] [--force-recreate] - ./deploy current-branch [--runtime docker|native] [--web-only|--api-only|--services-only] [--no-build] [--force-recreate] - ./deploy current branch [--runtime docker|native] [--web-only|--api-only|--services-only] [--no-build] [--force-recreate] + ./deploy main [--runtime docker|native] [--web-only|--api-only|--services-only] [--fast] [--no-build] [--force-recreate] + ./deploy current-branch [--runtime docker|native] [--web-only|--api-only|--services-only] [--fast] [--no-build] [--force-recreate] + ./deploy current branch [--runtime docker|native] [--web-only|--api-only|--services-only] [--fast] [--no-build] [--force-recreate] Modes: main Deploy origin/main to the live server checkout. @@ -89,6 +90,7 @@ Scopes: Options: --runtime Explicit runtime selector (docker or native). + --fast Prefer a quicker rollout profile (defaults full scope to --services-only and skips public API route suite). --no-build Skip docker image builds or native bun install/web build steps. --force-recreate Docker-only escalation path for docker compose when a normal refresh is not enough. --help Show this help text. @@ -218,11 +220,13 @@ function parseArgs(rawArgs: string[]): DeployOptions { const runtime = parseRuntime(rawArgs); const scope = parseScope(rawArgs); + const fast = rawArgs.includes("--fast"); const forceRecreate = rawArgs.includes("--force-recreate"); const noBuild = rawArgs.includes("--no-build"); const positional = rawArgs.filter( (arg, index) => arg !== "--force-recreate" && + arg !== "--fast" && arg !== "--no-build" && arg !== "--web-only" && arg !== "--api-only" && @@ -238,7 +242,7 @@ function parseArgs(rawArgs: string[]): DeployOptions { } if (positional.length === 1 && positional[0] === "main") { - return { mode: "main", runtime, scope, forceRecreate, noBuild }; + return { mode: "main", runtime, scope, fast, forceRecreate, noBuild }; } if ( @@ -249,6 +253,7 @@ function parseArgs(rawArgs: string[]): DeployOptions { mode: "current-branch", runtime, scope, + fast, forceRecreate, noBuild }; @@ -302,6 +307,13 @@ function describeScope(scope: DeployScope): string { } } +function effectiveScope(scope: DeployScope, fast: boolean): DeployScope { + if (fast && scope === "full") { + return "services"; + } + return scope; +} + function scopeIncludesWeb(scope: DeployScope): boolean { return scope === "full" || scope === "web"; } @@ -649,14 +661,16 @@ function remoteRollout( remoteNativeRollout(mode, branch, scope, noBuild); } -function remoteDockerVerification(scope: DeployScope): void { +function remoteDockerVerification(scope: DeployScope, fast: boolean): void { const psServices = dockerServicesForScope(scope); const logServices = dockerLogServicesForScope(scope); const psCommand = psServices.length > 0 ? `docker compose ps ${psServices.join(" ")}` : "docker compose ps"; - const logCommand = `docker compose logs --tail=100 ${logServices.join(" ")}`; + const logCommand = fast + ? `echo '[deploy] Fast mode: skipping docker compose logs tail for quicker feedback.'` + : `docker compose logs --tail=100 ${logServices.join(" ")}`; const checks: string[] = []; if (scopeIncludesApi(scope)) { @@ -684,7 +698,7 @@ ${checks.join("\n")} ); } -function remoteNativeVerification(scope: DeployScope): void { +function remoteNativeVerification(scope: DeployScope, fast: boolean): void { const units = nativeUnitsForScope(scope).map((value) => shellEscape(value)).join(" "); const checks: string[] = []; @@ -704,26 +718,29 @@ set -euo pipefail declare -a units=(${units}) for unit in "\${units[@]}"; do ${NATIVE_SYSTEMCTL_PREFIX} is-active --quiet "$unit" - ${NATIVE_SYSTEMCTL_PREFIX} status --no-pager "$unit" || true - journalctl -u "$unit" -n 50 --no-pager || true + ${fast ? "echo \"[deploy] Fast mode: skipping unit status and recent journal dump for $unit.\"": `${NATIVE_SYSTEMCTL_PREFIX} status --no-pager "$unit" || true\n journalctl -u "$unit" -n 50 --no-pager || true`} done ${checks.join("\n")} ` ); } -function remoteVerification(runtime: DeployRuntime, scope: DeployScope): void { +function remoteVerification(runtime: DeployRuntime, scope: DeployScope, fast: boolean): void { if (runtime === "docker") { - remoteDockerVerification(scope); + remoteDockerVerification(scope, fast); return; } - remoteNativeVerification(scope); + remoteNativeVerification(scope, fast); } -function publicVerification(scope: DeployScope): void { +function publicVerification(scope: DeployScope, fast: boolean): void { section("Public Verification"); - runChecked("curl", ["-I", "-fksS", PUBLIC_APP_URL]); + if (!fast || scopeIncludesWeb(scope)) { + runChecked("curl", ["-I", "-fksS", PUBLIC_APP_URL]); + } else { + console.log("[deploy] Fast mode: skipping public app HEAD check because web scope is not included."); + } if (scopeIncludesApi(scope) && PUBLIC_API_HEALTH_URL) { runChecked("curl", ["-fksS", PUBLIC_API_HEALTH_URL]); @@ -731,29 +748,39 @@ function publicVerification(scope: DeployScope): void { } if (scopeIncludesApi(scope)) { + if (fast) { + console.log( + "[deploy] Fast mode: skipping scripts/check-public-api-routes.ts route suite. Set DEPLOY_PUBLIC_API_HEALTH_URL to keep a public API health probe in fast mode." + ); + return; + } runChecked("bun", ["run", "scripts/check-public-api-routes.ts", PUBLIC_APP_URL]); } } function main(): void { const options = parseArgs(process.argv.slice(2)); + const scope = effectiveScope(options.scope, options.fast); assertSshKeyExists(); printRuntimeAdvisory(options.runtime); console.log( `Deploying ${options.mode === "main" ? "origin/main" : "the current local branch"} ` + - `via ${describeRuntime(options.runtime)} (${describeScope(options.scope)}).` + `via ${describeRuntime(options.runtime)} (${describeScope(scope)}${options.fast ? ", fast mode" : ""}).` ); + if (options.fast && options.scope === "full") { + console.log("[deploy] Fast mode changed default full scope to --services-only."); + } if (options.mode === "main") { localMainPrecheck(options.runtime, options.noBuild); remoteGitPrecheck(); - remoteRuntimePrecheck(options.runtime, options.scope); + remoteRuntimePrecheck(options.runtime, scope); remoteRollout( options.mode, options.runtime, null, - options.scope, + scope, options.forceRecreate, options.noBuild ); @@ -762,19 +789,19 @@ function main(): void { localBranchPrecheck(branch, options.runtime, options.noBuild); publishCurrentBranch(branch); remoteGitPrecheck(); - remoteRuntimePrecheck(options.runtime, options.scope); + remoteRuntimePrecheck(options.runtime, scope); remoteRollout( options.mode, options.runtime, branch, - options.scope, + scope, options.forceRecreate, options.noBuild ); } - remoteVerification(options.runtime, options.scope); - publicVerification(options.scope); + remoteVerification(options.runtime, scope, options.fast); + publicVerification(scope, options.fast); } main();