# Project Bundle Isolation Plan
Date: 2026-04-01
Status: Bundle ingestion, cloud-owned inventory enforcement, freshness hardening, replacement/rollback hardening, broader churn coverage, and lifecycle smoke are implemented

## 1. Product constraints
This plan follows the confirmed product constraints:
- Projects are defined by backend config, not local CRUD.
- The current source payload is `openclaw-employee-config`.
- The active bundle source is `skills[].skill.download_url`.
- `project_bundle_url` is not part of the current implementation.
- Project inventory is fixed by backend configuration.
- The current protocol returns a `skills` array.
- Each `skills[]` object uses one `skill.download_url` string to point at one project zip.
- Multiple projects are currently expressed by multiple `skills[]` objects.
- If the backend later changes to multi-value download URLs, that will be a separate protocol and implementation task.

## 2. What is already implemented
### Cloud bundle ingestion
- Runtime cloud bootstrap and payload update both trigger project bundle sync.
- Zip-backed remote skills are discovered and downloaded.
- Bundles are extracted and normalized.
- Project files are materialized into local project roots.
- Shared bundle `skills/` and `cron/` content is materialized.
- Synced projects are registered in local project metadata.

### Cloud-owned inventory enforcement
- No implicit local `default` project is created when there are no materialized projects.
- Session listing returns `[]` when no active project exists.
- Session creation is blocked with `Waiting for cloud project bundle sync.` until a cloud-backed project arrives.
- The first synced bundle-backed project becomes active cleanly.

### Project-bound execution
- Sessions are bound to projects.
- Each execution is prepared against the session's project.
- Project context snapshots are injected into request preparation.
- The router can choose `workspace-entry`, `skill`, or `chat-fallback`.
- `chat-fallback`, `skill`, and `workspace-entry` all schedule post-execution project context refresh.

### Stale bundle reconciliation
- Sync derives the expected bundle-backed project set from the current payload.
- Bundle-managed projects missing from the expected set are removed locally.
- Bundle-managed shared `skills/` entries missing from the expected set are removed locally.
- Bundle-managed shared `cron/` entries missing from the expected set are removed locally.
- Stale bundle manifest records are pruned.
- If the active bundle project is removed, active project falls back to the remaining valid project set.

### Freshness hardening
- Reuse is no longer assumed from `sourceUrl + configVersion` alone.
- When a manifest record is otherwise reusable, the bundle service performs a `HEAD` probe.
- The probe captures `ETag`, `Last-Modified`, and `Content-Length` when available.
- If those values change, the bundle is re-downloaded and re-materialized.
- If an older manifest lacks freshness identity and the remote now exposes one, the service forces one refresh to establish the baseline.
- Bundle manifest records now persist remote freshness metadata.

### Replacement and rollback hardening
- Bundle installation now follows `stage -> commit -> finalize/rollback`.
- Replacement can swap the project root plus shared `skills/` and `cron/` entries for the same logical `projectId`.
- If replacement fails after commit but before project metadata is fully updated, the previous project root, shared skill entries, and cron entries are restored.
- Recovery on the next sync is verified and keeps manifest ownership aligned with the recovered state.

### Regression smoke coverage
- `workspace-entry-smoke`
- `cloud-bundle-smoke`
- `project-context-refresh-smoke`
- `default-chat-smoke`
- `project-empty-inventory-smoke`
- `project-bundle-reconcile-smoke`
- `project-bundle-freshness-smoke`
- `project-bundle-replacement-smoke`
- `project-bundle-churn-smoke`
- `project-isolation-smoke`

Additional lifecycle coverage now verified:
- cloud bundle smoke covers payload `sync`
- cloud bundle smoke covers cached `init`
- cloud bundle smoke covers same-`projectId` replacement through Electron UI/main flow
- replacement smoke covers service-level rollback injection and recovery on the next sync
- churn smoke covers multi-project survivor/replacement/removal/addition paths
- `project-isolation-smoke.ps1` now aggregates reconcile, freshness, replacement, and churn service-level coverage

## 3. What is no longer pending
The previously pending items are now implemented:
- full cloud-owned project inventory enforcement
- strong rollback guarantees for partially failed bundle replacement
- broader service-level regression coverage for multi-project churn scenarios
- end-to-end lifecycle smoke for replacement scenarios

## 4. Remaining follow-up work
The main work left is follow-up hardening rather than missing core behavior:
- decide whether `project-isolation-smoke.ps1` and `cloud-bundle-smoke.ps1` should be wired into broader external CI or release gates
- expand deterministic churn coverage to larger or randomized stress matrices only when product changes raise the risk level
- keep the design docs aligned as the project-isolation surface expands
- if backend protocol evolves beyond single `skills[].skill.download_url`, implement that as a separate compatibility task

## 5. File-by-file status
### `apps/desktop/src/main/services/project-bundle.ts`
- Owns bundle freshness probing, expected-set reconciliation, replacement ordering, and rollback behavior.
- Persists manifest ownership and remote freshness metadata for bundle-managed state.

### `apps/desktop/src/main/services/project-store.ts`
- Owns active-project state without recreating a local fallback project when inventory is empty.
- Keeps active-project transitions consistent during bundle arrival, removal, replacement, and churn.

### `apps/desktop/src/main/ipc.ts`
- Keeps send and stream paths aligned on post-execution refresh behavior.
- Preserves background refresh semantics without adding reply latency.

### `build/scripts/*.ps1`
- Current smokes cover empty inventory, bundle reconcile, freshness, replacement rollback, multi-project churn, and Electron lifecycle replacement.
- `cloud-bundle-smoke.ps1` is the main Electron lifecycle regression for bundle replacement.
- `project-isolation-smoke.ps1` is the main service-level aggregation gate for project-isolation coverage.

## 6. Practical status summary
Current phase:
- not design-only
- not blocked on the earlier inventory / rollback gaps
- core execution chain works
- empty inventory semantics work
- stale bundle cleanup works
- bundle freshness hardening works
- replacement / rollback hardening works
- multi-project churn coverage exists at the service level
- lifecycle smoke for replacement is in place

Recommended immediate focus:
- keep the current smoke set green
- wire the current gate pair into broader rollout notes or external CI if needed
- expand regression breadth only if upcoming product changes need it
- keep `download_url` protocol evolution explicitly out of unrelated hardening work until backend is ready
