test(cli,cloud): harvest AR-448 workspace-identity coverage onto main - #1447
test(cli,cloud): harvest AR-448 workspace-identity coverage onto main#1447khaliqgant wants to merge 2 commits into
Conversation
AR-448's substance shipped through #1429 (the shared workspace precedence ladder) and 5c2ad8e (broker restart-reclaim), not through either AR-448 branded PR. This harvests the test value #1402 had and main lacks, rewritten against main's code rather than the pre-#1429 workspace.ts it was authored on. broker-lifecycle.test.ts covers the ladder one start at a time. The new workspace-identity-restart.test.ts covers what only appears ACROSS starts: a second start lands on the same workspace, the resident keeps its address, an explicit --workspace-key re-pins durably, and a checkout that never pinned drifts. Dropping the ladder's machine-global store step fails 6 of its 8 cases. Adds the data-plane half of the invariant, which main had no equivalent of: describeDataPlaneConvergence reports whether Relaycast, Relayfile, and RelayAuth resolve one workspace id. `workspace active` now emits it as a dataPlane block in --json, prints the Relaycast id it previously omitted, and gates on it with --require-unified. Wired to a caller rather than landed as an untested-in-situ module. One of #1402's assertions is not adapted but reversed. It asserted that a second checkout sharing the canonical workspace also shares the resident's address. 5c2ad8e post-dates it: registration is a fail-closed admission gate, and only a proof derived from the same broker state directory reclaims a held name, so a different checkout is rejected rather than handed the incumbent's credentials. The stand-in models all three outcomes (grant / reclaim / reject). specs/workspace-identity.md records the correction: AR-448 assumed workspace convergence was sufficient for address identity: it is necessary and not sufficient, and agent-identity admission is a separate second half. These are unit tests over the CLI's TypeScript with createRelay stubbed. They prove the CLI selects the right workspace across starts; they do not prove identity survives a live restart. That proof is not automated here. npx vitest run packages/cli packages/cloud: 1234 passed, 20 skipped, 0 failed. npm run typecheck: clean. npm run lint: 0 errors, no new warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
📝 WalkthroughWalkthroughThe ChangesWorkspace identity and convergence
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Operator
participant CLI as workspace active
participant Resolver as workspace resolution
participant Convergence as data-plane convergence
participant Stderr as stderr
Operator->>CLI: run workspace active
CLI->>Resolver: resolve active workspace
Resolver-->>CLI: workspace descriptor
CLI->>Convergence: describeDataPlaneConvergence
Convergence-->>CLI: unified or divergent result
CLI-->>Operator: print JSON or human-readable status
CLI->>Convergence: formatDataPlaneDivergence when divergent
Convergence-->>CLI: divergence warning
CLI->>Stderr: write warning
CLI-->>Operator: return status 1 when --require-unified is set
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7e7d8d9c0e
ℹ️ 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".
| // callers keep their exit code. | ||
| deps.error(formatDataPlaneDivergence(dataPlane)); | ||
| if (options.requireUnified) { | ||
| deps.exit(1); |
There was a problem hiding this comment.
Let the deliberate CLI exit escape
runSdk
When --require-unified detects a divergence, the production deps.exit(1) throws CliExit, but this call is inside runSdk, whose catch block treats that sanctioned exit as an ordinary error. Consequently the command emits the useful divergence message, then also prints the internal cli-exit:1 message and invokes deps.exit(1) a second time. Move the exit outside runSdk or make runSdk rethrow CliExit unchanged so this validation mode produces clean diagnostics.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/cli/src/cli/lib/workspace-identity-restart.test.ts (1)
402-412: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winCover every workspace-key input path in the redaction test.
This test checks only
CANONICAL_KEY. It does not check an explicit--workspace-keyor a broker-minted key. A leak limited to either path passes this test.Add starts for both paths. Assert that each returned
workspaceKeyis absent from its captured logs.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/cli/src/cli/lib/workspace-identity-restart.test.ts` around lines 402 - 412, Expand the redaction test around createMachine and start to cover explicit --workspace-key and broker-minted workspace-key inputs in addition to CANONICAL_KEY. Capture each start result’s workspaceKey and assert that it is absent from that start’s logs, preserving the existing no-key-material assertion across the sequence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/cli/lib/workspace-identity-restart.test.ts`:
- Around line 175-189: Update
packages/cli/src/cli/lib/workspace-identity-restart.test.ts#L175-L189 so
createMachine accepts and reuses a caller-provided relayHome, creating and
seeding workspaces.json only when needed; update `#L367-L377` to construct
original and clone with the same relayHome. Retain the coverage claim in
specs/workspace-identity.md#L242-L242 after the corrected fixture verifies
shared machine-global workspace state.
In `@specs/workspace-identity.md`:
- Around line 86-90: Update the fenced examples in specs/workspace-identity.md:
lines 86-90 and 133-152 use the console fence language for transcript output;
lines 163-165 and 218-223 use bash and remove shell prompts from command-only
examples; lines 170-178 use console. Preserve the example content while
satisfying markdownlint MD040 and MD014.
---
Nitpick comments:
In `@packages/cli/src/cli/lib/workspace-identity-restart.test.ts`:
- Around line 402-412: Expand the redaction test around createMachine and start
to cover explicit --workspace-key and broker-minted workspace-key inputs in
addition to CANONICAL_KEY. Capture each start result’s workspaceKey and assert
that it is absent from that start’s logs, preserving the existing
no-key-material assertion across the sequence.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 757e3c15-7d7f-4ede-8298-81a4c4216ae2
📒 Files selected for processing (8)
CHANGELOG.mdpackages/cli/src/cli/commands/workspace.test.tspackages/cli/src/cli/commands/workspace.tspackages/cli/src/cli/lib/workspace-identity-restart.test.tspackages/cloud/src/index.tspackages/cloud/src/workspace-convergence.test.tspackages/cloud/src/workspace-convergence.tsspecs/workspace-identity.md
| function createMachine(options: { canonicalWorkspaceKey?: string } = {}) { | ||
| const projectRoot = mkTmp('ar448-project-'); | ||
| const relayHome = mkTmp('ar448-home-'); | ||
| const dataDir = pathReal.join(projectRoot, '.agentworkforce', 'relay'); | ||
| fsReal.mkdirSync(dataDir, { recursive: true }); | ||
|
|
||
| if (options.canonicalWorkspaceKey) { | ||
| fsReal.writeFileSync( | ||
| pathReal.join(relayHome, 'workspaces.json'), | ||
| JSON.stringify({ | ||
| active: 'default', | ||
| workspaces: { default: { key: options.canonicalWorkspaceKey } }, | ||
| }) | ||
| ); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Model one shared AGENT_RELAY_HOME before claiming checkout convergence.
createMachine creates and seeds a separate relayHome for each checkout. The convergence test therefore supplies the same key twice instead of verifying that two checkouts read one machine-global active workspace.
packages/cli/src/cli/lib/workspace-identity-restart.test.ts#L175-L189: allow the fixture to reuse a caller-providedrelayHome; seedworkspaces.jsononly once.packages/cli/src/cli/lib/workspace-identity-restart.test.ts#L367-L377: constructoriginalandclonewith the samerelayHome.specs/workspace-identity.md#L242-L242: retain this coverage claim only after the corrected fixture proves it.
📍 Affects 2 files
packages/cli/src/cli/lib/workspace-identity-restart.test.ts#L175-L189(this comment)packages/cli/src/cli/lib/workspace-identity-restart.test.ts#L367-L377specs/workspace-identity.md#L242-L242
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/cli/lib/workspace-identity-restart.test.ts` around lines 175
- 189, Update
packages/cli/src/cli/lib/workspace-identity-restart.test.ts#L175-L189 so
createMachine accepts and reuses a caller-provided relayHome, creating and
seeding workspaces.json only when needed; update `#L367-L377` to construct
original and clone with the same relayHome. Retain the coverage claim in
specs/workspace-identity.md#L242-L242 after the corrected fixture verifies
shared machine-global workspace state.
| ``` | ||
| Workspace source: machine-global active workspace (~/.agentworkforce/relay/workspaces.json (active: "default")) | ||
| Workspace Key: rk_live_…de99 | ||
| Workspace: joined rw_7ccfea89 | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Specify fence languages and fix command-only examples.
markdownlint reports MD040 for every listed fence. It also reports MD014 for the command-only blocks.
specs/workspace-identity.md#L86-L90: use a transcript language such asconsole.specs/workspace-identity.md#L133-L152: useconsole.specs/workspace-identity.md#L163-L165: usebashand remove the shell prompt.specs/workspace-identity.md#L170-L178: useconsole.specs/workspace-identity.md#L218-L223: usebashand remove the shell prompts.
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)
[warning] 86-86: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
📍 Affects 1 file
specs/workspace-identity.md#L86-L90(this comment)specs/workspace-identity.md#L133-L152specs/workspace-identity.md#L163-L165specs/workspace-identity.md#L170-L178specs/workspace-identity.md#L218-L223
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@specs/workspace-identity.md` around lines 86 - 90, Update the fenced examples
in specs/workspace-identity.md: lines 86-90 and 133-152 use the console fence
language for transcript output; lines 163-165 and 218-223 use bash and remove
shell prompts from command-only examples; lines 170-178 use console. Preserve
the example content while satisfying markdownlint MD040 and MD014.
Source: Linters/SAST tools
There was a problem hiding this comment.
3 issues found and verified against the latest diff
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/cli/src/cli/lib/workspace-identity-restart.test.ts">
<violation number="1" location="packages/cli/src/cli/lib/workspace-identity-restart.test.ts:300">
P2: The headline AR-448 invariant ('resident keeps its address across a restart') is asserted against the test's own relaycast.register stub, not against the CLI. runUpCommand only resolves the workspace key; the resident address is computed by this file's register() mock, so `expect(second.residentAddress).toBe(first.residentAddress)` cannot fail on any real CLI/broker regression — it verifies the mock's own Map. This contradicts the file docstring ('that the resident keeps its address') and the PR's own acceptance note that address survival is 'not yet covered', and can give a false-green signal for the exact failure the file claims to guard.</violation>
<violation number="2" location="packages/cli/src/cli/lib/workspace-identity-restart.test.ts:368">
P2: `createMachine` allocates a brand-new `relayHome` (and seeds its own `workspaces.json`) on every call, so `original` and `clone` here never actually share a machine-global active-workspace store — they just happen to be seeded with the same literal `CANONICAL_KEY` string. This test doesn't verify that two checkouts converge via one shared `AGENT_RELAY_HOME`; it only verifies that two independent stores initialized with the same value produce the same value. Consider letting the fixture accept a caller-provided `relayHome` and seeding `workspaces.json` once, then constructing `original`/`clone` against that shared home.</violation>
</file>
<file name="packages/cli/src/cli/commands/workspace.ts">
<violation number="1" location="packages/cli/src/cli/commands/workspace.ts:108">
P3: When `workspace active --require-unified` encounters a divergence, the divergence message is printed to stderr and then the command also emits an internal `cli-exit:1` line and exits twice. That secondary line comes from the runSdk wrapper catching the `CliExit` that `deps.exit(1)` throws and re-printing `err.message` before exiting again. For a setup-doctor script reading stderr, the divergence we actually want to show is diluted by the internal sentinel. Consider throwing the formatted divergence message from the gate so runSdk prints it exactly once and performs the single non-zero exit, keeping `deps.error` (exit 0) only for the non-gated warning case.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| expect(second.workspaceId).toBe(first.workspaceId); | ||
| // The invariant AR-448 exists for: the address someone recorded before the | ||
| // restart still reaches the resident after it. | ||
| expect(second.residentAddress).toBe(first.residentAddress); |
There was a problem hiding this comment.
P2: The headline AR-448 invariant ('resident keeps its address across a restart') is asserted against the test's own relaycast.register stub, not against the CLI. runUpCommand only resolves the workspace key; the resident address is computed by this file's register() mock, so expect(second.residentAddress).toBe(first.residentAddress) cannot fail on any real CLI/broker regression — it verifies the mock's own Map. This contradicts the file docstring ('that the resident keeps its address') and the PR's own acceptance note that address survival is 'not yet covered', and can give a false-green signal for the exact failure the file claims to guard.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/cli/lib/workspace-identity-restart.test.ts, line 300:
<comment>The headline AR-448 invariant ('resident keeps its address across a restart') is asserted against the test's own relaycast.register stub, not against the CLI. runUpCommand only resolves the workspace key; the resident address is computed by this file's register() mock, so `expect(second.residentAddress).toBe(first.residentAddress)` cannot fail on any real CLI/broker regression — it verifies the mock's own Map. This contradicts the file docstring ('that the resident keeps its address') and the PR's own acceptance note that address survival is 'not yet covered', and can give a false-green signal for the exact failure the file claims to guard.</comment>
<file context>
@@ -0,0 +1,412 @@
+ expect(second.workspaceId).toBe(first.workspaceId);
+ // The invariant AR-448 exists for: the address someone recorded before the
+ // restart still reaches the resident after it.
+ expect(second.residentAddress).toBe(first.residentAddress);
+ expect(second.rejected).toBeUndefined();
+ });
</file context>
| }); | ||
|
|
||
| it('lands a second checkout in the canonical workspace without anyone copying a key', async () => { | ||
| const original = createMachine({ canonicalWorkspaceKey: CANONICAL_KEY }); |
There was a problem hiding this comment.
P2: createMachine allocates a brand-new relayHome (and seeds its own workspaces.json) on every call, so original and clone here never actually share a machine-global active-workspace store — they just happen to be seeded with the same literal CANONICAL_KEY string. This test doesn't verify that two checkouts converge via one shared AGENT_RELAY_HOME; it only verifies that two independent stores initialized with the same value produce the same value. Consider letting the fixture accept a caller-provided relayHome and seeding workspaces.json once, then constructing original/clone against that shared home.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/cli/lib/workspace-identity-restart.test.ts, line 368:
<comment>`createMachine` allocates a brand-new `relayHome` (and seeds its own `workspaces.json`) on every call, so `original` and `clone` here never actually share a machine-global active-workspace store — they just happen to be seeded with the same literal `CANONICAL_KEY` string. This test doesn't verify that two checkouts converge via one shared `AGENT_RELAY_HOME`; it only verifies that two independent stores initialized with the same value produce the same value. Consider letting the fixture accept a caller-provided `relayHome` and seeding `workspaces.json` once, then constructing `original`/`clone` against that shared home.</comment>
<file context>
@@ -0,0 +1,412 @@
+ });
+
+ it('lands a second checkout in the canonical workspace without anyone copying a key', async () => {
+ const original = createMachine({ canonicalWorkspaceKey: CANONICAL_KEY });
+ const clone = createMachine({ canonicalWorkspaceKey: CANONICAL_KEY });
+
</file context>
| if (!dataPlane.unified) { | ||
| // A divergence is reported on stderr either way; only | ||
| // --require-unified turns it into a gate, so existing scripted | ||
| // callers keep their exit code. | ||
| deps.error(formatDataPlaneDivergence(dataPlane)); | ||
| if (options.requireUnified) { | ||
| deps.exit(1); | ||
| } |
There was a problem hiding this comment.
P3: When workspace active --require-unified encounters a divergence, the divergence message is printed to stderr and then the command also emits an internal cli-exit:1 line and exits twice. That secondary line comes from the runSdk wrapper catching the CliExit that deps.exit(1) throws and re-printing err.message before exiting again. For a setup-doctor script reading stderr, the divergence we actually want to show is diluted by the internal sentinel. Consider throwing the formatted divergence message from the gate so runSdk prints it exactly once and performs the single non-zero exit, keeping deps.error (exit 0) only for the non-gated warning case.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/cli/commands/workspace.ts, line 111:
<comment>When `workspace active --require-unified` encounters a divergence, the divergence message is printed to stderr and then the command also emits an internal `cli-exit:1` line and exits twice. That secondary line comes from the runSdk wrapper catching the `CliExit` that `deps.exit(1)` throws and re-printing `err.message` before exiting again. For a setup-doctor script reading stderr, the divergence we actually want to show is diluted by the internal sentinel. Consider throwing the formatted divergence message from the gate so runSdk prints it exactly once and performs the single non-zero exit, keeping `deps.error` (exit 0) only for the non-gated warning case.</comment>
<file context>
@@ -68,27 +77,46 @@ export function registerWorkspaceCommands(
- deps.log(`Cloud workspace ID: ${workspace.cloudWorkspaceId}`);
- deps.log(`Relayfile workspace ID: ${workspace.relayfileWorkspaceId}`);
- deps.log(`Relayauth workspace ID: ${workspace.relayauthWorkspaceId}`);
+ if (!dataPlane.unified) {
+ // A divergence is reported on stderr either way; only
+ // --require-unified turns it into a gate, so existing scripted
</file context>
| if (!dataPlane.unified) { | |
| // A divergence is reported on stderr either way; only | |
| // --require-unified turns it into a gate, so existing scripted | |
| // callers keep their exit code. | |
| deps.error(formatDataPlaneDivergence(dataPlane)); | |
| if (options.requireUnified) { | |
| deps.exit(1); | |
| } | |
| if (!dataPlane.unified) { | |
| // A divergence is reported on stderr either way; only | |
| // --require-unified turns it into a gate, so existing scripted | |
| // callers keep their exit code. | |
| const message = formatDataPlaneDivergence(dataPlane); | |
| if (options.requireUnified) { | |
| // Let runSdk print the message once and exit non-zero. | |
| throw new Error(message); | |
| } | |
| deps.error(message); | |
| } |
|
Closing on Khaliq's decision. The premise behind this PR was mine and it was wrong. Why. This branch existed to harvest test evidence onto What review then removed from what was left. Two independent reviewers found the headline assertion — "resident keeps its address across a restart" — compares the test's own On What survives, and it is the valuable part. #1402's model encoded "re-registering a name in a workspace it already belongs to returns the existing agent", unconditionally. Nothing is lost. The branch |
Harvests the test value from #1402 onto current
main. Does not supersede or close #1402 or #1403 — that is Khaliq's call, after this is green.Why a fresh PR instead of merging #1402
AR-448's substance already shipped, through neither AR-448-branded PR:
4acdd97d4— the shared workspace precedence ladder (PR fix(broker): verify worker process before spawn success #1429)5c2ad8ee3— broker restart-reclaim + hashed identity proofBoth are ancestors of
main. The two AR-448-branded commits (492515a20,27494c862) are not. #1402 and #1403 are 7–8 days stale and both still editpackages/cli/src/cli/commands/workspace.ts, which #1429 rewrote underneath them.So the implementation half of #1402 is redundant:
resolveWorkspaceSelectioninpackages/cloud/src/project-workspace-key.tsalready is the ladder AR-448 proposed. Reintroducing #1402's version would create a second code path for one behavior. Its test half is not redundant, and that is what this PR carries — rewritten against main's code, not copied.What this adds
packages/cli/src/cli/lib/workspace-identity-restart.test.ts—broker-lifecycle.test.tscovers the ladder one start at a time. This covers what only appears across starts:--workspace-keyre-pins durably, so the move survives the next startpackages/cloud/src/workspace-convergence.ts+ test — the data-plane half of the invariant, whichmainhad no equivalent of. Reports whether Relaycast, Relayfile, and RelayAuth resolve onerw_…id, withholding a shared id on divergence rather than papering over the split. The Cloud id is excluded deliberately (control-plane UUID, different id space).workspace active— wired to that module rather than leaving it as a module nothing calls: adataPlaneblock in--json, the Relaycast id in human output (previously the one plane omitted), and--require-unifiedto turn a divergence into a non-zero exit for setup doctors. Without--require-unifiedthe exit code is unchanged, so existing scripted callers are unaffected.specs/workspace-identity.md— rewritten against main's five-step ladder and the admission gate.The premise correction
One #1402 assertion is not adapted but reversed, and it is the most important thing in this PR.
#1402 asserted that a second checkout sharing the canonical workspace also shares the resident's address — its Relaycast stand-in modeled re-registration as unconditionally returning the existing agent.
5c2ad8ee3post-dates it by a week and makes that false by design. Peradmit_agent_registrationincrates/broker/src/relaycast/auth.rs, a name collision is rejected by default; reclaim requires proving the same work unit via an identity key compared as a SHA-256 hash, and the broker proves its own withstable_node_identity_key— hash of its persisted state directory, stable across a kill+restart, different for any other checkout.AR-448's premise was that workspace convergence is sufficient for address identity. It is necessary and not sufficient. Agent-identity admission is a separate second half that landed a week later as its own fix. Checking only the workspace half is how a node that resolves the correct workspace can still fail to come back as itself. That is recorded in the spec, not just here.
The test's stand-in now models all three outcomes: grant a free name, reclaim on matching identity, reject on mismatch.
Acceptance criteria
node upjoins the canonical workspace with no manual key copyingbroker-lifecycle.test.tsworkspace active --jsonproves one data-plane workspace idworkspace-convergence.test.ts+workspace.test.tsbroker-lifecycle.test.tsalready covered the single-start casebroker-lifecycle.test.ts(already on main)specs/workspace-identity.mdCriterion 3 — what these tests do not buy
These are unit tests over the CLI's own TypeScript (
runUpCommand,resolveWorkspaceSelection) withcreateRelaystubbed. No broker binary participates, released or otherwise. They prove the CLI selects the canonical workspace across starts. They do not prove identity survives a real restart. Those are different claims and a green run here is not evidence for the second.The live proof is blocked on two independent things:
5c2ad8ee3is onmainbut in no release tag — it post-dates v11.4.0 (2026-08-02) and v11.4.1 (2026-08-03).crates/broker/src/relaycast/auth.rsdoes not exist in the v11.4.1 tree at all; the gate is main-only. A live re-verification against an installed 11.4.0/11.4.1 broker is expected to fail for that reason, and is not evidence against these tests.Test evidence
Run in a clean worktree off
origin/main(76232055c).Full
packages/cli+packages/cloud:npm run typecheck— exit 0, clean.npm run lint— exit 0, 76 pre-existing warnings, none in the files this PR touches.The tests were checked for bite, not just for green
Mutating the ladder to drop its machine-global store step (
return resolveActiveWorkspaceSelection(env)→return undefined) fails 6 of the 8 restart cases:The two that still pass are the ones that should: the no-canonical-workspace negative control, and the credential-leak check. The mutation was reverted before commit.
🤖 Generated with Claude Code