Commit 7a1be0e
fix: route interactive + implicit-context consumers through ephemeral context (PLA-1590)
Codex's round-5 review showed B+ (commit b5667e2) only covered half the
surface: my new helpers (CurrentProjectID etc.) plus a few skip-write
guards in project create / deploy. Two whole entry classes still walked
straight past them.
Path 1 — interactive ParamFiller bypass:
$ zeabur workspace switch team-A
$ zeabur context set project --id <team-A-project>
$ zeabur --workspace team-B service restart # interactive (default)
`runRestartInteractive` passed the raw `f.Config.GetContext()` to
ParamFiller.ServiceByNameWithEnvironment. The filler read team-A's
pinned project ID as `projectCtx.GetProject().GetID()`, used it as the
project scope for SelectService, and quietly restarted team-A's
service while the user thought they were operating on team-B. Worse,
when the persisted context was empty the filler called
`projectCtx.SetProject(picked)` and *wrote* the team-B project under
the persisted team-A workspace — cross-team contamination that
survived the command. Same shape across
service/{restart,delete,suspend,exec,network,port-forward,redeploy,
instruction,metric,get,update/tag},
variable/{create,delete,env,list,update},
deployment/{get,log,list},
domain/{create,delete,list}.
Path 2 — `project get/delete` PreRunE auto-fill:
$ zeabur --workspace team-B project delete --yes -i=false # no --id
`util.DefaultIDNameByContext(f.Config.GetContext().GetProject(), ...)`
was bound at Cobra command-construction time and unconditionally
copied team-A's persisted project ID into opts.id. The runE then
deleted team-A's project, even though every flag on the line said
team-B.
Fix: ephemeral context + EffectiveContext audit.
1. New `zcontext.NewEphemeralContext(workspace)` — an in-memory Context
implementation that starts empty, writes to memory only, and reports
the supplied workspace via GetWorkspace() (so consumers that ask
"what workspace is this context for?" get the override answer
rather than personal — a second-order trap Codex flagged).
2. New `Factory.EffectiveContext() zcontext.Context`:
- Without override: returns the persisted config context, byte-
equivalent to today.
- Under override: lazy-initialises a per-Factory ephemeral context
and returns it for every subsequent call (so ParamFiller's
`Set → later Get` cycle still works in-process; nothing leaks to
disk).
3. 24 interactive callers swap `f.Config.GetContext()` for
`f.EffectiveContext()`: every service/, variable/, deployment/,
domain/ command that ran ParamFiller.
4. project get/delete PreRunE swap to a lazy closure so
EffectiveContext is resolved at PreRunE time (after PersistentPreRunE
parses `--workspace`), not at command construction. The signature of
util.DefaultIDNameByContext changes from `BasicInfo` to
`func() BasicInfo` to make that lazy evaluation explicit.
5. `context get` reads EffectiveContext and, under override, prints an
extra "Note: --workspace is one-shot; persisted ... is not used"
line for human-readable output. JSON output stays structurally
identical so scripts keep parsing.
6. `context clear` rejects under override — clearing belongs to the
persisted state, not a one-shot override.
7. Deleted internal/util.NeedProjectContextWhenNonInteractive and
DefaultIDByContext — both had zero callers and would re-introduce
the same pattern if revived from copy/paste later.
What stays on `f.Config.GetContext()` (intentional):
workspace/{switch,clear}, auth/logout, root.go's lazy workspace
verify, context/{set,clear} command bodies (set already rejects
override; clear now does too), project create / deploy interactive
flows (already wrapped in `if !HasWorkspaceOverride` from b5667e2 with
a user-visible hint).
Tests:
zcontext/ephemeral_test.go (new):
TestEphemeralContext_WorkspaceFromConstructor
TestEphemeralContext_NilWorkspaceIsPersonal
TestEphemeralContext_ReadEmptyByDefault
TestEphemeralContext_SetReadCycleWorksInMemory
TestEphemeralContext_ClearAll
cmdutil/factory_test.go (added):
TestFactory_EffectiveContext_NoOverride
TestFactory_EffectiveContext_OverrideReturnsEphemeral_WithWorkspace
TestFactory_EffectiveContext_OverrideCachedWithinCommand
TestFactory_EffectiveContext_OverridePersistedUnpolluted
Dev-2 E2E (this round):
C1: --workspace team-B service delete --name redis (interactive,
persisted=team-A + pinned project) → opens Select project from
team-B; for-clone-test redis NOT deleted (verified via list).
C3: --workspace team-B project delete --yes -i=false (no --id) →
"please specify project by --name or --id"; for-clone-test
project NOT deleted.
C4: --workspace team-B context get → all inner context shows
"<not set>" + the Note line; --json stays structurally clean.
C5: --workspace team-B context clear → rejected with actionable
hint pointing at workspace switch.
C6: no override → context get reads persisted normally
(back-compat red line).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent b5667e2 commit 7a1be0e
31 files changed
Lines changed: 413 additions & 55 deletions
File tree
- internal
- cmdutil
- cmd
- context
- clear
- get
- deployment
- get
- list
- log
- domain
- create
- delete
- list
- project
- delete
- get
- service
- delete
- exec
- get
- instruction
- metric
- network
- port-forward
- redeploy
- restart
- suspend
- update/tag
- variable
- create
- delete
- env
- list
- update
- util
- pkg/zcontext
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
| 5 | + | |
4 | 6 | | |
5 | 7 | | |
6 | 8 | | |
| |||
24 | 26 | | |
25 | 27 | | |
26 | 28 | | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
27 | 41 | | |
28 | 42 | | |
29 | 43 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | | - | |
27 | | - | |
28 | | - | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
29 | 34 | | |
30 | 35 | | |
31 | 36 | | |
| |||
56 | 61 | | |
57 | 62 | | |
58 | 63 | | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
59 | 72 | | |
60 | 73 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
| 52 | + | |
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
48 | | - | |
| 48 | + | |
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
64 | | - | |
| 64 | + | |
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
55 | | - | |
| 55 | + | |
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
| 50 | + | |
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
26 | | - | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
27 | 35 | | |
28 | 36 | | |
29 | 37 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
26 | | - | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
27 | 35 | | |
28 | 36 | | |
29 | 37 | | |
| |||
0 commit comments