merge main into nextjs upgrade

This commit is contained in:
dirtydishes 2026-05-19 14:47:43 -04:00
commit 171cf52518
40 changed files with 2355 additions and 131 deletions

View file

@ -0,0 +1,93 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Plan: Native Fast Iterative Deployment</title>
<style>
body { font-family: Inter, system-ui, sans-serif; margin: 40px auto; max-width: 860px; line-height: 1.55; padding: 0 16px; }
h1, h2 { line-height: 1.2; }
.meta { color: #555; margin-bottom: 20px; }
section { margin: 22px 0; }
ul { padding-left: 20px; }
code { background: #f3f4f6; padding: 2px 6px; border-radius: 6px; }
</style>
</head>
<body>
<h1>Plan: Native, Fast, Iterative Deployment (Docker Optional)</h1>
<p class="meta">Date: 2026-05-18</p>
<section>
<h2>Plan Summary</h2>
<p>Define and execute a fast iteration deployment path centered on host-native services, while preserving Docker as a fallback/runtime option.</p>
</section>
<section>
<h2>Goals</h2>
<ul>
<li>Reduce deploy turnaround time immediately.</li>
<li>Identify concrete bottlenecks with timing evidence.</li>
<li>Stabilize proxy/runtime topology for reliable production rollouts.</li>
<li>Support both native and Docker strategies with explicit guardrails.</li>
</ul>
</section>
<section>
<h2>Proposed Changes</h2>
<ul>
<li>Use scoped fast deploys short-term.</li>
<li>Audit and remediate server-state blockers (duplicate compose/project drift).</li>
<li>Prepare native runtime prerequisites and checked-in operational assets.</li>
<li>Add deployment strategy prechecks, validation matrix, and staged cutover.</li>
</ul>
</section>
<section>
<h2>Relevant Context</h2>
<ul>
<li>Open issue <code>islandflow-2db</code>: stale duplicate compose stack cleanup.</li>
<li>Open issue <code>islandflow-sz8</code>: public <code>/replay/options</code> proxy regression.</li>
<li>Open issue <code>islandflow-38p</code>: native unit templates and rollback helpers.</li>
</ul>
</section>
<section>
<h2>Implementation Steps</h2>
<ol>
<li>Stop the bleeding immediately (current deploy loop).</li>
<li>Get hard timing data per deploy phase.</li>
<li>Live server state audit (when plan mode is off).</li>
<li>Resolve duplicate compose stack first (<code>islandflow-2db</code>).</li>
<li>Fix NPM proxy route regression (<code>islandflow-sz8</code>).</li>
<li>Define target iterative deployment model.</li>
<li>Prepare native runtime prerequisites on VPS.</li>
<li>Checked-in native ops assets (<code>islandflow-38p</code>).</li>
<li>Switch proxy topology for native mode carefully.</li>
<li>Deploy strategy guardrails.</li>
<li>Validation matrix.</li>
<li>Staged cutover plan.</li>
<li>Decision: final default runtime.</li>
<li>Decision: optimization priority.</li>
<li>Decision: immediate live audit kickoff.</li>
</ol>
</section>
<section>
<h2>Risks, Limitations, and Mitigations</h2>
<ul>
<li>Risk: native runtime not yet production-hardened. Mitigation: keep Docker fallback and explicit gating.</li>
<li>Risk: proxy misrouting breaks API routes. Mitigation: route checks and post-change smoke validation.</li>
<li>Risk: operational drift on VPS. Mitigation: preflight audits and documented rollback steps.</li>
</ul>
</section>
<section>
<h2>Open Questions</h2>
<ul>
<li>Should native become the default runtime now, or after hardening milestones?</li>
<li>Should backend iteration speed be prioritized ahead of web deploy speed?</li>
<li>Do we start immediate live server audit as soon as plan mode is disabled?</li>
</ul>
</section>
</body>
</html>

View file

@ -0,0 +1,153 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>2026-05-18: Native fast iterative deploy</title>
<style>
:root {
color-scheme: dark;
--bg: #0b1020;
--panel: #131a2b;
--panel-2: #182237;
--text: #eef3ff;
--muted: #a7b4d4;
--line: #2a3651;
--accent: #7dd3fc;
--good: #86efac;
--warn: #fbbf24;
}
* { box-sizing: border-box; }
body {
margin: 0;
background: linear-gradient(180deg, #060914, var(--bg));
color: var(--text);
font: 16px/1.6 Inter, system-ui, sans-serif;
}
main {
max-width: 920px;
margin: 0 auto;
padding: 40px 20px 64px;
}
section {
margin-top: 22px;
padding: 22px 24px;
border: 1px solid var(--line);
border-radius: 18px;
background: linear-gradient(180deg, var(--panel), var(--panel-2));
}
h1, h2 { line-height: 1.15; }
h1 { margin: 0 0 12px; font-size: 2rem; }
h2 { margin: 0 0 12px; font-size: 1.15rem; }
p, li { color: var(--text); }
.meta { color: var(--muted); margin-bottom: 18px; }
.lede { color: var(--muted); max-width: 72ch; }
code, pre { font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; }
code {
padding: 0.15rem 0.35rem;
border-radius: 8px;
background: rgba(125, 211, 252, 0.12);
color: var(--accent);
}
pre {
margin: 12px 0 0;
padding: 14px 16px;
overflow: auto;
border-radius: 14px;
border: 1px solid var(--line);
background: #0a0f1d;
}
ul { margin: 0; padding-left: 1.2rem; }
.good { color: var(--good); }
.warn { color: var(--warn); }
</style>
</head>
<body>
<main>
<div class="meta">Turn document · 2026-05-18 03:29 EDT · Issues: islandflow-9rc, islandflow-38p, islandflow-bsg, islandflow-2db</div>
<h1>Native fast iterative deploy</h1>
<p class="lede">Implemented the native-first iterative deploy plan by adding deploy timing output, a safe worker-only native fast path, checked-in systemd user units and rollback helpers, server-local deploy execution, and updated live-operational documentation based on a fresh VPS audit.</p>
<section>
<h2>Summary</h2>
<p>The deploy flow now supports a safer native worker iteration model without requiring public edge cutover first. It can run directly from the VPS checkout without SSH, emits phase timings, includes checked-in native unit files plus install/rollback/smoke-test helpers, and documents the staged cutover path. During live audit, the previously reported <code>/replay/options</code> proxy issue and duplicate <code>islandflow</code> compose stack were both confirmed resolved on the host.</p>
</section>
<section>
<h2>Changes Made</h2>
<ul>
<li>Extended <code>scripts/deploy.ts</code> with deploy timing summaries for precheck, rollout, and verification phases.</li>
<li>Added <code>--workers-only</code> deploy scope for Docker and native runtimes.</li>
<li>Changed native <code>--fast</code> behavior so default full-scope fast deploys become worker-only instead of touching web/API.</li>
<li>Added native edge guardrails via <code>DEPLOY_NATIVE_EDGE_READY=1</code> before web/API native deploys are allowed.</li>
<li>Added local-server execution mode so <code>./deploy</code> can run from <code>/home/delta/islandflow</code> without SSHing back into the same host.</li>
<li>Added <code>DEPLOY_SSH_KEY_PATH</code> and <code>DEPLOY_FORCE_SSH</code> overrides for operators with non-default SSH setups.</li>
<li>Checked in native ops assets under <code>deployment/native/</code>:</li>
<li><code>install-user-units.sh</code>, <code>check-native-health.sh</code>, <code>rollback.sh</code></li>
<li>six user unit files in <code>deployment/native/systemd/user/</code></li>
<li>Updated <code>README.md</code>, <code>deployment/docker/README.md</code>, and <code>deployment/native/README.md</code> to document the worker-first model, local execution mode, validation matrix, and staged cutover guidance.</li>
<li>Synced <code>deployment/docker/workspace-root/package.json</code> so Docker workspace validation passes again.</li>
<li>Installed the checked-in user unit files onto the live VPS in disabled form under <code>~/.config/systemd/user</code>.</li>
</ul>
</section>
<section>
<h2>Context</h2>
<p>The plan targeted faster deployment iteration while avoiding a premature move of the public edge away from the current Docker + Nginx Proxy Manager topology. The practical target was to make native runtime useful immediately for backend-worker iteration, while leaving web/API cutover deliberate and reversible.</p>
</section>
<section>
<h2>Important Implementation Details</h2>
<ul>
<li>Native fast mode now defaults to <code>--workers-only</code>; Docker fast mode still defaults to <code>--services-only</code>.</li>
<li>Native deploys that include public web/API scope now fail fast unless <code>DEPLOY_NATIVE_EDGE_READY=1</code> is set.</li>
<li>Running from the live VPS checkout automatically switches deploy execution from SSH mode to local mode.</li>
<li>The checked-in native unit files are user units aimed at the current VPS layout: <code>/home/delta/islandflow</code> and <code>/home/delta/.bun/bin/bun</code>.</li>
<li><code>install-user-units.sh</code> now installs units safely without enabling anything by default; enabling is explicit and scope-based.</li>
<li><code>rollback.sh</code> intentionally uses a detached git ref to make one-off native rollback practical without rewriting branch history.</li>
</ul>
<pre>export DEPLOY_NATIVE_SYSTEMCTL_PREFIX="systemctl --user"
./deploy main --runtime native --fast
# resolves to worker-only native deploy before public edge cutover</pre>
</section>
<section>
<h2>Expected Impact for End-Users</h2>
<p>End-users should see indirect benefits first: faster backend iteration, safer operational changes, and clearer rollback paths. Public traffic behavior should remain unchanged until a deliberate native edge cutover is performed.</p>
</section>
<section>
<h2>Validation</h2>
<ul>
<li class="good">Passed: <code>bun run scripts/check-public-api-routes.ts https://flow.deltaisland.io</code></li>
<li class="good">Passed: direct public <code>/replay/options</code> curl returned JSON</li>
<li class="good">Passed: live Nginx Proxy Manager config contains <code>/replay</code> in the API route matcher</li>
<li class="good">Passed: <code>docker compose ls</code> shows no duplicate <code>islandflow</code> project</li>
<li class="good">Passed: <code>bash -n deployment/native/install-user-units.sh deployment/native/check-native-health.sh deployment/native/rollback.sh</code></li>
<li class="good">Passed: <code>systemd-analyze verify deployment/native/systemd/user/*.service</code></li>
<li class="good">Passed: <code>bun run check:docker-workspace</code> after syncing workspace snapshot</li>
<li class="good">Passed: native edge guard refusal for <code>bun run scripts/deploy.ts main --runtime native --web-only --no-build</code></li>
<li class="good">Passed: <code>./deployment/native/install-user-units.sh</code> followed by <code>systemctl --user list-unit-files 'islandflow*'</code></li>
</ul>
</section>
<section>
<h2>Issues, Limitations, and Mitigations</h2>
<ul>
<li><span class="warn">Native units were installed but not enabled or started.</span> This is intentional to avoid conflicting with the current Docker production edge.</li>
<li><span class="warn">Public web/API native deploys are still gated.</span> Mitigation: explicit <code>DEPLOY_NATIVE_EDGE_READY=1</code> acknowledgment and staged cutover documentation.</li>
<li><span class="warn">Native worker runtime has not yet been exercised live against the existing Docker worker stack.</span> Mitigation: follow-up issue to soak worker-only native units before any default-runtime decision.</li>
<li><span class="warn">The known untracked Signal CLI tarball remains in the repo checkout.</span> This is already tolerated by the deploy helper allowlist and was not changed here.</li>
</ul>
</section>
<section>
<h2>Follow-up Work</h2>
<ul>
<li>Open follow-up: <code>islandflow-vvw</code> — stage native public-edge cutover after worker soak.</li>
<li>Decide whether native should ever replace Docker as the default runtime only after worker soak data and deliberate edge cutover validation.</li>
</ul>
</section>
</main>
</body>
</html>

View file

@ -0,0 +1,521 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Turn Document - Native Public Edge Cutover</title>
<style>
:root {
color-scheme: dark;
--bg-core: #06080b;
--bg-elevated: #0b1016;
--bg-pane: #111820;
--bg-pane-2: #0d141b;
--bg-soft: rgba(255, 255, 255, 0.03);
--border-subtle: rgba(255, 255, 255, 0.12);
--border-strong: rgba(245, 166, 35, 0.32);
--text-primary: #e6edf4;
--text-dim: #90a0b2;
--text-faint: #6e7b8c;
--signal-amber: #f5a623;
--signal-amber-soft: rgba(245, 166, 35, 0.12);
--confirm-green: #25c17a;
--confirm-green-soft: rgba(37, 193, 122, 0.14);
--risk-red: #ff6b5f;
--risk-red-soft: rgba(255, 107, 95, 0.12);
--info-blue: #4da3ff;
--info-blue-soft: rgba(77, 163, 255, 0.12);
--shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
background:
radial-gradient(circle at top right, rgba(245, 166, 35, 0.12), transparent 28%),
linear-gradient(180deg, #06080b 0%, #0a1117 100%);
color: var(--text-primary);
}
main {
width: min(1080px, calc(100vw - 32px));
margin: 0 auto;
padding: 28px 0 48px;
}
.hero {
background:
linear-gradient(140deg, rgba(245, 166, 35, 0.1), transparent 42%),
linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent 100%),
var(--bg-pane);
border: 1px solid var(--border-strong);
border-radius: 16px;
box-shadow: var(--shadow);
padding: 26px 28px;
margin-bottom: 18px;
}
.eyebrow,
h2,
.meta-label,
th {
font-family: "IBM Plex Mono", monospace;
text-transform: uppercase;
letter-spacing: 0.12em;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--signal-amber);
font-size: 0.72rem;
margin-bottom: 14px;
}
h1 {
margin: 0 0 10px;
font-family: "Quantico", "IBM Plex Sans", sans-serif;
font-size: clamp(2rem, 4vw, 3rem);
line-height: 1.05;
letter-spacing: 0.06em;
}
.lead {
margin: 0;
max-width: 72ch;
color: var(--text-dim);
line-height: 1.65;
}
.meta-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 10px;
margin-top: 18px;
}
.meta-card {
padding: 12px 14px;
border-radius: 12px;
background: var(--bg-soft);
border: 1px solid var(--border-subtle);
}
.meta-label {
color: var(--text-faint);
font-size: 0.68rem;
margin-bottom: 6px;
}
.meta-value {
color: var(--text-primary);
font-size: 0.95rem;
}
section {
background: var(--bg-pane);
border: 1px solid var(--border-subtle);
border-radius: 16px;
padding: 22px 24px;
margin-bottom: 16px;
}
h2 {
margin: 0 0 14px;
font-size: 0.76rem;
color: var(--signal-amber);
}
p,
li {
line-height: 1.65;
color: var(--text-dim);
}
ul {
margin: 0;
padding-left: 20px;
}
li + li {
margin-top: 8px;
}
strong {
color: var(--text-primary);
}
code {
font-family: "IBM Plex Mono", monospace;
font-size: 0.92em;
color: var(--signal-amber);
}
pre {
margin: 12px 0 0;
padding: 14px 16px;
border-radius: 12px;
background: var(--bg-pane-2);
border: 1px solid var(--border-subtle);
overflow-x: auto;
}
pre code {
color: var(--text-primary);
}
.status-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
}
.status-card {
border-radius: 12px;
border: 1px solid var(--border-subtle);
padding: 14px;
background: var(--bg-pane-2);
}
.status-card.good {
border-color: rgba(37, 193, 122, 0.32);
background: linear-gradient(180deg, var(--confirm-green-soft), transparent), var(--bg-pane-2);
}
.status-card.warn {
border-color: rgba(77, 163, 255, 0.28);
background: linear-gradient(180deg, var(--info-blue-soft), transparent), var(--bg-pane-2);
}
.status-title {
margin: 0 0 6px;
color: var(--text-primary);
font-weight: 600;
}
.status-copy {
margin: 0;
color: var(--text-dim);
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 8px;
}
th,
td {
text-align: left;
padding: 10px 0;
border-bottom: 1px solid var(--border-subtle);
vertical-align: top;
}
th {
color: var(--text-faint);
font-size: 0.68rem;
}
td {
color: var(--text-dim);
}
.pill {
display: inline-flex;
align-items: center;
gap: 6px;
border-radius: 999px;
padding: 4px 9px;
font-family: "IBM Plex Mono", monospace;
font-size: 0.7rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.pill.good {
color: var(--confirm-green);
background: var(--confirm-green-soft);
}
.pill.warn {
color: var(--info-blue);
background: var(--info-blue-soft);
}
.pill.risk {
color: var(--risk-red);
background: var(--risk-red-soft);
}
</style>
</head>
<body>
<main>
<section class="hero">
<div class="eyebrow">Islandflow Turn Document</div>
<h1>Native Public Edge Cutover</h1>
<p class="lead">
Completed the VPS native-first cutover for Islandflow infrastructure and app services while keeping Nginx
Proxy Manager as the outer edge and Docker as the rollback path. The final state now serves
<code>flow.deltaisland.io</code> and <code>api.flow.deltaisland.io</code> from the native web and API
processes, with verified public routing and a documented follow-up for the long-term API Cloudflare posture.
</p>
<div class="meta-grid">
<div class="meta-card">
<div class="meta-label">Generated</div>
<div class="meta-value">2026-05-18 19:52 EDT</div>
</div>
<div class="meta-card">
<div class="meta-label">Primary Issue</div>
<div class="meta-value"><code>islandflow-vvw</code></div>
</div>
<div class="meta-card">
<div class="meta-label">Follow-up</div>
<div class="meta-value"><code>islandflow-fl5</code></div>
</div>
<div class="meta-card">
<div class="meta-label">Runtime State</div>
<div class="meta-value">Native active, Docker retained for rollback</div>
</div>
</div>
</section>
<section>
<h2>Summary</h2>
<p>
The repository now contains the native infra units, native cutover scripts, Docker fallback adjustments, and
public-edge retargeting logic required to run Islandflow natively on the VPS. During validation, the live NPM
edge was switched from Docker container-name upstreams to native host ports, the host firewall was adjusted so
the NPM bridge could reach the native API, and the separate public API TLS problem was resolved by correcting
the Cloudflare DNS state for <code>api.flow.deltaisland.io</code>.
</p>
</section>
<section>
<h2>Changes Made</h2>
<ul>
<li>
Added checked-in native infra operations under <code>deployment/native/</code>, including
<code>bootstrap-infra.sh</code>, <code>check-native-infra.sh</code>, <code>cutover.sh</code>,
<code>full-rollback.sh</code>, <code>start-infra.sh</code>, and the native system units for NATS, Redis,
and ClickHouse.
</li>
<li>
Extended native app runtime units so the web and API bind on host-reachable interfaces, and forced the
native options ingest service to use the synthetic adapter during the cutover.
</li>
<li>
Updated <code>services/api</code> to support explicit host binding through <code>API_HOST</code>, and fixed
JetStream retention conversion in <code>packages/bus</code> so native services can start cleanly with the
configured max-age values.
</li>
<li>
Updated the Docker fallback assets to publish loopback web/API ports, share durable host data under
<code>/var/lib/islandflow</code>, and document the native-to-Docker rollback path.
</li>
<li>
Reworked <code>deployment/native/switch-npm-edge.sh</code> so it targets the NPM bridge gateway IP instead
of <code>host.docker.internal</code>, handles the root-owned NPM SQLite database, synchronizes generated
<code>proxy_host</code> configs, and reloads NPM deterministically after the edge switch.
</li>
<li>
Created Beads follow-up issue <code>islandflow-fl5</code> for the remaining decision about whether
<code>api.flow.deltaisland.io</code> should remain DNS-only or be re-proxied through Cloudflare.
</li>
</ul>
</section>
<section>
<h2>Context</h2>
<p>
The migration started from a Docker-owned production baseline where NATS, Redis, ClickHouse, API, workers, and
web all ran in Compose, while NPM routed Islandflow traffic to Docker service names. That setup blocked a safe
native cutover for two reasons: the native services could not reach Docker-only infra reliably, and NPM could
not send public traffic to host-native processes without a deliberate upstream retarget.
</p>
<p>
The runtime model for this work is exclusive ownership. Native and Docker are not allowed to run the same API
or worker scopes in parallel because JetStream durable consumers would conflict. The objective was therefore a
phased handoff, not a mixed soak for the same queues.
</p>
</section>
<section>
<h2>Important Implementation Details</h2>
<div class="status-grid">
<article class="status-card good">
<p class="status-title">NPM edge targeting</p>
<p class="status-copy">
NPM generates <code>proxy_pass</code> from a runtime-resolved <code>$server</code> variable, so the
Docker <code>/etc/hosts</code> alias for <code>host.docker.internal</code> was not sufficient. The switch
helper now detects the NPM bridge gateway and uses that IP for native upstreams.
</p>
</article>
<article class="status-card good">
<p class="status-title">Firewall path</p>
<p class="status-copy">
The host UFW policy already allowed port <code>3000</code> but not <code>4000</code>. The live fix was a
source-scoped allow for the NPM bridge subnet so the containerized edge could reach the native API.
</p>
</article>
<article class="status-card warn">
<p class="status-title">Cloudflare API hostname</p>
<p class="status-copy">
The API hostname failure was separate from the native cutover. The hostname is now a DNS-only
<code>A</code> record pointing at the VPS, which restored public TLS and health responses.
</p>
</article>
</div>
<table>
<thead>
<tr>
<th>Area</th>
<th>Implementation detail</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Native API</strong></td>
<td>
<code>services/api/src/index.ts</code> now accepts <code>API_HOST</code> and passes it to
<code>Bun.serve</code>. The native unit sets <code>API_HOST=0.0.0.0</code> and
<code>API_PORT=4000</code>.
</td>
</tr>
<tr>
<td><strong>Native web</strong></td>
<td>
The native web unit now starts from <code>apps/web</code> with
<code>bun x next start -H "$WEB_HOST" -p "$WEB_PORT"</code>, avoiding the earlier repo-root startup
failure and binding the service on <code>0.0.0.0:3000</code>.
</td>
</tr>
<tr>
<td><strong>JetStream retention</strong></td>
<td>
Native startup exposed a retention-unit bug. The shared bus layer now converts stream max-age values with
<code>nanos(...)</code> and formats them back with <code>millis(...)</code>.
</td>
</tr>
<tr>
<td><strong>Docker fallback</strong></td>
<td>
Docker Compose now uses <code>ISLANDFLOW_DATA_ROOT=/var/lib/islandflow</code>, publishes loopback
ports, and keeps the fallback runtime compatible with the same durable data directories as the native
services.
</td>
</tr>
<tr>
<td><strong>NPM switch helper</strong></td>
<td>
The helper now updates both the NPM database and the generated
<code>/data/nginx/proxy_host/*.conf</code> files, because a DB-only restart did not reliably rewrite the
live configs for Islandflow.
</td>
</tr>
</tbody>
</table>
<pre><code>sudo ufw allow proto tcp from 172.18.0.0/16 to any port 4000 comment 'npm bridge to native api'</code></pre>
</section>
<section>
<h2>Expected Impact for End-Users</h2>
<ul>
<li>
Public web and API traffic now reaches the native Islandflow services, which removes Docker from the primary
live request path while keeping the outer edge unchanged.
</li>
<li>
Same-origin public API routes such as <code>/prints</code>, <code>/history</code>, <code>/replay</code>,
<code>/nbbo</code>, and <code>/ws/live</code> continue to resolve correctly through the main app hostname.
</li>
<li>
Rollback remains fast and explicit: NPM can be pointed back at Docker service names and the Docker runtime
can reclaim the same durable data directories if native operation needs to be abandoned.
</li>
</ul>
</section>
<section>
<h2>Validation</h2>
<div class="status-grid">
<article class="status-card good">
<div class="pill good">Static checks</div>
<ul>
<li><code>bun run check:docker-workspace</code></li>
<li><code>docker compose -f deployment/docker/docker-compose.yml config --quiet</code></li>
<li><code>docker compose -f /home/delta/nginx-proxy-manager/docker-compose.yml config --quiet</code></li>
<li><code>bash -n deployment/native/*.sh</code></li>
<li><code>systemd-analyze verify deployment/native/systemd/user/*.service deployment/native/systemd/system/*.service</code></li>
<li><code>bun build services/api/src/index.ts --target=bun</code></li>
<li><code>bun build scripts/deploy.ts --target=bun</code></li>
</ul>
</article>
<article class="status-card good">
<div class="pill good">Native runtime</div>
<ul>
<li><code>./deployment/native/check-native-health.sh full</code></li>
<li><code>curl http://127.0.0.1:4000/health</code></li>
<li><code>curl -I http://127.0.0.1:3000/</code></li>
</ul>
</article>
<article class="status-card good">
<div class="pill good">Public edge</div>
<ul>
<li><code>curl -I -fksS https://flow.deltaisland.io</code></li>
<li><code>curl -fksS https://api.flow.deltaisland.io/health</code></li>
<li><code>bun run scripts/check-public-api-routes.ts https://flow.deltaisland.io</code></li>
</ul>
</article>
</div>
</section>
<section>
<h2>Issues, Limitations, and Mitigations</h2>
<ul>
<li>
The native ingest-options service required an explicit synthetic-adapter override because the environment file
still pointed at an Alpaca adapter that was returning <code>401</code> responses. The service now starts
cleanly for native cutover, but production adapter selection remains an operational decision.
</li>
<li>
The NPM helper still relies on direct config synchronization because NPM did not reliably regenerate the
Islandflow proxy files from SQLite changes alone. This is mitigated by keeping the synchronization logic
checked in and by reloading NPM as part of the helper itself.
</li>
<li>
The final public API recovery currently leaves <code>api.flow.deltaisland.io</code> as a DNS-only hostname.
That restored service, but it changes the edge posture relative to the web hostname and should be reviewed
deliberately.
</li>
<li>
A temporary Cloudflare API token was used to inspect and correct zone state during validation. That token
should be rotated outside this repository workflow.
</li>
</ul>
</section>
<section>
<h2>Follow-up Work</h2>
<ul>
<li>
<code>islandflow-fl5</code>: decide whether <code>api.flow.deltaisland.io</code> should remain DNS-only or
be re-proxied through Cloudflare, then re-validate TLS, websocket, and operational behavior for the chosen
posture.
</li>
<li>
After operational soak, decide whether native should become the default production runtime or remain a
supported alternative with Docker as the preferred steady-state runtime.
</li>
</ul>
</section>
</main>
</body>
</html>

View file

@ -0,0 +1,276 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Reconcile PR Conflicts</title>
<style>
:root {
color-scheme: dark;
--bg: oklch(13% 0.018 252);
--panel: oklch(18% 0.022 248);
--panel-2: oklch(22% 0.026 248);
--line: oklch(72% 0.03 248 / 0.18);
--text: oklch(93% 0.012 248);
--muted: oklch(72% 0.025 248);
--faint: oklch(58% 0.025 248);
--amber: oklch(78% 0.16 72);
--green: oklch(73% 0.16 154);
--blue: oklch(70% 0.14 245);
--red: oklch(68% 0.18 32);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
line-height: 1.55;
}
main {
width: min(1120px, calc(100% - 32px));
margin: 0 auto;
padding: 48px 0 72px;
}
header {
border-bottom: 1px solid var(--line);
padding-bottom: 28px;
margin-bottom: 28px;
}
.eyebrow,
h1,
h2,
code,
pre {
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}
.eyebrow {
color: var(--amber);
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}
h1 {
margin: 10px 0 12px;
font-size: clamp(2rem, 4vw, 3.2rem);
line-height: 1.06;
letter-spacing: 0;
}
h2 {
margin: 0 0 12px;
font-size: 1rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
p {
max-width: 76ch;
margin: 0 0 12px;
}
a {
color: var(--blue);
}
section {
padding: 24px 0;
border-bottom: 1px solid var(--line);
}
ul {
margin: 0;
padding-left: 20px;
}
li + li {
margin-top: 8px;
}
.summary {
color: var(--muted);
font-size: 1.05rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 12px;
margin-top: 14px;
}
.tile {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 8px;
padding: 14px;
}
.tile strong {
display: block;
margin-bottom: 6px;
}
code {
color: var(--text);
background: oklch(100% 0 0 / 0.06);
border: 1px solid oklch(100% 0 0 / 0.08);
border-radius: 6px;
padding: 0.08rem 0.28rem;
}
pre {
overflow: auto;
background: var(--panel-2);
border: 1px solid var(--line);
border-radius: 8px;
padding: 14px;
font-size: 0.86rem;
}
pre code {
background: transparent;
border: 0;
padding: 0;
}
.diff-add {
color: var(--green);
}
.diff-del {
color: var(--red);
}
.diff-meta {
color: var(--faint);
}
.callout {
border: 1px solid oklch(78% 0.16 72 / 0.35);
background: oklch(78% 0.16 72 / 0.08);
border-radius: 8px;
padding: 14px;
}
</style>
</head>
<body>
<main>
<header>
<div class="eyebrow">Turn document • 2026-05-19 18:56 ET</div>
<h1>Reconcile PR Conflicts</h1>
<p class="summary">
Merged <code>forgejo/main</code> into <code>nextjs-upgrade</code>, resolved the checked-in Beads and README conflicts, kept the native deployment work from main, and updated the JetStream tests for the merged nanosecond retention behavior.
</p>
</header>
<section>
<h2>Summary</h2>
<p>
The PR branch now incorporates the current mainline deployment changes while preserving the Next.js upgrade branch. The only hand-edited conflict resolution was in <code>.beads/issues.jsonl</code> and <code>README.md</code>; the rest of the mainline merge applied cleanly.
</p>
</section>
<section>
<h2>Changes Made</h2>
<ul>
<li>Resolved <code>.beads/issues.jsonl</code> by keeping issue records from both sides of the merge.</li>
<li>Resolved the README deployment workflow section by combining the branchs command-oriented guidance with mains newer worker-only, local-server, and native edge cutover notes.</li>
<li>Accepted mainline native deployment assets, Docker deployment refinements, API host binding support, deploy timing output, and worker-only deployment scope.</li>
<li>Adjusted <code>packages/bus/tests/jetstream.test.ts</code> so retention assertions expect NATS nanoseconds after the merged runtime change.</li>
</ul>
</section>
<section>
<h2>Context</h2>
<p>
The branch was clean before the merge, but Forgejo reported PR conflicts against <code>main</code>. Reproducing the merge locally showed conflicts in the Beads export file and the README deployment section. The automatic merge also brought in mainline native deployment work that touched deploy scripts, Docker deployment files, native systemd templates, public edge documentation, the API host setting, and JetStream retention units.
</p>
</section>
<section>
<h2>Important Implementation Details</h2>
<div class="grid">
<div class="tile">
<strong>README resolution</strong>
<p>Kept Docker as the recommended VPS path, preserved explicit deploy commands, and added <code>--workers-only</code>, local server execution, and native worker iteration guidance.</p>
</div>
<div class="tile">
<strong>Beads resolution</strong>
<p>Removed conflict markers without dropping either branchs issue records, so Beads history remains complete.</p>
</div>
<div class="tile">
<strong>Test repair</strong>
<p>Main now stores JetStream <code>max_age</code> in nanoseconds via NATS helpers. Tests now assert against <code>nanos(...)</code> instead of raw millisecond values.</p>
</div>
</div>
</section>
<section>
<h2>Relevant Diff Snippets</h2>
<p>
Diff snippets are presented in the style of <a href="https://diffs.com/docs">diffs.com</a>, using structured additions and deletions for quick review.
</p>
<pre><code><span class="diff-meta">diff --git a/README.md b/README.md</span>
<span class="diff-del">- Partial deploys are supported with `--web-only`, `--api-only`, `--services-only`, `--fast`, `--no-build`, and `--force-recreate`.</span>
<span class="diff-add">+ Partial deploys are supported with `--web-only`, `--api-only`, `--services-only`, `--workers-only`, `--fast`, `--no-build`, and `--force-recreate`.</span>
<span class="diff-add">+ When run from `/home/delta/islandflow` on the VPS itself, `./deploy` can execute locally instead of SSHing back into the same server.</span>
<span class="diff-del">- Native deployment expects Bun, systemd units, host-reachable infra, and deliberate reverse-proxy changes. The open follow-up is to add native unit templates and rollback helpers.</span>
<span class="diff-add">+ Native deployment expects Bun, systemd units, host-reachable infra, and deliberate reverse-proxy changes. Native deploys are intended primarily for worker-only fast iteration until the public edge is cut over deliberately.</span></code></pre>
<pre><code><span class="diff-meta">diff --git a/packages/bus/tests/jetstream.test.ts b/packages/bus/tests/jetstream.test.ts</span>
<span class="diff-del">- import type { JetStreamManager, StreamConfig } from "nats";</span>
<span class="diff-add">+ import { nanos, type JetStreamManager, type StreamConfig } from "nats";</span>
<span class="diff-del">- max_age: 3_600_000,</span>
<span class="diff-add">+ max_age: nanos(3_600_000),</span>
<span class="diff-del">- max_age: 43_200_000,</span>
<span class="diff-add">+ max_age: nanos(43_200_000),</span></code></pre>
</section>
<section>
<h2>Expected Impact for End-Users</h2>
<p>
The PR should no longer show merge conflicts against main. Users and operators get the Next.js upgrade branch plus the newer deployment safety work from main, including worker-only native deploy guidance and current Docker deployment notes.
</p>
</section>
<section>
<h2>Validation</h2>
<ul>
<li><code>git diff --check</code> passed.</li>
<li><code>bun run scripts/deploy.ts --help</code> passed.</li>
<li><code>bun run check:docker-workspace</code> passed.</li>
<li><code>bun test services/api/tests packages/bus/tests</code> passed with 45 tests.</li>
<li><code>bun --cwd=apps/web run build</code> passed on Next.js 16.2.6.</li>
</ul>
</section>
<section>
<h2>Issues, Limitations, and Mitigations</h2>
<div class="callout">
<p>
The first focused test run failed because the merged JetStream implementation correctly returned nanosecond retention values while the existing tests still expected milliseconds. The tests were updated to use the same NATS <code>nanos</code> helper as the runtime behavior, then the suite passed.
</p>
</div>
</section>
<section>
<h2>Follow-up Work</h2>
<ul>
<li>No new follow-up was created from this reconciliation.</li>
<li>Existing deployment follow-ups remain in Beads, including native public edge posture and cutover decisions.</li>
</ul>
</section>
</main>
</body>
</html>