fix(migration): fail-closed legacy runtime project identity migration for imported stacks - #146
Conversation
Add ProjectIdentity resolver that compares folder-derived and legacy name-derived candidates against live compose project label ownership across containers, volumes, and networks. Persist resolved runtime identity to project_name metadata and wire StackInfo to always use that centralized pinned identity for docker compose -p. Fail closed on ambiguous or unprobeable identity and add explicit owner-choice support via StackInfo.
Enforce resolved project identity across compose action builders, single and multi-stack command launchers, manual and scheduled auto-update paths, and event/script consumers via compose_args. Add read-only identity preview endpoint plus explicit identity selection endpoint for owner-driven conflict resolution. Surface blocked stacks in list/UI with warning indicator and chooser modal when actions hit ambiguous identity.
…osed behavior Add ProjectIdentity tests covering legacy name mismatch, running/stopped ownership, volume-only ownership, ambiguous dual ownership, no ownership, Docker probe failure, pinned reuse, owner choice, and logs-vs-mutation guard behavior. Set deterministic default probe in test bootstrap so unit tests never depend on host Docker state.
There was a problem hiding this comment.
🟡 Changes recommended
The current fail-closed enforcement blocks composeLogs (contradicting the “logs allowed” contract) and the identity error response shape is inconsistent across single vs multi-stack callers, causing confusing/incorrect frontend behavior.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR implements a fail-closed migration for stacks imported from the legacy Compose plugin where the runtime docker compose -p identity may differ from the folder-derived identity. It introduces a resolver that uses Docker resource ownership evidence to pick (and persist) the effective project identity, blocks mutating actions when identity is ambiguous/unverifiable, and adds UI flows and tests to support explicit owner selection.
Changes:
- Added
ProjectIdentityresolver with Docker-label probing, pinnedproject_namemetadata, and explicit owner-choice support. - Updated backend action paths (single stack, multi stack, auto-update) to fail closed when identity is unresolved.
- Added UI warning/chooser flow and regression tests covering ownership, ambiguity, probe failure, and pinning behavior.
File summaries
| File | Description |
|---|---|
| tests/unit/ProjectIdentityTest.php | Adds regression coverage for identity resolution, pinning, ambiguity, and fail-closed behavior. |
| tests/bootstrap.php | Sets a deterministic default probe for tests to avoid host Docker dependencies. |
| source/compose.manager/javascript/composeManagerMain.js | Adds identity chooser UI and handles identity-block error responses from backend actions. |
| source/compose.manager/include/Util.php | Integrates identity resolution into StackInfo construction and pins identity for new stacks. |
| source/compose.manager/include/ProjectIdentity.php | Introduces centralized identity resolution, Docker probing, pinning, and messaging. |
| source/compose.manager/include/Helpers.php | Enforces fail-closed behavior for compose command generation (single + multi-stack). |
| source/compose.manager/include/Exec.php | Adds identity preview and identity pinning endpoints. |
| source/compose.manager/include/ComposeList.php | Adds identity-block warning indicator and data attributes for UI chooser. |
| source/compose.manager/include/ComposeCommandBuilder.php | Blocks mutating compose arg generation when identity is unresolved (logs allowed). |
| source/compose.manager/include/AutoUpdateRunner.php | Skips scheduled auto-updates when identity is unresolved. |
| source/compose.manager/include/AutoUpdate.php | Blocks manual auto-updates when identity is unresolved (returns 409 + message). |
Review details
Suppressed comments (1)
source/compose.manager/include/Helpers.php:388
echoComposeCommandMultiple()returns{error: 'identity', stacks: [...]}when all stacks are blocked. Several multi-stack callers (e.g. Start All / Stop All) only treat responses as either{background:true}or a ttyd URL string, so this JSON payload will be shown as raw text (and action-in-progress spinners may never be cleared). Consider either (a) returning a ttyd script/URL that prints the blocked-stack message, or (b) using a distinct error shape/code and updating the corresponding JS callers to handle it and clear UI state.
if (!empty($blockedStacks)) {
echo json_encode([
'error' => 'identity',
'stacks' => $blockedStacks,
'message' => 'Compose project identity is unresolved for: ' . implode(', ', $blockedStacks),
- Files reviewed: 11/11 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Scope the fail-closed identity guard in echoComposeCommand() to mutating actions only. composeLogs stays read-only and continues opening ttyd even when identity is unresolved.
Handle identity error payloads that do not include per-stack candidates (e.g. multi-stack operations) with a simple warning dialog instead of opening the chooser with no options.
Summary
Imported stacks from original Compose Manager can have runtime project identity different from folder-derived identity.
This change makes migration verified and fail-closed, not best-effort.
Problem
Plus uses folder-derived identity for compose -p.
Legacy plugin used name-file identity for compose -p.
Mismatch risk:
What Changed
Fail-Closed Enforcement
Mutating paths now block when identity unresolved:
Read-only logs path remains allowed.
UI
Tests
Added regression coverage for:
Bootstrap now sets deterministic probe default so tests never depend on host Docker state.
Outcome
Upgrade path safer for legacy users: