Skip to content

Commit 5c59385

Browse files
committed
feat: add project-scoped feature state stores
1 parent 5e80820 commit 5c59385

36 files changed

Lines changed: 676 additions & 114 deletions

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,8 @@ COPY entrypoint.sh /usr/local/bin/entrypoint.sh
335335
RUN chmod a+rx /usr/local/bin/entrypoint.sh \
336336
/usr/local/share/enclave/auth-reconcile.sh \
337337
/usr/local/share/enclave/net.sh && \
338-
chmod a+r /usr/local/share/enclave/tmux-session.conf
338+
chmod a+r /usr/local/share/enclave/tmux-session.conf \
339+
/usr/local/share/enclave/kit-init.sh
339340
USER ${USERNAME}
340341
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
341342
CMD ["/bin/bash"]

docs/ARCHITECTURE.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ flowchart LR
3838
EnvStore["projects/&lt;hash&gt;/&lt;tool&gt;/env<br/>persisted env secrets, not mounted into the tool container"]
3939
SharedAuthStore["tools/&lt;tool&gt;/auth<br/>shared tool auth store, only with --auth-scope=shared"]
4040
FeatureAuthStore["features/&lt;feature&gt;/auth<br/>shared feature auth store, only with --auth-scope=shared"]
41+
FeatureStateStore["projects/&lt;hash&gt;/features/&lt;feature&gt;/state<br/>project-scoped state shared across tools"]
4142
end
4243
4344
subgraph Docker
@@ -54,6 +55,7 @@ flowchart LR
5455
EnvStore -.->|"host-side read/write env file"| Tool
5556
SharedAuthStore -->|"bind mount (~/.enclave-auth)"| Tool
5657
FeatureAuthStore -->|"bind mount (~/.enclave-feature-auth/&lt;feature&gt;)"| Tool
58+
FeatureStateStore -->|"bind mount (~/.enclave-feature-state/&lt;feature&gt;)"| Tool
5759
Gateway --> DnsNet
5860
Tool --> DnsNet
5961
Tool -.->|"resolver/proxy"| Gateway
@@ -166,7 +168,7 @@ The restricted network request flow has a separate
166168
- **User-defined subcommands**: executables under `~/.config/enclave/commands/{host,session}/` become `enclave <name>` verbs. `cli.Parse` discovers them, registers name-only stub commands (Cobra group "User Commands") so they list in `--help` and shell completion, and intercepts a matching first positional *before* `normalizeArgs`/Cobra so the trailing line reaches the script verbatim (preserving the unknown-command rejection for everything else). enclave flags must precede the name: host commands accept only the global group, session commands accept the full session flag set. `host/` commands exec directly on the host (`os/exec`, exit code/stdin/stdout passthrough, `ENCLAVE_BIN`/`ENCLAVE_PROJECT_ROOT`/`ENCLAVE_CONFIG_DIR` injected). `session/` commands run through the normal run pipeline as a shell-style execution (`opts.Shell=true`, argv `bash -c 'exec "$@"' <name> <container-path> <args>` so the script's shebang is honored via execve).
167169
- **Session command isolation boundary**: session commands mount only the `session/` tree, read-only, at the fixed neutral container path `/opt/enclave/commands` (`model.UserCommandsContainerDir`) via a dedicated `model.UserCommandMount` — never through `mounts.AddAdditional` (which mirrors host paths and would leak the home layout). No mount/backend code path ever references the `host/` tree, and no enclave host-data directory is otherwise mounted, so host commands stay invisible in-container by construction. Session commands receive no `ENCLAVE_*` env injection.
168170
- **Detached sessions**: `--background` runs detached tool containers that can be reattached. There is no separate daemon run mode.
169-
- **Persistent stores**: per-tool/project stores hold tool configs; an optional env store persists env auth and `--pass-env` values when persistence is enabled (default unless `--ephemeral`). Stores are host directories under `~/.local/state/enclave/` bind-mounted into the container (no Docker volumes). See [`docs/runtime/stores.md`](runtime/stores.md) for detailed store lifecycle, auth symlinks, and scoping documentation.
171+
- **Persistent stores**: per-tool/project stores hold tool configs; an optional env store persists env auth and `--pass-env` values; opted-in mixins receive per-feature/project state shared across tools. Persistence is enabled by default unless `--ephemeral`. Stores are host directories under `~/.local/state/enclave/` bind-mounted into the container (no Docker volumes). See [`docs/runtime/stores.md`](runtime/stores.md) for detailed store lifecycle, auth symlinks, and scoping documentation.
170172
- **Caches/history**: host-side caches are stored under `~/.cache/enclave/` and shell history under `~/.local/state/enclave/projects/`.
171173
- **Network isolation**: by default, a gateway sidecar (dnsmasq + transparent proxy) restricts outbound domains. DNS blocks unknown domains, and the proxy is passthrough-by-default with MITM only for hosts that need secret release rewriting unless `network_log=requests` forces MITM for all allowlisted HTTPS.
172174
- **Declared secrets and HTTP release**: tool profiles and enabled feature manifests can declare `secrets`. Each secret lists env-var aliases and can optionally define `release.http` target hosts/header formatting. Declared secrets are resolved from host env, layered secrets files, or persisted env; when gateway release is enabled, matching secrets are replaced with `ENCLAVE_SECRET_*` placeholders inside the container. The flow is: extension `secrets` config → `PlaceholderResolver` generates placeholders → `SecretMapping` entries written to a JSON file → gateway proxy loads secret release rules and performs header rewriting on HTTPS requests. Plaintext HTTP requests carrying placeholders are denied. This protection is limited to env-var injection: credential files written by auth hooks can still contain the real secret in the config/auth store.
@@ -189,11 +191,12 @@ Persistent stores are host directories under `~/.local/state/enclave/` (honoring
189191
- **Env store**: `~/.local/state/enclave/projects/<hash>/<tool>/env/` stores persisted env auth data and additional `--pass-env` values when persistence is enabled (default unless `--ephemeral`).
190192
- **Shared auth store** (when `--auth-scope=shared`): `~/.local/state/enclave/tools/<tool>/auth/<identity>/` stores OAuth files shared across projects for the tool; `<identity>` is `default` or the `--auth-name` slug.
191193
- **Feature auth store** (when `--auth-scope=shared` and feature defines `authFiles`): `~/.local/state/enclave/features/<feature>/auth/` stores feature auth files shared across tools/projects (for example `github-cli`).
194+
- **Feature state store** (when an enabled mixin declares `state: true`): `~/.local/state/enclave/projects/<hash>/features/<feature>/state/` stores feature-owned project data shared across tools. It is not created for ephemeral sessions and is independent of auth scope.
192195

193196
Other host-side data:
194197

195198
- **Embedded asset cache**: standalone binaries extract into `${XDG_CACHE_HOME:-~/.cache}/enclave/assets/<hash>/` on Linux or `~/Library/Caches/org.eclipse.enclave/assets/<hash>/` on macOS. Extraction uses a per-content lock, temporary sibling directory, and atomic rename so concurrent first runs share one complete entry. Missing or invalid entries are recreated from the binary.
196-
- **QEMU stores**: the experimental QEMU backend mounts the same host-directory stores as the Docker backend (resolved via `internal/backend/hoststore`) into the guest over 9p, so auth, config, and env state are shared across backends.
199+
- **QEMU stores**: the experimental QEMU backend mounts the same host-directory stores as the Docker backend (resolved via `internal/backend/hoststore`) into the guest over 9p. The store layer supports feature state, but current QEMU bundles disable all mixins, so normal QEMU runs do not request it.
197200
- **QEMU bundles**: generated microVM bundles live under `${XDG_CACHE_HOME:-~/.cache}/enclave/microvm/<tool>/<hash>/` unless `--image-name` points at an explicit bundle directory.
198201
- **Caches**: `~/.cache/enclave/<tool>/<hash>/` for package managers and build tools:
199202
- `npm/` - npm package cache

docs/cli-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ Mutation commands (`add-domain`, `remove-domain`, `set-mode`) apply the new poli
115115
| `enclave cleanup --all` | All projects and tools |
116116
| `enclave cleanup --ephemeral` | Remove stopped containers and ephemeral session stores |
117117
| `enclave cleanup --dry-run` | Preview what would be removed |
118-
| `enclave cleanup --keep cache,history,auth,memory` | Preserve the listed stores (comma-separated or repeated `--keep`): `cache` (package caches), `history` (shell history), `auth` (auth stores, with `--all`), `memory` (per-project agent memory, no selective effect with `--all`) |
118+
| `enclave cleanup --keep cache,history,auth,memory,feature-state` | Preserve the listed stores (comma-separated or repeated `--keep`): `cache` (package caches), `history` (shell history), `auth` (auth stores, with `--all`), `memory` (per-project agent memory, no selective effect with `--all`), `feature-state` (project-scoped feature state) |
119119
| `enclave cleanup --build-cache` | Prune Docker build cache (requires confirmation) |
120120

121121
---

docs/extensions/README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,15 @@ aptPackages: [<packages>]
112112
needsRoot: <bool>
113113
failOnInstallError: <bool>
114114
defaultEnabled: <bool>
115+
state: <bool, default false> # project-scoped persistent state shared across tools
115116

116117
# tool-only field (kind: sandbox)
117118
defaultIncluded: <bool>
118119
```
119120
120121
`name` and `kind` are validated against the file's location and directory
121-
name at load time.
122+
name at load time. The sandbox name `features` is reserved for the
123+
project-scoped feature-state namespace.
122124

123125
### Reserved and deferred fields
124126

@@ -524,6 +526,31 @@ every host the proxy injects credentials for is unioned into the session
524526
allow set so it resolves. A service mixin thus declares its own reachability
525527
without help from the tool spec.
526528

529+
Set `state: true` only when a feature owns persistent project data that must be
530+
shared across tools. Enclave mounts the store at
531+
`~/.enclave-feature-state/<feature>/` and exports
532+
`ENCLAVE_FEATURE_STATE_DIR` while sourcing that feature's
533+
`feature-entrypoint.d/*.sh`. The variable is unset afterward. State is not
534+
created under `--ephemeral` and is independent of `--auth-scope`.
535+
536+
```yaml
537+
schemaVersion: "1"
538+
kind: mixin
539+
name: state-probe
540+
state: true
541+
```
542+
543+
A setup script can link the feature's native path to the neutral store:
544+
545+
```sh
546+
if [ -n "${ENCLAVE_FEATURE_STATE_DIR:-}" ] && [ ! -e "$HOME/.state-probe" ] && [ ! -L "$HOME/.state-probe" ]; then
547+
ln -s "$ENCLAVE_FEATURE_STATE_DIR" "$HOME/.state-probe"
548+
fi
549+
```
550+
551+
The store may be mounted into multiple sessions concurrently. Stateful
552+
features must provide their own process-safe locking or database concurrency.
553+
527554
### Optional Files
528555

529556
| File | Purpose |
@@ -618,6 +645,7 @@ resolved port appears in the printed `openUrl` and in `enclave ps`.
618645
| `needsRoot` | N/A | Controls install user |
619646
| `aptPackages` | N/A | Auto-installed apt packages |
620647
| Declared `ports` | Published for every session of the tool | Published when the feature is enabled |
648+
| `state: true` | Invalid | Opts into project-scoped state shared across tools |
621649

622650
## How It Works
623651

@@ -665,7 +693,7 @@ CLI sets it to the single selected tool):
665693

666694
1. Determines current tool from `$TOOL` env var
667695
2. Sources scripts from `extensions/tools/{tool}/entrypoint.d/*.sh` (tool-specific)
668-
3. Sources scripts from `extensions/*/feature-entrypoint.d/*.sh` (all features)
696+
3. Sources scripts from `extensions/*/feature-entrypoint.d/*.sh` (all enabled features), setting `ENCLAVE_FEATURE_STATE_DIR` only for a feature that declares `state: true`
669697

670698
### Go Registration (Tools Only)
671699

@@ -725,6 +753,7 @@ Hooks run in a fixed order during runtime auth preparation:
725753
3. Optionally add:
726754
- `install.sh` for custom installation (set `needsRoot: true` if it needs root)
727755
- `feature-entrypoint.d/setup.sh` for runtime initialization
756+
- `state: true` when the feature needs persistent project data shared across tools
728757

729758
### Feature Guidelines
730759

docs/persistence.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Per-project data is stored on the host and reused across sessions:
4444
| Shell history | `~/.local/state/enclave/projects/<project-hash>/<tool>/history/` |
4545
| Agent memory | `~/.local/state/enclave/projects/<project-hash>/<tool>/memory/` (Claude) |
4646
| Config/env/auth stores | Host directories under `~/.local/state/enclave/` (bind-mounted; no Docker volumes) |
47+
| Feature state | `~/.local/state/enclave/projects/<project-hash>/features/<feature>/state/` for enabled mixins that opt in with `state: true`; shared across tools |
4748
| Embedded runtime assets | `~/.cache/enclave/assets/<content-hash>/` |
4849

4950
Extracted runtime asset entries are reproducible cache data. Deleting them is
@@ -55,8 +56,9 @@ the standard Apple locations, in a reverse-DNS application directory: config
5556
and state under `~/Library/Application Support/org.eclipse.enclave/`
5657
(`config/`, `state/`) and caches under `~/Library/Caches/org.eclipse.enclave/`.
5758

58-
Agent memory is skipped for `--ephemeral` sessions: memory written during them
59-
is discarded with the session's config store.
59+
Agent memory and feature state are skipped for `--ephemeral` sessions. Feature
60+
state is independent of auth scope and is shared by all tools running the same
61+
enabled feature in a project.
6062

6163
Disable specific persistence:
6264

@@ -81,7 +83,7 @@ Options:
8183
|------|--------|
8284
| `--all` | Remove stores and caches for all projects and tools |
8385
| `--ephemeral` | Remove stopped containers and ephemeral session stores |
84-
| `--keep <kinds>` | Preserve the listed stores (comma-separated or repeated): `cache` (package caches), `history` (shell history), `memory` (per-project agent memory, removed by default; no selective effect with `--all`), `auth` (auth stores, with `--all`) |
86+
| `--keep <kinds>` | Preserve the listed stores (comma-separated or repeated): `cache` (package caches), `history` (shell history), `memory` (per-project agent memory, removed by default; no selective effect with `--all`), `auth` (auth stores, with `--all`), `feature-state` (project-scoped feature state) |
8587
| `--build-cache` | Prune Docker build cache (requires confirmation) |
8688
| `--dry-run` | Preview what would be removed |
8789

@@ -91,6 +93,7 @@ Examples:
9193
enclave cleanup --dry-run
9294
enclave cleanup --keep cache
9395
enclave cleanup --keep cache,history
96+
enclave cleanup --keep feature-state
9497
enclave cleanup --ephemeral
9598
enclave cleanup --all
9699
```

0 commit comments

Comments
 (0)