Skip to content

feat(stack): run the complete native service graph - #6385

Open
jgoux wants to merge 29 commits into
developfrom
juliengoux/cli-2141-stack-run-the-remaining-native-service-graph-from-slim
Open

feat(stack): run the complete native service graph#6385
jgoux wants to merge 29 commits into
developfrom
juliengoux/cli-2141-stack-run-the-remaining-native-service-graph-from-slim

Conversation

@jgoux

@jgoux jgoux commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends the strict native stack runtime from the Postgres/Auth/PostgREST core to the complete service graph using the frozen slim-services releases.

Adds native launch, configuration, lifecycle, and private companion ownership for Edge Runtime, Realtime, Storage/imgproxy, PgMeta/Studio, Analytics/Vector, Pooler, and Mailpit. Native logs remain supervisor-owned and isolated per stack, while the public package surface preserves eager, lazy, and explicit preparation closure semantics.

The representative consumer journey covers the public service graph and exact resource ownership. This change does not add per-service Docker fallback or absorb unrelated proxy hardening.

@jgoux
jgoux requested a review from a team as a code owner August 28, 2026 18:01
Comment thread packages/stack/src/services/imgproxy.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1515d4700

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/stack/src/StackBuilder.ts
Comment thread packages/stack/src/ServiceCatalog.ts Outdated
Comment thread packages/stack/src/services/analytics.ts Outdated
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T21:11:20.871233Z 9f3f0fb New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread packages/stack/src/services/pooler.ts
Comment thread packages/stack/src/ServiceCatalog.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c912c31e38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/stack/src/ServiceCatalog.ts Outdated
Comment thread packages/stack/src/versions.ts
Comment thread packages/stack/src/services/imgproxy.ts
Comment thread packages/stack/src/services/realtime.ts
Comment thread packages/stack/vitest.config.ts Outdated
Comment thread packages/stack/src/services/analytics.ts
Comment thread packages/stack/src/services/realtime.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3a0834d944

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/stack/src/PortCatalog.ts
Comment thread packages/stack/src/managed/document.ts
Comment thread packages/stack/src/services/edge-runtime.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5fe089ab55

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/stack/src/services/storage.ts
Comment thread apps/cli/src/next/commands/services/services.handler.ts
Comment thread apps/cli/src/next/commands/link/link.handler.ts
@jgoux

jgoux commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Automated review convergence checkpoint after three batched fix rounds:

  • Fixed the in-scope, reproduced findings through 69acdd0, including runtime-specific version planning, normal integration parallelism, Analytics loopback binding, and the CliConfig Pooler-port projection.
  • Deferred direct-listener/proxy confinement that the frozen runtimes cannot currently express (Pooler and Realtime), plus coordinated imgproxy hardening, to CLI-2227. Edge Runtime loopback binding belongs with that same direct-listener audit rather than another isolated patch.
  • Reproduced the Storage cleanup concern: the child supervisor currently runs configured cleanup actions on requested shutdown as well as owner loss, so auto-managed Storage data can be removed on restart. This is a valid lifecycle question and may be a merge blocker; it needs a human scope decision rather than a fourth automated patch round.
  • Fresh-stack stack services runtime selection and partial linked-version fallback are CLI-consumer semantics outside the authoritative CLI-2141 public @supabase/stack scope. They arose in code expanded during the previous review round and are deferred for separate CLI follow-up.
  • Rejected migration support for the unreleased managed db.pooler.port key per the explicit no-backward-compatibility direction.

The automated loop is now capped per AGENTS.md because new findings are primarily in code introduced by prior bot-driven rounds. Please provide human review, especially on whether Storage restart persistence should be folded into CLI-2141 before merge or tracked separately.

Comment thread packages/stack/src/services/pgmeta.ts Outdated
Comment thread packages/stack/src/services/pooler.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69acdd0e84

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/stack/src/services/pooler.ts
Comment thread packages/stack/src/services/realtime.ts
@jgoux jgoux self-assigned this Aug 31, 2026

@avallete avallete left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Cross-reviewed the stacked diff (ccbf5c5...69acdd0) only: Cursor + Claude Fable + Codex. Graph wiring, companions, journals, version planning, and eager/lazy/explicit prepare closures look coherent. Remaining problems sit at the host-native seam this PR introduced.

Changes requested — three small, diff-local fixes. Storage wipe is a follow-up, not a hold.

Ask before merge

  1. Native Analytics / BEAM cookiesanalytics.ts still uses RELEASE_COOKIE: "cookie" on the host EPMD. Thread a random per-stack cookie to Analytics/Realtime/Pooler and bind epmd/dist to loopback. See inline.
  2. Native PgMeta bind — set PG_META_HOST=127.0.0.1. It is the only native HTTP service left on 0.0.0.0. See inline.
  3. Realtime Host probe on Node — native health check sets Host: <tenantId> via fetch. Please confirm Node does not strip it; Bun e2e would miss that. See inline.

Parked (do not hold CLI-2141)

  • Storage/Mailpit orphanCleanup on requested stop/restart is real (supervisor-runtime.ts runs cleanup on ShutdownRequested). CLI managed start always passes a persisted stackRoot, so supabase start/stop/restart does not wipe uploads. The wart is programmatic createStack() temp roots and mid-session restartService("storage") there — same parent contract already on Postgres. Fix in process-compose: RemovePath only on owner loss.
  • imgproxy IMGPROXY_LOCAL_FILESYSTEM_ROOT: "/" stays with CLI-2227; native blast radius is wider than Docker.
  • start --mode restamping the previous mode's versions is a next/ consumer follow-up, not the stack graph.
  • Realtime ERL_CRASH_DUMP at cwd, missing runtime.env footprint profile, and the NativeLogWriter NUL sentinel leaking through LogBuffer are real ownership/DX misses. Follow-up.

Cleanup

No review/tool attributions. Leftovers worth trimming if you are already in the files:

  • Pooler native env asserted twice (services.unit.test.ts and pooler.unit.test.ts).
  • orphanCleanup copied in storage.ts / mailpit.ts.
  • Dead aliases after applyNativeDefaults removal (supervisor.ts runtimeConfigInput, SupervisorUpgradeRestart.ts effectiveConfigInput).
  • startNativeLogWriter exported from effect.ts with only LocalStack as a consumer.
  • e2e bindAndClose after dispose is released-port reuse; the PID check is enough.

Cross-review logs: Claude Fable + Codex on the stacked head. Nothing else posted besides this review.

Comment thread packages/stack/src/services/analytics.ts Outdated
Comment thread packages/stack/src/services/pgmeta.ts
Comment thread packages/stack/src/services/realtime.ts
Base automatically changed from juliengoux/cli-2121-stack-qualify-the-native-postgres-auth-and-postgrest-core to develop August 31, 2026 09:51
@jgoux
jgoux force-pushed the juliengoux/cli-2141-stack-run-the-remaining-native-service-graph-from-slim branch from 69acdd0 to 246d59d Compare August 31, 2026 20:25
@jgoux
jgoux requested a review from avallete August 31, 2026 20:29
@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Supabase CLI preview

npx --yes https://pkg.pr.new/supabase/cli/supabase@9f3f0fbf21ae9f16b92f51bb8d40008cdac83dbe

Preview package for commit 9f3f0fb.

command: `${opts.binPath}/bin/.edge-runtime-wrapped`,
args: [...edgeRuntimeArgs(opts, ".")],
cwd: opts.bootstrapDir,
env: edgeRuntimeEnv(opts),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Severity: MEDIUM

Native Edge Runtime is launched with only --port and no loopback bind. Deno.serve defaults to 0.0.0.0 (https://docs.deno.com/api/deno/http-server/), exposing the function listener on every host interface instead of only through the loopback API proxy. A network client can directly invoke configured functions, including verifyJWT: false functions.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: The root cause is that Deno.serve in edge-runtime-main.ts (line 229) does not specify a hostname, causing it to default to 0.0.0.0 and bind on all network interfaces when running natively on the host OS. To fix this:

  1. In edge-runtime-main.ts, modify the Deno.serve call to read a host binding from an environment variable with a loopback default:
Deno.serve({
  hostname: Deno.env.get("EDGE_RUNTIME_HOST") ?? "127.0.0.1",
  handler: async (req: Request) => { ... },
  onListen: async () => { ... },
});
  1. In edge-runtime.ts, update edgeRuntimeEnv (or the Docker-specific env override) to explicitly set EDGE_RUNTIME_HOST=0.0.0.0 for the Docker variant so container bridge networking continues to work (Docker requires binding on all interfaces inside the container for port publishing to function). The native variant will naturally fall through to the 127.0.0.1 default, restricting the listener to the loopback interface only and preventing remote clients from directly invoking functions—including those with verifyJWT: false—by bypassing the API proxy.

readonly releaseCookie?: string;
},
): Record<string, string> => ({
PORT: String(opts.port),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Severity: MEDIUM

Native Realtime is started with PORT but no loopback HTTP bind setting. Its release endpoint therefore listens on host interfaces, allowing a network client to bypass the loopback API proxy and reach /socket and HTTP endpoints on the Realtime port using the publishable key, exposing the local stack beyond its intended boundary.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Add PHX_HTTP_IP: "127.0.0.1" inside the native-mode conditional spread block (lines 76–84) to restrict the Realtime Phoenix HTTP server to the loopback interface only. The existing conditional already distinguishes native from Docker mode via the presence of nodeName/releaseCookie. The fix should be added there alongside the other native loopback bindings:

...(opts.nodeName === undefined || opts.releaseCookie === undefined
    ? {}
    : {
        ...nativeBeamLoopbackEnv,
        NODE_NAME: opts.nodeName,
        NODE_IP: "127.0.0.1",
        RELEASE_NODE: `${opts.nodeName}@127.0.0.1`,
        RELEASE_COOKIE: opts.releaseCookie,
        PHX_HTTP_IP: "127.0.0.1",
      }),

This follows the exact same pattern used by the Analytics/Logflare service in analytics.ts, which sets PHX_HTTP_IP: opts.nodeHost with nodeHost: "127.0.0.1" for native mode (and "0.0.0.0" for Docker, where container-network isolation applies instead). Without this setting, Phoenix defaults to binding on all host interfaces (0.0.0.0), allowing external network clients to reach /socket and HTTP endpoints on the Realtime port directly, bypassing the intended loopback API proxy boundary. Note: confirm that PHX_HTTP_IP is the correct runtime-config variable in Supabase Realtime's config/runtime.exs before merging.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 246d59d7bf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// URLs addressable on the host while the data directory remains owned by
// Storage and is not treated as imgproxy's independent state.
IMGPROXY_LOCAL_FILESYSTEM_ROOT: "/",
IMGPROXY_ALLOWED_SOURCES: `local:///${normalizeLocalSourcePrefix(dataDir)}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Build the imgproxy allowlist with three slashes

When image transformation is enabled, normalizeLocalSourcePrefix returns an absolute path beginning with /, so this produces local:////tmp/... (and local:////var/lib/storage/ in Docker). Storage emits local:///tmp/...-style source URLs, as the adjacent comment notes, so they do not match this URL-prefix allowlist and image transformation requests are rejected. Prefix the absolute path with local://, not local:///.

Useful? React with 👍 / 👎.

return closeEvent === undefined ? Effect.fail(error) : Effect.succeed(closeEvent);
}),
);
const bridge = Effect.race(Fiber.join(clientFiber), backendToClient).pipe(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Terminate the bridge on the first socket failure

When either WebSocket side terminates with a non-close SocketError—for example, the Realtime backend emits a read error after connecting—the corresponding effect fails, but Effect.race is success-biased and continues waiting for the other side instead of returning that failure. The client can therefore remain connected indefinitely and the later 1011 error-close branch is never reached. Use first-completion race semantics or convert each terminal failure into a successful bridge outcome before racing.

Useful? React with 👍 / 👎.

// Log delivery must never block child stdout/stderr draining. The bounded history refs
// below remain authoritative; live subscribers receive the newest entries when slow.
const globalPubSub = yield* PubSub.sliding<LogEntry>(4096);
const internalPubSub = yield* PubSub.unbounded<LogBufferInternalEvent>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound the native journal event queue

When native services produce logs faster than the filesystem journal can write them—for example during slow-disk operation or repeated write retries—every entry is retained by this unbounded PubSub. Unlike the existing sliding live-log queues, the journal consumer performs filesystem I/O and can remain behind indefinitely, so a noisy stack can grow the daemon's memory without limit and eventually be killed. Use a bounded nonblocking queue while preserving ordered delivery of the shutdown control marker.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f3f0fbf21

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

},
pgmetaPort: { field: "pgmetaPort", service: "pgmeta", persistence: "runtime" },
// postgres-meta binds its administrative listener at PG_META_PORT + 1.
pgmetaPort: { field: "pgmetaPort", service: "pgmeta", span: 2, persistence: "runtime" },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit PgMeta's adjacent-port lease to native mode

When a Docker stack enables PgMeta, this span requires both the configured port and port + 1 to be free, even though makePgmetaServiceDocker publishes only the configured port. A process occupying the adjacent host port therefore rejects an otherwise valid Docker start or restart, and the lease unnecessarily prevents sibling stacks from selecting that unused port. Reserve the two-port span only for native PgMeta, where the administrative listener actually binds on the host.

Useful? React with 👍 / 👎.

Comment on lines +154 to +156
const result = handler(chunk);
if (Effect.isEffect(result)) {
run(result);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound frames buffered during Realtime activation

When a client sends frames while lazy Realtime activation or the backend handshake is still pending, every message event forks another effect that retains its payload while waiting on backendWriter; there is no queue bound or backpressure. A fast or faulty loopback client can therefore create an unbounded number of fibers during the potentially minutes-long activation window and exhaust the daemon's memory. Feed frames through a bounded, serialized queue and close the connection on overflow instead of forking once per event.

AGENTS.md reference: AGENTS.md:L142-L146

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants