Compare commits
19 commits
88b2c33ab3
...
6e6788bea4
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e6788bea4 | |||
| 073c1dee9d | |||
| 75ed6f3a89 | |||
|
|
49efc24a53 | ||
| 7d818cfa6a | |||
|
|
a27d499140 | ||
| dc932cf18e | |||
|
|
8b166a5916 | ||
| 58e57fad6e | |||
| 219d3fd4be | |||
| 8631a5342b | |||
| 5ddfbfa4e7 | |||
|
|
3e089554f8 | ||
| 2f218ec43f | |||
| c0b5b6dbeb | |||
| cd0a1dd9e5 | |||
| d0d8bd40b9 | |||
| 0416194df5 | |||
| 37bd393f5c |
21 changed files with 2395 additions and 113 deletions
|
|
@ -52,3 +52,8 @@
|
||||||
# - linear.api-key
|
# - linear.api-key
|
||||||
# - github.org
|
# - github.org
|
||||||
# - github.repo
|
# - github.repo
|
||||||
|
|
||||||
|
sync:
|
||||||
|
remote: git+https://git.deltaisland.io/dirtydishes/islandflow.git
|
||||||
|
|
||||||
|
sync.remote: "git+https://github.com/dirtydishes/islandflow.git"
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{"_type":"issue","id":"islandflow-9dg","title":"Fix live tape scroll stability","description":"Live tape rows can shift while a user is scrolled away from the hot head because newer live prints and ClickHouse history are merged into the displayed segment. Implement held-history freezing so only truly older rows append below the current tail, resync on jump-to-top, and tune virtualization/background rendering to reduce fast-scroll blank gaps.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T07:28:52Z","created_by":"dirtydishes","updated_at":"2026-05-17T07:32:53Z","started_at":"2026-05-17T07:29:00Z","closed_at":"2026-05-17T07:32:53Z","close_reason":"Implemented held live tape history freezing, older-only held history append, jump-to-top resync behavior, virtualizer overscan tuning, and stable row-lane table background. Validated with scoped Bun tests, web production build, and local /tape HTTP smoke check.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-jbi","title":"Hydrate alert evidence details from ClickHouse","description":"Alert detail drawers need to fetch persisted alert context from ClickHouse by trace id, including linked flow packets, option prints, preserved execution context, and explicit missing refs for UI diagnostics.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T14:55:43Z","created_by":"dirtydishes","updated_at":"2026-05-17T15:01:58Z","started_at":"2026-05-17T14:55:53Z","closed_at":"2026-05-17T15:01:58Z","close_reason":"Implemented ClickHouse-backed alert context hydration across storage, API, terminal drawer, tests, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-qso","title":"Fix durable options tape history routing","description":"Implement the fix-tape plan: make same-origin history routing durable, add deployment/public smoke checks for required API routes, expose tape history loading failures in the UI, document the work, and track api.flow.deltaisland.io migration separately.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T01:53:22Z","created_by":"dirtydishes","updated_at":"2026-05-17T02:00:04Z","started_at":"2026-05-17T01:53:25Z","closed_at":"2026-05-17T02:00:04Z","close_reason":"Implemented durable same-origin history routing, public route smoke checks, tape history diagnostics, docs, validation, and follow-up tracking for api.flow.deltaisland.io.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-8kj","title":"Configure persistent beads Dolt remote on deltaisland server","description":"Install the beads and Dolt CLIs on the server, configure a persistent Dolt sync remote backed by the server-hosted Forgejo repository, verify refs/dolt/data publication, and document Nginx Proxy Manager / firewall considerations.","status":"closed","priority":1,"issue_type":"task","assignee":"delta","created_at":"2026-05-17T10:31:31Z","created_by":"delta","updated_at":"2026-05-17T10:37:47Z","started_at":"2026-05-17T10:32:16Z","closed_at":"2026-05-17T10:37:47Z","close_reason":"Installed bd and dolt on the server, configured the Forgejo-backed Dolt remote, published refs/dolt/data, and documented the setup.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-200","title":"Implement durable options tape history","description":"Implement the plan from docs/plans/2026-05-16-1711-durable-options-tape-history.html: durable ClickHouse-backed options history, signal/all prints view selection, preserved execution context, stale semantics limited to live health, reset runbook, tests, and turn documentation.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:21:30Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:26:51Z","started_at":"2026-05-16T21:21:33Z","closed_at":"2026-05-16T21:26:51Z","close_reason":"Implemented durable options tape history, signal/raw view selection, reset runbook, tests, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-200","title":"Implement durable options tape history","description":"Implement the plan from docs/plans/2026-05-16-1711-durable-options-tape-history.html: durable ClickHouse-backed options history, signal/all prints view selection, preserved execution context, stale semantics limited to live health, reset runbook, tests, and turn documentation.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:21:30Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:26:51Z","started_at":"2026-05-16T21:21:33Z","closed_at":"2026-05-16T21:26:51Z","close_reason":"Implemented durable options tape history, signal/raw view selection, reset runbook, tests, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-k4f","title":"Gate deploy script on docker workspace snapshot sync","description":"Prevent frozen-lockfile build failures during deploy by adding a local preflight in scripts/deploy.ts that runs bun run check:docker-workspace and aborts with a clear sync+commit remediation message when stale.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:01:44Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:04:11Z","started_at":"2026-05-15T23:01:48Z","closed_at":"2026-05-15T23:04:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-k4f","title":"Gate deploy script on docker workspace snapshot sync","description":"Prevent frozen-lockfile build failures during deploy by adding a local preflight in scripts/deploy.ts that runs bun run check:docker-workspace and aborts with a clear sync+commit remediation message when stale.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:01:44Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:04:11Z","started_at":"2026-05-15T23:01:48Z","closed_at":"2026-05-15T23:04:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-xll","title":"Fix bun.lock drift causing frozen-lockfile Docker build failures","description":"Docker image builds fail in multiple targets (candles, web, ingest services) because bun install --frozen-lockfile detects lockfile changes. Update workspace lockfile to match manifests and verify frozen install succeeds.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T22:52:38Z","created_by":"dirtydishes","updated_at":"2026-05-15T22:55:23Z","started_at":"2026-05-15T22:52:40Z","closed_at":"2026-05-15T22:55:23Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-xll","title":"Fix bun.lock drift causing frozen-lockfile Docker build failures","description":"Docker image builds fail in multiple targets (candles, web, ingest services) because bun install --frozen-lockfile detects lockfile changes. Update workspace lockfile to match manifests and verify frozen install succeeds.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T22:52:38Z","created_by":"dirtydishes","updated_at":"2026-05-15T22:55:23Z","started_at":"2026-05-15T22:52:40Z","closed_at":"2026-05-15T22:55:23Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
|
@ -13,8 +13,12 @@
|
||||||
{"_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-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-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-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-qd7","title":"Migrate production web to api.flow.deltaisland.io","description":"Follow-up from the durable options tape history fix. Plan and migrate production from same-origin API path proxying on flow.deltaisland.io to a dedicated api.flow.deltaisland.io origin, including DNS, proxy config, CORS/websocket behavior, deployment docs, and public smoke checks.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-17T01:55:02Z","created_by":"dirtydishes","updated_at":"2026-05-17T01:55:02Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-1ei","title":"Make deploy helper remote-aware for Forgejo","description":"Why: scripts/deploy.ts hardcodes git remote name origin for fetch/pull/push and branch verification, but this repository now uses forgejo/github remotes and may not have an origin remote. What: update deploy.ts to resolve the deploy git remote robustly (Forgejo-aware), use it across local prechecks, branch publish, and remote rollout git operations, and keep behavior explicit in output.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T03:20:12Z","created_by":"dirtydishes","updated_at":"2026-05-18T03:22:39Z","started_at":"2026-05-18T03:20:16Z","closed_at":"2026-05-18T03:22:39Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-09a","title":"Speed up Docker deployment builds","description":"Implement the Docker deployment optimization plan from /Users/kell/Desktop/speed-up-docker.md: split dependency installation from source copy, add BuildKit caches, make scoped deploys build only their target services, update Docker deployment docs, validate, document the turn, commit, and push.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:50:24Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:53:48Z","started_at":"2026-05-16T21:50:37Z","closed_at":"2026-05-16T21:53:48Z","close_reason":"Implemented Docker dependency-layer caching, scoped deploy build/up flow, Docker docs updates, validation, and turn documentation. Follow-up islandflow-cnk tracks daemon-backed image build verification.","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}
|
||||||
|
{"_type":"issue","id":"islandflow-sz8","title":"Fix public /replay/options proxy regression","description":"## Summary\nThe new deploy-time public route checker added in commit 1424a27 (\"fix durable options history routing\") currently fails against https://flow.deltaisland.io because GET /replay/options returns HTML instead of JSON.\n\n## Evidence\n- `bun run scripts/check-public-api-routes.ts https://flow.deltaisland.io` fails on `/replay/options?view=signal\u0026after_ts=0\u0026after_seq=0\u0026limit=1` with `returned non-JSON content (text/html; charset=UTF-8)`\n- `services/api/src/index.ts` implements `GET /replay/options`, so the HTML response indicates the request is landing on the web app instead of the API service\n- `deployment/docker/README.md` documents that same-origin proxy mode must include `/replay/*` in the API route matcher\n\n## Minimal Fix\nUpdate the live reverse proxy / edge route matcher for flow.deltaisland.io so `/replay/*` is forwarded to the API host, then rerun `bun run check:public-api-routes`.\n\n## Notes\nThis looks like a production proxy configuration regression rather than an in-repo application bug.","status":"open","priority":2,"issue_type":"bug","owner":"dishes@dpdrm.com","created_at":"2026-05-17T13:06:11Z","created_by":"dirtydishes","updated_at":"2026-05-17T13:06:11Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-0sa","title":"Fix live tape auto-hold, history seam, and remove manual pause control","description":"The live tape should automatically hold when the user scrolls away from the top, resume when they return to the top or use Jump to top, and keep older prints available seamlessly beyond the hot window. Manual Pause/Resume control is now redundant and should be removed from live tape panes. This work should also fix the current regression where paused/held tapes still mutate, and align the options tape with a strict 100-row hot head backed by ClickHouse history.","notes":"Implemented live scroll-hold with no live pause button, demand-loaded ClickHouse history, a 100-row options hot head, and cache-first scoped snapshots. Validated with bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts and bun --cwd=apps/web run build.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T18:12:51Z","created_by":"dirtydishes","updated_at":"2026-05-16T18:23:43Z","started_at":"2026-05-16T18:12:54Z","closed_at":"2026-05-16T18:23:43Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-0sa","title":"Fix live tape auto-hold, history seam, and remove manual pause control","description":"The live tape should automatically hold when the user scrolls away from the top, resume when they return to the top or use Jump to top, and keep older prints available seamlessly beyond the hot window. Manual Pause/Resume control is now redundant and should be removed from live tape panes. This work should also fix the current regression where paused/held tapes still mutate, and align the options tape with a strict 100-row hot head backed by ClickHouse history.","notes":"Implemented live scroll-hold with no live pause button, demand-loaded ClickHouse history, a 100-row options hot head, and cache-first scoped snapshots. Validated with bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts and bun --cwd=apps/web run build.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T18:12:51Z","created_by":"dirtydishes","updated_at":"2026-05-16T18:23:43Z","started_at":"2026-05-16T18:12:54Z","closed_at":"2026-05-16T18:23:43Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-2db","title":"Manually remove stale islandflow local-infra containers from VPS","description":"The live VPS still has an older compose project named islandflow created from the repo-root docker-compose.yml. Inspection shows it is separate from the supported islandflow-vps deployment stack and exposes NATS, ClickHouse, and Redis on host ports. Container removal commands currently hang when run as the delta user through Docker, so cleanup likely needs a focused maintenance window and possibly host-level intervention or a Docker daemon restart.","notes":"The duplicate islandflow compose project on the VPS was confirmed live during inspection. Nginx Proxy Manager routes public traffic only to islandflow-vps web/api by Docker name, so the stale islandflow project appears to be stray local-infra state rather than part of the supported production path. Attempts to remove the stale containers with docker compose down and docker rm -f as the delta user hung and timed out, so manual cleanup likely needs a maintenance window and possibly Docker daemon intervention.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:27:27Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:59Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-2db","title":"Manually remove stale islandflow local-infra containers from VPS","description":"The live VPS still has an older compose project named islandflow created from the repo-root docker-compose.yml. Inspection shows it is separate from the supported islandflow-vps deployment stack and exposes NATS, ClickHouse, and Redis on host ports. Container removal commands currently hang when run as the delta user through Docker, so cleanup likely needs a focused maintenance window and possibly host-level intervention or a Docker daemon restart.","notes":"The duplicate islandflow compose project on the VPS was confirmed live during inspection. Nginx Proxy Manager routes public traffic only to islandflow-vps web/api by Docker name, so the stale islandflow project appears to be stray local-infra state rather than part of the supported production path. Attempts to remove the stale containers with docker compose down and docker rm -f as the delta user hung and timed out, so manual cleanup likely needs a maintenance window and possibly Docker daemon intervention.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:27:27Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:59Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-c87","title":"Clean up duplicate Islandflow Docker infra on VPS","description":"The live VPS is currently running both the production-style islandflow-vps Docker stack and an older root-level islandflow infra stack that publishes NATS, ClickHouse, and Redis on host ports. Investigate whether the older stack is unused, remove it safely if so, and update docs/deploy guidance so the server topology is clearer.","notes":"Inspected the live VPS and confirmed the duplicate compose project: islandflow-vps is the supported deployment stack, while a separate islandflow project from the repo-root docker-compose.yml still runs exposed NATS/ClickHouse/Redis containers. Verified Nginx Proxy Manager routes only to islandflow-vps web/api by Docker name. Attempted cleanup via docker compose down and docker rm -f on the stale islandflow containers, but those commands hung for the delta user and timed out. Added repo guardrails and docs so deploy warns when the duplicate project exists, and opened islandflow-2db for manual host-level cleanup during a maintenance window.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:16:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:07Z","started_at":"2026-05-16T01:16:09Z","closed_at":"2026-05-16T01:28:07Z","close_reason":"Completed the repo-side investigation and guardrails. Actual server-side container removal is blocked by hanging Docker operations and is tracked separately in islandflow-2db for a maintenance window.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-c87","title":"Clean up duplicate Islandflow Docker infra on VPS","description":"The live VPS is currently running both the production-style islandflow-vps Docker stack and an older root-level islandflow infra stack that publishes NATS, ClickHouse, and Redis on host ports. Investigate whether the older stack is unused, remove it safely if so, and update docs/deploy guidance so the server topology is clearer.","notes":"Inspected the live VPS and confirmed the duplicate compose project: islandflow-vps is the supported deployment stack, while a separate islandflow project from the repo-root docker-compose.yml still runs exposed NATS/ClickHouse/Redis containers. Verified Nginx Proxy Manager routes only to islandflow-vps web/api by Docker name. Attempted cleanup via docker compose down and docker rm -f on the stale islandflow containers, but those commands hung for the delta user and timed out. Added repo guardrails and docs so deploy warns when the duplicate project exists, and opened islandflow-2db for manual host-level cleanup during a maintenance window.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:16:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:07Z","started_at":"2026-05-16T01:16:09Z","closed_at":"2026-05-16T01:28:07Z","close_reason":"Completed the repo-side investigation and guardrails. Actual server-side container removal is blocked by hanging Docker operations and is tracked separately in islandflow-2db for a maintenance window.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
|
@ -32,7 +36,7 @@
|
||||||
{"_type":"issue","id":"islandflow-dod","title":"Publish terminal audit to GitHub Pages","description":"Why this issue exists and what needs to be done: publish the generated terminal audit HTML to dirtydishes.github.io at /terminal-audit.html so it can be shared publicly.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T08:39:45Z","created_by":"dirtydishes","updated_at":"2026-05-14T08:42:59Z","started_at":"2026-05-14T08:40:02Z","closed_at":"2026-05-14T08:42:59Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-dod","title":"Publish terminal audit to GitHub Pages","description":"Why this issue exists and what needs to be done: publish the generated terminal audit HTML to dirtydishes.github.io at /terminal-audit.html so it can be shared publicly.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T08:39:45Z","created_by":"dirtydishes","updated_at":"2026-05-14T08:42:59Z","started_at":"2026-05-14T08:40:02Z","closed_at":"2026-05-14T08:42:59Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-dxu","title":"Document terminal audit findings as HTML","description":"Why this issue exists and what needs to be done: capture the completed terminal view audit findings in a user-readable HTML document under docs/ with the full score summary and all detailed findings preserved.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T08:32:22Z","created_by":"dirtydishes","updated_at":"2026-05-14T08:34:57Z","started_at":"2026-05-14T08:32:30Z","closed_at":"2026-05-14T08:34:57Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-dxu","title":"Document terminal audit findings as HTML","description":"Why this issue exists and what needs to be done: capture the completed terminal view audit findings in a user-readable HTML document under docs/ with the full score summary and all detailed findings preserved.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T08:32:22Z","created_by":"dirtydishes","updated_at":"2026-05-14T08:34:57Z","started_at":"2026-05-14T08:32:30Z","closed_at":"2026-05-14T08:34:57Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-a50","title":"Add HTML plan docs for synthetic tape redesign","description":"Create two HTML planning docs under plans/: one straightforward end-user readable version and one more polished impeccable-style version, both covering the hosted synthetic tape redesign with summary, scope, affected services, UI notes, rollout, tests, and the full detailed implementation plan.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T02:47:44Z","created_by":"dirtydishes","updated_at":"2026-05-14T02:53:11Z","started_at":"2026-05-14T02:47:48Z","closed_at":"2026-05-14T02:53:11Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-a50","title":"Add HTML plan docs for synthetic tape redesign","description":"Create two HTML planning docs under plans/: one straightforward end-user readable version and one more polished impeccable-style version, both covering the hosted synthetic tape redesign with summary, scope, affected services, UI notes, rollout, tests, and the full detailed implementation plan.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T02:47:44Z","created_by":"dirtydishes","updated_at":"2026-05-14T02:53:11Z","started_at":"2026-05-14T02:47:48Z","closed_at":"2026-05-14T02:53:11Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-932","title":"Desktop follow-up native features","description":"Track deferred native desktop features after the thin hosted-wrapper v1 lands: notifications, keyboard shortcuts, local preferences storage, remembered window state, signed/notarized macOS distribution, auto-update evaluation, and optional local frontend bundling.\n","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-13T13:20:12Z","created_by":"dirtydishes","updated_at":"2026-05-13T13:20:12Z","dependencies":[{"issue_id":"islandflow-932","depends_on_id":"islandflow-9ug","type":"discovered-from","created_at":"2026-05-13T09:20:12Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-932","title":"Desktop follow-up native features","description":"Track deferred native desktop features after the thin hosted-wrapper v1 lands: notifications, keyboard shortcuts, local preferences storage, remembered window state, signed/notarized macOS distribution, auto-update evaluation, and optional local frontend bundling.\n","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-13T13:20:12Z","created_by":"dirtydishes","updated_at":"2026-05-13T13:20:12Z","dependencies":[{"issue_id":"islandflow-932","depends_on_id":"islandflow-9ug","type":"discovered-from","created_at":"2026-05-13T09:20:12Z","created_by":"auto-import","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-vbk","title":"Remove deprecated Alpaca key-pair auth","description":"Remove legacy Alpaca key-pair authentication support and keep ALPACA_API_KEY as the only supported auth method across options/equities ingest and docs.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T07:19:51Z","created_by":"dirtydishes","updated_at":"2026-05-05T07:21:10Z","started_at":"2026-05-05T07:19:54Z","closed_at":"2026-05-05T07:21:10Z","close_reason":"Removed key-pair auth and kept ALPACA_API_KEY only","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-vbk","title":"Remove deprecated Alpaca key-pair auth","description":"Remove legacy Alpaca key-pair authentication support and keep ALPACA_API_KEY as the only supported auth method across options/equities ingest and docs.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T07:19:51Z","created_by":"dirtydishes","updated_at":"2026-05-05T07:21:10Z","started_at":"2026-05-05T07:19:54Z","closed_at":"2026-05-05T07:21:10Z","close_reason":"Removed key-pair auth and kept ALPACA_API_KEY only","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-h47","title":"Support single-token Alpaca auth","description":"Support single-token Alpaca authentication across ingest adapters using ALPACA_API_KEY with fallback to ALPACA_KEY_ID/ALPACA_SECRET_KEY, and document env usage.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T07:12:22Z","created_by":"dirtydishes","updated_at":"2026-05-05T07:13:54Z","started_at":"2026-05-05T07:12:25Z","closed_at":"2026-05-05T07:13:54Z","close_reason":"Added ALPACA_API_KEY support with key-pair fallback","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-h47","title":"Support single-token Alpaca auth","description":"Support single-token Alpaca authentication across ingest adapters using ALPACA_API_KEY with fallback to ALPACA_KEY_ID/ALPACA_SECRET_KEY, and document env usage.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T07:12:22Z","created_by":"dirtydishes","updated_at":"2026-05-05T07:13:54Z","started_at":"2026-05-05T07:12:25Z","closed_at":"2026-05-05T07:13:54Z","close_reason":"Added ALPACA_API_KEY support with key-pair fallback","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-neu","title":"Add Alpha Vantage event calendar provider","description":"Add an Alpha Vantage earnings-calendar provider to services/refdata that fetches CSV, normalizes entries, writes the JSON cache consumed by compute, and documents the required env variables.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T07:00:31Z","created_by":"dirtydishes","updated_at":"2026-05-05T07:02:30Z","started_at":"2026-05-05T07:00:37Z","closed_at":"2026-05-05T07:02:30Z","close_reason":"Added Alpha Vantage event-calendar provider","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-neu","title":"Add Alpha Vantage event calendar provider","description":"Add an Alpha Vantage earnings-calendar provider to services/refdata that fetches CSV, normalizes entries, writes the JSON cache consumed by compute, and documents the required env variables.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T07:00:31Z","created_by":"dirtydishes","updated_at":"2026-05-05T07:02:30Z","started_at":"2026-05-05T07:00:37Z","closed_at":"2026-05-05T07:02:30Z","close_reason":"Added Alpha Vantage event-calendar provider","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
|
@ -41,7 +45,7 @@
|
||||||
{"_type":"issue","id":"islandflow-020","title":"Rebuild synthetic smart-money scenarios","description":"Rework services/ingest-options synthetic generation around labeled parent-event templates for the six core smart-money profiles plus neutral background noise, with deterministic test/demo modes and hidden labels for tests.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:24Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:29:27Z","started_at":"2026-05-05T05:25:39Z","closed_at":"2026-05-05T05:29:27Z","close_reason":"Completed Phase 5 synthetic smart-money scenario rebuild","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-020","title":"Rebuild synthetic smart-money scenarios","description":"Rework services/ingest-options synthetic generation around labeled parent-event templates for the six core smart-money profiles plus neutral background noise, with deterministic test/demo modes and hidden labels for tests.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:24Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:29:27Z","started_at":"2026-05-05T05:25:39Z","closed_at":"2026-05-05T05:29:27Z","close_reason":"Completed Phase 5 synthetic smart-money scenario rebuild","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-zs0","title":"Migrate terminal UI to smart-money profiles","description":"Migrate apps/web terminal rendering to consume SmartMoneyEvent directly: primary profile, probability ladder, reason codes, and suppression/abstention state, while preserving legacy alert/classifier displays during the bridge.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:23Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:39:58Z","closed_at":"2026-05-05T05:39:58Z","close_reason":"Completed terminal smart-money profile migration","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-zs0","title":"Migrate terminal UI to smart-money profiles","description":"Migrate apps/web terminal rendering to consume SmartMoneyEvent directly: primary profile, probability ladder, reason codes, and suppression/abstention state, while preserving legacy alert/classifier displays during the bridge.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:23Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:39:58Z","closed_at":"2026-05-05T05:39:58Z","close_reason":"Completed terminal smart-money profile migration","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-igk","title":"Add plan mode","description":"Implement a user-facing plan mode in the application so users can switch into planning before taking action. Scope to be clarified from existing app patterns.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-04T04:22:37Z","created_by":"dirtydishes","updated_at":"2026-05-04T04:26:18Z","started_at":"2026-05-04T04:22:40Z","closed_at":"2026-05-04T04:26:18Z","close_reason":"Implemented as a global pi extension toggled with Shift+P","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-igk","title":"Add plan mode","description":"Implement a user-facing plan mode in the application so users can switch into planning before taking action. Scope to be clarified from existing app patterns.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-04T04:22:37Z","created_by":"dirtydishes","updated_at":"2026-05-04T04:26:18Z","started_at":"2026-05-04T04:22:40Z","closed_at":"2026-05-04T04:26:18Z","close_reason":"Implemented as a global pi extension toggled with Shift+P","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"auto-import","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-cnk","title":"Run Docker image build verification with active Docker daemon","description":"Targeted image builds could not run in the implementation session because the local Docker daemon was unavailable at unix:///Users/kell/.orbstack/run/docker.sock. When Docker or OrbStack is running, validate the refactored deployment Dockerfiles with: docker compose -f deployment/docker/docker-compose.yml build api; docker compose -f deployment/docker/docker-compose.yml build web; docker compose -f deployment/docker/docker-compose.yml build ingest-options.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:53:41Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:53:41Z","dependencies":[{"issue_id":"islandflow-cnk","depends_on_id":"islandflow-09a","type":"discovered-from","created_at":"2026-05-16T17:53:40Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-zsy","title":"Expose Forgejo SSH on a direct DNS hostname","description":"git.deltaisland.io currently resolves through Cloudflare's proxy, so SSH on port 2222 does not complete even though the Forgejo container is listening on the host. If SSH-based git/beads workflows are desired, add a DNS-only hostname (or adjust the existing record) that points directly at the server for Forgejo SSH.","status":"open","priority":3,"issue_type":"task","created_at":"2026-05-17T10:34:06Z","created_by":"delta","updated_at":"2026-05-17T10:34:06Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-38p","title":"Add native deployment unit templates and rollback helpers","description":"The deploy helper now supports --runtime native, but the repo still relies on operator-managed systemd units and manual rollback. Add checked-in native deployment templates or provisioning guidance for the expected units, and consider lightweight rollback/smoke-test helpers once the host-native path is exercised on the real VPS.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:46:42Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:46:42Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-38p","title":"Add native deployment unit templates and rollback helpers","description":"The deploy helper now supports --runtime native, but the repo still relies on operator-managed systemd units and manual rollback. Add checked-in native deployment templates or provisioning guidance for the expected units, and consider lightweight rollback/smoke-test helpers once the host-native path is exercised on the real VPS.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:46:42Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:46:42Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-575","title":"Document smart-money event calendar env","description":"Document smart-money event-calendar environment configuration in env examples and README.\n","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T06:57:14Z","created_by":"dirtydishes","updated_at":"2026-05-05T06:57:57Z","started_at":"2026-05-05T06:57:17Z","closed_at":"2026-05-05T06:57:57Z","close_reason":"Documented event-calendar env variables","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-575","title":"Document smart-money event calendar env","description":"Document smart-money event-calendar environment configuration in env examples and README.\n","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T06:57:14Z","created_by":"dirtydishes","updated_at":"2026-05-05T06:57:57Z","started_at":"2026-05-05T06:57:17Z","closed_at":"2026-05-05T06:57:57Z","close_reason":"Documented event-calendar env variables","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
|
|
||||||
|
|
@ -1818,6 +1818,28 @@ h3 {
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drawer-context-loading {
|
||||||
|
padding: 12px 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-skeleton {
|
||||||
|
width: 64%;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(90deg, var(--bg-soft), rgba(245, 166, 35, 0.14), var(--bg-soft));
|
||||||
|
background-size: 180% 100%;
|
||||||
|
animation: drawer-skeleton 1.2s ease-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-skeleton-wide {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-evidence-context {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: var(--text-faint);
|
||||||
|
}
|
||||||
|
|
||||||
.drawer-row {
|
.drawer-row {
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|
@ -1825,6 +1847,15 @@ h3 {
|
||||||
background: var(--bg-soft);
|
background: var(--bg-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes drawer-skeleton {
|
||||||
|
0% {
|
||||||
|
background-position: 100% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -100% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0% {
|
0% {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ import { getSubscriptionKey as getLiveSubscriptionKey } from "@islandflow/types"
|
||||||
import {
|
import {
|
||||||
NAV_ITEMS,
|
NAV_ITEMS,
|
||||||
appendHistoryTail,
|
appendHistoryTail,
|
||||||
|
buildAlertContextPath,
|
||||||
buildDefaultFlowFilters,
|
buildDefaultFlowFilters,
|
||||||
buildOptionTapeQueryParams,
|
buildOptionTapeQueryParams,
|
||||||
classifierToneForFamily,
|
classifierToneForFamily,
|
||||||
|
collectAlertContextEvidence,
|
||||||
composeTapeItems,
|
composeTapeItems,
|
||||||
deriveAlertDirection,
|
deriveAlertDirection,
|
||||||
countActiveFlowFilterGroups,
|
countActiveFlowFilterGroups,
|
||||||
|
|
@ -95,6 +97,44 @@ describe("pinned evidence pruning", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("alert context hydration helpers", () => {
|
||||||
|
it("builds the persisted ClickHouse context endpoint path", () => {
|
||||||
|
expect(buildAlertContextPath("alert:large_call/one")).toBe(
|
||||||
|
"/flow/alerts/alert%3Alarge_call%2Fone/context"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("merges hydrated packets and prints into pinned evidence maps", () => {
|
||||||
|
const packet = {
|
||||||
|
trace_id: "flowpacket:1",
|
||||||
|
id: "flowpacket:1",
|
||||||
|
members: ["print:1"],
|
||||||
|
source_ts: 1,
|
||||||
|
ingest_ts: 2,
|
||||||
|
seq: 1,
|
||||||
|
features: {},
|
||||||
|
join_quality: {}
|
||||||
|
} as any;
|
||||||
|
const print = makeOptionPrint({
|
||||||
|
trace_id: "print:1",
|
||||||
|
execution_nbbo_bid: 1.2,
|
||||||
|
execution_nbbo_ask: 1.3,
|
||||||
|
execution_underlying_spot: 450.05
|
||||||
|
});
|
||||||
|
|
||||||
|
const evidence = collectAlertContextEvidence({
|
||||||
|
alert: makeAlert({ evidence_refs: ["flowpacket:1", "print:1"] }),
|
||||||
|
flow_packets: [packet],
|
||||||
|
option_prints: [print],
|
||||||
|
missing_refs: []
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(evidence.packets.get("flowpacket:1")).toBe(packet);
|
||||||
|
expect(evidence.prints.get("print:1")?.execution_nbbo_bid).toBe(1.2);
|
||||||
|
expect(evidence.prints.get("print:1")?.execution_underlying_spot).toBe(450.05);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("live manifest", () => {
|
describe("live manifest", () => {
|
||||||
it("includes only tape channels on /tape", () => {
|
it("includes only tape channels on /tape", () => {
|
||||||
const filters = buildDefaultFlowFilters();
|
const filters = buildDefaultFlowFilters();
|
||||||
|
|
|
||||||
|
|
@ -4604,6 +4604,49 @@ type EvidenceItem =
|
||||||
| { kind: "print"; id: string; print: OptionPrint }
|
| { kind: "print"; id: string; print: OptionPrint }
|
||||||
| { kind: "unknown"; id: string };
|
| { kind: "unknown"; id: string };
|
||||||
|
|
||||||
|
type AlertContextBundle = {
|
||||||
|
alert: AlertEvent | null;
|
||||||
|
flow_packets: FlowPacket[];
|
||||||
|
option_prints: OptionPrint[];
|
||||||
|
missing_refs: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type AlertContextStatus = {
|
||||||
|
traceId: string | null;
|
||||||
|
loading: boolean;
|
||||||
|
missingRefs: string[];
|
||||||
|
error: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildAlertContextPath = (traceId: string): string =>
|
||||||
|
`/flow/alerts/${encodeURIComponent(traceId)}/context`;
|
||||||
|
|
||||||
|
export const collectAlertContextEvidence = (
|
||||||
|
bundle: AlertContextBundle
|
||||||
|
): {
|
||||||
|
packets: Map<string, FlowPacket>;
|
||||||
|
prints: Map<string, OptionPrint>;
|
||||||
|
} => {
|
||||||
|
const packets = new Map<string, FlowPacket>();
|
||||||
|
const prints = new Map<string, OptionPrint>();
|
||||||
|
|
||||||
|
for (const packet of bundle.flow_packets) {
|
||||||
|
if (packet.id) {
|
||||||
|
packets.set(packet.id, packet);
|
||||||
|
}
|
||||||
|
if (packet.trace_id) {
|
||||||
|
packets.set(packet.trace_id, packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const print of bundle.option_prints) {
|
||||||
|
if (print.trace_id) {
|
||||||
|
prints.set(print.trace_id, print);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { packets, prints };
|
||||||
|
};
|
||||||
|
|
||||||
type DarkEvidenceItem =
|
type DarkEvidenceItem =
|
||||||
| { kind: "join"; id: string; join: EquityPrintJoin }
|
| { kind: "join"; id: string; join: EquityPrintJoin }
|
||||||
| { kind: "unknown"; id: string };
|
| { kind: "unknown"; id: string };
|
||||||
|
|
@ -4612,15 +4655,28 @@ type AlertDrawerProps = {
|
||||||
alert: AlertEvent;
|
alert: AlertEvent;
|
||||||
flowPacket: FlowPacket | null;
|
flowPacket: FlowPacket | null;
|
||||||
evidence: EvidenceItem[];
|
evidence: EvidenceItem[];
|
||||||
|
contextStatus: AlertContextStatus;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AlertDrawer = ({ alert, flowPacket, evidence, onClose }: AlertDrawerProps) => {
|
const formatOptionalMoney = (value: unknown): string | null => {
|
||||||
|
const parsed = parseNumber(value, Number.NaN);
|
||||||
|
return Number.isFinite(parsed) ? `$${formatPrice(parsed)}` : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatOptionalMs = (value: unknown): string | null => {
|
||||||
|
const parsed = parseNumber(value, Number.NaN);
|
||||||
|
return Number.isFinite(parsed) ? `${Math.round(parsed)}ms` : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AlertDrawer = ({ alert, flowPacket, evidence, contextStatus, onClose }: AlertDrawerProps) => {
|
||||||
const primary = alert.hits[0];
|
const primary = alert.hits[0];
|
||||||
const direction = deriveAlertDirection(alert);
|
const direction = deriveAlertDirection(alert);
|
||||||
const severity = normalizeAlertSeverity(alert);
|
const severity = normalizeAlertSeverity(alert);
|
||||||
const evidencePrints = evidence.filter((item) => item.kind === "print");
|
const evidencePrints = evidence.filter((item) => item.kind === "print");
|
||||||
const unknownCount = evidence.filter((item) => item.kind === "unknown").length;
|
const unknownCount = evidence.filter((item) => item.kind === "unknown").length;
|
||||||
|
const isContextLoading = contextStatus.traceId === alert.trace_id && contextStatus.loading;
|
||||||
|
const missingRefs = contextStatus.traceId === alert.trace_id ? contextStatus.missingRefs : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="drawer">
|
<aside className="drawer">
|
||||||
|
|
@ -4639,7 +4695,17 @@ const AlertDrawer = ({ alert, flowPacket, evidence, onClose }: AlertDrawerProps)
|
||||||
<span className={`pill severity-${severity}`}>{severity}</span>
|
<span className={`pill severity-${severity}`}>{severity}</span>
|
||||||
<span className="drawer-chip">Score {Math.round(alert.score)}</span>
|
<span className="drawer-chip">Score {Math.round(alert.score)}</span>
|
||||||
<span className={`pill direction-${direction}`}>{direction}</span>
|
<span className={`pill direction-${direction}`}>{direction}</span>
|
||||||
|
{isContextLoading ? <span className="drawer-chip">Loading context</span> : null}
|
||||||
</div>
|
</div>
|
||||||
|
{isContextLoading ? (
|
||||||
|
<div className="drawer-section drawer-context-loading" aria-label="Loading persisted evidence">
|
||||||
|
<div className="drawer-skeleton drawer-skeleton-wide" />
|
||||||
|
<div className="drawer-skeleton" />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{contextStatus.traceId === alert.trace_id && contextStatus.error ? (
|
||||||
|
<p className="drawer-empty">Persisted context could not be loaded: {contextStatus.error}</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div className="drawer-section">
|
<div className="drawer-section">
|
||||||
<h4>Classifier hits</h4>
|
<h4>Classifier hits</h4>
|
||||||
|
|
@ -4692,14 +4758,14 @@ const AlertDrawer = ({ alert, flowPacket, evidence, onClose }: AlertDrawerProps)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="drawer-empty">Flow packet not in the current live cache.</p>
|
<p className="drawer-empty">Persisted flow packet is not available for this alert.</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="drawer-section">
|
<div className="drawer-section">
|
||||||
<h4>Evidence prints</h4>
|
<h4>Evidence prints</h4>
|
||||||
{evidencePrints.length === 0 ? (
|
{evidencePrints.length === 0 ? (
|
||||||
<p className="drawer-empty">No evidence prints in the live cache yet.</p>
|
<p className="drawer-empty">Persisted evidence prints are not available for this alert.</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="drawer-list">
|
<div className="drawer-list">
|
||||||
{evidencePrints.slice(0, 6).map((item) => (
|
{evidencePrints.slice(0, 6).map((item) => (
|
||||||
|
|
@ -4709,6 +4775,36 @@ const AlertDrawer = ({ alert, flowPacket, evidence, onClose }: AlertDrawerProps)
|
||||||
<span>${formatPrice(item.print.price)}</span>
|
<span>${formatPrice(item.print.price)}</span>
|
||||||
<span>{formatSize(item.print.size)}x</span>
|
<span>{formatSize(item.print.size)}x</span>
|
||||||
<span>{item.print.exchange}</span>
|
<span>{item.print.exchange}</span>
|
||||||
|
{item.print.execution_nbbo_side ? <span>Side {item.print.execution_nbbo_side}</span> : null}
|
||||||
|
{formatOptionalMs(item.print.execution_nbbo_age_ms) ? (
|
||||||
|
<span>Quote {formatOptionalMs(item.print.execution_nbbo_age_ms)}</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="drawer-row-meta drawer-evidence-context">
|
||||||
|
{formatOptionalMoney(item.print.execution_nbbo_bid) ? (
|
||||||
|
<span>Bid {formatOptionalMoney(item.print.execution_nbbo_bid)}</span>
|
||||||
|
) : null}
|
||||||
|
{formatOptionalMoney(item.print.execution_nbbo_ask) ? (
|
||||||
|
<span>Ask {formatOptionalMoney(item.print.execution_nbbo_ask)}</span>
|
||||||
|
) : null}
|
||||||
|
{formatOptionalMoney(item.print.execution_nbbo_mid) ? (
|
||||||
|
<span>Mid {formatOptionalMoney(item.print.execution_nbbo_mid)}</span>
|
||||||
|
) : null}
|
||||||
|
{formatOptionalMoney(item.print.execution_nbbo_spread) ? (
|
||||||
|
<span>Spr {formatOptionalMoney(item.print.execution_nbbo_spread)}</span>
|
||||||
|
) : null}
|
||||||
|
{formatOptionalMoney(item.print.execution_underlying_spot) ? (
|
||||||
|
<span>Spot {formatOptionalMoney(item.print.execution_underlying_spot)}</span>
|
||||||
|
) : null}
|
||||||
|
{formatOptionalMoney(item.print.execution_underlying_bid) ? (
|
||||||
|
<span>U Bid {formatOptionalMoney(item.print.execution_underlying_bid)}</span>
|
||||||
|
) : null}
|
||||||
|
{formatOptionalMoney(item.print.execution_underlying_ask) ? (
|
||||||
|
<span>U Ask {formatOptionalMoney(item.print.execution_underlying_ask)}</span>
|
||||||
|
) : null}
|
||||||
|
{formatOptionalMoney(item.print.execution_underlying_mid) ? (
|
||||||
|
<span>U Mid {formatOptionalMoney(item.print.execution_underlying_mid)}</span>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<p className="drawer-note">{formatTime(item.print.ts)}</p>
|
<p className="drawer-note">{formatTime(item.print.ts)}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -4716,7 +4812,10 @@ const AlertDrawer = ({ alert, flowPacket, evidence, onClose }: AlertDrawerProps)
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{unknownCount > 0 ? (
|
{unknownCount > 0 ? (
|
||||||
<p className="drawer-empty">+{unknownCount} evidence prints not in cache.</p>
|
<p className="drawer-empty">+{unknownCount} evidence refs unresolved in persisted context.</p>
|
||||||
|
) : null}
|
||||||
|
{missingRefs.length > 0 ? (
|
||||||
|
<p className="drawer-empty">Missing refs: {missingRefs.slice(0, 4).join(", ")}</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
@ -5548,6 +5647,12 @@ const useTerminalState = () => {
|
||||||
const [pinnedEquityJoinMap, setPinnedEquityJoinMap] = useState<
|
const [pinnedEquityJoinMap, setPinnedEquityJoinMap] = useState<
|
||||||
Map<string, PinnedEntry<EquityPrintJoin>>
|
Map<string, PinnedEntry<EquityPrintJoin>>
|
||||||
>(() => new Map());
|
>(() => new Map());
|
||||||
|
const [selectedAlertContextStatus, setSelectedAlertContextStatus] = useState<AlertContextStatus>({
|
||||||
|
traceId: null,
|
||||||
|
loading: false,
|
||||||
|
missingRefs: [],
|
||||||
|
error: null
|
||||||
|
});
|
||||||
const [optionSupportSmartMoney, setOptionSupportSmartMoney] = useState<SmartMoneyEvent[]>([]);
|
const [optionSupportSmartMoney, setOptionSupportSmartMoney] = useState<SmartMoneyEvent[]>([]);
|
||||||
const [optionSupportClassifierHits, setOptionSupportClassifierHits] = useState<ClassifierHitEvent[]>([]);
|
const [optionSupportClassifierHits, setOptionSupportClassifierHits] = useState<ClassifierHitEvent[]>([]);
|
||||||
const [historicalNbboByTraceId, setHistoricalNbboByTraceId] = useState<Map<string, OptionNBBO | null>>(
|
const [historicalNbboByTraceId, setHistoricalNbboByTraceId] = useState<Map<string, OptionNBBO | null>>(
|
||||||
|
|
@ -5593,69 +5698,67 @@ const useTerminalState = () => {
|
||||||
}, [pinnedOptionPrintMap.size, pinnedFlowPacketMap.size, pinnedEquityJoinMap.size]);
|
}, [pinnedOptionPrintMap.size, pinnedFlowPacketMap.size, pinnedEquityJoinMap.size]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedAlert || mode !== "live") {
|
if (!selectedAlert) {
|
||||||
|
setSelectedAlertContextStatus({
|
||||||
|
traceId: null,
|
||||||
|
loading: false,
|
||||||
|
missingRefs: [],
|
||||||
|
error: null
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const packetId = selectedAlert.evidence_refs[0];
|
const abort = new AbortController();
|
||||||
if (packetId && !resolvedFlowPacketMap.has(packetId)) {
|
setSelectedAlertContextStatus({
|
||||||
incrementRetentionMetric("pinnedFetchMisses", 1);
|
traceId: selectedAlert.trace_id,
|
||||||
void fetch(buildApiUrl(`/flow/packets/${encodeURIComponent(packetId)}`))
|
loading: true,
|
||||||
.then(async (response) => {
|
missingRefs: [],
|
||||||
if (!response.ok) {
|
error: null
|
||||||
throw new Error(await readErrorDetail(response));
|
});
|
||||||
}
|
incrementRetentionMetric("pinnedFetchMisses", selectedAlert.evidence_refs.length);
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then((payload: { data?: FlowPacket | null }) => {
|
|
||||||
if (!payload.data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const now = Date.now();
|
|
||||||
const next = new Map<string, FlowPacket>([[payload.data.id, payload.data]]);
|
|
||||||
setPinnedFlowPacketMap((prev) => upsertPinnedEntries(prev, next, now));
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
incrementRetentionMetric("pinnedFetchFailures", 1);
|
|
||||||
console.warn("Failed to fetch flow packet evidence", error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const missingPrintIds = selectedAlert.evidence_refs.filter(
|
void fetch(buildApiUrl(buildAlertContextPath(selectedAlert.trace_id)), { signal: abort.signal })
|
||||||
(id) => !resolvedFlowPacketMap.has(id) && !resolvedOptionPrintMap.has(id)
|
.then(async (response) => {
|
||||||
);
|
if (!response.ok) {
|
||||||
if (missingPrintIds.length > 0) {
|
throw new Error(await readErrorDetail(response));
|
||||||
incrementRetentionMetric("pinnedFetchMisses", missingPrintIds.length);
|
}
|
||||||
const url = new URL(buildApiUrl("/option-prints/by-trace"));
|
return response.json();
|
||||||
for (const traceId of missingPrintIds) {
|
})
|
||||||
url.searchParams.append("trace_id", traceId);
|
.then((payload: AlertContextBundle) => {
|
||||||
}
|
if (abort.signal.aborted) {
|
||||||
void fetch(url.toString())
|
return;
|
||||||
.then(async (response) => {
|
}
|
||||||
if (!response.ok) {
|
const { packets, prints } = collectAlertContextEvidence(payload);
|
||||||
throw new Error(await readErrorDetail(response));
|
const now = Date.now();
|
||||||
}
|
if (packets.size > 0) {
|
||||||
return response.json();
|
setPinnedFlowPacketMap((prev) => upsertPinnedEntries(prev, packets, now));
|
||||||
})
|
}
|
||||||
.then((payload: { data?: OptionPrint[] }) => {
|
if (prints.size > 0) {
|
||||||
const next = new Map<string, OptionPrint>();
|
setPinnedOptionPrintMap((prev) => upsertPinnedEntries(prev, prints, now));
|
||||||
for (const item of payload.data ?? []) {
|
}
|
||||||
if (!item || !item.trace_id) {
|
setSelectedAlertContextStatus({
|
||||||
continue;
|
traceId: selectedAlert.trace_id,
|
||||||
}
|
loading: false,
|
||||||
next.set(item.trace_id, item);
|
missingRefs: payload.missing_refs ?? [],
|
||||||
}
|
error: null
|
||||||
if (next.size > 0) {
|
|
||||||
const now = Date.now();
|
|
||||||
setPinnedOptionPrintMap((prev) => upsertPinnedEntries(prev, next, now));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
incrementRetentionMetric("pinnedFetchFailures", 1);
|
|
||||||
console.warn("Failed to fetch option print evidence", error);
|
|
||||||
});
|
});
|
||||||
}
|
})
|
||||||
}, [selectedAlert, mode, resolvedFlowPacketMap, resolvedOptionPrintMap]);
|
.catch((error) => {
|
||||||
|
if (abort.signal.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
incrementRetentionMetric("pinnedFetchFailures", 1);
|
||||||
|
console.warn("Failed to fetch persisted alert context", error);
|
||||||
|
setSelectedAlertContextStatus({
|
||||||
|
traceId: selectedAlert.trace_id,
|
||||||
|
loading: false,
|
||||||
|
missingRefs: [],
|
||||||
|
error: error instanceof Error ? error.message : String(error)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => abort.abort();
|
||||||
|
}, [selectedAlert]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedDarkEvent || mode !== "live") {
|
if (!selectedDarkEvent || mode !== "live") {
|
||||||
|
|
@ -6802,6 +6905,7 @@ const useTerminalState = () => {
|
||||||
packetIdByOptionTraceId,
|
packetIdByOptionTraceId,
|
||||||
classifierDecorByOptionTraceId,
|
classifierDecorByOptionTraceId,
|
||||||
selectedEvidence,
|
selectedEvidence,
|
||||||
|
selectedAlertContextStatus,
|
||||||
selectedFlowPacket,
|
selectedFlowPacket,
|
||||||
selectedDarkEvidence,
|
selectedDarkEvidence,
|
||||||
selectedDarkUnderlying,
|
selectedDarkUnderlying,
|
||||||
|
|
@ -8515,6 +8619,7 @@ export function TerminalAppShell({ children }: { children: ReactNode }) {
|
||||||
alert={state.selectedAlert}
|
alert={state.selectedAlert}
|
||||||
flowPacket={state.selectedFlowPacket}
|
flowPacket={state.selectedFlowPacket}
|
||||||
evidence={state.selectedEvidence}
|
evidence={state.selectedEvidence}
|
||||||
|
contextStatus={state.selectedAlertContextStatus}
|
||||||
onClose={() => state.setSelectedAlert(null)}
|
onClose={() => state.setSelectedAlert(null)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
||||||
|
|
@ -271,6 +271,7 @@ Examples:
|
||||||
./deploy main --runtime docker --web-only
|
./deploy main --runtime docker --web-only
|
||||||
./deploy main --runtime docker --api-only
|
./deploy main --runtime docker --api-only
|
||||||
./deploy current-branch --runtime docker --services-only
|
./deploy current-branch --runtime docker --services-only
|
||||||
|
./deploy main --runtime docker --fast
|
||||||
./deploy main --runtime docker --web-only --no-build
|
./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`
|
- `--web-only`: `docker compose build web`, then `docker compose up -d web`
|
||||||
- `--api-only`: `docker compose build api`, then `docker compose up -d api`
|
- `--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`
|
- `--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.
|
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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ Examples:
|
||||||
./deploy main --runtime native --web-only
|
./deploy main --runtime native --web-only
|
||||||
./deploy main --runtime native --api-only
|
./deploy main --runtime native --api-only
|
||||||
./deploy current-branch --runtime native --services-only
|
./deploy current-branch --runtime native --services-only
|
||||||
|
./deploy main --runtime native --fast
|
||||||
./deploy main --runtime native --web-only --no-build
|
./deploy main --runtime native --web-only --no-build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -84,6 +85,7 @@ Scope behavior:
|
||||||
- `--web-only`: rebuild/restart only the web unit
|
- `--web-only`: rebuild/restart only the web unit
|
||||||
- `--api-only`: restart only the API unit
|
- `--api-only`: restart only the API unit
|
||||||
- `--services-only`: restart API + backend units without touching the web 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
|
- `--no-build`: skip `bun install --frozen-lockfile` and skip the web build step
|
||||||
|
|
||||||
## Current status
|
## Current status
|
||||||
|
|
|
||||||
493
docs/general/2026-05-17-standup-summary-2026-05-16.html
Normal file
493
docs/general/2026-05-17-standup-summary-2026-05-16.html
Normal file
|
|
@ -0,0 +1,493 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Standup Summary for 2026-05-16</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: #06080b;
|
||||||
|
--panel: #111820;
|
||||||
|
--panel-2: #0d141b;
|
||||||
|
--border: rgba(255, 255, 255, 0.08);
|
||||||
|
--border-strong: rgba(245, 166, 35, 0.35);
|
||||||
|
--text: #e6edf4;
|
||||||
|
--muted: #90a0b2;
|
||||||
|
--faint: #6e7b8c;
|
||||||
|
--accent: #f5a623;
|
||||||
|
--accent-soft: rgba(245, 166, 35, 0.12);
|
||||||
|
--green: #25c17a;
|
||||||
|
--blue: #4da3ff;
|
||||||
|
--red: #ff6b5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top right, rgba(245, 166, 35, 0.12), transparent 28%),
|
||||||
|
linear-gradient(180deg, #091018 0%, var(--bg) 28%, #05070a 100%);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: min(1080px, calc(100% - 40px));
|
||||||
|
margin: 32px auto 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero,
|
||||||
|
section {
|
||||||
|
background: linear-gradient(180deg, rgba(17, 24, 32, 0.94), rgba(13, 20, 27, 0.96));
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
padding: 32px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--accent-soft);
|
||||||
|
color: var(--accent);
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-family: "Quantico", "Segoe UI", sans-serif;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 18px;
|
||||||
|
font-size: clamp(2rem, 4vw, 3rem);
|
||||||
|
line-height: 1.04;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
max-width: 78ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero p {
|
||||||
|
margin-top: 14px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-card {
|
||||||
|
padding: 14px 16px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-card strong {
|
||||||
|
display: block;
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: 24px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
li + li {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event {
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(255, 255, 255, 0.025);
|
||||||
|
}
|
||||||
|
|
||||||
|
.event header {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px 14px;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event time,
|
||||||
|
.commit-id {
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-id {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-pill {
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.045);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid rgba(77, 163, 255, 0.22);
|
||||||
|
background: rgba(77, 163, 255, 0.09);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 0.92em;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-good {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-caution {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-risk {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<article class="hero">
|
||||||
|
<div class="eyebrow">Standup Summary</div>
|
||||||
|
<h1>Git Activity for Friday, 2026-05-16</h1>
|
||||||
|
<p>
|
||||||
|
Yesterday's git history shows three product-facing workstreams: live tape behavior fixes,
|
||||||
|
durable options history support, and faster Docker deploy builds. The day also included
|
||||||
|
merge commit <code>f4108b9</code> for PR <code>#39</code> and two small <code>AGENTS.md</code>
|
||||||
|
housekeeping updates.
|
||||||
|
</p>
|
||||||
|
<div class="meta">
|
||||||
|
<div class="meta-card">
|
||||||
|
<strong>Commits</strong>
|
||||||
|
8 commits recorded on 2026-05-16
|
||||||
|
</div>
|
||||||
|
<div class="meta-card">
|
||||||
|
<strong>Author</strong>
|
||||||
|
dirtydishes
|
||||||
|
</div>
|
||||||
|
<div class="meta-card">
|
||||||
|
<strong>Primary Areas</strong>
|
||||||
|
<code>apps/web</code>, <code>services/api</code>, <code>deployment/docker</code>, <code>scripts</code>
|
||||||
|
</div>
|
||||||
|
<div class="meta-card">
|
||||||
|
<strong>Docs Added</strong>
|
||||||
|
4 turn docs and 1 runbook file
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Live tape behavior was updated in commit <code>39fb5ce</code>, touching
|
||||||
|
<code>apps/web/app/terminal.tsx</code> and <code>services/api/src/live.ts</code>, with
|
||||||
|
companion test updates in <code>apps/web/app/terminal.test.ts</code> and
|
||||||
|
<code>services/api/tests/live.test.ts</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Durable options history work landed across commits <code>bd60d0d</code>,
|
||||||
|
<code>2abdd24</code>, and <code>1424a27</code>, spanning web terminal behavior, API
|
||||||
|
live routing, storage tests, and a new route checker in
|
||||||
|
<code>scripts/check-public-api-routes.ts</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Deploy build performance was adjusted in commit <code>23ed380</code> through Dockerfile
|
||||||
|
and deployment script changes under <code>deployment/docker</code> and
|
||||||
|
<code>scripts/deploy.ts</code>.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<div class="timeline">
|
||||||
|
<article class="event">
|
||||||
|
<header>
|
||||||
|
<h3>Fix live tape scroll hold and lazy history</h3>
|
||||||
|
<span class="commit-id">39fb5ce</span>
|
||||||
|
<time datetime="2026-05-16T14:23:51-04:00">2026-05-16 14:23 EDT</time>
|
||||||
|
</header>
|
||||||
|
<p>
|
||||||
|
Updated live tape behavior in the terminal and API layers, with matching test edits
|
||||||
|
and a turn document added in <code>docs/turns/2026-05-16-live-tape-scroll-hold-history.html</code>.
|
||||||
|
</p>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">apps/web/app/terminal.tsx</span>
|
||||||
|
<span class="file-pill">apps/web/app/terminal.test.ts</span>
|
||||||
|
<span class="file-pill">services/api/src/live.ts</span>
|
||||||
|
<span class="file-pill">services/api/tests/live.test.ts</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="event">
|
||||||
|
<header>
|
||||||
|
<h3>Durable options tape history implementation</h3>
|
||||||
|
<span class="commit-id">bd60d0d</span>
|
||||||
|
<time datetime="2026-05-16T17:27:02-04:00">2026-05-16 17:27 EDT</time>
|
||||||
|
</header>
|
||||||
|
<p>
|
||||||
|
Added another round of durable options history work across the terminal UI, API live
|
||||||
|
stream logic, storage tests, and a ClickHouse reset runbook.
|
||||||
|
</p>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">apps/web/app/terminal.tsx</span>
|
||||||
|
<span class="file-pill">packages/storage/tests/option-prints.test.ts</span>
|
||||||
|
<span class="file-pill">services/api/src/live.ts</span>
|
||||||
|
<span class="file-pill">docs/clickhouse-reset-runbook.md</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="event">
|
||||||
|
<header>
|
||||||
|
<h3>Durable options tape history follow-up and merge</h3>
|
||||||
|
<span class="commit-id">2abdd24 / f4108b9</span>
|
||||||
|
<time datetime="2026-05-16T17:44:51-04:00">2026-05-16 17:44-17:45 EDT</time>
|
||||||
|
</header>
|
||||||
|
<p>
|
||||||
|
A follow-up implementation commit added <code>.codex/hooks.json</code> and another
|
||||||
|
turn document, followed immediately by merge commit <code>f4108b9</code> for PR
|
||||||
|
<code>#39</code> from <code>dirtydishes/options-cache</code>.
|
||||||
|
</p>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">.codex/hooks.json</span>
|
||||||
|
<span class="file-pill">docs/turns/2026-05-16-1711-durable-options-tape-history.html</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="event">
|
||||||
|
<header>
|
||||||
|
<h3>Speed up Docker deploy builds</h3>
|
||||||
|
<span class="commit-id">23ed380</span>
|
||||||
|
<time datetime="2026-05-16T17:54:00-04:00">2026-05-16 17:54 EDT</time>
|
||||||
|
</header>
|
||||||
|
<p>
|
||||||
|
Adjusted Docker build inputs and deployment scripting, plus updated the Docker README
|
||||||
|
and added a matching turn document.
|
||||||
|
</p>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">deployment/docker/Dockerfile.ingest-options</span>
|
||||||
|
<span class="file-pill">deployment/docker/Dockerfile.service</span>
|
||||||
|
<span class="file-pill">deployment/docker/Dockerfile.web</span>
|
||||||
|
<span class="file-pill">scripts/deploy.ts</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="event">
|
||||||
|
<header>
|
||||||
|
<h3>Fix durable options history routing</h3>
|
||||||
|
<span class="commit-id">1424a27</span>
|
||||||
|
<time datetime="2026-05-16T22:00:21-04:00">2026-05-16 22:00 EDT</time>
|
||||||
|
</header>
|
||||||
|
<p>
|
||||||
|
Closed the day with routing fixes for durable options history, including terminal
|
||||||
|
styling updates, deployment script changes, and a new public API route checker.
|
||||||
|
</p>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">apps/web/app/globals.css</span>
|
||||||
|
<span class="file-pill">apps/web/app/terminal.tsx</span>
|
||||||
|
<span class="file-pill">scripts/check-public-api-routes.ts</span>
|
||||||
|
<span class="file-pill">scripts/deploy.ts</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="event">
|
||||||
|
<header>
|
||||||
|
<h3>Repository instruction updates</h3>
|
||||||
|
<span class="commit-id">eaddf4b / e3940eb</span>
|
||||||
|
<time datetime="2026-05-16T14:14:56-04:00">2026-05-16 14:14 and 14:57 EDT</time>
|
||||||
|
</header>
|
||||||
|
<p>
|
||||||
|
Two small commits updated <code>AGENTS.md</code>. One also modified
|
||||||
|
<code>.beads/issues.jsonl</code>.
|
||||||
|
</p>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">AGENTS.md</span>
|
||||||
|
<span class="file-pill">.beads/issues.jsonl</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
This report is derived from <code>git log</code> for the local repository over the full
|
||||||
|
America/New_York day window from <code>2026-05-16 00:00:00 -0400</code> through
|
||||||
|
<code>2026-05-16 23:59:59 -0400</code>. The goal is standup-ready reporting, so the
|
||||||
|
narrative groups related commits together while keeping every statement anchored to a
|
||||||
|
commit, merge, or changed file.
|
||||||
|
</p>
|
||||||
|
<div class="note">
|
||||||
|
The strongest product-facing cluster is the options history work. It appears in three
|
||||||
|
separate commits plus merge commit <code>f4108b9</code>, and those commits repeatedly touch
|
||||||
|
<code>apps/web/app/terminal.tsx</code>, <code>services/api/src/live.ts</code>, and related tests.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Commit <code>39fb5ce</code> paired UI and API changes with test edits in both the web
|
||||||
|
and API packages, which is a useful signal that the live tape behavior change was not
|
||||||
|
isolated to a single layer.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Commit <code>bd60d0d</code> added <code>docs/clickhouse-reset-runbook.md</code>, so the
|
||||||
|
durable options history work included operational documentation alongside code changes.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Commit <code>23ed380</code> changed all three Dockerfiles used in deployment plus
|
||||||
|
<code>scripts/deploy.ts</code>, so the build-speed update touched both image definition
|
||||||
|
and deployment orchestration.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Commit <code>1424a27</code> introduced <code>scripts/check-public-api-routes.ts</code>,
|
||||||
|
which is the only brand-new script added in yesterday's activity.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Users of the live terminal should see changes connected to tape scroll behavior and
|
||||||
|
history handling because commits <code>39fb5ce</code>, <code>bd60d0d</code>, and
|
||||||
|
<code>1424a27</code> all modified <code>apps/web/app/terminal.tsx</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
API consumers and live/replay flows were also part of the day because
|
||||||
|
<code>services/api/src/live.ts</code> changed in two separate commits.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Deployment operators should expect updated Docker build behavior after commit
|
||||||
|
<code>23ed380</code> and the later deployment-script follow-up in <code>1424a27</code>.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span class="status-good">Completed:</span> Git history was queried directly with
|
||||||
|
<code>git log --since='2026-05-16 00:00:00 -0400' --until='2026-05-16 23:59:59 -0400'</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="status-caution">Not run in this reporting task:</span> no fresh
|
||||||
|
<code>bun test</code>, build, or lint commands were executed.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="status-caution">Evidence available in history:</span> yesterday's commits
|
||||||
|
modified test files <code>apps/web/app/terminal.test.ts</code>,
|
||||||
|
<code>services/api/tests/live.test.ts</code>, and
|
||||||
|
<code>packages/storage/tests/option-prints.test.ts</code>.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
This summary is limited to local git history for one calendar day, so it does not infer
|
||||||
|
intent beyond what commit subjects, merge text, and changed files support.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
PR metadata is only explicitly available for merge commit <code>f4108b9</code>, which
|
||||||
|
names PR <code>#39</code>. Other commits are reported without attaching unverified PR context.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The repo currently has local beads file modifications unrelated to this documenting task,
|
||||||
|
so this report avoids treating current workspace state as part of yesterday's activity.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
No additional follow-up beads issues were created from the git history itself because
|
||||||
|
the reporting task did not uncover a concrete defect or missing deliverable.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Reporting task tracked in beads issue <code>islandflow-lyt</code>.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
194
docs/turns/2026-05-17-1101-clickhouse-alert-context.html
Normal file
194
docs/turns/2026-05-17-1101-clickhouse-alert-context.html
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>ClickHouse Alert Context Hydration</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: #06080b;
|
||||||
|
--panel: #111820;
|
||||||
|
--panel-soft: #0d141b;
|
||||||
|
--border: rgba(255, 255, 255, 0.12);
|
||||||
|
--text: #e6edf4;
|
||||||
|
--dim: #90a0b2;
|
||||||
|
--faint: #6e7b8c;
|
||||||
|
--accent: #f5a623;
|
||||||
|
--accent-soft: rgba(245, 166, 35, 0.14);
|
||||||
|
--ok: #25c17a;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 42px 22px 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding-bottom: 22px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 30px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--accent);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
max-width: 74ch;
|
||||||
|
color: var(--dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
padding-left: 20px;
|
||||||
|
color: var(--dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 2px 5px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--panel-soft);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "SFMono-Regular", Consolas, monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid rgba(245, 166, 35, 0.28);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--accent-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
display: inline-flex;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 4px 9px;
|
||||||
|
border: 1px solid rgba(37, 193, 122, 0.34);
|
||||||
|
border-radius: 999px;
|
||||||
|
color: var(--ok);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<header>
|
||||||
|
<h1>ClickHouse Alert Context Hydration</h1>
|
||||||
|
<p class="summary">
|
||||||
|
Alert detail drawers now fetch persisted investigative context from ClickHouse by alert trace id, then merge linked flow packets and option prints into the existing pinned evidence maps.
|
||||||
|
</p>
|
||||||
|
<span class="status">Validated</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<p>
|
||||||
|
This change makes alert details durable. Selecting an alert no longer depends only on the live cache to resolve evidence; the terminal asks the API for a ClickHouse-backed alert context bundle and uses that bundle to populate the existing drawer, classifier support, smart-money support, and prefetch evidence stores.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Added <code>fetchAlertContextByTraceId</code> in storage to load an alert, linked flow packets, linked option prints, and unresolved evidence refs.</li>
|
||||||
|
<li>Added <code>GET /flow/alerts/:trace_id/context</code> to the API without changing existing alert list, history, replay, or websocket feeds.</li>
|
||||||
|
<li>Updated the terminal alert selection effect to fetch persisted context in live, replay, and history modes.</li>
|
||||||
|
<li>Merged hydrated packets and prints into pinned maps so existing evidence consumers share the resolved context.</li>
|
||||||
|
<li>Adjusted alert drawer copy and loading state to reference persisted context rather than live cache misses.</li>
|
||||||
|
<li>Expanded alert evidence print rows with execution NBBO side, bid, ask, mid, spread, quote age, underlying spot, bid, ask, and mid where available.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
Alert rows intentionally remain lightweight for live bursts. The detail drawer is the right place to hydrate heavier investigative context because it runs only when a user asks for a specific alert. The authoritative linkage remains <code>AlertEvent.evidence_refs</code>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<p>The new API response shape is:</p>
|
||||||
|
<pre><code>{
|
||||||
|
alert: AlertEvent | null,
|
||||||
|
flow_packets: FlowPacket[],
|
||||||
|
option_prints: OptionPrint[],
|
||||||
|
missing_refs: string[]
|
||||||
|
}</code></pre>
|
||||||
|
<p>
|
||||||
|
Flow packet refs are resolved with both prefixed and unprefixed candidates. Option print refs are resolved by <code>trace_id</code>. Missing refs are returned explicitly instead of failing the whole response.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<p>
|
||||||
|
Alert details should feel more trustworthy after cache churn or replay navigation. Users can select an older or non-hot alert and still see the preserved evidence context needed to evaluate the signal.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>bun test packages/storage/tests</code></li>
|
||||||
|
<li><code>bun test services/api/tests</code></li>
|
||||||
|
<li><code>bun test apps/web/app/terminal.test.ts</code></li>
|
||||||
|
<li><code>bun --cwd=apps/web run build</code></li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<ul>
|
||||||
|
<li>The endpoint is detail-time only, which avoids making alert list payloads heavier during bursts.</li>
|
||||||
|
<li>Malformed trace ids are rejected by route-level validation.</li>
|
||||||
|
<li>Missing evidence refs remain visible to the drawer as diagnostics rather than hiding partial context.</li>
|
||||||
|
<li>No schema migration was needed because option prints already persist execution context fields.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<p>No follow-up beads issue was filed. The requested storage, API, frontend, tests, build, and documentation work is complete.</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
137
docs/turns/2026-05-17-add-fast-deploy-mode.html
Normal file
137
docs/turns/2026-05-17-add-fast-deploy-mode.html
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Turn Report: Add --fast Deploy Mode</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #f6f7f8;
|
||||||
|
--panel: #ffffff;
|
||||||
|
--text: #1f2933;
|
||||||
|
--muted: #52606d;
|
||||||
|
--accent: #0b6e99;
|
||||||
|
--border: #d9e2ec;
|
||||||
|
--code-bg: #f0f4f8;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: linear-gradient(180deg, #f6f7f8 0%, #eef3f7 100%);
|
||||||
|
color: var(--text);
|
||||||
|
font: 16px/1.6 "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Palatino, serif;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 40px auto;
|
||||||
|
padding: 0 20px 40px;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
h1, h2 { line-height: 1.25; margin: 0 0 12px; }
|
||||||
|
h1 { font-size: 1.9rem; }
|
||||||
|
h2 {
|
||||||
|
margin-top: 24px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
p { margin: 0 0 12px; }
|
||||||
|
ul { margin: 0 0 12px 20px; }
|
||||||
|
code, pre {
|
||||||
|
font-family: "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||||
|
font-size: 0.93em;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
background: var(--code-bg);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background: var(--code-bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
overflow: auto;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<article class="card">
|
||||||
|
<h1>Added <code>--fast</code> mode to deploy helper</h1>
|
||||||
|
<p class="meta">Date: 2026-05-17 · Repo: islandflow · Task: make <code>./deploy main</code> faster for routine rollouts</p>
|
||||||
|
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<p>
|
||||||
|
Added a new <code>--fast</code> flag to <code>./deploy</code> 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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Updated <code>scripts/deploy.ts</code> to parse and advertise <code>--fast</code>.</li>
|
||||||
|
<li>Added effective-scope logic so <code>--fast</code> + default scope behaves like <code>--services-only</code>.</li>
|
||||||
|
<li>Adjusted verification behavior in fast mode:</li>
|
||||||
|
<li>Skipped Docker log tail dump during remote verification.</li>
|
||||||
|
<li>Skipped verbose native <code>systemctl status</code> / <code>journalctl</code> output.</li>
|
||||||
|
<li>Skipped public API route suite (<code>scripts/check-public-api-routes.ts</code>) in fast mode.</li>
|
||||||
|
<li>Documented fast mode in <code>deployment/docker/README.md</code> and <code>deployment/native/README.md</code>.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
The default <code>./deploy main</code> 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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<p>
|
||||||
|
Fast mode does not silently alter explicitly requested scopes. It only remaps scope when the caller leaves scope at default full-stack.
|
||||||
|
</p>
|
||||||
|
<pre><code>function effectiveScope(scope: DeployScope, fast: boolean): DeployScope {
|
||||||
|
if (fast && scope === "full") {
|
||||||
|
return "services";
|
||||||
|
}
|
||||||
|
return scope;
|
||||||
|
}</code></pre>
|
||||||
|
<p>
|
||||||
|
Public verification now keeps behavior explicit. In fast mode, it logs why API route checks were skipped and points operators to <code>DEPLOY_PUBLIC_API_HEALTH_URL</code> if they want a public API probe.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Ran <code>bun run scripts/deploy.ts --help</code> to validate CLI parsing/help output for the new flag.</li>
|
||||||
|
<li>Ran full test suite with <code>bun test</code> (pass, 232 passing tests).</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>--fast</code> intentionally reduces verification depth; it is not equivalent to the full rollout safety envelope.</li>
|
||||||
|
<li>Fast mode defaults away from web rollout on full scope, so web changes should use explicit web/full scope deploys.</li>
|
||||||
|
<li>Mitigation: behavior is opt-in, surfaced in help text, and documented in deployment READMEs.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<ul>
|
||||||
|
<li>No immediate follow-up required for this change.</li>
|
||||||
|
<li>Optional future work: add an automatic changed-path-to-scope mapper to choose the smallest safe build set without operator guesswork.</li>
|
||||||
|
<li>Beads issue: <code>islandflow-xod</code> (this task).</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
364
docs/turns/2026-05-17-clickhouse-alert-context.html
Normal file
364
docs/turns/2026-05-17-clickhouse-alert-context.html
Normal file
|
|
@ -0,0 +1,364 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Turn Doc | ClickHouse Alert Context Hydration</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: oklch(0.16 0.012 244);
|
||||||
|
--bg-2: oklch(0.2 0.014 244);
|
||||||
|
--surface: oklch(0.23 0.016 244);
|
||||||
|
--surface-2: oklch(0.26 0.018 244);
|
||||||
|
--line: oklch(0.42 0.02 244 / 0.42);
|
||||||
|
--line-strong: oklch(0.62 0.055 76 / 0.42);
|
||||||
|
--text: oklch(0.93 0.012 244);
|
||||||
|
--text-muted: oklch(0.76 0.014 244);
|
||||||
|
--text-faint: oklch(0.64 0.014 244);
|
||||||
|
--accent: oklch(0.78 0.14 76);
|
||||||
|
--ok: oklch(0.78 0.14 152);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
background:
|
||||||
|
radial-gradient(1100px 520px at 96% -8%, oklch(0.34 0.05 76 / 0.18), transparent 60%),
|
||||||
|
linear-gradient(180deg, var(--bg-2), var(--bg));
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
padding: 30px 20px 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 1140px;
|
||||||
|
display: grid;
|
||||||
|
gap: 26px;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mast {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: linear-gradient(180deg, oklch(0.25 0.017 244 / 0.94), oklch(0.22 0.015 244 / 0.94));
|
||||||
|
padding: 22px 20px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kicker {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
font-weight: 650;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 8px 0 10px;
|
||||||
|
font-family: "Quantico", "IBM Plex Sans", sans-serif;
|
||||||
|
font-size: clamp(1.55rem, 2.2vw, 2.05rem);
|
||||||
|
line-height: 1.1;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 72ch;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin-top: 14px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid oklch(0.68 0.09 152 / 0.42);
|
||||||
|
background: oklch(0.28 0.034 152 / 0.2);
|
||||||
|
color: var(--ok);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status::before {
|
||||||
|
content: "";
|
||||||
|
width: 0.48rem;
|
||||||
|
height: 0.48rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
display: grid;
|
||||||
|
gap: 20px;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
aside {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: oklch(0.22 0.015 244 / 0.9);
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-row {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 9px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-label {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.66rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
color: var(--text-faint);
|
||||||
|
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-value {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: oklch(0.22 0.015 244 / 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: 18px 18px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section + section {
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0 0 9px;
|
||||||
|
font-size: 0.77rem;
|
||||||
|
letter-spacing: 0.13em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--accent);
|
||||||
|
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 74ch;
|
||||||
|
color: var(--text-muted);
|
||||||
|
line-height: 1.58;
|
||||||
|
}
|
||||||
|
|
||||||
|
p + p {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 18px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
line-height: 1.54;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 0.92em;
|
||||||
|
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: var(--surface);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px 11px;
|
||||||
|
border: 1px solid var(--line-strong);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: oklch(0.3 0.04 76 / 0.12);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
.body {
|
||||||
|
grid-template-columns: 280px minmax(0, 1fr);
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside {
|
||||||
|
position: sticky;
|
||||||
|
top: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.mast,
|
||||||
|
aside,
|
||||||
|
article {
|
||||||
|
animation: reveal 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes reveal {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="page">
|
||||||
|
<div class="layout">
|
||||||
|
<header class="mast">
|
||||||
|
<p class="kicker">Turn Documentation</p>
|
||||||
|
<h1>ClickHouse Alert Context Hydration</h1>
|
||||||
|
<p class="summary">
|
||||||
|
Alert detail drawers now load persisted evidence context from ClickHouse by alert trace id, then hydrate linked flow packets and option prints into the existing pinned evidence maps.
|
||||||
|
</p>
|
||||||
|
<span class="status">Validation complete</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
<aside aria-label="Document metadata">
|
||||||
|
<div class="meta-row">
|
||||||
|
<p class="meta-label">Date</p>
|
||||||
|
<p class="meta-value">2026-05-17</p>
|
||||||
|
</div>
|
||||||
|
<div class="meta-row">
|
||||||
|
<p class="meta-label">Scope</p>
|
||||||
|
<p class="meta-value">Storage, API, Web Terminal</p>
|
||||||
|
</div>
|
||||||
|
<div class="meta-row">
|
||||||
|
<p class="meta-label">Issue</p>
|
||||||
|
<p class="meta-value"><code>islandflow-cif</code></p>
|
||||||
|
</div>
|
||||||
|
<div class="meta-row">
|
||||||
|
<p class="meta-label">Route</p>
|
||||||
|
<p class="meta-value"><code>GET /flow/alerts/:trace_id/context</code></p>
|
||||||
|
</div>
|
||||||
|
<div class="meta-row">
|
||||||
|
<p class="meta-label">Status</p>
|
||||||
|
<p class="meta-value">Merged context hydration path</p>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<section>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<p>
|
||||||
|
Alert detail hydration no longer depends only on live cache residency. When a user selects an alert, the terminal now requests a persisted context bundle and resolves linked evidence from ClickHouse.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Added storage lookup for alert context by <code>trace_id</code> with explicit <code>missing_refs</code> diagnostics.</li>
|
||||||
|
<li>Added API endpoint <code>GET /flow/alerts/:trace_id/context</code> for detail-time evidence hydration.</li>
|
||||||
|
<li>Updated terminal selection flow so hydrated packets and prints merge into pinned evidence maps shared by drawers and support paths.</li>
|
||||||
|
<li>Updated drawer copy from live-cache miss language to persisted-context language.</li>
|
||||||
|
<li>Preserved dense drawer structure while surfacing execution context fields such as NBBO side, bid/ask/mid/spread, quote age, and underlying spot/bid/ask/mid.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
Existing list feeds remain unchanged, including <code>/flow/alerts</code>, <code>/history/alerts</code>, <code>/replay/alerts</code>, and live websocket rows. This keeps burst-time payloads lean while moving heavy evidence lookup to detail interactions.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<p>Context endpoint payload:</p>
|
||||||
|
<pre><code>{
|
||||||
|
alert: AlertEvent | null,
|
||||||
|
flow_packets: FlowPacket[],
|
||||||
|
option_prints: OptionPrint[],
|
||||||
|
missing_refs: string[]
|
||||||
|
}</code></pre>
|
||||||
|
<p class="callout">
|
||||||
|
Evidence refs are resolved without failing the whole response when some refs are stale or absent. Unresolved refs are surfaced to UI as diagnostics.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<p>
|
||||||
|
Alert investigation should remain reliable after live cache churn. Users can open an alert and still inspect preserved evidence context needed for decision-making, even when original live rows rotated out.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>bun test packages/storage/tests</code> passed</li>
|
||||||
|
<li><code>bun test services/api/tests</code> passed</li>
|
||||||
|
<li><code>bun test apps/web/app/terminal.test.ts</code> passed</li>
|
||||||
|
<li><code>bun --cwd=apps/web run build</code> passed</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Detail-time hydration adds a request on selection; this intentionally avoids inflating live alert table payloads.</li>
|
||||||
|
<li>Malformed trace ids are rejected safely at the route layer.</li>
|
||||||
|
<li>Missing evidence refs are reported as <code>missing_refs</code> instead of causing hard failure.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<p>
|
||||||
|
No mandatory follow-up remains for baseline delivery. Further UI refinement could add richer missing-ref drilldown and stronger loading placeholders if desired.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
193
docs/turns/2026-05-17-configure-beads-dolt-remote.html
Normal file
193
docs/turns/2026-05-17-configure-beads-dolt-remote.html
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Turn Document - Configure Beads Dolt Remote</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: #071018;
|
||||||
|
--panel: #101a24;
|
||||||
|
--panel-2: #0b141d;
|
||||||
|
--border: rgba(255, 255, 255, 0.12);
|
||||||
|
--text: #e7edf5;
|
||||||
|
--muted: #94a5b8;
|
||||||
|
--accent: #7cc7ff;
|
||||||
|
--good: #55d38a;
|
||||||
|
--warn: #ffb454;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Inter, Arial, sans-serif;
|
||||||
|
background: linear-gradient(180deg, #08111a 0%, #05080d 100%);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
width: min(980px, calc(100vw - 32px));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 28px 0 40px;
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 22px 24px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
h1, h2 { margin-top: 0; }
|
||||||
|
h1 { font-size: 2rem; }
|
||||||
|
h2 { font-size: 1rem; text-transform: uppercase; letter-spacing: 0.08em; }
|
||||||
|
p, li { line-height: 1.6; }
|
||||||
|
ul { margin-bottom: 0; }
|
||||||
|
code, pre { font-family: "IBM Plex Mono", Menlo, monospace; }
|
||||||
|
code { color: #a8d8ff; }
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 14px;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--panel-2);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.meta { color: var(--muted); }
|
||||||
|
.pill {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 6px 8px 0 0;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
font-size: 0.86rem;
|
||||||
|
}
|
||||||
|
.good { background: rgba(85, 211, 138, 0.14); }
|
||||||
|
.warn { background: rgba(255, 180, 84, 0.14); }
|
||||||
|
.accent { background: rgba(124, 199, 255, 0.14); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<h1>Configure Beads Dolt Remote</h1>
|
||||||
|
<p>
|
||||||
|
Configured a persistent beads/Dolt sync remote for this repo using the server-hosted Forgejo instance at
|
||||||
|
<code>git.deltaisland.io</code>, published Dolt data to <code>refs/dolt/data</code>, and documented the
|
||||||
|
operational constraints around Nginx Proxy Manager, HTTPS, and SSH reachability.
|
||||||
|
</p>
|
||||||
|
<p class="meta">Generated: 2026-05-17 06:36 EDT</p>
|
||||||
|
<div>
|
||||||
|
<span class="pill accent">Beads issue: islandflow-8kj</span>
|
||||||
|
<span class="pill good">Remote published</span>
|
||||||
|
<span class="pill good">HTTPS validated</span>
|
||||||
|
<span class="pill warn">SSH follow-up: islandflow-zsy</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<p>
|
||||||
|
The repo now has a committed beads sync target in <code>.beads/config.yaml</code> and the server now has the
|
||||||
|
required local tooling and credentials to run <code>bd dolt push</code> successfully against Forgejo over HTTPS.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Installed <code>bd</code> 1.0.4 for the <code>delta</code> user.</li>
|
||||||
|
<li>Installed <code>dolt</code> 2.0.3 in <code>~/.local/bin</code>.</li>
|
||||||
|
<li>Configured a persistent local Forgejo credential for non-interactive beads/Dolt pushes on this server.</li>
|
||||||
|
<li>Added the public beads sync URL to <code>.beads/config.yaml</code>:</li>
|
||||||
|
</ul>
|
||||||
|
<pre><code>sync:
|
||||||
|
remote: git+https://git.deltaisland.io/dirtydishes/islandflow.git</code></pre>
|
||||||
|
<ul>
|
||||||
|
<li>Published the current Dolt history to Forgejo and verified <code>refs/dolt/data</code> exists on the remote.</li>
|
||||||
|
<li>Created a follow-up issue for SSH reachability via DNS/Cloudflare: <code>islandflow-zsy</code>.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
This repo already used beads locally, but it had no Dolt remote configured. Earlier work in the repo had
|
||||||
|
explicitly noted that <code>bd dolt pull</code> was unavailable because no remote existed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The server already hosted Forgejo behind Nginx Proxy Manager at <code>git.deltaisland.io</code>, which made an
|
||||||
|
HTTPS-backed beads remote the lowest-friction persistent option.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
The public remote URL for collaborators is:
|
||||||
|
<code>git+https://git.deltaisland.io/dirtydishes/islandflow.git</code>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The actual server-side push path is authenticated locally with a Forgejo personal access token stored only on
|
||||||
|
the server, so the committed repo configuration does not contain secrets.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The Nginx Proxy Manager host for <code>git.deltaisland.io</code> already proxies Forgejo on ports 80/443, so no
|
||||||
|
new public port exposure was needed for the working HTTPS path.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
A dedicated Forgejo SSH key was also prepared on the server, but end-to-end SSH to <code>git.deltaisland.io:2222</code>
|
||||||
|
is still blocked by the current DNS/proxy setup rather than by the host listener itself.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Future clones can bootstrap beads from the server-backed remote instead of starting with an empty local database.</li>
|
||||||
|
<li>Operators can now run <code>bd dolt push</code> on this server without manual one-off setup.</li>
|
||||||
|
<li>Beads issue history is now backed by a persistent remote rather than being local-only state.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>bd version</code> → 1.0.4</li>
|
||||||
|
<li><code>dolt version</code> → 2.0.3</li>
|
||||||
|
<li><code>bd dolt push</code> completed successfully.</li>
|
||||||
|
<li><code>git ls-remote https://git.deltaisland.io/dirtydishes/islandflow.git refs/dolt/data</code> returned a ref.</li>
|
||||||
|
<li><code>ss -tulpn</code> confirmed listeners on 80, 443, and 2222.</li>
|
||||||
|
<li>Inspected the Nginx Proxy Manager config for <code>git.deltaisland.io</code> and confirmed HTTPS proxying to the Forgejo container.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>SSH hostname reachability:</strong> Forgejo is listening on host port <code>2222</code>, but the
|
||||||
|
current public hostname resolves through a proxy path that does not complete SSH connections. HTTPS remains the
|
||||||
|
supported path today.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Server-local credential material:</strong> a local Forgejo token was required so this server can push
|
||||||
|
beads data non-interactively. The secret was kept out of tracked repo files.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Pre-existing repo dirtiness:</strong> unrelated local changes already existed in this working tree and
|
||||||
|
were intentionally left untouched.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>islandflow-zsy</code> — expose Forgejo SSH on a direct DNS hostname if SSH-based Git/beads sync should work publicly.</li>
|
||||||
|
<li>If additional machines need write access, create Forgejo credentials or PATs for those operators and use the public HTTPS remote above.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
151
docs/turns/2026-05-17-deploy-allowlist-pr-packaging.html
Normal file
151
docs/turns/2026-05-17-deploy-allowlist-pr-packaging.html
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Turn Document - Deploy Allowlist PR Packaging</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: #0a1118;
|
||||||
|
--panel: #121b24;
|
||||||
|
--panel-2: #0d151e;
|
||||||
|
--border: rgba(255, 255, 255, 0.14);
|
||||||
|
--text: #e6edf3;
|
||||||
|
--muted: #95a8bb;
|
||||||
|
--accent: #89d1ff;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: "Segoe UI", Tahoma, sans-serif;
|
||||||
|
background: linear-gradient(180deg, #09121a 0%, #060b10 100%);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
width: min(960px, calc(100vw - 32px));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 28px 0 40px;
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 20px 22px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
h1, h2 { margin-top: 0; }
|
||||||
|
h2 { font-size: 1rem; text-transform: uppercase; letter-spacing: 0.08em; }
|
||||||
|
p, li { line-height: 1.6; }
|
||||||
|
code, pre { font-family: "IBM Plex Mono", Menlo, monospace; }
|
||||||
|
code { color: var(--accent); }
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow-x: auto;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--panel-2);
|
||||||
|
}
|
||||||
|
.meta { color: var(--muted); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<h1>Deploy Allowlist PR Packaging</h1>
|
||||||
|
<p>
|
||||||
|
Packaged the deploy allowlist cleanup into a PR-ready branch with multiple commits, documented all changes,
|
||||||
|
and tracked work in Beads issue <code>islandflow-9j5</code>.
|
||||||
|
</p>
|
||||||
|
<p class="meta">Generated: 2026-05-17 11:48 EDT</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<p>
|
||||||
|
Removed <code>deployment/npm/</code> from the deploy script's remote untracked allowlist so deploy preflight
|
||||||
|
only tolerates the required signal-cli tarball artifact.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Updated <code>scripts/deploy.ts</code> to tighten <code>ALLOWED_REMOTE_UNTRACKED</code>.</li>
|
||||||
|
<li>Created this turn document in <code>docs/turns/</code> as required by repository workflow.</li>
|
||||||
|
<li>Tracked and managed the work through Beads issue <code>islandflow-9j5</code>.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
The deploy preflight checks remote repository cleanliness before rollout. Keeping broad allowlist exceptions
|
||||||
|
can hide stale or accidental files on the target host and reduce deployment confidence.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<p>
|
||||||
|
The allowlist now contains only:
|
||||||
|
</p>
|
||||||
|
<pre><code>deployment/docker/signal-cli-0.14.3-Linux-native.tar.gz</code></pre>
|
||||||
|
<p>
|
||||||
|
The removed entry:
|
||||||
|
</p>
|
||||||
|
<pre><code>deployment/npm/</code></pre>
|
||||||
|
<p>
|
||||||
|
This change ensures remote preflight fails if <code>deployment/npm/</code> appears unexpectedly.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Deployments should fail faster when unexpected remote workspace artifacts exist.</li>
|
||||||
|
<li>Operators get stricter hygiene checks before production rollouts.</li>
|
||||||
|
<li>No runtime behavior change to API/web/services outside deploy validation logic.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>bun test</code> was run for the repository and reported 2 failing tests plus 1 module-loading error:
|
||||||
|
<code>services/api/tests/live.test.ts</code> (hot-head cap expectation mismatch) and
|
||||||
|
<code>apps/web/app/terminal.test.ts</code> (Next navigation export mismatch).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The user requested skipping dependency-install remediation before completion, so no additional test-fix work
|
||||||
|
was performed in this turn.
|
||||||
|
</li>
|
||||||
|
<li><code>git diff</code> review to confirm only intended allowlist and documentation updates were included.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
This turn did not add new deploy integration tests for the allowlist branch logic. Mitigation: kept the
|
||||||
|
change scoped to one constant and validated via repository test run plus manual diff inspection.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
A local untracked signal-cli tarball remains in the working tree by design and was not added to Git.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<ul>
|
||||||
|
<li>No additional follow-up issues were created from this scoped cleanup.</li>
|
||||||
|
<li>If full CI confidence is required, run <code>bun install</code> and <code>bun test</code> in a dependency-ready environment.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
126
docs/turns/2026-05-17-forgejo-deploy-remote-resolution.html
Normal file
126
docs/turns/2026-05-17-forgejo-deploy-remote-resolution.html
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Turn Report: Forgejo-Aware Deploy Remote Resolution</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #f5f7fa;
|
||||||
|
--panel: #ffffff;
|
||||||
|
--text: #1f2d3d;
|
||||||
|
--muted: #5b6b7a;
|
||||||
|
--accent: #0e6ba8;
|
||||||
|
--line: #d7e0ea;
|
||||||
|
--code: #eef3f8;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: linear-gradient(180deg, #f7fafc, #eef3f8);
|
||||||
|
color: var(--text);
|
||||||
|
font: 16px/1.58 "Avenir Next", "Segoe UI", system-ui, sans-serif;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
max-width: 920px;
|
||||||
|
margin: 36px auto;
|
||||||
|
padding: 0 18px 32px;
|
||||||
|
}
|
||||||
|
article {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
h1 { margin: 0 0 6px; font-size: 1.8rem; }
|
||||||
|
h2 { margin: 24px 0 10px; color: var(--accent); font-size: 1.15rem; }
|
||||||
|
p { margin: 0 0 10px; }
|
||||||
|
ul { margin: 0 0 12px 20px; }
|
||||||
|
.meta { color: var(--muted); font-size: 0.95rem; margin-bottom: 14px; }
|
||||||
|
code {
|
||||||
|
background: var(--code);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-family: "SFMono-Regular", Menlo, Consolas, monospace;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--code);
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: "SFMono-Regular", Menlo, Consolas, monospace;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<article>
|
||||||
|
<h1>Deploy helper now resolves Forgejo/GitHub remotes without hardcoded <code>origin</code></h1>
|
||||||
|
<p class="meta">Date: 2026-05-17 · Issue: islandflow-1ei · Files changed: scripts/deploy.ts</p>
|
||||||
|
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<p>
|
||||||
|
Updated <code>scripts/deploy.ts</code> so deploy operations no longer assume a git remote named <code>origin</code>. The deploy helper now auto-resolves an available remote (Forgejo-aware), uses it consistently across fetch/pull/push and remote checkout updates, and supports explicit override with <code>DEPLOY_GIT_REMOTE</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Added <code>DEPLOY_GIT_REMOTE</code> environment override to force a specific remote when needed.</li>
|
||||||
|
<li>Added local helper functions to discover remotes, inspect branch upstream metadata, and resolve deploy remote candidates.</li>
|
||||||
|
<li>Changed local prechecks from hardcoded <code>git fetch origin</code> / <code>origin/main</code> to resolved remote values.</li>
|
||||||
|
<li>Changed branch publish from hardcoded pushes to remote-aware push commands.</li>
|
||||||
|
<li>Changed remote VPS git update steps from hardcoded <code>origin</code> fetch/pull/track to remote-aware commands.</li>
|
||||||
|
<li>Updated deploy CLI help/environment text and rollout log output to show selected git remote.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
The repository now includes <code>forgejo</code> and <code>github</code> remotes and may not define <code>origin</code> at all. Hardcoding <code>origin</code> caused deploy fragility in both local precheck and remote rollout flows.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<p>
|
||||||
|
Remote resolution prioritizes explicit operator intent and branch metadata, then falls back to a stable preference order and discovered remotes.
|
||||||
|
</p>
|
||||||
|
<pre><code>candidates = [
|
||||||
|
DEPLOY_GIT_REMOTE,
|
||||||
|
branch.<name>.remote,
|
||||||
|
upstream remote,
|
||||||
|
branch.main.remote,
|
||||||
|
forgejo, origin, github,
|
||||||
|
all discovered remotes
|
||||||
|
]</code></pre>
|
||||||
|
<p>
|
||||||
|
The selected remote is then threaded through all deploy git operations to avoid local/remote mismatch from hardcoded remote names.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<p>
|
||||||
|
Operators should no longer see deploy failures caused solely by missing <code>origin</code>. Deploy commands should work in mixed Forgejo/GitHub environments with fewer manual fixes and less confusion.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Ran <code>bun run scripts/deploy.ts --help</code> to verify updated usage and environment output.</li>
|
||||||
|
<li>Ran <code>bun test</code> (232 passing, 0 failing) after code changes.</li>
|
||||||
|
<li>Searched the updated file to verify key <code>origin</code> hardcodes were removed from deploy flow paths.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<ul>
|
||||||
|
<li>If local and VPS remote naming differ unexpectedly, deploy can still fail during remote git update.</li>
|
||||||
|
<li>Mitigation: <code>DEPLOY_GIT_REMOTE</code> allows explicit remote selection per run.</li>
|
||||||
|
<li>The current change does not rewrite deployment README examples; they may still mention <code>origin</code> in historical/manual sections.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Optional: update deployment docs to describe dynamic remote resolution and <code>DEPLOY_GIT_REMOTE</code> usage examples.</li>
|
||||||
|
<li>No additional code follow-up required for the reported deploy.ts Forgejo mismatch.</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
0
forgejo.test
Normal file
0
forgejo.test
Normal file
|
|
@ -746,6 +746,13 @@ export type EquityPrintQueryFilters = {
|
||||||
sinceTs?: number;
|
sinceTs?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AlertContextBundle = {
|
||||||
|
alert: AlertEvent | null;
|
||||||
|
flow_packets: FlowPacket[];
|
||||||
|
option_prints: OptionPrint[];
|
||||||
|
missing_refs: string[];
|
||||||
|
};
|
||||||
|
|
||||||
const buildOptionPrintFilterConditions = (
|
const buildOptionPrintFilterConditions = (
|
||||||
filters: OptionPrintQueryFilters | undefined,
|
filters: OptionPrintQueryFilters | undefined,
|
||||||
tracePrefix: string | undefined
|
tracePrefix: string | undefined
|
||||||
|
|
@ -1200,6 +1207,101 @@ export const fetchRecentAlerts = async (
|
||||||
return AlertEventSchema.array().parse(alerts);
|
return AlertEventSchema.array().parse(alerts);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeAlertEvidenceRefs = (refs: string[]): string[] => {
|
||||||
|
return Array.from(new Set(refs.map((ref) => ref.trim()).filter(Boolean)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const flowPacketCandidatesFromRef = (ref: string): string[] => {
|
||||||
|
if (!ref) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (ref.startsWith("flowpacket:")) {
|
||||||
|
const raw = ref.slice("flowpacket:".length);
|
||||||
|
return raw ? [ref, raw] : [ref];
|
||||||
|
}
|
||||||
|
return [ref, `flowpacket:${ref}`];
|
||||||
|
};
|
||||||
|
|
||||||
|
const optionPrintCandidatesFromRef = (ref: string): string[] => {
|
||||||
|
if (!ref || ref.startsWith("flowpacket:")) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [ref];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchAlertContextByTraceId = async (
|
||||||
|
client: ClickHouseClient,
|
||||||
|
traceId: string
|
||||||
|
): Promise<AlertContextBundle> => {
|
||||||
|
const normalizedTraceId = traceId.trim();
|
||||||
|
if (!normalizedTraceId) {
|
||||||
|
return {
|
||||||
|
alert: null,
|
||||||
|
flow_packets: [],
|
||||||
|
option_prints: [],
|
||||||
|
missing_refs: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const alertResult = await client.query({
|
||||||
|
query: `SELECT * FROM ${ALERTS_TABLE} WHERE trace_id = ${quoteString(normalizedTraceId)} ORDER BY source_ts DESC, seq DESC LIMIT 1`,
|
||||||
|
format: "JSONEachRow"
|
||||||
|
});
|
||||||
|
const alertRows = await alertResult.json<unknown[]>();
|
||||||
|
const alertRecord = alertRows
|
||||||
|
.map(normalizeAlertRow)
|
||||||
|
.find((record): record is AlertRecord => record !== null);
|
||||||
|
const alert = alertRecord ? AlertEventSchema.parse(fromAlertRecord(alertRecord)) : null;
|
||||||
|
|
||||||
|
if (!alert) {
|
||||||
|
return {
|
||||||
|
alert: null,
|
||||||
|
flow_packets: [],
|
||||||
|
option_prints: [],
|
||||||
|
missing_refs: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const refs = normalizeAlertEvidenceRefs(alert.evidence_refs);
|
||||||
|
const packetLookupIds = Array.from(new Set(refs.flatMap(flowPacketCandidatesFromRef)));
|
||||||
|
const printLookupIds = Array.from(new Set(refs.flatMap(optionPrintCandidatesFromRef)));
|
||||||
|
|
||||||
|
const [flowPackets, optionPrints] = await Promise.all([
|
||||||
|
packetLookupIds.length > 0
|
||||||
|
? client
|
||||||
|
.query({
|
||||||
|
query: `SELECT * FROM ${FLOW_PACKETS_TABLE} WHERE id IN (${buildStringList(packetLookupIds)}) ORDER BY source_ts DESC, seq DESC LIMIT ${clampLookupLimit(packetLookupIds.length)}`,
|
||||||
|
format: "JSONEachRow"
|
||||||
|
})
|
||||||
|
.then(async (result) => {
|
||||||
|
const rows = await result.json<unknown[]>();
|
||||||
|
const records = rows
|
||||||
|
.map(normalizeFlowPacketRow)
|
||||||
|
.filter((record): record is FlowPacketRecord => record !== null);
|
||||||
|
return FlowPacketSchema.array().parse(records.map(fromFlowPacketRecord));
|
||||||
|
})
|
||||||
|
: Promise.resolve([]),
|
||||||
|
printLookupIds.length > 0
|
||||||
|
? fetchOptionPrintsByTraceIds(client, printLookupIds)
|
||||||
|
: Promise.resolve([])
|
||||||
|
]);
|
||||||
|
|
||||||
|
const packetIds = new Set(flowPackets.flatMap((packet) => [packet.id, packet.trace_id]));
|
||||||
|
const printIds = new Set(optionPrints.map((print) => print.trace_id));
|
||||||
|
const missingRefs = refs.filter((ref) => {
|
||||||
|
const packetResolved = flowPacketCandidatesFromRef(ref).some((candidate) => packetIds.has(candidate));
|
||||||
|
const printResolved = optionPrintCandidatesFromRef(ref).some((candidate) => printIds.has(candidate));
|
||||||
|
return !packetResolved && !printResolved;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
alert,
|
||||||
|
flow_packets: flowPackets,
|
||||||
|
option_prints: optionPrints,
|
||||||
|
missing_refs: missingRefs
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchOptionPrintsAfter = async (
|
export const fetchOptionPrintsAfter = async (
|
||||||
client: ClickHouseClient,
|
client: ClickHouseClient,
|
||||||
afterTs: number,
|
afterTs: number,
|
||||||
|
|
@ -1711,6 +1813,25 @@ export const fetchFlowPacketById = async (
|
||||||
return record ? FlowPacketSchema.parse(fromFlowPacketRecord(record)) : null;
|
return record ? FlowPacketSchema.parse(fromFlowPacketRecord(record)) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchFlowPacketsByIds = async (
|
||||||
|
client: ClickHouseClient,
|
||||||
|
ids: string[]
|
||||||
|
): Promise<FlowPacket[]> => {
|
||||||
|
const uniqueIds = Array.from(new Set(ids.map((id) => id.trim()).filter(Boolean)));
|
||||||
|
if (uniqueIds.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result = await client.query({
|
||||||
|
query: `SELECT * FROM ${FLOW_PACKETS_TABLE} WHERE id IN (${buildStringList(uniqueIds)}) ORDER BY source_ts DESC, seq DESC LIMIT ${clampLookupLimit(uniqueIds.length)}`,
|
||||||
|
format: "JSONEachRow"
|
||||||
|
});
|
||||||
|
const rows = await result.json<unknown[]>();
|
||||||
|
const records = rows
|
||||||
|
.map(normalizeFlowPacketRow)
|
||||||
|
.filter((record): record is FlowPacketRecord => record !== null);
|
||||||
|
return FlowPacketSchema.array().parse(records.map(fromFlowPacketRecord));
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchFlowPacketsByMemberTraceIds = async (
|
export const fetchFlowPacketsByMemberTraceIds = async (
|
||||||
client: ClickHouseClient,
|
client: ClickHouseClient,
|
||||||
traceIds: string[]
|
traceIds: string[]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import { describe, expect, it } from "bun:test";
|
import { describe, expect, it } from "bun:test";
|
||||||
|
import type { ClickHouseClient } from "../src/clickhouse";
|
||||||
import { alertsTableDDL, ALERTS_TABLE, fromAlertRecord, toAlertRecord } from "../src/alerts";
|
import { alertsTableDDL, ALERTS_TABLE, fromAlertRecord, toAlertRecord } from "../src/alerts";
|
||||||
|
import { fetchAlertContextByTraceId } from "../src/clickhouse";
|
||||||
|
import { toFlowPacketRecord } from "../src/flow-packets";
|
||||||
|
|
||||||
const alert = {
|
const alert = {
|
||||||
source_ts: 10,
|
source_ts: 10,
|
||||||
|
|
@ -19,6 +22,62 @@ const alert = {
|
||||||
evidence_refs: ["flowpacket:1", "print:1"]
|
evidence_refs: ["flowpacket:1", "print:1"]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const packet = {
|
||||||
|
source_ts: 11,
|
||||||
|
ingest_ts: 21,
|
||||||
|
seq: 2,
|
||||||
|
trace_id: "flowpacket:1",
|
||||||
|
id: "flowpacket:1",
|
||||||
|
members: ["print:1"],
|
||||||
|
features: {
|
||||||
|
option_contract_id: "SPY-2026-06-19-500-C",
|
||||||
|
count: 1,
|
||||||
|
total_size: 50
|
||||||
|
},
|
||||||
|
join_quality: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const print = {
|
||||||
|
source_ts: 12,
|
||||||
|
ingest_ts: 22,
|
||||||
|
seq: 3,
|
||||||
|
trace_id: "print:1",
|
||||||
|
ts: 12,
|
||||||
|
option_contract_id: "SPY-2026-06-19-500-C",
|
||||||
|
price: 1.45,
|
||||||
|
size: 50,
|
||||||
|
exchange: "XTEST",
|
||||||
|
conditions: [],
|
||||||
|
nbbo_side: "A",
|
||||||
|
execution_nbbo_bid: 1.4,
|
||||||
|
execution_nbbo_ask: 1.5,
|
||||||
|
execution_nbbo_mid: 1.45,
|
||||||
|
execution_nbbo_spread: 0.1,
|
||||||
|
execution_nbbo_age_ms: 14,
|
||||||
|
execution_nbbo_side: "A",
|
||||||
|
execution_underlying_spot: 500.25,
|
||||||
|
execution_underlying_bid: 500.2,
|
||||||
|
execution_underlying_ask: 500.3,
|
||||||
|
execution_underlying_mid: 500.25,
|
||||||
|
execution_underlying_age_ms: 9,
|
||||||
|
execution_iv: 0.31,
|
||||||
|
signal_reasons: [],
|
||||||
|
signal_pass: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeClient = (resolver: (query: string) => unknown[]): ClickHouseClient =>
|
||||||
|
({
|
||||||
|
exec: async () => {},
|
||||||
|
insert: async () => {},
|
||||||
|
ping: async () => ({ success: true }),
|
||||||
|
close: async () => {},
|
||||||
|
query: async ({ query }: { query: string }) => ({
|
||||||
|
async json<T>() {
|
||||||
|
return resolver(query) as T;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}) as ClickHouseClient;
|
||||||
|
|
||||||
describe("alerts storage helpers", () => {
|
describe("alerts storage helpers", () => {
|
||||||
it("includes the correct table name in the DDL", () => {
|
it("includes the correct table name in the DDL", () => {
|
||||||
const ddl = alertsTableDDL();
|
const ddl = alertsTableDDL();
|
||||||
|
|
@ -33,4 +92,51 @@ describe("alerts storage helpers", () => {
|
||||||
expect(restored.evidence_refs).toEqual(alert.evidence_refs);
|
expect(restored.evidence_refs).toEqual(alert.evidence_refs);
|
||||||
expect(restored.severity).toBe(alert.severity);
|
expect(restored.severity).toBe(alert.severity);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("fetches persisted alert context and reports unresolved refs", async () => {
|
||||||
|
const contextAlert = {
|
||||||
|
...alert,
|
||||||
|
trace_id: "alert:ctx",
|
||||||
|
evidence_refs: ["flowpacket:1", "print:1", "print:missing"]
|
||||||
|
};
|
||||||
|
const queries: string[] = [];
|
||||||
|
const client = makeClient((query) => {
|
||||||
|
queries.push(query);
|
||||||
|
if (query.includes(ALERTS_TABLE)) {
|
||||||
|
return [toAlertRecord(contextAlert)];
|
||||||
|
}
|
||||||
|
if (query.includes("flow_packets")) {
|
||||||
|
return [toFlowPacketRecord(packet)];
|
||||||
|
}
|
||||||
|
if (query.includes("option_prints")) {
|
||||||
|
return [print];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const bundle = await fetchAlertContextByTraceId(client, "alert:ctx");
|
||||||
|
|
||||||
|
expect(bundle.alert?.trace_id).toBe("alert:ctx");
|
||||||
|
expect(bundle.flow_packets.map((item) => item.id)).toEqual(["flowpacket:1"]);
|
||||||
|
expect(bundle.option_prints.map((item) => item.trace_id)).toEqual(["print:1"]);
|
||||||
|
expect(bundle.option_prints[0]?.execution_nbbo_side).toBe("A");
|
||||||
|
expect(bundle.option_prints[0]?.execution_nbbo_bid).toBe(1.4);
|
||||||
|
expect(bundle.option_prints[0]?.execution_underlying_spot).toBe(500.25);
|
||||||
|
expect(bundle.option_prints[0]?.execution_iv).toBe(0.31);
|
||||||
|
expect(bundle.missing_refs).toEqual(["print:missing"]);
|
||||||
|
expect(queries[0]).toContain("trace_id = 'alert:ctx'");
|
||||||
|
expect(queries[1]).toContain("id IN");
|
||||||
|
expect(queries[2]).toContain("trace_id IN ('print:1', 'print:missing')");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an empty context when the alert is missing", async () => {
|
||||||
|
const bundle = await fetchAlertContextByTraceId(makeClient(() => []), "alert:missing");
|
||||||
|
|
||||||
|
expect(bundle).toEqual({
|
||||||
|
alert: null,
|
||||||
|
flow_packets: [],
|
||||||
|
option_prints: [],
|
||||||
|
missing_refs: []
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ type DeployOptions = {
|
||||||
mode: DeployMode;
|
mode: DeployMode;
|
||||||
runtime: DeployRuntime;
|
runtime: DeployRuntime;
|
||||||
scope: DeployScope;
|
scope: DeployScope;
|
||||||
|
fast: boolean;
|
||||||
forceRecreate: boolean;
|
forceRecreate: boolean;
|
||||||
noBuild: boolean;
|
noBuild: boolean;
|
||||||
};
|
};
|
||||||
|
|
@ -30,13 +31,13 @@ const SSH_OPTIONS = [
|
||||||
"BatchMode=yes"
|
"BatchMode=yes"
|
||||||
];
|
];
|
||||||
const ALLOWED_REMOTE_UNTRACKED = new Set([
|
const ALLOWED_REMOTE_UNTRACKED = new Set([
|
||||||
"deployment/docker/signal-cli-0.14.3-Linux-native.tar.gz",
|
"deployment/docker/signal-cli-0.14.3-Linux-native.tar.gz"
|
||||||
"deployment/npm/"
|
|
||||||
]);
|
]);
|
||||||
const PUBLIC_APP_URL =
|
const PUBLIC_APP_URL =
|
||||||
process.env.DEPLOY_PUBLIC_APP_URL?.trim() || "https://flow.deltaisland.io";
|
process.env.DEPLOY_PUBLIC_APP_URL?.trim() || "https://flow.deltaisland.io";
|
||||||
const PUBLIC_API_HEALTH_URL =
|
const PUBLIC_API_HEALTH_URL =
|
||||||
process.env.DEPLOY_PUBLIC_API_HEALTH_URL?.trim() || null;
|
process.env.DEPLOY_PUBLIC_API_HEALTH_URL?.trim() || null;
|
||||||
|
const DEPLOY_GIT_REMOTE_OVERRIDE = process.env.DEPLOY_GIT_REMOTE?.trim() || null;
|
||||||
const NATIVE_SYSTEMCTL_PREFIX =
|
const NATIVE_SYSTEMCTL_PREFIX =
|
||||||
process.env.DEPLOY_NATIVE_SYSTEMCTL_PREFIX?.trim() || "sudo -n systemctl";
|
process.env.DEPLOY_NATIVE_SYSTEMCTL_PREFIX?.trim() || "sudo -n systemctl";
|
||||||
const NATIVE_UNITS = {
|
const NATIVE_UNITS = {
|
||||||
|
|
@ -70,12 +71,12 @@ const repoRoot = path.resolve(path.dirname(scriptPath), "..");
|
||||||
|
|
||||||
function usage(exitCode = 1): never {
|
function usage(exitCode = 1): never {
|
||||||
console.error(`Usage:
|
console.error(`Usage:
|
||||||
./deploy main [--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] [--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] [--no-build] [--force-recreate]
|
./deploy current branch [--runtime docker|native] [--web-only|--api-only|--services-only] [--fast] [--no-build] [--force-recreate]
|
||||||
|
|
||||||
Modes:
|
Modes:
|
||||||
main Deploy origin/main to the live server checkout.
|
main Deploy <remote>/main to the live server checkout.
|
||||||
current-branch Push the current local branch, switch the server to it, and deploy it.
|
current-branch Push the current local branch, switch the server to it, and deploy it.
|
||||||
|
|
||||||
Runtimes:
|
Runtimes:
|
||||||
|
|
@ -90,11 +91,13 @@ Scopes:
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--runtime <name> Explicit runtime selector (docker or native).
|
--runtime <name> 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.
|
--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.
|
--force-recreate Docker-only escalation path for docker compose when a normal refresh is not enough.
|
||||||
--help Show this help text.
|
--help Show this help text.
|
||||||
|
|
||||||
Environment:
|
Environment:
|
||||||
|
DEPLOY_GIT_REMOTE Override git remote used for deploy fetch/pull/push (auto-detected by default).
|
||||||
DEPLOY_PUBLIC_APP_URL Override the public app URL (default: https://flow.deltaisland.io).
|
DEPLOY_PUBLIC_APP_URL Override the public app URL (default: https://flow.deltaisland.io).
|
||||||
DEPLOY_PUBLIC_API_HEALTH_URL Optional separate public API health URL for two-origin deployments.
|
DEPLOY_PUBLIC_API_HEALTH_URL Optional separate public API health URL for two-origin deployments.
|
||||||
DEPLOY_NATIVE_SYSTEMCTL_PREFIX Override systemctl invocation for native rollouts (default: sudo -n systemctl).
|
DEPLOY_NATIVE_SYSTEMCTL_PREFIX Override systemctl invocation for native rollouts (default: sudo -n systemctl).
|
||||||
|
|
@ -154,6 +157,23 @@ function captureChecked(
|
||||||
return result.stdout ?? "";
|
return result.stdout ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tryCapture(
|
||||||
|
command: string,
|
||||||
|
args: string[],
|
||||||
|
options: SpawnSyncOptions = {}
|
||||||
|
): string | null {
|
||||||
|
const result = spawnSync(command, args, {
|
||||||
|
cwd: repoRoot,
|
||||||
|
encoding: "utf8",
|
||||||
|
stdio: ["inherit", "pipe", "pipe"],
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
if (result.status !== 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return result.stdout ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
function runRemoteScript(
|
function runRemoteScript(
|
||||||
title: string,
|
title: string,
|
||||||
script: string,
|
script: string,
|
||||||
|
|
@ -219,11 +239,13 @@ function parseArgs(rawArgs: string[]): DeployOptions {
|
||||||
|
|
||||||
const runtime = parseRuntime(rawArgs);
|
const runtime = parseRuntime(rawArgs);
|
||||||
const scope = parseScope(rawArgs);
|
const scope = parseScope(rawArgs);
|
||||||
|
const fast = rawArgs.includes("--fast");
|
||||||
const forceRecreate = rawArgs.includes("--force-recreate");
|
const forceRecreate = rawArgs.includes("--force-recreate");
|
||||||
const noBuild = rawArgs.includes("--no-build");
|
const noBuild = rawArgs.includes("--no-build");
|
||||||
const positional = rawArgs.filter(
|
const positional = rawArgs.filter(
|
||||||
(arg, index) =>
|
(arg, index) =>
|
||||||
arg !== "--force-recreate" &&
|
arg !== "--force-recreate" &&
|
||||||
|
arg !== "--fast" &&
|
||||||
arg !== "--no-build" &&
|
arg !== "--no-build" &&
|
||||||
arg !== "--web-only" &&
|
arg !== "--web-only" &&
|
||||||
arg !== "--api-only" &&
|
arg !== "--api-only" &&
|
||||||
|
|
@ -239,7 +261,7 @@ function parseArgs(rawArgs: string[]): DeployOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (positional.length === 1 && positional[0] === "main") {
|
if (positional.length === 1 && positional[0] === "main") {
|
||||||
return { mode: "main", runtime, scope, forceRecreate, noBuild };
|
return { mode: "main", runtime, scope, fast, forceRecreate, noBuild };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
@ -250,6 +272,7 @@ function parseArgs(rawArgs: string[]): DeployOptions {
|
||||||
mode: "current-branch",
|
mode: "current-branch",
|
||||||
runtime,
|
runtime,
|
||||||
scope,
|
scope,
|
||||||
|
fast,
|
||||||
forceRecreate,
|
forceRecreate,
|
||||||
noBuild
|
noBuild
|
||||||
};
|
};
|
||||||
|
|
@ -276,6 +299,83 @@ function shellPattern(value: string): string {
|
||||||
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseUpstreamRemote(upstreamRef: string | null): string | null {
|
||||||
|
if (!upstreamRef) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const trimmed = upstreamRef.trim();
|
||||||
|
if (!trimmed || !trimmed.includes("/")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return trimmed.split("/", 1)[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function localGitRemotes(): string[] {
|
||||||
|
const raw = tryCapture("git", ["remote"]);
|
||||||
|
if (!raw) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
.split("\n")
|
||||||
|
.map((value) => value.trim())
|
||||||
|
.filter((value) => value.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function localHasRemote(name: string): boolean {
|
||||||
|
return spawnSync("git", ["remote", "get-url", name], {
|
||||||
|
cwd: repoRoot,
|
||||||
|
stdio: "ignore"
|
||||||
|
}).status === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDeployRemote(mode: DeployMode, branch: string | null): string {
|
||||||
|
const candidates: string[] = [];
|
||||||
|
|
||||||
|
if (DEPLOY_GIT_REMOTE_OVERRIDE) {
|
||||||
|
candidates.push(DEPLOY_GIT_REMOTE_OVERRIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === "current-branch" && branch) {
|
||||||
|
const branchRemote = tryCapture("git", ["config", "--get", `branch.${branch}.remote`])?.trim();
|
||||||
|
if (branchRemote) {
|
||||||
|
candidates.push(branchRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upstreamRef = tryCapture("git", [
|
||||||
|
"rev-parse",
|
||||||
|
"--abbrev-ref",
|
||||||
|
"--symbolic-full-name",
|
||||||
|
"@{u}"
|
||||||
|
]);
|
||||||
|
const upstreamRemote = parseUpstreamRemote(upstreamRef);
|
||||||
|
if (upstreamRemote) {
|
||||||
|
candidates.push(upstreamRemote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainRemote = tryCapture("git", ["config", "--get", "branch.main.remote"])?.trim();
|
||||||
|
if (mainRemote) {
|
||||||
|
candidates.push(mainRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.push("forgejo", "origin", "github", ...localGitRemotes());
|
||||||
|
|
||||||
|
const deduped = Array.from(new Set(candidates.filter((value) => value.length > 0)));
|
||||||
|
const selected = deduped.find((name) => localHasRemote(name));
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(
|
||||||
|
`Unable to resolve a deploy git remote. Checked candidates: ${deduped.join(", ")}`
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
"Set DEPLOY_GIT_REMOTE to a valid remote name or configure branch.<name>.remote."
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
function describeRuntime(runtime: DeployRuntime): string {
|
function describeRuntime(runtime: DeployRuntime): string {
|
||||||
return runtime === "docker" ? "Docker Compose" : "experimental native systemd/Bun";
|
return runtime === "docker" ? "Docker Compose" : "experimental native systemd/Bun";
|
||||||
}
|
}
|
||||||
|
|
@ -303,6 +403,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 {
|
function scopeIncludesWeb(scope: DeployScope): boolean {
|
||||||
return scope === "full" || scope === "web";
|
return scope === "full" || scope === "web";
|
||||||
}
|
}
|
||||||
|
|
@ -393,12 +500,12 @@ function localRuntimePrecheck(runtime: DeployRuntime, noBuild: boolean): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function localMainPrecheck(runtime: DeployRuntime, noBuild: boolean): void {
|
function localMainPrecheck(remote: string, runtime: DeployRuntime, noBuild: boolean): void {
|
||||||
section("Local Precheck");
|
section("Local Precheck");
|
||||||
runChecked("git", ["fetch", "origin"]);
|
runChecked("git", ["fetch", remote]);
|
||||||
runChecked("git", ["status", "--short", "--branch"]);
|
runChecked("git", ["status", "--short", "--branch"]);
|
||||||
runChecked("git", ["rev-parse", "--verify", "HEAD"]);
|
runChecked("git", ["rev-parse", "--verify", "HEAD"]);
|
||||||
runChecked("git", ["rev-parse", "origin/main"]);
|
runChecked("git", ["rev-parse", `${remote}/main`]);
|
||||||
localRuntimePrecheck(runtime, noBuild);
|
localRuntimePrecheck(runtime, noBuild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -412,6 +519,7 @@ function currentBranchName(): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function localBranchPrecheck(
|
function localBranchPrecheck(
|
||||||
|
remote: string,
|
||||||
branch: string,
|
branch: string,
|
||||||
runtime: DeployRuntime,
|
runtime: DeployRuntime,
|
||||||
noBuild: boolean
|
noBuild: boolean
|
||||||
|
|
@ -419,7 +527,7 @@ function localBranchPrecheck(
|
||||||
section("Local Precheck");
|
section("Local Precheck");
|
||||||
runChecked("git", ["branch", "--show-current"]);
|
runChecked("git", ["branch", "--show-current"]);
|
||||||
runChecked("git", ["status", "--short", "--branch"]);
|
runChecked("git", ["status", "--short", "--branch"]);
|
||||||
runChecked("git", ["fetch", "origin"]);
|
runChecked("git", ["fetch", remote]);
|
||||||
|
|
||||||
const porcelain = captureChecked("git", ["status", "--porcelain=v1"]).trim();
|
const porcelain = captureChecked("git", ["status", "--porcelain=v1"]).trim();
|
||||||
if (porcelain) {
|
if (porcelain) {
|
||||||
|
|
@ -432,7 +540,7 @@ function localBranchPrecheck(
|
||||||
localRuntimePrecheck(runtime, noBuild);
|
localRuntimePrecheck(runtime, noBuild);
|
||||||
}
|
}
|
||||||
|
|
||||||
function publishCurrentBranch(branch: string): void {
|
function publishCurrentBranch(remote: string, branch: string): void {
|
||||||
section("Local Publish");
|
section("Local Publish");
|
||||||
const upstreamResult = spawnSync(
|
const upstreamResult = spawnSync(
|
||||||
"git",
|
"git",
|
||||||
|
|
@ -445,11 +553,11 @@ function publishCurrentBranch(branch: string): void {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (upstreamResult.status === 0) {
|
if (upstreamResult.status === 0) {
|
||||||
runChecked("git", ["push", "origin", branch]);
|
runChecked("git", ["push", remote, branch]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
runChecked("git", ["push", "-u", "origin", branch]);
|
runChecked("git", ["push", "-u", remote, branch]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function remoteGitPrecheck(): void {
|
function remoteGitPrecheck(): void {
|
||||||
|
|
@ -557,18 +665,20 @@ done
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function remoteGitUpdateScript(mode: DeployMode, branch: string | null): string {
|
function remoteGitUpdateScript(mode: DeployMode, remote: string, branch: string | null): string {
|
||||||
const escapedBranch = branch ? shellEscape(branch) : null;
|
const escapedBranch = branch ? shellEscape(branch) : null;
|
||||||
|
const escapedRemote = shellEscape(remote);
|
||||||
const switchCommand =
|
const switchCommand =
|
||||||
mode === "main"
|
mode === "main"
|
||||||
? `git switch main\ngit pull --ff-only origin main`
|
? `git switch main\ngit pull --ff-only ${escapedRemote} main`
|
||||||
: `git switch ${escapedBranch} || git switch -c ${escapedBranch} --track origin/${escapedBranch}\ngit pull --ff-only origin ${escapedBranch}`;
|
: `git switch ${escapedBranch} || git switch -c ${escapedBranch} --track ${escapedRemote}/${escapedBranch}\ngit pull --ff-only ${escapedRemote} ${escapedBranch}`;
|
||||||
|
|
||||||
return `cd ${shellEscape(REMOTE_REPO)}\ngit fetch origin\n${switchCommand}`;
|
return `cd ${shellEscape(REMOTE_REPO)}\ngit remote get-url ${escapedRemote} >/dev/null\ngit fetch ${escapedRemote}\n${switchCommand}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function remoteDockerRollout(
|
function remoteDockerRollout(
|
||||||
mode: DeployMode,
|
mode: DeployMode,
|
||||||
|
remote: string,
|
||||||
branch: string | null,
|
branch: string | null,
|
||||||
scope: DeployScope,
|
scope: DeployScope,
|
||||||
forceRecreate: boolean,
|
forceRecreate: boolean,
|
||||||
|
|
@ -590,7 +700,7 @@ function remoteDockerRollout(
|
||||||
`#!/usr/bin/env bash
|
`#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
${remoteGitUpdateScript(mode, branch)}
|
${remoteGitUpdateScript(mode, remote, branch)}
|
||||||
|
|
||||||
cd ${shellEscape(REMOTE_DOCKER_DEPLOYMENT)}
|
cd ${shellEscape(REMOTE_DOCKER_DEPLOYMENT)}
|
||||||
${buildCommand ? `${buildCommand}\n` : ""}${upCommand}
|
${buildCommand ? `${buildCommand}\n` : ""}${upCommand}
|
||||||
|
|
@ -600,6 +710,7 @@ ${buildCommand ? `${buildCommand}\n` : ""}${upCommand}
|
||||||
|
|
||||||
function remoteNativeRollout(
|
function remoteNativeRollout(
|
||||||
mode: DeployMode,
|
mode: DeployMode,
|
||||||
|
remote: string,
|
||||||
branch: string | null,
|
branch: string | null,
|
||||||
scope: DeployScope,
|
scope: DeployScope,
|
||||||
noBuild: boolean
|
noBuild: boolean
|
||||||
|
|
@ -621,7 +732,7 @@ function remoteNativeRollout(
|
||||||
`#!/usr/bin/env bash
|
`#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
${remoteGitUpdateScript(mode, branch)}
|
${remoteGitUpdateScript(mode, remote, branch)}
|
||||||
|
|
||||||
cd ${shellEscape(REMOTE_REPO)}
|
cd ${shellEscape(REMOTE_REPO)}
|
||||||
${buildSteps.join("\n")}
|
${buildSteps.join("\n")}
|
||||||
|
|
@ -636,6 +747,7 @@ done
|
||||||
|
|
||||||
function remoteRollout(
|
function remoteRollout(
|
||||||
mode: DeployMode,
|
mode: DeployMode,
|
||||||
|
remote: string,
|
||||||
runtime: DeployRuntime,
|
runtime: DeployRuntime,
|
||||||
branch: string | null,
|
branch: string | null,
|
||||||
scope: DeployScope,
|
scope: DeployScope,
|
||||||
|
|
@ -643,21 +755,23 @@ function remoteRollout(
|
||||||
noBuild: boolean
|
noBuild: boolean
|
||||||
): void {
|
): void {
|
||||||
if (runtime === "docker") {
|
if (runtime === "docker") {
|
||||||
remoteDockerRollout(mode, branch, scope, forceRecreate, noBuild);
|
remoteDockerRollout(mode, remote, branch, scope, forceRecreate, noBuild);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteNativeRollout(mode, branch, scope, noBuild);
|
remoteNativeRollout(mode, remote, branch, scope, noBuild);
|
||||||
}
|
}
|
||||||
|
|
||||||
function remoteDockerVerification(scope: DeployScope): void {
|
function remoteDockerVerification(scope: DeployScope, fast: boolean): void {
|
||||||
const psServices = dockerServicesForScope(scope);
|
const psServices = dockerServicesForScope(scope);
|
||||||
const logServices = dockerLogServicesForScope(scope);
|
const logServices = dockerLogServicesForScope(scope);
|
||||||
const psCommand =
|
const psCommand =
|
||||||
psServices.length > 0
|
psServices.length > 0
|
||||||
? `docker compose ps ${psServices.join(" ")}`
|
? `docker compose ps ${psServices.join(" ")}`
|
||||||
: "docker compose ps";
|
: "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[] = [];
|
const checks: string[] = [];
|
||||||
|
|
||||||
if (scopeIncludesApi(scope)) {
|
if (scopeIncludesApi(scope)) {
|
||||||
|
|
@ -685,7 +799,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 units = nativeUnitsForScope(scope).map((value) => shellEscape(value)).join(" ");
|
||||||
const checks: string[] = [];
|
const checks: string[] = [];
|
||||||
|
|
||||||
|
|
@ -705,26 +819,29 @@ set -euo pipefail
|
||||||
declare -a units=(${units})
|
declare -a units=(${units})
|
||||||
for unit in "\${units[@]}"; do
|
for unit in "\${units[@]}"; do
|
||||||
${NATIVE_SYSTEMCTL_PREFIX} is-active --quiet "$unit"
|
${NATIVE_SYSTEMCTL_PREFIX} is-active --quiet "$unit"
|
||||||
${NATIVE_SYSTEMCTL_PREFIX} status --no-pager "$unit" || 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`}
|
||||||
journalctl -u "$unit" -n 50 --no-pager || true
|
|
||||||
done
|
done
|
||||||
${checks.join("\n")}
|
${checks.join("\n")}
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function remoteVerification(runtime: DeployRuntime, scope: DeployScope): void {
|
function remoteVerification(runtime: DeployRuntime, scope: DeployScope, fast: boolean): void {
|
||||||
if (runtime === "docker") {
|
if (runtime === "docker") {
|
||||||
remoteDockerVerification(scope);
|
remoteDockerVerification(scope, fast);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteNativeVerification(scope);
|
remoteNativeVerification(scope, fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
function publicVerification(scope: DeployScope): void {
|
function publicVerification(scope: DeployScope, fast: boolean): void {
|
||||||
section("Public Verification");
|
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) {
|
if (scopeIncludesApi(scope) && PUBLIC_API_HEALTH_URL) {
|
||||||
runChecked("curl", ["-fksS", PUBLIC_API_HEALTH_URL]);
|
runChecked("curl", ["-fksS", PUBLIC_API_HEALTH_URL]);
|
||||||
|
|
@ -732,50 +849,69 @@ function publicVerification(scope: DeployScope): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scopeIncludesApi(scope)) {
|
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]);
|
runChecked("bun", ["run", "scripts/check-public-api-routes.ts", PUBLIC_APP_URL]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function main(): void {
|
function main(): void {
|
||||||
const options = parseArgs(process.argv.slice(2));
|
const options = parseArgs(process.argv.slice(2));
|
||||||
|
const scope = effectiveScope(options.scope, options.fast);
|
||||||
|
const currentBranch = options.mode === "current-branch" ? currentBranchName() : null;
|
||||||
|
const deployRemote = resolveDeployRemote(options.mode, currentBranch);
|
||||||
assertSshKeyExists();
|
assertSshKeyExists();
|
||||||
printRuntimeAdvisory(options.runtime);
|
printRuntimeAdvisory(options.runtime);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`Deploying ${options.mode === "main" ? "origin/main" : "the current local branch"} ` +
|
`Deploying ${options.mode === "main" ? `${deployRemote}/main` : "the current local branch"} ` +
|
||||||
`via ${describeRuntime(options.runtime)} (${describeScope(options.scope)}).`
|
`via ${describeRuntime(options.runtime)} (${describeScope(scope)}${options.fast ? ", fast mode" : ""}).`
|
||||||
);
|
);
|
||||||
|
console.log(`[deploy] Using git remote: ${deployRemote}`);
|
||||||
|
if (options.fast && options.scope === "full") {
|
||||||
|
console.log("[deploy] Fast mode changed default full scope to --services-only.");
|
||||||
|
}
|
||||||
|
|
||||||
if (options.mode === "main") {
|
if (options.mode === "main") {
|
||||||
localMainPrecheck(options.runtime, options.noBuild);
|
localMainPrecheck(deployRemote, options.runtime, options.noBuild);
|
||||||
remoteGitPrecheck();
|
remoteGitPrecheck();
|
||||||
remoteRuntimePrecheck(options.runtime, options.scope);
|
remoteRuntimePrecheck(options.runtime, scope);
|
||||||
remoteRollout(
|
remoteRollout(
|
||||||
options.mode,
|
options.mode,
|
||||||
|
deployRemote,
|
||||||
options.runtime,
|
options.runtime,
|
||||||
null,
|
null,
|
||||||
options.scope,
|
scope,
|
||||||
options.forceRecreate,
|
options.forceRecreate,
|
||||||
options.noBuild
|
options.noBuild
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const branch = currentBranchName();
|
const branch = currentBranch;
|
||||||
localBranchPrecheck(branch, options.runtime, options.noBuild);
|
if (!branch) {
|
||||||
publishCurrentBranch(branch);
|
console.error("Unable to resolve current branch for current-branch deploy mode.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
localBranchPrecheck(deployRemote, branch, options.runtime, options.noBuild);
|
||||||
|
publishCurrentBranch(deployRemote, branch);
|
||||||
remoteGitPrecheck();
|
remoteGitPrecheck();
|
||||||
remoteRuntimePrecheck(options.runtime, options.scope);
|
remoteRuntimePrecheck(options.runtime, scope);
|
||||||
remoteRollout(
|
remoteRollout(
|
||||||
options.mode,
|
options.mode,
|
||||||
|
deployRemote,
|
||||||
options.runtime,
|
options.runtime,
|
||||||
branch,
|
branch,
|
||||||
options.scope,
|
scope,
|
||||||
options.forceRecreate,
|
options.forceRecreate,
|
||||||
options.noBuild
|
options.noBuild
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteVerification(options.runtime, options.scope);
|
remoteVerification(options.runtime, scope, options.fast);
|
||||||
publicVerification(options.scope);
|
publicVerification(scope, options.fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|
|
||||||
21
services/api/src/alert-context.ts
Normal file
21
services/api/src/alert-context.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const alertContextTraceIdSchema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.max(256)
|
||||||
|
.regex(/^[A-Za-z0-9][A-Za-z0-9:_./-]*$/);
|
||||||
|
|
||||||
|
export const isAlertContextPath = (pathname: string): boolean => {
|
||||||
|
return /^\/flow\/alerts\/[^/]+\/context$/.test(pathname);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseAlertContextTraceIdPath = (pathname: string): string | null => {
|
||||||
|
if (!isAlertContextPath(pathname)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const encodedTraceId = pathname.slice("/flow/alerts/".length, -"/context".length);
|
||||||
|
return alertContextTraceIdSchema.parse(decodeURIComponent(encodedTraceId));
|
||||||
|
};
|
||||||
|
|
@ -47,12 +47,14 @@ import {
|
||||||
ensureOptionPrintsTable,
|
ensureOptionPrintsTable,
|
||||||
fetchAlertsAfter,
|
fetchAlertsAfter,
|
||||||
fetchAlertsBefore,
|
fetchAlertsBefore,
|
||||||
|
fetchAlertContextByTraceId,
|
||||||
fetchClassifierHitsAfter,
|
fetchClassifierHitsAfter,
|
||||||
fetchClassifierHitsBefore,
|
fetchClassifierHitsBefore,
|
||||||
fetchSmartMoneyEventsAfter,
|
fetchSmartMoneyEventsAfter,
|
||||||
fetchSmartMoneyEventsBefore,
|
fetchSmartMoneyEventsBefore,
|
||||||
fetchFlowPacketsAfter,
|
fetchFlowPacketsAfter,
|
||||||
fetchFlowPacketById,
|
fetchFlowPacketById,
|
||||||
|
fetchAlertContextByTraceId,
|
||||||
fetchFlowPacketsByMemberTraceIds,
|
fetchFlowPacketsByMemberTraceIds,
|
||||||
fetchFlowPacketsBefore,
|
fetchFlowPacketsBefore,
|
||||||
fetchRecentAlerts,
|
fetchRecentAlerts,
|
||||||
|
|
@ -118,6 +120,7 @@ import {
|
||||||
resolveLiveStateConfig,
|
resolveLiveStateConfig,
|
||||||
shouldFanoutLiveEvent
|
shouldFanoutLiveEvent
|
||||||
} from "./live";
|
} from "./live";
|
||||||
|
import { isAlertContextPath, parseAlertContextTraceIdPath } from "./alert-context";
|
||||||
import { parseOptionPrintQuery } from "./option-queries";
|
import { parseOptionPrintQuery } from "./option-queries";
|
||||||
import {
|
import {
|
||||||
buildSyntheticDerivedStatus,
|
buildSyntheticDerivedStatus,
|
||||||
|
|
@ -1487,6 +1490,25 @@ const run = async () => {
|
||||||
return jsonResponse({ data });
|
return jsonResponse({ data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.method === "GET" && isAlertContextPath(url.pathname)) {
|
||||||
|
try {
|
||||||
|
const traceId = parseAlertContextTraceIdPath(url.pathname);
|
||||||
|
if (traceId === null) {
|
||||||
|
return jsonResponse({ error: "not found" }, 404);
|
||||||
|
}
|
||||||
|
const data = await fetchAlertContextByTraceId(clickhouse, traceId);
|
||||||
|
return jsonResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return jsonResponse(
|
||||||
|
{
|
||||||
|
error: "invalid alert context query",
|
||||||
|
detail: error instanceof Error ? error.message : String(error)
|
||||||
|
},
|
||||||
|
400
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (req.method === "GET" && url.pathname === "/history/options") {
|
if (req.method === "GET" && url.pathname === "/history/options") {
|
||||||
try {
|
try {
|
||||||
const { beforeTs, beforeSeq, limit } = parseBeforeParams(url);
|
const { beforeTs, beforeSeq, limit } = parseBeforeParams(url);
|
||||||
|
|
@ -1591,6 +1613,17 @@ const run = async () => {
|
||||||
return jsonResponse({ data });
|
return jsonResponse({ data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.method === "GET" && /^\/flow\/alerts\/[^/]+\/context$/.test(url.pathname)) {
|
||||||
|
const traceId = decodeURIComponent(
|
||||||
|
url.pathname.slice("/flow/alerts/".length, -"/context".length)
|
||||||
|
).trim();
|
||||||
|
if (!traceId || traceId.length > 512) {
|
||||||
|
return jsonResponse({ error: "invalid alert trace id" }, 400);
|
||||||
|
}
|
||||||
|
const data = await fetchAlertContextByTraceId(clickhouse, traceId);
|
||||||
|
return jsonResponse(data);
|
||||||
|
}
|
||||||
|
|
||||||
if (req.method === "GET" && url.pathname === "/option-prints/by-trace") {
|
if (req.method === "GET" && url.pathname === "/option-prints/by-trace") {
|
||||||
const traceIds = url.searchParams.getAll("trace_id");
|
const traceIds = url.searchParams.getAll("trace_id");
|
||||||
const data = await fetchOptionPrintsByTraceIds(clickhouse, traceIds);
|
const data = await fetchOptionPrintsByTraceIds(clickhouse, traceIds);
|
||||||
|
|
|
||||||
18
services/api/tests/alert-context.test.ts
Normal file
18
services/api/tests/alert-context.test.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { describe, expect, it } from "bun:test";
|
||||||
|
import { isAlertContextPath, parseAlertContextTraceIdPath } from "../src/alert-context";
|
||||||
|
|
||||||
|
describe("alert context route helpers", () => {
|
||||||
|
it("extracts a valid alert trace id from the context endpoint path", () => {
|
||||||
|
expect(parseAlertContextTraceIdPath("/flow/alerts/alert%3Actx%2Fone/context")).toBe("alert:ctx/one");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null for unrelated alert paths", () => {
|
||||||
|
expect(isAlertContextPath("/flow/alerts")).toBe(false);
|
||||||
|
expect(parseAlertContextTraceIdPath("/flow/alerts/alert:ctx")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects malformed trace ids safely", () => {
|
||||||
|
expect(() => parseAlertContextTraceIdPath("/flow/alerts/%20/context")).toThrow();
|
||||||
|
expect(() => parseAlertContextTraceIdPath("/flow/alerts/%24bad/context")).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue