Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
b4e4659
docs(stack): define managed identity cleanup scope
jgoux Aug 15, 2026
cabc1a5
chore(stack): remove obsolete managed path canonicalizer
jgoux Aug 15, 2026
2a32b01
refactor(stack): centralize managed workspace metadata
jgoux Aug 15, 2026
6be1e89
chore(stack): centralize managed settlement policy
jgoux Aug 15, 2026
0ce6f25
chore(stack): name Git lock retry policy
jgoux Aug 15, 2026
550001d
test(stack): strengthen managed discovery race coverage
jgoux Aug 15, 2026
3c2fbf1
Merge branch 'develop' into juliengoux/cli-2108-managed-identity-main…
jgoux Aug 15, 2026
b9f2ec2
docs(stack): design managed port allocation
jgoux Aug 15, 2026
104de7c
docs(stack): refine managed port ownership
jgoux Aug 15, 2026
8387956
docs(stack): plan managed port allocation
jgoux Aug 15, 2026
02f00bb
feat(config): retain effective value origins
jgoux Aug 15, 2026
2d6744b
fix(config): normalize array value origins
jgoux Aug 15, 2026
c2764b2
refactor(stack): add selected-field port leases
jgoux Aug 16, 2026
4bf4d83
fix(stack): harden selected-field port leases
jgoux Aug 16, 2026
e7e90dc
feat(stack): resolve managed port intent
jgoux Aug 16, 2026
becca37
test(stack): split managed port planning scenarios
jgoux Aug 16, 2026
246bcdb
test(stack): isolate managed port transition scenarios
jgoux Aug 16, 2026
15cd755
feat(stack): enforce managed port ownership matrix
jgoux Aug 16, 2026
828ec62
test(stack): cover managed start claim contract
jgoux Aug 16, 2026
de18144
feat(stack): coordinate managed port claims
jgoux Aug 16, 2026
be2eb1d
feat(stack): coordinate managed port claims
jgoux Aug 16, 2026
f8d986f
feat(stack): coordinate managed port claims
jgoux Aug 16, 2026
993f848
feat(stack): start managed stacks with sticky ports
jgoux Aug 16, 2026
cfa5b2e
fix(stack): require managed runtime initialization
jgoux Aug 16, 2026
1d697af
test(stack): name managed port lifecycle scenarios
jgoux Aug 16, 2026
2b5fbbd
fix(stack): harden managed running lifecycle
jgoux Aug 16, 2026
7c7ecc3
fix(stack): settle managed recovery transitions
jgoux Aug 16, 2026
7f34f20
test(stack): bound effect exact port handoffs
jgoux Aug 16, 2026
1450afc
test(stack): retry dynamic running port handoffs
jgoux Aug 16, 2026
c1798ce
feat(stack): allocate managed ports inside daemon
jgoux Aug 16, 2026
59561f2
fix(stack): transfer managed port lease ownership
jgoux Aug 16, 2026
32c342b
fix(stack): preserve reused daemon socket
jgoux Aug 16, 2026
6468050
refactor(stack): simplify managed port policy
jgoux Aug 16, 2026
89344a2
docs(stack): define managed port ownership contract
jgoux Aug 16, 2026
ff02cea
test(stack): harden managed port contract handoff
jgoux Aug 16, 2026
06feb0a
fix(stack): reconcile managed identity before reuse
jgoux Aug 16, 2026
87afe21
chore(stack): remove implementation report
jgoux Aug 16, 2026
526249a
fix(stack): harden managed port coordination
jgoux Aug 16, 2026
f9144ce
fix(stack): harden managed port lifecycle
jgoux Aug 16, 2026
bb502df
Merge remote-tracking branch 'origin/develop' into juliengoux/cli-211…
jgoux Aug 16, 2026
b9f7b62
docs(stack): design managed stack simplification
jgoux Aug 16, 2026
67f96c8
refactor(stack): add internal managed file store
jgoux Aug 16, 2026
198f1c7
fix(stack): close managed store races
jgoux Aug 16, 2026
5517529
refactor(stack): remove global registry lock
jgoux Aug 16, 2026
a77eaab
docs(stack): remove global registry mutex
jgoux Aug 16, 2026
f74c1f6
refactor(stack): simplify managed environment identity
jgoux Aug 16, 2026
6e4df02
refactor(stack): validate managed repair plans
jgoux Aug 16, 2026
38c8711
refactor(stack): validate nested repair updates
jgoux Aug 16, 2026
1a37393
refactor(stack): use control endpoints for ownership
jgoux Aug 16, 2026
815da4a
docs(stack): define canonical control address
jgoux Aug 16, 2026
f70e368
fix(stack): share control listener with daemon
jgoux Aug 16, 2026
93b7753
fix(stack): harden control acquisition errors
jgoux Aug 16, 2026
f63737a
refactor(stack): deepen the managed stack manager
jgoux Aug 16, 2026
a70c288
fix(stack): close managed manager races
jgoux Aug 16, 2026
99afa32
refactor(stack): unify detached supervision
jgoux Aug 16, 2026
011062a
refactor(stack): remove managed coordination machinery
jgoux Aug 16, 2026
60612ec
fix(stack): restore managed concurrency coverage
jgoux Aug 16, 2026
fbc9aad
fix(stack): prove attached startup waiting
jgoux Aug 16, 2026
e7dca82
refactor(stack): complete managed stack simplification
jgoux Aug 17, 2026
0c28f31
fix(stack): harden simplified managed lifecycle
jgoux Aug 17, 2026
18bbbd5
fix(stack): isolate nested local project identities
jgoux Aug 17, 2026
1ee7aeb
fix(stack): reserve managed control ports
jgoux Aug 17, 2026
59a8773
fix(stack): harden managed lifecycle races
jgoux Aug 17, 2026
5d83571
fix(stack): harden managed state correctness
jgoux Aug 17, 2026
cafba86
fix(stack): bound supervisor lifecycle shutdown
jgoux Aug 17, 2026
50e4c4c
refactor(stack): simplify managed supervisor surface
jgoux Aug 17, 2026
3783efc
fix(stack): close lifecycle handoff races
jgoux Aug 17, 2026
eefc4a0
fix(stack): allow explicit control-range ports
jgoux Aug 17, 2026
a77f181
fix(stack): close managed startup edge cases
jgoux Aug 17, 2026
f85175e
fix(stack): isolate managed port lifecycles
jgoux Aug 17, 2026
0ec47ba
docs(stack): consolidate architecture guide
jgoux Aug 17, 2026
00dce3d
fix(stack): harden concurrent managed operations
jgoux Aug 17, 2026
5958f95
test(stack): isolate concurrent integration ports
jgoux Aug 17, 2026
be018f1
fix(stack): stabilize concurrent lifecycle tests
jgoux Aug 17, 2026
c13653f
fix(stack): preserve supervisor startup errors
jgoux Aug 17, 2026
f233e06
test(stack): streamline managed test journeys
jgoux Aug 17, 2026
426c148
fix(stack): surface corrupt state during stop
jgoux Aug 17, 2026
8fdd3f1
test(cli): align no-backup stop journey
jgoux Aug 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ use. The package API itself keeps eager startup as its default.
Useful companion docs:

- [`../../packages/stack/docs/architecture.md`](../../packages/stack/docs/architecture.md)
- [`../../packages/stack/docs/detach-mode.md`](../../packages/stack/docs/detach-mode.md)
- [`docs/ui.md`](./docs/ui.md)

## Development
Expand Down
331 changes: 72 additions & 259 deletions apps/cli/docs/supabase-home.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Supabase CLI State Layout

This document describes how CLI-owned state is split between the repo-local `.supabase/`
directory and the global `SUPABASE_HOME`.
The CLI keeps authored project configuration in `supabase/`, checkout-local link and version
caches in `.supabase/`, and managed runtime state in the global `SUPABASE_HOME` directory.

By default:

Expand All @@ -11,297 +11,110 @@ SUPABASE_HOME = ~/.supabase

The path can be overridden with the `SUPABASE_HOME` environment variable.

## Goals
## State roots

- keep committed project intent in `supabase/`
- keep checkout-specific machine state explicit and discoverable in `.supabase/`
- keep machine-global auth, telemetry, and binary caches in `SUPABASE_HOME`
- keep live runtime socket state under the OS temp directory
### Project files

## Two State Roots

### Repo-local project state

Project-scoped local state lives next to the repo as a gitignored sibling of `supabase/`:
User-authored configuration and migrations live in the repository:

```text
<project-root>/
supabase/
config.json
config.toml
migrations/
functions/
.supabase/
project.json
local-versions.json
stacks/
default/
stack.json
state.json
data/
```

This state is:

- local to one checkout
- readable by humans and agents directly from the repo root
- intentionally not committed

### Global CLI home

Machine-global state remains under `SUPABASE_HOME`:

```text
~/.supabase/
access-token
telemetry.json
traces/
<date>.ndjson
bin/
<service>/
<version>/
<platform>/
...
```

This state is shared across all local projects on the machine.

## Project Root Resolution

For project-local CLI state, the CLI resolves the active project root from `cwd` using this
order:

1. nearest ancestor containing `supabase/config.toml` or `supabase/config.json`
2. otherwise nearest ancestor containing `.supabase/project.json`
3. otherwise `cwd`

That means:

- `supabase link` and `supabase unlink` can work before `supabase init`
- `supabase start`, `supabase stop`, `supabase status`, `supabase stack list`, and `supabase logs`
can be run from nested subdirectories inside a linked checkout
- stack persistence is no longer keyed by a hashed global project directory

`@supabase/config` still only discovers `supabase/config.*`. The broader `.supabase/project.json`
fallback is CLI-specific runtime behavior.

## Repo-local Files

### `project.json`

`.supabase/project.json` stores cached linked-remote metadata for the checkout.

Shape:

```json
{
"ref": "abcdefghijklmnopqrst",
"name": "my-project",
"fetchedAt": "2026-03-25T12:34:56.000Z",
"versions": {
"postgres": "17.6.1.084",
"postgrest": "14.4",
"auth": "2.188.1",
"storage": "1.43.3"
}
}
```

This file is written by `supabase link` and removed by `supabase unlink`.

It is CLI runtime state, not committed project config. The linked project ref does not live in
`supabase/config.json`.

### `local-versions.json`

`.supabase/local-versions.json` stores optional checkout-local service version overrides.

Shape:

```json
{
"updatedAt": "2026-03-23T10:15:00.000Z",
"versions": {
"auth": "2.180.0",
"storage": "1.39.2"
}
}
```

This is a power-user escape hatch. There is no dedicated top-level command for it yet. Advanced
users can edit it directly if they want persistent local overrides.
The CLI discovers the active project from the nearest ancestor containing `supabase/config.toml`
(or the legacy config filename where supported), then passes that canonical project root to the
managed stack identity. Managed `workspaceId` is local repository/folder lineage and is unrelated
to the remote Supabase project ID in `config.toml` or link state.

### `stacks/<name>/stack.json`
### Checkout-local caches

Each project can own multiple named local stacks:
Gitignored checkout metadata lives beside the project:

```text
.supabase/stacks/
default/
preview/
ci/
<project-root>/.supabase/
project.json
local-versions.json
```

The implicit stack name is `default`.

`stack.json` is the durable per-stack metadata record. It stores:

- `schemaVersion`
- `updatedAt`
- `ports`
- the pinned baseline `services` manifest for that stack
- `lastNotifiedUpdateFingerprint` when the CLI has already warned about available updates

### `stacks/<name>/state.json`

`state.json` is the live runtime record for a running stack. It stores connection info,
service endpoints, process identifiers, and the exact service versions currently running.

It is written when the managed stack is running and removed on normal `supabase stop`.

### `stacks/<name>/data/`

`data/` stores persisted local service data for that stack.

The CLI does not currently persist stack logs under `.supabase/`; logs are buffered in memory by
the daemon and streamed on demand through `supabase logs`.

## Service Version Resolution

There are two separate concepts:

- the **candidate baseline**, computed from cached linked-remote versions plus CLI defaults
- the **pinned baseline**, stored in `.supabase/stacks/<name>/stack.json`

The candidate baseline is:
`project.json` is the linked remote version cache. `local-versions.json` contains optional
checkout-local service-version overrides. Neither file records whether a local stack is running.
For ordinary non-Git folders, `.supabase/identity.json` stores the managed workspace identity. Git
checkouts keep checkout identity in Git metadata instead and do not use that marker.

1. cached linked service versions from `.supabase/project.json`
2. CLI `DEFAULT_VERSIONS` as fallback for everything else
### Global managed runtime

The pinned baseline is what a named stack actually uses by default on subsequent starts.

Runtime precedence is:

1. per-run `supabase start --service-version service=version`
2. checkout-local overrides from `.supabase/local-versions.json`
3. pinned stack versions from `.supabase/stacks/<name>/stack.json`

If a stack has never been started before and `stack.json` does not exist yet, the CLI creates it
from the current candidate baseline.

This keeps linked remote parity, persistent local experimentation, and one-off overrides separate
from committed project config.

## Local and Remote Sync Workflow

### `supabase init`

`supabase init` creates a minimal repo-scoped config file:
Managed stack documents and runtime artifacts are shared through the global CLI home:

```text
supabase/config.json
```

with only a top-level `"$schema"` reference:

```json
{
"$schema": "https://supabase.com/docs/cli/config.schema.json"
}
<SUPABASE_HOME>/
access-token
telemetry.json
traces/
bin/
managed/
stacks/
<stack-id>/
stack.json
data/
logs/
runtime/
```

It does not link a remote project and does not create `.supabase/project.json`.

### `supabase link`

`supabase link` binds the local project to a remote Supabase project and refreshes the cached
linked metadata in `.supabase/project.json`.

If the linked remote service versions differ from any existing pinned stack metadata, `link`
warns and tells the user to run `supabase stack update`.

### `supabase stack update`

`supabase stack update` is the explicit adoption step for pinned local stack versions.

When the project is linked, it first fetches the latest remote service versions and rewrites
`.supabase/project.json`. It then recomputes the candidate baseline and writes the pinned stack
versions into `.supabase/stacks/<name>/stack.json`.

If the stack is currently running, `update` warns that the user must stop and start it again for
the new pinned versions to take effect.
`stack.json` is the single durable managed record. It contains stack identity, including the
canonical local-project key relative to the enclosing Git checkout, and sticky port intents
and assignments, lifecycle, runtime control endpoint, and launch metadata (mode, versions,
exclusions, and update-notification fingerprint). Runtime-only service ports are allocated for the
supervisor run and are not persisted as sticky intents. The deterministic loopback control endpoint
and ownership protocol are the liveness authority; a stale document is reclaimed by a subsequent
managed lifecycle operation.

### `supabase stack status`
There is no project-local `stacks/<name>` directory, `state.json`, daemon socket file, or second
StateManager metadata format.

`supabase stack status` is local-only. It does not make a network call.
## Service-version inputs

It shows:
The candidate baseline is computed from linked versions in `.supabase/project.json` and the CLI's
`DEFAULT_VERSIONS` catalog. Runtime precedence is:

- a detailed running view when `state.json` exists and the daemon is alive
- a detailed stopped view when only `stack.json` exists
- whether pinned stack versions are up to date against the current candidate baseline
1. `supabase start --service-version service=version` for one invocation;
2. checkout-local values in `.supabase/local-versions.json`;
3. the managed document's `launch.versions` baseline;
4. catalog defaults for values not supplied by the preceding sources.

### `supabase stack list`
`supabase stack update` refreshes the linked cache when possible and updates `launch` through the
managed lifecycle (directly when stopped, or through the owner control route when running). It does
not write a second project-level pinned-version file.

`supabase stack list` scans `.supabase/stacks/*/stack.json` for the current project and overlays
live `state.json` data when a daemon is running.
## Port intents

## What Is Not Under `.supabase/`
Raw `supabase/config.toml` values and their origins are loaded before defaults are applied. Explicit
sticky values are persisted as `exact` intents in each managed document. Omitted values remain
`automatic`; sibling worktrees have independent stack identities and allocations, while live exact
port conflicts are rejected by the manager. Runtime-only service ports are selected by the managed
supervisor and are not written to the document.

Not all runtime files live in the repo.

### Auth state

Auth is still machine-global today:

- keyring entry: `Supabase CLI/access-token`
- filesystem fallback: `<SUPABASE_HOME or ~/.supabase>/access-token`

### Telemetry and traces

Telemetry state remains in `SUPABASE_HOME`:

- `telemetry.json`
- `traces/`

### Shared binaries

Downloaded binaries remain shared across projects in:

```text
<SUPABASE_HOME or ~/.supabase>/bin/
```

The legacy Go installer stores its Deno binary directly under the state root:

```text
<SUPABASE_HOME or ~/.supabase>/deno
```

### Live runtime sockets

Managed daemon runtime directories, including the live Unix socket path, still use the OS temp
directory:

```text
/tmp/supabase/
```

The durable stack record remains in the repo-local state directory:

```text
<project-root>/.supabase/stacks/<stack-name>/stack.json
```
## Command behavior

## Ownership Rules
- `supabase start` resolves launch metadata and port intents, acquires managed control, and records
the document before starting services.
- `supabase status`, `logs`, and `services` attach through the deterministic control endpoint and
report only a live owner as running.
- `supabase stop` asks the owner to stop, waits for `stopped` and control release, and keeps the
document by default. `--no-backup` removes it after deterministic cleanup.
- `supabase stack list` enumerates healthy managed documents and checks control ownership for live
status; `--project-dir` scopes the result to one checkout.

When deciding where something belongs, use this rule of thumb:
## Auth, telemetry, and binaries

- user-authored project config belongs in the repository under `supabase/`
- checkout-specific machine state belongs in `.supabase/`
- machine-global auth, telemetry, and caches belong in `SUPABASE_HOME`
- live runtime temp/socket state belongs under the OS temp directory
Auth, telemetry, traces, and downloaded binaries remain machine-global under `SUPABASE_HOME`.
Legacy installer artifacts may coexist under that root, but they are not part of managed stack
coordination.

## Related Docs
## Related docs

- [CLI Code Structure](./code-structure.md)
- [Service Versioning](../../../packages/stack/docs/service-versioning.md)
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/legacy/commands/start/start.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export type LegacyStartFlags = CliCommand.Command.Config.Infer<typeof config>;
// are not listed here: they come from `BunServices`/`processControlLayer`/
// `runtimeInfoLayer` in the root runtime (`shared/cli/run.ts`), the same way
// `stop`/`status` rely on the former. `HttpClient.HttpClient` is NOT provided by the
// root runtime — `BunServices.layer` never supplies it, and `unixHttpClientLayer` is a
// root runtime — `BunServices.layer` never supplies it, and `httpTransportClientLayer` is a
// different service tag entirely — so it's composed here via `legacyHttpClientLayer`,
// the same `FetchHttpClient`-backed layer `db reset`/`seed buckets` use, needed for the
// health-check probes (`legacyWaitForHealthyServices`) and `legacySeedBucketsRun`.
Expand Down
Loading
Loading