@crvouga/dev-fleet 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -16
- package/ansible/github/git.yml +66 -0
- package/ansible/macos/manager.yml +3 -0
- package/ansible/macos/policy.json +11 -1
- package/ansible/macos/t3-policy.yml +49 -0
- package/ansible/monitoring/compose.yml +10 -0
- package/ansible/monitoring/container-watchdog.py +118 -0
- package/ansible/monitoring/hub-darwin.yml +146 -0
- package/ansible/net/remote-probe.sh +5 -0
- package/ansible/ubuntu/baseline.yml +2 -0
- package/ansible/ubuntu/disk-gc.py +25 -3
- package/ansible/ubuntu/disk-guard.py +24 -5
- package/ansible/ubuntu/policy.json +51 -1
- package/ansible/ubuntu/t3-desktop.yml +19 -0
- package/ansible/ubuntu/t3-health.sh +93 -22
- package/ansible/ubuntu/t3-idle.py +42 -0
- package/ansible/ubuntu/t3-policy.yml +4 -11
- package/ansible/ubuntu/tasks/browser.yml +56 -0
- package/ansible/ubuntu/tasks/docker.yml +52 -0
- package/ansible/ubuntu/tasks/recovery.yml +60 -1
- package/ansible/ubuntu/tasks/resources.yml +2 -2
- package/ansible/ubuntu/tasks/services.yml +13 -0
- package/ansible/ubuntu/tasks/t3-restart.yml +37 -0
- package/ansible/ubuntu/wip-guard.py +121 -0
- package/bootstrap/doctor.ts +32 -6
- package/bootstrap/fleet-remote-bootstrap.sh +31 -4
- package/bootstrap/native-t3.ts +22 -11
- package/bootstrap/setup.ts +23 -8
- package/bootstrap/worker-tools.ts +118 -0
- package/cliproxy/README.md +4 -4
- package/cliproxy/compose.yaml +4 -0
- package/cliproxy/scripts/accounts.py +216 -0
- package/cliproxy/scripts/checkpoint.py +30 -2
- package/cliproxy/scripts/cli.py +1 -1
- package/cliproxy/scripts/clientkeys.py +99 -0
- package/cliproxy/scripts/lease.py +80 -0
- package/cliproxy/scripts/ops +137 -25
- package/cliproxy/scripts/restore-verify +15 -0
- package/docs/accounts.md +154 -0
- package/docs/authentication.md +90 -0
- package/docs/declared.md +5 -1
- package/docs/durability.md +26 -0
- package/docs/machine-setup.md +26 -15
- package/docs/operations.md +87 -31
- package/docs/runbooks.md +380 -0
- package/docs/state.md +13 -7
- package/docs/topology.md +2 -0
- package/docs/ubuntu-sync.md +35 -28
- package/examples/fleet.json +19 -1
- package/llms.txt +3 -0
- package/mise.toml +10 -1
- package/package.json +2 -2
- package/schema/check-ids.json +522 -0
- package/schema/fleet.schema.json +244 -1
- package/schema/output/check.schema.json +23 -0
- package/schema/output/error.schema.json +14 -0
- package/schema/output/fleet-report.schema.json +79 -0
- package/schema/output/fleet-sync.schema.json +112 -0
- package/schema/output/report.schema.json +38 -0
- package/src/accounts/checks.ts +220 -0
- package/src/accounts/context.ts +76 -0
- package/src/accounts/propagate.ts +62 -0
- package/src/accounts/roster.ts +85 -0
- package/src/accounts/selection.ts +67 -0
- package/src/accounts/sessions.ts +120 -0
- package/src/agents/account-relay.ts +176 -26
- package/src/agents/launch.ts +84 -5
- package/src/agents/native-verify.ts +128 -2
- package/src/auth-checks.ts +3 -0
- package/src/cli/agent-guide.ts +5 -3
- package/src/cli/fleet-accounts.ts +253 -0
- package/src/cli/fleet-report.ts +126 -0
- package/src/cli/fleet-sync-accounts.ts +58 -0
- package/src/cli/fleet-sync-manager.ts +31 -4
- package/src/cli/fleet-sync-output.ts +3 -2
- package/src/cli/fleet-sync-plan.ts +200 -0
- package/src/cli/fleet-sync-remote.ts +23 -14
- package/src/cli/fleet-sync-schedule.ts +142 -35
- package/src/cli/fleet-sync.ts +126 -8
- package/src/cli/fleet-watch.ts +198 -0
- package/src/cli/fleet.ts +4 -0
- package/src/cli/register-fleet.ts +86 -0
- package/src/cli/registry.ts +26 -0
- package/src/cli/schedule-logs.ts +36 -0
- package/src/clock.ts +63 -0
- package/src/definition.ts +226 -7
- package/src/git-identity.ts +160 -0
- package/src/ide/definition.ts +86 -0
- package/src/ide/ide.ts +43 -0
- package/src/ide/index.ts +5 -0
- package/src/ide/orchestrate.ts +50 -0
- package/src/ide/t3.ts +111 -0
- package/src/machine-health.ts +51 -4
- package/src/manager-health.ts +205 -0
- package/src/manager-sync.ts +13 -17
- package/src/monitoring/hub-backup.ts +63 -17
- package/src/monitoring/manager-hub.ts +44 -2
- package/src/node-converge-linux.ts +44 -12
- package/src/notify.ts +239 -0
- package/src/platform/darwin.ts +0 -13
- package/src/platform/fallback.ts +0 -8
- package/src/platform/linux.ts +0 -10
- package/src/platform/t3-policy.ts +33 -6
- package/src/platform/t3-service.ts +104 -0
- package/src/platform/types.ts +0 -4
- package/src/platform/windows.ts +0 -14
- package/src/remote-bootstrap.ts +87 -25
- package/src/remote-entry.ts +15 -1
- package/src/remote-operation.ts +213 -0
- package/src/schedule-check.ts +40 -0
- package/src/secrets.ts +26 -8
- package/src/service-auth.ts +15 -5
- package/src/ssh.ts +10 -2
- package/src/state/history.ts +172 -0
- package/src/state/store.ts +11 -4
- package/src/t3/accounts.ts +7 -1
- package/src/t3/models.ts +133 -0
- package/src/t3/pairing.ts +31 -0
- package/src/t3/profile.ts +203 -0
- package/src/t3/version.ts +119 -0
- package/src/t3-linked-check.ts +1 -1
- package/src/tailscale.ts +51 -0
- package/src/toolchain-checks.ts +8 -3
- package/src/transport.ts +3 -2
- package/src/worker-capability-checks.ts +167 -0
- package/src/worker-capacity.ts +19 -7
- package/src/worker-host-checks.ts +68 -13
- package/src/worker-policy.ts +33 -0
- package/src/worker-readiness.ts +10 -1
package/docs/accounts.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Provider accounts
|
|
2
|
+
|
|
3
|
+
**The rule.** Provider accounts exist only as the CLI proxy's credential files
|
|
4
|
+
on the manager. Every node and IDE gets a view derived from them, and nothing
|
|
5
|
+
on a node holds a provider login of its own. T3 offers one instance per
|
|
6
|
+
account, named `<Provider> <account email>`, and every launcher reaches the
|
|
7
|
+
provider through the proxy.
|
|
8
|
+
|
|
9
|
+
## The roster
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npx devfleet fleet-accounts # provider, index, label, state, when a cooldown clears
|
|
13
|
+
npx devfleet fleet-accounts --diff # and what this machine offers against it
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Each account has one state, derived from its credential file and the proxy's
|
|
17
|
+
management API:
|
|
18
|
+
|
|
19
|
+
| State | Meaning |
|
|
20
|
+
| ----------------- | -------------------------------------------------------------------------- |
|
|
21
|
+
| `usable` | Serves requests |
|
|
22
|
+
| `cooling-down` | The provider limited it; `until` says when it clears, when the proxy knows |
|
|
23
|
+
| `refresh-failing` | Its token refresh fails for now, and its access token is still valid |
|
|
24
|
+
| `needs-login` | The provider refused its refresh token: only signing in again helps |
|
|
25
|
+
| `removed` | Disabled on the proxy |
|
|
26
|
+
|
|
27
|
+
The index of an account is its position among its provider's accounts, sorted
|
|
28
|
+
by sign-in email. Checks name accounts by provider and index only.
|
|
29
|
+
|
|
30
|
+
The **roster version** is a digest of the routing prefixes of the accounts.
|
|
31
|
+
The manager computes it from the credential files and every node from the
|
|
32
|
+
proxy's model list, so the two agree exactly when a node offers what the
|
|
33
|
+
proxy holds.
|
|
34
|
+
|
|
35
|
+
## Checks
|
|
36
|
+
|
|
37
|
+
| Check | Fails when |
|
|
38
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
39
|
+
| `proxy:account:<provider>` | The provider has no usable account; it warns below `services.proxy.accounts.minimumUsable` (default 2) |
|
|
40
|
+
| `accounts:roster-current` | The node offers another roster version than the proxy's |
|
|
41
|
+
| `accounts:native-login` | (warns) An agent CLI on the node keeps a provider login of its own |
|
|
42
|
+
| `proxy:reachable-from` | The node gets no answer from the proxy; it warns above two seconds |
|
|
43
|
+
| `agent:<tool>:account-<n>:session` | That account's instance does not run a session; it warns while the account is cooling down |
|
|
44
|
+
| `agent:<tool>:removed-<n>:session` | The node still offers an instance for an account the proxy no longer holds |
|
|
45
|
+
|
|
46
|
+
Session results are cached for six hours under a key that holds the roster
|
|
47
|
+
version and the version of the client key, so any account change or key
|
|
48
|
+
rotation re-verifies at once.
|
|
49
|
+
|
|
50
|
+
## Adding, signing in again, removing
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npx devfleet fleet-accounts --add claude
|
|
54
|
+
npx devfleet fleet-accounts --relogin codex 2
|
|
55
|
+
npx devfleet fleet-accounts --remove codex 2
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A sign-in writes into a staging directory beside the credentials; only a
|
|
59
|
+
complete credential is moved in, with one rename, so an interrupted sign-in
|
|
60
|
+
leaves nothing behind. Routing prefixes are applied in the same command. Every
|
|
61
|
+
change is then propagated to every node.
|
|
62
|
+
|
|
63
|
+
## Propagation without a full sync
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
npx devfleet fleet-sync --only accounts
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
On the manager and every worker at once, each node re-reads the proxy's
|
|
70
|
+
roster and rewrites only its per-account T3 instances. T3 watches its
|
|
71
|
+
settings, so nothing restarts and no running turn ends. Instance ids derive
|
|
72
|
+
from the account's routing prefix and stay the same. A node that cannot be
|
|
73
|
+
reached catches up on the next propagation or sync; until then
|
|
74
|
+
`accounts:roster-current` reports the version it has.
|
|
75
|
+
|
|
76
|
+
## Choosing an account
|
|
77
|
+
|
|
78
|
+
Unpinned requests go through a loopback relay on every node. When the fleet
|
|
79
|
+
declares a policy for a provider, the relay picks the account of each request:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
"services": {
|
|
83
|
+
"proxy": {
|
|
84
|
+
"endpoint": "https://cli-proxy.example.com",
|
|
85
|
+
"accounts": {
|
|
86
|
+
"minimumUsable": 2,
|
|
87
|
+
"selection": {
|
|
88
|
+
"codex": { "strategy": "fill-first", "order": ["a@example.com"] }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Strategy | Order |
|
|
96
|
+
| ------------------------ | ------------------------------------------------ |
|
|
97
|
+
| `fill-first` | The same order for every request |
|
|
98
|
+
| `round-robin` | Each request starts one account further along |
|
|
99
|
+
| `least-recently-limited` | The account limited longest ago (or never) first |
|
|
100
|
+
|
|
101
|
+
The order is the accounts `order` names, in that order, then the others in
|
|
102
|
+
the order of their routing prefix. A quota or authentication refusal sends
|
|
103
|
+
the request on to the next account, and the refused one cools down for its
|
|
104
|
+
`Retry-After` (a minute without one). Without a policy the proxy's own
|
|
105
|
+
fill-first pool chooses. An instance pinned to one account answers "This
|
|
106
|
+
account is cooling down until …" and names the other instances of its
|
|
107
|
+
provider.
|
|
108
|
+
|
|
109
|
+
## Proxy outages
|
|
110
|
+
|
|
111
|
+
Every relayed launch (Claude, Codex, Gemini and Antigravity, pinned or not)
|
|
112
|
+
retries a proxy that does not answer for about a minute. If it stays down,
|
|
113
|
+
the agent gets one distinct, retryable error: "The CLI proxy has been
|
|
114
|
+
unreachable since …".
|
|
115
|
+
|
|
116
|
+
## Checkpoints and the proxy state
|
|
117
|
+
|
|
118
|
+
Each checkpoint records a short digest of every credential's refresh token,
|
|
119
|
+
never the token. After restoring an older checkpoint, `restore-verify
|
|
120
|
+
RESTORED --newest NEWEST` names exactly the accounts whose refresh token the
|
|
121
|
+
provider has since replaced (`needs-login: <provider> <index>`).
|
|
122
|
+
|
|
123
|
+
`cliproxy/scripts/lease.py` is a fencing lease on the proxy state: a second
|
|
124
|
+
proxy cannot take it while the first holds it, and taking over an expired
|
|
125
|
+
lease fences the old holder.
|
|
126
|
+
|
|
127
|
+
## Client keys
|
|
128
|
+
|
|
129
|
+
`devfleet cliproxy issue-key|rotate-key|retire-key|revoke-key NODE` keep one
|
|
130
|
+
proxy client key per node in the manager's key ring. A rotation keeps the old
|
|
131
|
+
key accepted until `retire-key`, so no running agent fails while nodes move
|
|
132
|
+
over. Revoking one node's key leaves the others working.
|
|
133
|
+
`devfleet cliproxy client-keys` reports each node's key by fingerprint only.
|
|
134
|
+
|
|
135
|
+
## Not yet done
|
|
136
|
+
|
|
137
|
+
- The proxy does not notify anyone when an account turns `needs-login`, and
|
|
138
|
+
no watcher on the manager triggers propagation or a checkpoint when a
|
|
139
|
+
credential file changes. Run `fleet-sync --only accounts` or
|
|
140
|
+
`fleet-accounts` instead.
|
|
141
|
+
- Nothing forces a refresh of every account after a restore. The digest
|
|
142
|
+
comparison above is offline.
|
|
143
|
+
- The lease is not yet taken by the proxy service at start, and its
|
|
144
|
+
production store (the checkpoint bucket) is not wired.
|
|
145
|
+
- Per-node client keys are not yet distributed to nodes through the secrets
|
|
146
|
+
provider, so nodes still present the shared key. No
|
|
147
|
+
`proxy:client-key-current` check exists yet.
|
|
148
|
+
- OpenCode and Grok read the proxy's address from rendered configuration and
|
|
149
|
+
are not relayed yet. There is no warm standby proxy.
|
|
150
|
+
- The selection policy is applied by the client relay; the proxy itself stays
|
|
151
|
+
fill-first.
|
|
152
|
+
- A sign-in uses the provider's device code where the proxy offers one.
|
|
153
|
+
Other providers print a callback URL, and no loopback forward is managed
|
|
154
|
+
for them.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Authentication and accounts
|
|
2
|
+
|
|
3
|
+
Where each credential dev-fleet uses lives, who can read it, how it reaches
|
|
4
|
+
each node, how it rotates and what happens when it fails. The three tables
|
|
5
|
+
are generated from the code; `features/rules/authentication.feature` fails
|
|
6
|
+
when one is out of date (`DEVFLEET_UPDATE_DOCS=1 bun test features/rules`
|
|
7
|
+
rewrites them).
|
|
8
|
+
|
|
9
|
+
## Where credentials live
|
|
10
|
+
|
|
11
|
+
| Credential | Lives in | Read by |
|
|
12
|
+
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
|
|
13
|
+
| The fleet's secrets (table below) | The provider `secrets` names in `fleet.json` | The node that needs it, through `src/secrets.ts` |
|
|
14
|
+
| The bucket's two keys | The environment of the `devfleet` command; `~/.config/dev-fleet/bucket.json` (0600) on the manager after `fleet-state --init` | The manager only |
|
|
15
|
+
| A node's Vault token | That node's own Vault session (`~/.vault-token`) | That node only; never copied |
|
|
16
|
+
| A node's GitHub login | `gh`'s own store on that node | `gh`, and git through its credential helper |
|
|
17
|
+
| Provider accounts (Claude, Codex …) | The CLI proxy's store on the manager | The proxy; nodes hold only the proxy client key |
|
|
18
|
+
| Tailscale and T3 pairing | Each machine, signed in by a person | That machine |
|
|
19
|
+
|
|
20
|
+
## How they reach each node
|
|
21
|
+
|
|
22
|
+
- **Vault.** A person logs each machine in once (`fleet-sync` asks on the
|
|
23
|
+
manager and passes the password to the worker over SSH on standard input).
|
|
24
|
+
From then on the worker's `personal-worker-auth` timer renews its own token
|
|
25
|
+
(`bootstrap/worker-auth.ts`). Nothing renders provider configuration from
|
|
26
|
+
Vault on a worker: agent profiles come from `fleet-sync`.
|
|
27
|
+
- **GitHub.** The same job restores the declared account (`worker.githubLogin`)
|
|
28
|
+
with the GitHub service token it reads from its own Vault session
|
|
29
|
+
(`bootstrap/worker-github.ts`, on standard input, never argv). `fleet-sync`
|
|
30
|
+
makes `gh` git's credential helper and writes the declared identity
|
|
31
|
+
(`git` in `fleet.json`, `ansible/github/git.yml`) on every node.
|
|
32
|
+
- **The proxy client key** is read by each node from its own secrets provider
|
|
33
|
+
whenever a launcher starts an agent.
|
|
34
|
+
- **The monitoring hub's backup storage keys** are read on the manager by
|
|
35
|
+
`fleet-sync` and handed to the hub's playbook in a private variables file.
|
|
36
|
+
|
|
37
|
+
## Rotation and failure
|
|
38
|
+
|
|
39
|
+
Rotate a secret where the provider keeps it; the next read uses it, nothing
|
|
40
|
+
caches it. A Vault token that is not renewed expires: `worker:vault-renewal`
|
|
41
|
+
fails and the fix is one login of that machine. A GitHub token that stops
|
|
42
|
+
working fails `auth:github` and `worker:git-push-auth`; store the new one in
|
|
43
|
+
Vault and the worker restores itself within ten minutes. During a proxy
|
|
44
|
+
outage agents on workers wait: no worker is configured for native clients.
|
|
45
|
+
Every failure code is in [the runbooks](runbooks.md).
|
|
46
|
+
|
|
47
|
+
## The fleet's secrets
|
|
48
|
+
|
|
49
|
+
<!-- generated: secrets -->
|
|
50
|
+
|
|
51
|
+
| Secret | Required | `env` provider variable | `file` provider file |
|
|
52
|
+
| ------------------------------ | ---------------------------- | ---------------------------------------------- | ---------------------------------------------------------- |
|
|
53
|
+
| `proxy-client-key` | yes | `DEVFLEET_SECRET_PROXY_CLIENT_KEY` | `~/.config/dev-fleet/secrets/proxy-client-key` |
|
|
54
|
+
| `monitoring-account-password` | yes | `DEVFLEET_SECRET_MONITORING_ACCOUNT_PASSWORD` | `~/.config/dev-fleet/secrets/monitoring-account-password` |
|
|
55
|
+
| `monitoring-tunnel-token` | yes | `DEVFLEET_SECRET_MONITORING_TUNNEL_TOKEN` | `~/.config/dev-fleet/secrets/monitoring-tunnel-token` |
|
|
56
|
+
| `proxy-tunnel-token` | yes | `DEVFLEET_SECRET_PROXY_TUNNEL_TOKEN` | `~/.config/dev-fleet/secrets/proxy-tunnel-token` |
|
|
57
|
+
| `monitoring-backup-access-key` | when its feature is declared | `DEVFLEET_SECRET_MONITORING_BACKUP_ACCESS_KEY` | `~/.config/dev-fleet/secrets/monitoring-backup-access-key` |
|
|
58
|
+
| `monitoring-backup-secret-key` | when its feature is declared | `DEVFLEET_SECRET_MONITORING_BACKUP_SECRET_KEY` | `~/.config/dev-fleet/secrets/monitoring-backup-secret-key` |
|
|
59
|
+
| `notify-webhook-url` | when its feature is declared | `DEVFLEET_SECRET_NOTIFY_WEBHOOK_URL` | `~/.config/dev-fleet/secrets/notify-webhook-url` |
|
|
60
|
+
| `notify-ntfy-url` | when its feature is declared | `DEVFLEET_SECRET_NOTIFY_NTFY_URL` | `~/.config/dev-fleet/secrets/notify-ntfy-url` |
|
|
61
|
+
|
|
62
|
+
<!-- end: secrets -->
|
|
63
|
+
|
|
64
|
+
## Checks of credentials
|
|
65
|
+
|
|
66
|
+
<!-- generated: checks -->
|
|
67
|
+
|
|
68
|
+
| Check | Measured in |
|
|
69
|
+
| ----------------------------- | ------------------------------------------ |
|
|
70
|
+
| `auth:vault` | `src/cli/fleet-sync-vault.ts` |
|
|
71
|
+
| `worker:git-identity` | `src/git-identity.ts` |
|
|
72
|
+
| `worker:git-push-auth` | `src/definition.ts`, `src/git-identity.ts` |
|
|
73
|
+
| `worker:github-principal` | `src/worker-access-checks.ts` |
|
|
74
|
+
| `worker:proxy-authentication` | `src/worker-access-checks.ts` |
|
|
75
|
+
| `worker:vault-renewal` | `src/worker-access-checks.ts` |
|
|
76
|
+
|
|
77
|
+
<!-- end: checks -->
|
|
78
|
+
|
|
79
|
+
## Timers
|
|
80
|
+
|
|
81
|
+
<!-- generated: timers -->
|
|
82
|
+
|
|
83
|
+
| Timer | Declared in | Schedule |
|
|
84
|
+
| -------------------------------- | -------------------------------------- | ------------------------------------- |
|
|
85
|
+
| `personal-fleet-disk-gc.timer` | `ansible/ubuntu/tasks/resources.yml` | OnBootSec=10min, OnCalendar=hourly |
|
|
86
|
+
| `personal-worker-auth.timer` | `ansible/ubuntu/tasks/credentials.yml` | OnBootSec=2min, OnUnitActiveSec=10min |
|
|
87
|
+
| `personal-worker-recovery.timer` | `ansible/ubuntu/tasks/recovery.yml` | OnBootSec=1min, OnUnitActiveSec=1min |
|
|
88
|
+
| `personal-worker-recovery.timer` | `ansible/ubuntu/tasks/recovery.yml` | OnBootSec=5min, OnUnitActiveSec=15min |
|
|
89
|
+
|
|
90
|
+
<!-- end: timers -->
|
package/docs/declared.md
CHANGED
|
@@ -18,7 +18,9 @@ may only shrink.
|
|
|
18
18
|
| Tools: languages, CLIs, coding agents | `mise.toml` | `mise.lock`: version and checksum for each platform | `mise install --locked` | Tool and `agent:verification` checks |
|
|
19
19
|
| mise itself | `min_version` and `mise_*_sha256` in `mise.toml` | The digest of its release archive, held here | `bootstrap/bootstrap.sh` | Its version, at every toolchain setup |
|
|
20
20
|
| JavaScript dependencies | `package.json` | `bun.lock` | `bun install --frozen-lockfile` | The install fails on a difference |
|
|
21
|
-
| T3 Code, server and desktop app | `t3_*` under `[vars]` in `mise.toml` | SHA-256 of each artifact | `bootstrap/native-t3.ts`; `ansible/ubuntu/t3-desktop.yml` | T3 checks
|
|
21
|
+
| T3 Code, server and desktop app | `t3_*` under `[vars]` in `mise.toml` | SHA-256 of each artifact | `bootstrap/native-t3.ts`; `ansible/ubuntu/t3-desktop.yml` | T3 checks; `t3:version` on every machine |
|
|
22
|
+
| T3 policy on the manager (updates off) | `ansible/ubuntu/t3/policy.json`, shared with the workers | The commit | `ansible/macos/t3-policy.yml`, once, as an administrator | `t3:updates` |
|
|
23
|
+
| Worker work-in-progress guard | `ansible/ubuntu/wip-guard.py`, `ansible/ubuntu/tasks/recovery.yml` | The commit | `fleet-sync` | `features/ide/work-in-progress.feature` |
|
|
22
24
|
| Ubuntu packages | `packages` and `snapshot` in `ansible/ubuntu/policy.json` | Exact versions from a dated archive snapshot | `ansible/ubuntu/tasks/packages.yml` | `package:*` |
|
|
23
25
|
| Tailscale on workers | `tailscale` in `ansible/ubuntu/policy.json` | Exact version; digest of the signing key | `ansible/ubuntu/tailscale.yml` | `package:tailscale` |
|
|
24
26
|
| Worker policy: power, SSH, sudo, memory, disk | `ansible/ubuntu/*.yml`, values shared with checks in `policy.json` | The commit | `fleet-sync` | Host and resource checks |
|
|
@@ -29,6 +31,8 @@ may only shrink.
|
|
|
29
31
|
| Ansible on a worker | `ansible-core` in `ansible/ubuntu/policy.json` | Exact version | `ansible/ubuntu/tasks/packages.yml` | `package:ansible-core` |
|
|
30
32
|
| The CLI proxy | `cliproxy/compose.yaml`, `image.lock.json`, `requirements.lock`, `launchd/`, `config.template.yaml` | Image digests; hashed Python dependencies | `devfleet cliproxy install`, and `fleet-sync` after it | The proxy's audit |
|
|
31
33
|
| Agent profiles and T3 providers | `agents/registry.json`, `agents/t3.json` | The commit | `fleet-sync` | Profile receipts; the plan after every apply |
|
|
34
|
+
| Worker time service | `ansible/ubuntu/tasks/services.yml`, offsets in `clock` in `ansible/ubuntu/policy.json` | The commit | `fleet-sync` | `worker:clock` |
|
|
35
|
+
| The manager's container watchdog | `ansible/monitoring/container-watchdog.py`, `containerWatchdog` in `ansible/macos/policy.json` | The commit | `fleet-sync` (`hub-darwin.yml`) | `manager:container-watchdog` |
|
|
32
36
|
| Docker Desktop on the manager | `docker_desktop_*` under `[vars]` in `mise.toml` | Version and build | You | `manager:docker` (warns) |
|
|
33
37
|
| CI | `.github/workflows/` | Each action at a commit; tools from `mise.lock` | GitHub | `features/rules/` |
|
|
34
38
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# What survives the loss of a machine
|
|
2
|
+
|
|
3
|
+
This is the one place that says what dev-fleet keeps when a machine is lost.
|
|
4
|
+
|
|
5
|
+
| Lost | Kept | Lost with it |
|
|
6
|
+
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| A worker | Everything `fleet-sync` rebuilds from Git and secrets. Work in agent worktrees (`~/.t3/worktrees`) up to the last run of the work-in-progress guard, every 15 minutes, in `refs/recovery/<node>/<branch>` of each worktree's remote. The T3 environment ID in the roster. | Up to 15 minutes of uncommitted work; T3 threads and their transcripts on that worker. |
|
|
8
|
+
| The manager | The fleet state, from the bucket ([state](state.md)). The T3 profile, provider instances and policy, from `fleet.json` and Git. | T3's own data on the manager: its linked environments and its threads. They are not in a backup set yet. |
|
|
9
|
+
|
|
10
|
+
## Recovering work from a lost worker
|
|
11
|
+
|
|
12
|
+
The guard pushes the files of each worktree as they were (changed and new
|
|
13
|
+
files, not ignored ones) as one commit on top of its `HEAD`, without touching
|
|
14
|
+
the worktree, its index or its branches:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
git fetch origin 'refs/recovery/*:refs/recovery/*'
|
|
18
|
+
git log refs/recovery/<node>/<branch>
|
|
19
|
+
git switch -c recovered refs/recovery/<node>/<branch>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A worktree whose work is already on a branch of its remote pushes nothing.
|
|
23
|
+
The guard is `ansible/ubuntu/wip-guard.py`, run by the user timer
|
|
24
|
+
`personal-worker-wip-guard.timer` that `ansible/ubuntu/tasks/recovery.yml`
|
|
25
|
+
installs. It pushes with the worker's own Git credentials, so a worktree
|
|
26
|
+
whose remote the worker cannot write to is reported and skipped.
|
package/docs/machine-setup.md
CHANGED
|
@@ -181,19 +181,21 @@ wrangler login
|
|
|
181
181
|
railway login
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
Only sign into providers the task actually needs.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
184
|
+
Only sign into providers the task actually needs. On fleet machines the Git
|
|
185
|
+
identity and the GitHub credential helper come from the `git` block of
|
|
186
|
+
`fleet.json`: `fleet-sync` writes them in `~/.config/git/config` and checks
|
|
187
|
+
them as `worker:git-identity` and `worker:git-push-auth`. Elsewhere set `git
|
|
188
|
+
config --global user.name` and `user.email` after confirming them; do not guess
|
|
189
|
+
an email or overwrite an existing identity. Enable Git LFS hooks when needed with `git lfs install`.
|
|
188
190
|
|
|
189
191
|
Optional existing workstation/agent settings:
|
|
190
192
|
|
|
191
193
|
```sh
|
|
192
194
|
sh bootstrap/install-launcher.sh
|
|
193
|
-
|
|
195
|
+
devfleet fleet-status --local
|
|
194
196
|
# Only for tasks needing the workspace's managed secrets:
|
|
195
197
|
vault login --method=userpass username=example-user
|
|
196
|
-
|
|
198
|
+
devfleet fleet-sync
|
|
197
199
|
```
|
|
198
200
|
|
|
199
201
|
The machine bootstrap installs tools/dependencies only. `personal:install` additionally
|
|
@@ -231,7 +233,7 @@ The manifest installs the **Docker client**. Containers also require a local or
|
|
|
231
233
|
remote Docker engine, Compose v2 and Buildx plugins. These cannot be installed as
|
|
232
234
|
one identical unprivileged binary on all OSes.
|
|
233
235
|
|
|
234
|
-
- **macOS:** run `
|
|
236
|
+
- **macOS:** run `bash bootstrap/docker-desktop.sh` to install the pinned,
|
|
235
237
|
checksum-verified Docker Desktop bundle from this repository and open it.
|
|
236
238
|
Complete its first-run agreement/setup in the app. Repeated invocation reuses
|
|
237
239
|
an existing installation. The shared mise Docker client remains in use.
|
|
@@ -280,11 +282,11 @@ mise install --locked bun
|
|
|
280
282
|
mise exec --locked bun -- bun bootstrap/setup.ts
|
|
281
283
|
```
|
|
282
284
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
without
|
|
287
|
-
them. Sync is explicit; Git commits do not silently update other machines.
|
|
285
|
+
On fleet machines `devfleet fleet-sync` refreshes the toolchain, the global
|
|
286
|
+
configuration and frozen dependencies; `mise exec --locked bun -- bun
|
|
287
|
+
bootstrap/setup.ts --tools-only` reapplies tool pins and the global
|
|
288
|
+
configuration without touching project dependencies, and `bun
|
|
289
|
+
bootstrap/doctor.ts --json` verifies them. Sync is explicit; Git commits do not silently update other machines.
|
|
288
290
|
|
|
289
291
|
For multiple development computers, use [the fleet package](operations.md):
|
|
290
292
|
`devfleet fleet-node-add` registers an existing SSH alias in a local roster,
|
|
@@ -352,9 +354,10 @@ only running T3 is the T3 Code desktop app, installed from the same release's
|
|
|
352
354
|
upstream `SHA256SUMS` omits packages) and started by the `t3code-desktop.service`
|
|
353
355
|
user unit in the GDM auto-login session. It shares `~/.t3` with the former
|
|
354
356
|
headless service, so environment identity and pairing are kept. A conflicting
|
|
355
|
-
launcher version
|
|
356
|
-
|
|
357
|
-
pin
|
|
357
|
+
launcher version requires reviewed maintenance; the recovery timer stops a
|
|
358
|
+
still-running headless service. Routine fleet-sync never ends a running turn
|
|
359
|
+
to load a new pin or policy: the app restarts into them at the first idle
|
|
360
|
+
check (`t3:running-version`, `t3:policy-loaded`). The disposable Ubuntu VM test still exercises the former headless service
|
|
358
361
|
and does not cover the desktop session. Private routing and paired execution
|
|
359
362
|
require separate evidence.
|
|
360
363
|
|
|
@@ -394,6 +397,14 @@ It installs the `vars.worker_tools` subset from `mise.toml`, with versions and
|
|
|
394
397
|
artifacts taken from the same `mise.lock`; native T3 and Ubuntu Docker/service
|
|
395
398
|
policy remain source-pinned. `bun bootstrap/doctor.ts --worker --docker --json`
|
|
396
399
|
checks that profile. It does not require unrelated cloud CLIs on workers.
|
|
400
|
+
|
|
401
|
+
Tool profiles add locked tool sets on top of it: `worker.toolProfiles` in
|
|
402
|
+
`fleet.json` (fleet-wide) or `toolProfiles` on one worker selects `web` (pnpm,
|
|
403
|
+
Deno, Yarn and headless Chromium for browser tests), `go`, `rust` (with CMake
|
|
404
|
+
and Ninja) or `jvm` (Temurin), each a `worker_profile_<name>` under `[vars]` in
|
|
405
|
+
`mise.toml`. `worker.tools` adds single tools dev-fleet already pins. The setup
|
|
406
|
+
installs them (`--node` reads the installed configuration) and the doctor
|
|
407
|
+
checks each as `tool:<name>` (`--profiles`, `--tools`).
|
|
397
408
|
The source-owned `worker-auth.ts` timer renews the worker's existing Vault token
|
|
398
409
|
and can restore the declared GitHub account from its authorized Vault service
|
|
399
410
|
credential, without copying another machine's login directory.
|
package/docs/operations.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
# Developer fleet
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`devfleet` diagnoses development computers: SSH, VS Code Remote-SSH,
|
|
8
8
|
per-machine authentication, T3 Connect and shared toolchain/dependency drift.
|
|
9
|
-
It is a local CLI
|
|
9
|
+
It is a local CLI, not a library, a daemon or a cloud manager. It reuses the
|
|
10
10
|
existing mise/workstation owners rather than introducing another installer.
|
|
11
11
|
|
|
12
12
|
The fleet is one singleton **manager** (the human's macOS machine, which runs
|
|
@@ -46,8 +46,9 @@ devfleet fleet-repair --node dev-linux --repair workspace --json # plan only
|
|
|
46
46
|
devfleet fleet-repair --node dev-linux --repair workspace --apply --yes --json
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
dev-fleet is a command with no library API: run it as `npx devfleet` from the
|
|
50
|
+
project that holds your `fleet.json` (`./devfleet` from a checkout of dev-fleet
|
|
51
|
+
itself). Schema, roster
|
|
51
52
|
listing, onboarding and repair previews do not contact nodes or auth services.
|
|
52
53
|
Status never opens a login flow.
|
|
53
54
|
|
|
@@ -61,7 +62,7 @@ remains the sole cloud resource owner; fleet creates no networks, hosts or secre
|
|
|
61
62
|
|
|
62
63
|
```json
|
|
63
64
|
{
|
|
64
|
-
"$schema": "./
|
|
65
|
+
"$schema": "./node_modules/@crvouga/dev-fleet/schema/roster.schema.json",
|
|
65
66
|
"version": 1,
|
|
66
67
|
"nodes": [
|
|
67
68
|
{
|
|
@@ -97,21 +98,50 @@ remote nodes' required lists. Run `fleet-nodes --schema` for editor/agent valida
|
|
|
97
98
|
|
|
98
99
|
## Evidence and diagnosis
|
|
99
100
|
|
|
100
|
-
| Check | Evidence / next action
|
|
101
|
-
| ---------------------------------------------------------------------------------- |
|
|
102
|
-
| `workspace`, `git:clean` | HEAD, input availability and dirty-tree status; no paths/diffs/file contents logged.
|
|
103
|
-
| `revision`, `sync:*` | Compare exact HEAD and SHA-256 of package.json, bun.lock, mise.toml, mise.lock against manager. Missing inputs never pass.
|
|
104
|
-
| `dependencies` | Last successful fleet frozen-sync receipt matches input hashes. Missing receipt is unverified, not in-sync.
|
|
105
|
-
| `auth:toolchain` | Existing bootstrap/doctor.ts runs in the node checkout with its Bun, bounded to 90s.
|
|
106
|
-
| `ssh:agent` | Manager agent exposes an identity; missing agent warns because IdentityFile keys may still work.
|
|
107
|
-
| `ssh:session` | Fresh batch SSH session actually runs the Node/Bun probe. No reused multiplexed connection or agent forwarding.
|
|
108
|
-
| `ssh:host-key`, `ssh:dns`, `ssh:auth`, `ssh:network`, `ssh:runtime`, `ssh:timeout` | Classified failure and targeted remedy, without raw logs.
|
|
109
|
-
| `auth:github` | gh auth status --active succeeds on this machine; not proof of Git SSH or every repository permission.
|
|
110
|
-
| `auth:vault` | Server-side vault token lookup succeeds in this context; not proof of access to every secret path.
|
|
111
|
-
| `auth:vscode` | code CLI finds the local Remote-SSH extension. Other profiles/Cursor/GUI PATH/agents can differ.
|
|
112
|
-
| `t3:settings` | Connect explicitly enabled and not revoked in t3 status --json; settings are not connectivity.
|
|
113
|
-
| `t3:api` | Read-only t3 environment list --json succeeds against the local API; no pairing/token minting.
|
|
114
|
-
| `t3:end-to-end`, `vscode:forwarding` | SKIP: actual relay/peer authorization and VS Code tunnel/server installation require operator testing.
|
|
101
|
+
| Check | Evidence / next action |
|
|
102
|
+
| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| `workspace`, `git:clean` | HEAD, input availability and dirty-tree status; no paths/diffs/file contents logged. |
|
|
104
|
+
| `revision`, `sync:*` | Compare exact HEAD and SHA-256 of package.json, bun.lock, mise.toml, mise.lock against manager. Missing inputs never pass. |
|
|
105
|
+
| `dependencies` | Last successful fleet frozen-sync receipt matches input hashes. Missing receipt is unverified, not in-sync. |
|
|
106
|
+
| `auth:toolchain` | Existing bootstrap/doctor.ts runs in the node checkout with its Bun, bounded to 90s. Run `bun bootstrap/doctor.ts --json` in the node's checkout for individual pins. |
|
|
107
|
+
| `ssh:agent` | Manager agent exposes an identity; missing agent warns because IdentityFile keys may still work. |
|
|
108
|
+
| `ssh:session` | Fresh batch SSH session actually runs the Node/Bun probe. No reused multiplexed connection or agent forwarding. |
|
|
109
|
+
| `ssh:host-key`, `ssh:dns`, `ssh:auth`, `ssh:network`, `ssh:runtime`, `ssh:timeout` | Classified failure and targeted remedy, without raw logs. |
|
|
110
|
+
| `auth:github` | gh auth status --active succeeds on this machine; not proof of Git SSH or every repository permission. |
|
|
111
|
+
| `auth:vault` | Server-side vault token lookup succeeds in this context; not proof of access to every secret path. |
|
|
112
|
+
| `auth:vscode` | code CLI finds the local Remote-SSH extension. Other profiles/Cursor/GUI PATH/agents can differ. |
|
|
113
|
+
| `t3:settings` | Connect explicitly enabled and not revoked in t3 status --json; settings are not connectivity. |
|
|
114
|
+
| `t3:api` | Read-only t3 environment list --json succeeds against the local API; no pairing/token minting. |
|
|
115
|
+
| `t3:end-to-end`, `vscode:forwarding` | SKIP: actual relay/peer authorization and VS Code tunnel/server installation require operator testing. |
|
|
116
|
+
|
|
117
|
+
### Machine health
|
|
118
|
+
|
|
119
|
+
| Check | Evidence / next action |
|
|
120
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
121
|
+
| `worker:capacity:inodes` | Free inodes on the home filesystem, against `inodeWarnFreePercent`/`inodeFailFreePercent` in `ansible/ubuntu/policy.json`. The disk guard and disk collector act on the same floors. |
|
|
122
|
+
| `worker:clock` | `timedatectl` says the clock is synchronised and a time service is enabled; with a reference time, the offset is held to `clock` in the policy. |
|
|
123
|
+
| `worker:reboot-pending` | `/var/run/reboot-required` exists: warns with its age, fails after `rebootPendingFailDays`. Reboot when no agent turn is running. |
|
|
124
|
+
| `tailscale:key-expiry` | The node key expiry Tailscale reports: warns 14 days ahead, fails 3 days ahead. See [Tailscale key expiry](#tailscale-key-expiry). |
|
|
125
|
+
| `manager:release` | `sw_vers` against `minimumMajorVersion` in `ansible/macos/policy.json`. |
|
|
126
|
+
| `manager:updates` | `softwareupdate --list --no-scan` (nothing is downloaded): warns on pending updates, and names one that waits for a restart. Names never shown. |
|
|
127
|
+
| `manager:disk` | Free space on the data volume and the space Docker holds, against `disk` in `ansible/macos/policy.json`. |
|
|
128
|
+
| `manager:container-watchdog` | The container watchdog (`ansible/monitoring/container-watchdog.py`, a launch agent `hub-darwin.yml` installs) restarts a labelled container that was unhealthy for `unhealthyChecks` runs, at most `maxRestartsPerHour` times an hour (`containerWatchdog` in `ansible/macos/policy.json`). Warns after a restart; fails once it stops restarting. |
|
|
129
|
+
|
|
130
|
+
### Tailscale key expiry
|
|
131
|
+
|
|
132
|
+
A node drops off the tailnet on the day its key expires. Keep workers from
|
|
133
|
+
expiring: tag them, or disable key expiry for each in the Tailscale admin
|
|
134
|
+
console (tagged nodes do not expire). `tailscale:key-expiry` warns two weeks
|
|
135
|
+
ahead and fails three days ahead.
|
|
136
|
+
|
|
137
|
+
To re-join a worker whose key expired, a person signs it in again, because
|
|
138
|
+
only a person's identity can authorise a device:
|
|
139
|
+
|
|
140
|
+
1. On the worker's console, run `sudo tailscale up` and open the link it
|
|
141
|
+
prints, signed in to the fleet's tailnet.
|
|
142
|
+
2. In the admin console, disable key expiry for the worker, or tag it.
|
|
143
|
+
3. From the manager, run `devfleet fleet-sync`: `ansible/ubuntu/tailscale.yml`
|
|
144
|
+
verifies the login and `tailscale:key-expiry` passes.
|
|
115
145
|
|
|
116
146
|
An SSH shell is not proof that VS Code or T3 Connect works. Diagnostics check
|
|
117
147
|
**directed manager → node** connectivity, not all pairs. Run from each intended
|
|
@@ -147,7 +177,7 @@ need normal repository validation. A green install is not a healthy fleet.
|
|
|
147
177
|
5. Authenticate separately: GitHub, Git SSH, Vault, coding providers and T3 pairing
|
|
148
178
|
are distinct sessions. Use gh auth login/setup-git, approved vault login and
|
|
149
179
|
operator-driven t3 up --connect as needed. Never put secrets in reports.
|
|
150
|
-
Workstation
|
|
180
|
+
Workstation and provider configuration are applied by `devfleet fleet-sync`.
|
|
151
181
|
6. In VS Code's local profile install Remote-SSH, select the same SSH binary,
|
|
152
182
|
alias/config, enable its login terminal temporarily and inspect Output →
|
|
153
183
|
Remote - SSH. Restart after PATH/agent changes. Check disk space, remote
|
|
@@ -178,7 +208,7 @@ serially; diagnostics run at most four remote probes concurrently.
|
|
|
178
208
|
setup can leave partial installs; no rollback is claimed. Trusted checkout
|
|
179
209
|
package lifecycle scripts can execute during installation.
|
|
180
210
|
- No other application repos, credentials, provider settings or cloud resources
|
|
181
|
-
are synchronized. `
|
|
211
|
+
are synchronized. `devfleet fleet-sync` remains the broader convergence.
|
|
182
212
|
- Run doctor again afterward; successful sync does not prove connectivity/auth.
|
|
183
213
|
|
|
184
214
|
Subprocesses have deadlines and a combined 1 MiB output cap. SSH probes default
|
|
@@ -204,17 +234,43 @@ not a way to certify the whole fleet from one shell. Usage errors use
|
|
|
204
234
|
exception strings. Fleet never prompts; --non-interactive is accepted.
|
|
205
235
|
|
|
206
236
|
No telemetry or report file is written automatically. Reports still contain
|
|
207
|
-
aliases/hashes: review before sharing publicly.
|
|
208
|
-
|
|
209
|
-
|
|
237
|
+
aliases/hashes: review before sharing publicly. Do not schedule automatic
|
|
238
|
+
sync/repairs.
|
|
239
|
+
|
|
240
|
+
## Notifications and the heartbeat
|
|
241
|
+
|
|
242
|
+
`devfleet fleet-watch` runs `fleet-status --workers`, compares the result with
|
|
243
|
+
the previous run and notifies only when a worker changes between ready and
|
|
244
|
+
degraded, and again every `repeatHours` (default 24) while it stays degraded.
|
|
245
|
+
Name the targets in `fleet.json`; the address of each is a secret, read through
|
|
246
|
+
the secret provider like any other:
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
"notify": { "targets": ["webhook", "ntfy"], "repeatHours": 24 }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
| Target | Secret | What is sent |
|
|
253
|
+
| --------- | -------------------- | -------------------------------------------------------------------- |
|
|
254
|
+
| `webhook` | `notify-webhook-url` | A JSON POST: `source`, `event`, `node`, `checks` (check IDs), `text` |
|
|
255
|
+
| `ntfy` | `notify-ntfy-url` | A text POST to the topic URL |
|
|
256
|
+
|
|
257
|
+
A notification carries node and check IDs only. `devfleet fleet-watch --test`
|
|
258
|
+
sends one test notification to every target.
|
|
259
|
+
|
|
260
|
+
Every run stores what it saw, and when, as `watch.json` beside the fleet state
|
|
261
|
+
in the bucket. That time is the manager's heartbeat. A watcher on the manager
|
|
262
|
+
notices nothing when the manager itself is off, so call the reusable
|
|
263
|
+
`fleet-heartbeat.yml` workflow from the repository that holds your
|
|
264
|
+
`fleet.json` (its header shows the one `uses:` line): it fails, and GitHub
|
|
265
|
+
notifies you, when the heartbeat is older than `max-age-minutes`. `bun run health` remains the separate
|
|
210
266
|
single read-only **cloud platform** report.
|
|
211
267
|
|
|
212
268
|
## Development
|
|
213
269
|
|
|
214
270
|
```sh
|
|
215
|
-
bun run
|
|
216
|
-
bun run
|
|
217
|
-
bun
|
|
271
|
+
bun run tc
|
|
272
|
+
bun run lint
|
|
273
|
+
bun test
|
|
218
274
|
```
|
|
219
275
|
|
|
220
276
|
Tests inject results/use temporary fixtures; they do not contact nodes, login or
|
|
@@ -238,7 +294,7 @@ Explicit `services` requirements retain Railway, Vercel, Cloudflare, AWS and npm
|
|
|
238
294
|
checks. No raw account/auth output is retained. Version checks keep
|
|
239
295
|
per-tool results, including the expected pinned version when available, and require
|
|
240
296
|
Docker Engine/Compose/Buildx readiness. `workstation` delegates to read-only
|
|
241
|
-
|
|
297
|
+
the checkout's `bootstrap/doctor.ts --json`; provider configuration presence is not live
|
|
242
298
|
authorization. Warnings/skips fail the strict whole-tailnet health coverage gate.
|
|
243
299
|
|
|
244
300
|
## Repair planning and shared access
|
|
@@ -257,7 +313,7 @@ installation never substitutes for authentication or resource authorization.
|
|
|
257
313
|
|
|
258
314
|
The workstation owner reads permitted shared provider keys from each machine's
|
|
259
315
|
existing Vault session. For process-scoped shared secret access, use the existing
|
|
260
|
-
`
|
|
316
|
+
`vault` session on that machine with its intended
|
|
261
317
|
project/config. Infrastructure, access policy and secret seeding remain owned by
|
|
262
318
|
OpenTofu. No OAuth/session/keychain/SSH credential transfer is implemented.
|
|
263
319
|
|
|
@@ -270,7 +326,7 @@ reviewed configuration; without `--dry-run` it applies. `devfleet fleet-sync
|
|
|
270
326
|
--revision FULL_SHA` rolls the same reviewed revision out to the enrolled fleet
|
|
271
327
|
after approval. The [setup runbook](../agents/setup.md)
|
|
272
328
|
covers canaries, offline/partial failures, per-node receipts and rollback. Both
|
|
273
|
-
delegate
|
|
329
|
+
delegate installation to mise and `bootstrap/setup.ts`.
|
|
274
330
|
They never reset the developer checkout or transfer credentials. Status delegates
|
|
275
331
|
managed-profile drift evidence to the workstation owner; historical rollout receipts
|
|
276
332
|
alone are not a live readiness assertion.
|