From 4a1ddf8a26290f776e2a633eede9403c843111b1 Mon Sep 17 00:00:00 2001 From: Awesome Rustacean Date: Mon, 17 Aug 2026 13:43:18 +0300 Subject: [PATCH 1/4] docs(rfd): propose managed Symposium uninstall Define receipt-backed ownership, guarded hooks, bounded cleanup, legacy migration, reporting, and the implementation plan. Co-authored-by: Codex --- md/SUMMARY.md | 3 + md/rfds/symposium-uninstall/README.md | 422 ++++++++++++++++++ .../proposed-cargo-agents-uninstall.md | 132 ++++++ .../proposed-managed-integrations.md | 53 +++ 4 files changed, 610 insertions(+) create mode 100644 md/rfds/symposium-uninstall/README.md create mode 100644 md/rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md create mode 100644 md/rfds/symposium-uninstall/proposed-managed-integrations.md diff --git a/md/SUMMARY.md b/md/SUMMARY.md index 0ed860b4..51554d13 100644 --- a/md/SUMMARY.md +++ b/md/SUMMARY.md @@ -96,6 +96,9 @@ - [Discovery & sync](./rfds/registry-centric-plugins/discovery-sync/README.md) - [User-managed plugins](./rfds/registry-centric-plugins/user-managed-plugins/README.md) - [Predicate caching](./rfds/predicate-caching/README.md) + - [Managed Symposium uninstall](./rfds/symposium-uninstall/README.md) + - [Proposed: `cargo agents uninstall`](./rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md) + - [Proposed: managed integrations](./rfds/symposium-uninstall/proposed-managed-integrations.md) - [Completed](./rfds/completed.md) - [Configuration parsing and normalization](./rfds/config-normalization/README.md) - [RFD Process](./rfds/rfd-process/README.md) diff --git a/md/rfds/symposium-uninstall/README.md b/md/rfds/symposium-uninstall/README.md new file mode 100644 index 00000000..b2208968 --- /dev/null +++ b/md/rfds/symposium-uninstall/README.md @@ -0,0 +1,422 @@ +# Managed Symposium uninstall + +## TL;DR + +- Add `cargo agents uninstall` as a non-interactive, idempotent cleanup command that runs before `cargo uninstall symposium`. +- Remove every integration and private artifact that Symposium can prove it manages across known global and workspace scopes. +- Preserve user-authored configuration, custom plugin sources, shared tools, external integrations, and anything whose ownership is ambiguous. +- Record managed writes in durable, versioned ownership receipts and give hook registrations stable IDs. +- Make generated hook commands harmless when either their local activation permit or the `cargo-agents` binary is absent. +- Discover recorded workspaces instead of scanning the user's filesystem. +- Report every removed, preserved, and blocked item, and only declare it safe to remove the binary after cleanup has no blockers. + +## Motivation + +`cargo uninstall symposium` knows which package binaries Cargo installed. It does not know that Symposium also wrote hook registrations, MCP server entries, generated skills, cache data, or workspace state. + +Today an unguarded hook registration can survive removal of the `cargo-agents` binary. Every later hook event then asks the shell to run a command that no longer exists. The user sees errors such as: + +```text +/usr/bin/bash: line 1: cargo-agents: command not found +PostToolUse:Bash hook error +``` + +This can affect every project when the surviving registration is global. Project-scoped installation creates a second problem: cleanup cannot find every workspace by scanning an unknown number of directories, and it must not guess which similar-looking entries belong to Symposium. + +The cleanup contract therefore needs more than a list of paths. Symposium needs durable evidence of what it created, a bounded way to find project scopes later, and hook registrations that fail closed when local ownership state or the binary disappears. + +## Change in a nutshell + +Introduce a central managed-mutation layer. Agent adapters and other subsystems use this layer whenever they add, update, or remove external state. The layer assigns a stable managed ID, validates that the target is safe to modify, performs the mutation atomically, and stores a durable ownership receipt. + +Hook registrations also receive a small derived activation permit. The adapter-generated command performs two checks before ordinary hook startup: + +1. an outer, adapter-specific guard exits successfully if `cargo-agents` is not installed; and +2. `cargo-agents hook --managed-id ...` performs a direct permit lookup and quietly exits if this checkout is not locally activated. + +`cargo agents uninstall` uses receipts, exact legacy signatures, and a bounded set of known locations to build a cleanup plan. It removes only artifacts for which the applicable ownership check succeeds. It preserves and reports anything external or ambiguous. + +The intended user workflow is: + +```console +$ cargo agents uninstall +# restart any running coding agents +$ cargo uninstall symposium +``` + +The command cannot safely restart the parent coding agent that may be hosting the terminal. It prints that step after successful cleanup instead of attempting to terminate or replace another process. + +## Detailed plans + +### Terminology + +This RFD uses three related terms: + +- A **managed ID** is a non-secret UUID assigned to one logical Symposium integration. It remains stable while that integration is updated. +- An **ownership receipt** is the authoritative durable record of the artifacts written for that managed ID and the evidence needed to verify them later. +- An **activation permit** is a tiny, derived hot-path record that says where a hook registration is locally allowed to run. It is the concrete record for the receipt-backed lease and is not sufficient evidence for deletion. + +The receipt answers “may Symposium modify or remove this artifact?” The permit answers “may this hook run in this checkout?” Keeping those questions separate lets uninstall remain conservative without making every hook invocation parse the full ownership store. + +### Command contract + +`cargo agents uninstall` cleans all known scopes in one operation. It has no initial `--global`, `--project`, or `--force` mode. Selective cleanup would make a successful result too easy to misread as permission to remove the binary while active integrations remain elsewhere. + +The command is: + +- non-interactive by default; +- safe to rerun after success or partial failure; +- best-effort across independent artifacts; +- strict about its final result; and +- usable both inside and outside a Cargo workspace. + +One failure does not prevent cleanup of unrelated artifacts. The command aggregates blockers and exits unsuccessfully if any managed item could not be removed or safely classified. A successful exit means all discovered managed artifacts are removed or already absent. The verdict cannot prove the absence of an unrecorded pre-receipt project file; when pre-receipt state is detected, the report includes that transition limitation and the sync guidance separately from the successful known-scope verdict. + +The global `--quiet` and `--json` options apply. The subcommand adds `--dry-run`. + +### Discovery boundaries + +Cleanup examines a bounded set of locations: + +1. the active resolved Symposium configuration directory; +2. every supported agent's known global configuration locations; +3. the current workspace, when the command is run inside one; +4. every workspace root named by a valid ownership receipt; and +5. exact historical Symposium cache, state, and registration locations needed for legacy cleanup. + +It does not scan home directories, mounted volumes, Git repositories, or the full filesystem for strings resembling Symposium commands. + +All supported global adapters are inspected, even when an agent is no longer present in `config.toml`. This is necessary because removing an agent from current configuration does not prove that an older registration disappeared. + +A moved or renamed workspace becomes known again when the user runs `cargo agents sync` from its new root. If an old receipt points to a path that no longer exists, uninstall treats that artifact as absent and retires the stale receipt. A checkout whose registration was written or migrated under this design still has the outer binary guard when its recorded location becomes unreachable, so it remains quiet after binary removal. + +Legacy workspace-state files already contain workspace roots and are another exact discovery source during the transition. One unavoidable gap remains: a pre-receipt project hook that is neither in the current workspace nor recoverable from historical workspace state cannot be found automatically. Active legacy hooks migrate when they next invoke the installed binary, and users upgrading from a pre-receipt version are told to run sync in known project-scoped checkouts before removal. Uninstall reports the limited legacy coverage when it detects pre-receipt state; it does not hide the limitation behind a filesystem scan. + +One invocation operates on the currently resolved Symposium configuration home. Enumerating multiple historical homes selected by different environment overrides is outside this RFD. + +### Removal and preservation rules + +The cleanup boundary is based on ownership, not on whether a path or command contains the word `symposium`. + +| Artifact | Cleanup behavior | Ownership evidence | +|---|---|---| +| Hook registration | Remove the owned structural entry or dedicated file | Managed ID plus adapter-specific command identity; exact historical signature for legacy entries | +| MCP registration | Remove the owned structural entry | Managed key/ID plus command identity where the host schema permits it | +| Generated skill or mirror | Remove only the generated directory or file | Marker and manifest tied to a receipt | +| Dedicated generated file | Remove the file | Marker or content identity tied to a receipt | +| Symposium-private cache, logs, telemetry data, and state | Remove within its fixed private boundary | Known root plus receipt or exact built-in layout | +| `config.toml` | Preserve | User-owned configuration | +| Custom plugin source | Preserve | User-authored or user-selected source | +| User or third-party entry near a managed entry | Preserve | No valid Symposium ownership evidence | +| Cargo-installed shared tool | Preserve and report as external | Shared package-manager location, not exclusive Symposium ownership | +| Side effect of an arbitrary `install_commands` script | Preserve and report when known | Effect cannot be reconstructed safely | +| Unknown pre-receipt project checkout | Cannot discover automatically; report the transition limitation | No stored workspace root to inspect | + +Preserving `config.toml` also preserves the user's telemetry preference and plugin declarations. Removing their derived hook, MCP, and skill integrations ensures they no longer affect the user's agents. A later Symposium installation can reuse the preferences after an explicit sync. + +If the user edits an artifact but its stable identity and adapter-specific invariants still establish Symposium ownership, cleanup removes it. If another program has replaced the entry, the key is occupied by different content, or the evidence is otherwise ambiguous, cleanup preserves it and reports a conflict. + +### Managed mutation layer + +Managed writes go through one internal interface rather than letting adapters edit external state independently. A mutation declares: + +- artifact kind and adapter; +- global or workspace scope; +- logical configuration path and structural locator; +- stable managed ID; +- expected prior ownership state; and +- type-specific evidence for later verification. + +The layer owns collision checks, receipt transitions, safe path handling, atomic writes, and reporting. Low-level config writers remain private to it or require an explicit managed/unmanaged classification. Tests enforce that adapter registration paths do not bypass this boundary. + +This makes new skills, MCP registrations, hooks, and generated files participate automatically when they use an existing managed artifact type. A genuinely new side-effect type still needs one ownership adapter defining how it is created, verified, and removed. That definition is made once in the managed layer rather than reimplemented in init, sync, and uninstall. + +Registration never overwrites an occupied external key. Updates mutate only an entry whose previous ownership can be verified. The system does not create arbitrary backups of user configuration as a substitute for proving ownership. + +### Receipt storage and lifecycle + +Receipts live outside disposable caches beneath the resolved configuration directory, for example: + +```text +managed/ + receipts/.json + permits/.json +``` + +The exact on-disk representation is an implementation detail, but it is versioned and independently readable. A receipt stores only the data needed for safe discovery and ownership verification: + +- schema version and managed ID; +- artifact kind, adapter, and scope; +- logical configuration path and recorded target identity; +- structural locator or generated-file manifest; +- ownership evidence; and +- lifecycle state. + +It does not store whole agent configuration files, plugin source contents, environment values, tokens, arbitrary commands, or command output. + +Receipts have three lifecycle states: + +- `pending`: published before an external write starts; +- `applied`: the managed artifact was successfully installed; and +- `retiring`: execution has been disabled and removal is in progress. + +Installation writes `pending`, performs and verifies the external mutation, then transitions to `applied`. Cleanup transitions to `retiring`, disables any permit, removes and verifies the artifact, then deletes the receipt. A crash can therefore be resumed without assuming that the previous operation completed. + +Receipts do not expire by age. Long-lived records are small, and age does not prove that an integration is abandoned. Sync refreshes current workspace information; uninstall retires receipts whose targets are confirmed absent. + +### Stable hook IDs and activation permits + +An adapter generates the managed ID when it creates a logical hook registration. All event entries belonging to that registration may share the ID. The ID is embedded only in the Symposium command arguments or in a host schema's supported metadata. It does not change the hook event or plugin payload seen by the agent. + +The activation permit is indexed directly by managed ID. It contains the valid global state or a small set of locally activated workspace roots. It is written only after the corresponding receipt and hook registration are applied. Project sync adds the current root; uninstall retires the permit before touching the registration. + +The hook path performs no directory scan, network access, registry refresh, plugin loading, or Cargo metadata query before permit validation. Its decision is: + +- valid applied permit for this scope: continue with ordinary hook startup; +- missing, corrupt, or retiring permit: exit successfully without output; or +- structurally invalid managed ID or unsafe permit data: emit a bounded diagnostic and perform no writes. + +Receipts remain authoritative. If the derived permit is lost or corrupt, `cargo agents sync` rebuilds it from verified local state. + +### Adapter-specific guarded commands + +The outer guard is generated by the agent adapter because host schemas represent shell commands differently. + +| Adapter | Managed representation | +|---|---| +| Claude, Gemini, Codex | Owned command entries inside their nested configuration structures | +| GitHub Copilot | Separate Bash and PowerShell command forms in global config or the dedicated project hook file | +| Kiro | A dedicated Symposium agent definition file containing its hook commands | +| Goose, OpenCode | No shell-hook registration; their managed skills and MCP state still use the cleanup system | + +Every generated form follows the same semantic contract: + +1. check for `cargo-agents` using the adapter's actual shell; +2. return status zero with no output if it is absent; +3. invoke `cargo-agents hook --managed-id ...` if present; and +4. preserve the real command's stdout, stderr, and exit status. + +The guard must not turn an installed but failing Symposium hook into success. Command templates are static, managed IDs are validated UUIDs, and adapters apply platform-appropriate quoting. Receipt text is never interpolated into a shell command. + +Some project hook formats are inherently platform-specific. A checked-in hook copied to an incompatible operating system after Symposium has already been removed may not be executable by that host. Cross-platform translation of an unreachable stale configuration is outside this RFD; newly synced checkouts receive the correct adapter form. + +### Project checkout activation + +Today a copied project registration can become active as soon as the agent trusts and opens the checkout. Under this RFD, a project hook is inactive unless its managed ID has a permit for that local workspace root. + +After cloning, copying, or moving a project with a checked-in hook registration, the user runs: + +```console +$ cargo agents sync +``` + +Sync verifies or adopts the managed registration, records the new root, and publishes its permit. Global hooks are already locally permitted and do not need per-project activation. + +`cargo agents status` reports a present but inactive project hook and says to run `cargo agents sync`. The hook itself remains silent. Init, sync, installation, configuration, and per-agent documentation must explain the per-checkout activation rule. + +A managed ID is non-secret and can safely travel in a committed project configuration. Copying it grants no authority because the receiving machine has neither a matching receipt nor a local activation permit. Multiple local clones may share an ID; each root has an independent permit entry. + +### Legacy adoption and cleanup + +Older installations have no managed IDs or receipts. The migration policy is conservative: + +- ordinary init or sync adopts only an exact historical Symposium registration and rewrites it to the guarded, ID-bearing form; +- a legacy hook invocation without an ID may run only after verifying its exact on-disk historical registration; +- uninstall can remove an exact historical fragment directly, even if no migration occurred; and +- prefix matches, similar commands, malformed structures, and partially edited entries are preserved and reported. + +Exact historical signatures are versioned test fixtures, not broad substring searches. This prevents a third-party hook that also invokes Cargo or uses a similar key from being mistaken for Symposium state. + +### Cleanup algorithm + +Apply mode performs these phases while holding the installation-wide mutation lock: + +1. Load and validate receipts and the exact legacy signature catalog. +2. Discover known global and workspace locations. +3. Read each target and classify it as owned, already absent, external, or blocked. +4. Build and report a deterministic plan. +5. Mark owned hook receipts and permits as retiring so cached registrations become inert. +6. Remove owned structural fragments and generated artifacts, rechecking ownership immediately before each write. +7. Remove private caches, logs, telemetry events and identifiers, and obsolete workspace state. +8. Verify results, retire successful or absent receipts, and aggregate blockers. + +Configuration entries are removed structurally. Shared JSON, JSONC, TOML, or other host configuration files are never deleted just because Symposium owned one nested entry. + +`--dry-run` performs the same discovery, parsing, classification, and ownership verification under the same operation lock, but makes no persistent writes. It prints `Would remove` instead of `Removed`. It exits unsuccessfully when apply mode would encounter a blocker, so automation can use it as a preflight rather than treating it as an alternative cleanup operation. + +### Concurrency, retries, and crash recovery + +Init, sync, managed status repair, and uninstall share one OS-backed lock for installation-wide mutation. Uninstall holds it through planning, mutation, and verification. Ordinary permit lookup on the hook hot path is lock-free. A persistent hook-side repair, if required, acquires the same lock and revalidates before writing. + +Agent configuration remains writable by external programs. Every shared-config mutation therefore uses a conservative read-modify-recheck-atomic-replace sequence. A changed file or transient filesystem error is retried with short exponential backoff: one initial attempt and at most two retries. Parse errors, unsupported schemas, invalid receipts, ownership conflicts, and unsafe paths are not transient and are not retried. + +Successful changes are not rolled back when another target fails. Rerunning the command observes removed artifacts as absent and resumes receipts left in `retiring`. This is safer than attempting a broad rollback that could overwrite concurrent user or agent changes. + +### Filesystem and receipt safety + +Receipt data is untrusted input. Cleanup validates schema versions, artifact kinds, path boundaries, structural locators, and managed IDs before acting. It never executes a command from a receipt, follows a receipt-provided arbitrary path, logs secret-bearing values, or requests elevated privileges. + +Managed state is created with user-private permissions where the platform supports them. Cleanup affects only the current user's Symposium and agent configuration locations. + +Symlinks and junctions use artifact-specific rules: + +- A shared configuration file may be edited through a symlink only when the logical path is a known agent configuration path and it still resolves to the same target identity recorded at registration. +- Atomic replacement updates the resolved configuration target without replacing the symlink itself. +- A changed target is preserved and reported as a conflict. +- Generated files and directories are never traversed through symlinks or junctions. +- A generated path that has been replaced by a link is preserved and reported. +- A generated directory is removed only when its manifest accounts for its contents; unknown contents prevent directory deletion. + +### Minimal uninstall startup + +The current binary initializes ordinary logging and state, refreshes registries, and may check for or install an update before dispatching most commands. `uninstall` must be selected before that startup path. + +The uninstall path may parse the CLI, resolve the active configuration directory, acquire its operation lock, and initialize cleanup-specific reporting. It must not: + +- stamp ordinary `state.toml`; +- fetch or refresh plugin registries; +- auto-update or re-execute the binary; +- initialize normal telemetry recording; or +- emit an uninstall telemetry event. + +Cleanup removes telemetry event and metric files, pending sets, local identifiers or cohort state, and telemetry-private locks or state. It preserves the telemetry preference in `config.toml`. A telemetry lock that remains busy after the retry budget is a blocker rather than permission to leave data behind silently. + +### Human and machine output + +Human output is an action audit, not a count-only summary. It groups entries by scope, then agent, then artifact kind: + +```text +Removed + Global + Claude hooks + ~/.claude/settings.json: Symposium hook entries + Workspaces + /work/example + Codex skills + .agents/skills/example-skill + +Preserved + ~/.symposium/config.toml: user configuration + cargo-binstall: shared Cargo tool + +Cleanup complete. It is safe to remove the Symposium binary. +Next: restart running coding agents, then run `cargo uninstall symposium`. +``` + +Partial failure uses `Not removed` for blockers, prints the reason and a rerun instruction, and says not to remove the binary yet. `--quiet` suppresses successful detail but still prints blockers and the final verdict. + +`--json` writes one versioned document to stdout. Catastrophic diagnostics that prevent creating the document go to stderr. Its top-level fields are: + +```text +schema_version +mode +outcome +safe_to_remove_binary +actions +preserved +blockers +next_step +``` + +`mode` is `apply` or `dry-run`; `outcome` is `ready`, `incomplete`, or `preview`. Each action carries a disposition, scope, adapter when applicable, artifact kind, safe display path, managed ID when available, and reason. Non-blocking legacy-coverage notices appear in `preserved` with a machine-readable reason. Counts may be derived by consumers; a count-only summary does not replace the action list. + +### Performance contract + +The receipt system is not on most command hot paths. Hook permit validation is the exception and has an explicit budget: steady-state p95 added latency must be no more than the larger of 2 ms or 5% of the existing hook startup baseline on the same machine. + +Benchmarks cover p50 and p95 across supported operating systems and varying receipt counts. The hot path opens the permit file by managed ID and checks the current scope; its work does not grow with the total number of receipts. + +### Cost and operational impact + +The design adds costs in five places: + +| Area | Cost and bound | +|---|---| +| Storage | One small receipt per logical managed integration plus a derived permit per hook ID. Growth is linear in recorded integrations and checkouts; no project contents are copied. Retired and confirmed-absent records are removed. | +| Hook startup | One direct permit-file lookup and scope comparison before ordinary startup, bounded by the latency contract above. | +| Generated configuration | Hook command strings gain a static binary-existence guard and `--managed-id `. This is visible in agent configuration but does not change the event or payload delivered to hooks. | +| User workflow | A copied, cloned, or moved project-scoped registration needs one explicit `cargo agents sync` on that machine before it becomes active. | +| Implementation and maintenance | The first implementation adds a state machine, safe atomic writers, platform locking, adapter ownership checks, legacy migration, and failure-injection tests. Later features reuse existing artifact types automatically; only a genuinely new side-effect type needs new ownership logic. | + +Receipts disclose local workspace paths to anyone who can already read the user's private Symposium configuration directory. Those paths are not telemetry, are never uploaded, and receive the same restrictive permissions as the rest of managed state. + +### Documentation changes + +Implementation updates: + +- the installation guide with the staged removal workflow; +- the `uninstall`, `sync`, `status`, configuration, and global command references; +- every relevant agent page with its generated guard and project activation behavior; +- hook, state, module-structure, important-flow, and telemetry design chapters; and +- init and sync output to state that a copied or moved project needs one local sync. + +The proposed user-facing command reference is [Proposed: `cargo agents uninstall`](./proposed-cargo-agents-uninstall.md). The ownership and checkout behavior is described in [Proposed: managed integrations](./proposed-managed-integrations.md). + +## Frequently asked questions + +### Why can Cargo not perform this cleanup? + +Cargo tracks installed package binaries and removes those binaries. Symposium's external effects live in agent-specific configuration and workspace paths that Cargo neither owns nor understands. The staged workflow lets Symposium clean its domain while its binary still exists, then lets Cargo remove the package it owns. + +### Why not make `cargo agents implode` remove the binary too? + +A running process cannot portably remove its installed package, restart its parent coding agent, and still provide reliable recovery and diagnostics. Package removal also belongs to Cargo. Keeping the operations separate provides a clear checkpoint: cleanup succeeds, the user restarts agents, then Cargo removes the binary. + +### Why keep durable state for years? + +Workspace paths may be needed years later to remove registrations safely. Receipts are small metadata records, contain no project contents or secrets, and do not affect a project's build. They are refreshed when a workspace syncs and removed when uninstall confirms their targets are gone. Expiring them by age would discard the only ownership evidence without proving that an integration disappeared. + +### What if a project was moved or deleted? + +Sync from the new location records that location. During uninstall, a missing recorded path counts as already absent and its stale receipt is retired. A moved managed checkout that was never synced again cannot be discovered without an unsafe filesystem scan, but its guarded hook quietly exits after the binary is removed. A legacy unguarded checkout that was never recorded is the transition exception described under discovery boundaries; it must first be rediscovered or handled manually. + +### Does the first managed release remove every project hook created by older releases? + +It removes legacy hooks in global locations, the current workspace, and roots recoverable from historical workspace state. It cannot prove that an unrecorded checkout elsewhere on disk does not contain an old project hook. The report calls out this limited legacy coverage, and the upgrade documentation tells project-scope users to sync dormant checkouts they still use before removing Symposium. This is a transition limitation; registrations created or migrated under this design are recorded and guarded. + +### What does `--dry-run` prove? + +It proves what the command can determine at that moment: the known locations were discovered, current contents were parsed, ownership checks were applied, and blockers were identified. It does not clean anything or guarantee that an external program will not change a file afterward. Apply mode repeats the checks immediately before mutation. + +### How are hooks from another program distinguished? + +New registrations carry a stable managed ID and type-specific command identity tied to a receipt. Legacy cleanup requires an exact historical Symposium structure and command. A matching event name, key prefix, or reference to Cargo is not enough. Ambiguity is preserved as a conflict. + +### Will every future feature need uninstall-specific code? + +Not when it uses an existing managed artifact type. The central writer injects identity and receipts automatically. A new kind of external side effect needs one ownership adapter because safe deletion rules differ between a nested config entry, a generated directory, and a package-manager installation. + +### Are receipts a new security risk? + +They create a small new parser and state store, so they require defensive handling. The design bounds that cost: versioned minimal data, private permissions, no stored secrets, no executable receipt content, fixed path boundaries, stable UUID validation, atomic writes, and tests for traversal and link attacks. A forged receipt cannot authorize arbitrary deletion because type-specific verification and allowlisted roots are also required. + +### Why preserve globally installed tools? + +Tools installed into shared locations such as Cargo's binary directory may be used independently of Symposium or by another program. Symposium removes its integrations and reports those tools, but ownership is not exclusive enough to uninstall them. Private copies acquired into Symposium's cache are removed with that cache. + +### What happens to arbitrary installation scripts? + +Legacy `install_commands` can perform effects outside any declared boundary. Symposium does not run guessed uninstall commands or replay commands from receipts. Known untracked setup is reported as preserved and does not block binary removal after all live Symposium integrations are gone. A future RFD can add declarative, receipt-backed actions such as managed links or copies. + +### Why is there no `--force`? + +Forcing deletion through failed ownership checks would erase the safety property this command exists to provide. The first version reports the exact conflict so the user can resolve it or remove it manually. A later override would need a narrower, evidence-based contract. + +### Can a partial failure leave the system worse? + +Before removing hook configuration, uninstall retires its permit, making a surviving guarded command a quiet no-op. Successful removals remain complete, failed receipts remain resumable, and the command gives a nonzero result with an explicit warning not to remove the binary yet. + +## Implementation plan + +1. **Introduce managed-state primitives without changing generated integrations.** Add versioned receipt and permit schemas, lifecycle transitions, the installation-wide lock, safe-path validation, and atomic storage. Cover schema evolution, corrupt state, permissions, symlinks/junctions, traversal attempts, crash injection, and concurrent writers with unit and property tests. +2. **Route existing managed writes through the ownership layer.** Refactor hook, MCP, skill, generated-file, cache, and workspace-state mutations to declare their artifact type. Add collision behavior, exact legacy signatures, pending/applied recovery, and adapter contract tests. This step must preserve current user-visible behavior while beginning to publish receipts. +3. **Add guarded hooks and local activation permits.** Generate stable IDs and adapter-specific guards, add the minimal pre-startup permit lookup, teach sync to activate a checkout, and teach status to report inactive copied hooks. Test missing binaries, missing/corrupt/retiring permits, clones, moved roots, multiple clones, shell quoting, legacy migration, real error propagation, and the p50/p95 latency budget. +4. **Build the cleanup planner and engine.** Implement bounded discovery, ownership classification, structural removal, retries, partial progress, dry-run, receipt retirement, private-state cleanup, and telemetry-specific coordination. Test every removal/preservation row, absent paths, unknown contents, external modifications, locked files, interrupted cleanup, and idempotent reruns. +5. **Expose `cargo agents uninstall` through minimal startup and reporting.** Add CLI parsing, human grouping, `--quiet`, the versioned JSON document, exit semantics, and safe/unsafe next steps. Integration tests use isolated fake homes and workspaces and assert exact state as well as output. +6. **Complete platform integration and documentation.** Exercise global and project scope across all supported adapters on Linux, macOS, and Windows, including the original stale-global-hook regression. Update the user and design documentation listed above. These tests extend the existing deterministic integration harness and do not depend on replacing it with the separate agent-interaction suite. + +Each step is independently reviewable and includes its tests. No implementation step requires a rewrite of the existing integration-test infrastructure. + +## Implementation status + +This RFD describes proposed behavior. Implementation has not begun. diff --git a/md/rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md b/md/rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md new file mode 100644 index 00000000..e5272e19 --- /dev/null +++ b/md/rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md @@ -0,0 +1,132 @@ +# Proposed: `cargo agents uninstall` + +Remove the agent integrations and local data managed by Symposium before uninstalling the Symposium package. + +## Usage + +```console +cargo agents uninstall [--dry-run] +``` + +The global `--quiet` and `--json` options are also supported. + +The command cleans all known global and project scopes. It can run inside or outside a Cargo workspace and does not prompt. + +## Recommended removal workflow + +```console +$ cargo agents uninstall +# Restart any running coding agents. +$ cargo uninstall symposium +``` + +Do not remove the package when `cargo agents uninstall` reports an incomplete cleanup. Resolve the listed blockers and rerun it. + +Restarting matters because a running agent may have cached its hook configuration. Symposium cannot safely restart the agent process that launched or contains the current terminal. + +## What it removes + +When ownership can be verified, the command removes: + +- Symposium hook registrations from supported agent configurations; +- Symposium MCP server registrations; +- generated and mirrored skill copies; +- generated files and directories; +- private plugin and installation caches; +- logs, telemetry events and identifiers, and other private runtime state; +- workspace state and hook activation permits; and +- ownership receipts after their artifacts are absent. + +The command checks the active global configuration locations, the current workspace, previously recorded workspaces, and exact locations used by older Symposium versions. It does not search the entire filesystem. + +## What it preserves + +The command preserves: + +- Symposium's `config.toml`, including plugin declarations and telemetry preference; +- user-authored custom plugin sources; +- agent configuration that is not owned by Symposium; +- entries another program has replaced or whose ownership is ambiguous; +- shared tools installed into locations such as Cargo's binary directory; and +- side effects from arbitrary legacy installation scripts that cannot be identified safely. + +Preserved external tools and known untracked setup are shown in the report. For upgrades from a pre-receipt version, the report also identifies the possibility of an unknown legacy project checkout when detectable. An ownership conflict is a blocker; a deliberately preserved shared tool or legacy-coverage notice is not. + +## Output + +A successful cleanup lists concrete actions and ends with the next steps: + +```text +Removed + Global + Claude hooks + ~/.claude/settings.json: Symposium hook entries + Workspaces + /work/example + Codex skills + .agents/skills/example-skill + Symposium state + telemetry events + plugin cache + +Preserved + ~/.symposium/config.toml: user configuration + cargo-binstall: shared Cargo tool + +Cleanup complete. It is safe to remove the Symposium binary. +Next: restart running coding agents, then run `cargo uninstall symposium`. +``` + +If cleanup is incomplete, a `Not removed` section identifies every blocker. Successful removals are retained; rerunning the command safely skips artifacts that are already absent. + +`--quiet` hides successful detail but retains blockers and the final verdict. + +## Dry run + +`--dry-run` performs discovery, reads current state, and applies the same ownership checks as cleanup without making persistent changes. Its report uses `Would remove`: + +```console +$ cargo agents uninstall --dry-run +Would remove + Global + Claude hooks + ~/.claude/settings.json: Symposium hook entries + +Preserved + ~/.symposium/config.toml: user configuration + +Preview complete. Apply with `cargo agents uninstall`. +``` + +Dry run is a preflight, not an alternative to uninstall. It exits unsuccessfully if the real cleanup would have a known blocker. Apply mode rechecks files before changing them because another program may modify configuration after the preview. + +## JSON output + +`--json` writes one versioned document to stdout with: + +- mode and outcome; +- whether it is safe to remove the binary; +- exact removed, would-remove, or already-absent actions; +- preserved items; +- blockers; and +- the next required step. + +Paths and ownership reasons are included where safe. Secrets, configuration values, plugin source contents, and command output are not included. + +## Exit status + +The command exits successfully only when apply mode has no cleanup blockers. In dry-run mode it exits successfully only when the preview finds no blocker. + +Missing artifacts are treated as already absent. Invalid receipts, unsafe paths, ownership conflicts, persistent file locks, unsupported configuration schemas, and failed removals produce a nonzero exit. + +## Copied and moved workspaces + +Project hook registrations are locally inactive until their checkout has been synchronized. After cloning, copying, or moving a workspace that contains Symposium-generated project configuration, run: + +```console +$ cargo agents sync +``` + +This records the local workspace and activates its hooks. `cargo agents status` reports a present but inactive hook and recommends sync. A stale project hook remains quiet when it has no local activation record or when the Symposium binary is absent. + +Registrations created before managed IDs and guards existed are a transition case. Uninstall finds the current workspace and roots retained in historical workspace state, and active legacy hooks migrate when they run. It cannot discover an old project hook in an otherwise unknown checkout without scanning the filesystem. Before removing a pre-receipt installation, sync any known project-scoped checkouts that have not been used recently; the uninstall report identifies limited legacy coverage when detectable. diff --git a/md/rfds/symposium-uninstall/proposed-managed-integrations.md b/md/rfds/symposium-uninstall/proposed-managed-integrations.md new file mode 100644 index 00000000..55f5b710 --- /dev/null +++ b/md/rfds/symposium-uninstall/proposed-managed-integrations.md @@ -0,0 +1,53 @@ +# Proposed: managed integrations + +Symposium writes derived state into coding-agent configuration and project directories when it initializes or synchronizes a workspace. Managed integrations make those writes identifiable, locally activatable, and removable without claiming ownership of the surrounding user files. + +## Ownership + +Every managed integration receives a stable, non-secret ID. Symposium keeps a small ownership receipt in its configuration directory describing the artifact type, scope, location, and evidence required to recognize that integration again. + +Receipts do not contain project source, complete agent configuration, plugin source contents, environment variables, tokens, or command output. They remain until the managed artifact is removed or confirmed absent; they do not expire merely because a workspace has not been opened recently. + +The receipt allows Symposium to remove its own nested hook or MCP entry while preserving the shared configuration file and adjacent entries from the user or another program. If the current state no longer matches the ownership evidence, Symposium reports a conflict instead of deleting it. + +## Generated files and directories + +Generated skills and other dedicated output carry a marker and manifest tied to their receipt. Symposium removes only accounted-for content within known generated boundaries. + +If a generated directory contains an unknown file, or a managed path has been replaced by a symlink or junction, cleanup preserves it and reports the conflict. User-authored skills and custom plugin sources are not generated output and are preserved. + +## Hook activation + +Hook registrations carry their managed ID in the `cargo-agents` invocation. A small local activation permit lets the binary decide whether that ID may run in the current scope before loading plugins or inspecting the Cargo workspace. + +Global hooks have a local global permit. Project hooks have a permit for each locally synchronized checkout root. + +The generated command is also guarded by the shell form supported by that agent. If the `cargo-agents` binary is missing, the command exits successfully without output. If the binary exists but no matching local permit exists, the hook also exits successfully without output. Genuine Symposium hook failures retain their output and exit status. + +## After cloning, copying, or moving a project + +A managed ID in a project file is not itself permission to execute. Run sync once from the new checkout: + +```console +$ cargo agents sync +``` + +Sync verifies the registration, records the local root, and activates it. This prevents a copied or committed hook from becoming active merely because an agent opened the project. + +Until then, `cargo agents status` reports that the hook is present but inactive and recommends sync. The hook stays silent so stale configuration does not interrupt the agent workflow. + +Multiple local clones may share the same managed ID. Each root is activated separately. + +## Moving or deleting an old checkout + +Syncing after a move records the new root. The old record remains small and inert until cleanup confirms its old path is absent. `cargo agents uninstall` then retires it. + +Symposium does not scan the filesystem to locate a project that moved without being synchronized again. A remaining managed registration is still harmless after removal of the binary because it contains the outer guard. + +An unrecorded project hook from a version that predates managed IDs is the transition exception: Symposium cannot guard a file it cannot locate. Active legacy hooks migrate automatically while the binary exists, historical workspace state recovers known roots, and users upgrading from an older version should sync any dormant project-scoped checkouts they still use before uninstalling. + +## Removing Symposium + +Run `cargo agents uninstall` while the binary still exists. It uses receipts and exact legacy signatures to remove known managed integrations, private state, and generated data. It preserves user configuration and anything it cannot prove it owns. + +After successful cleanup, restart running coding agents and run `cargo uninstall symposium`. From 968715acdfc10ae535743fa1d63ef893fe1217f9 Mon Sep 17 00:00:00 2001 From: Awesome Rustacean Date: Thu, 20 Aug 2026 04:28:05 +0300 Subject: [PATCH 2/4] docs: design managed Symposium uninstall Co-authored-by: Codex --- md/rfds/symposium-uninstall/README.md | 699 +++++++++++------- .../proposed-cargo-agents-uninstall.md | 159 ++-- .../proposed-managed-integrations.md | 106 ++- 3 files changed, 630 insertions(+), 334 deletions(-) diff --git a/md/rfds/symposium-uninstall/README.md b/md/rfds/symposium-uninstall/README.md index b2208968..e373eba1 100644 --- a/md/rfds/symposium-uninstall/README.md +++ b/md/rfds/symposium-uninstall/README.md @@ -1,421 +1,626 @@ -# Managed Symposium uninstall +# Managed Symposium uninstall ## TL;DR -- Add `cargo agents uninstall` as a non-interactive, idempotent cleanup command that runs before `cargo uninstall symposium`. -- Remove every integration and private artifact that Symposium can prove it manages across known global and workspace scopes. -- Preserve user-authored configuration, custom plugin sources, shared tools, external integrations, and anything whose ownership is ambiguous. -- Record managed writes in durable, versioned ownership receipts and give hook registrations stable IDs. -- Make generated hook commands harmless when either their local activation permit or the `cargo-agents` binary is absent. -- Discover recorded workspaces instead of scanning the user's filesystem. -- Report every removed, preserved, and blocked item, and only declare it safe to remove the binary after cleanup has no blockers. +- Add `cargo agents uninstall` as an idempotent cleanup command that runs before `cargo uninstall symposium`. +- Ask users to quit every running agent before cleanup, then restart agents only after the binary has been removed. +- Record Symposium-owned writes in durable receipts. Use receipts to discover artifacts and adapter-specific evidence to prove their identity. +- Give project hook registrations stable IDs and root-bound activation permits. A permit for an ID alone is never enough to run a project hook. +- Use positive permits for project hooks and positive retirement tombstones for global hooks. Losing the managed store must not disable the recommended global workflow. +- Preserve user configuration, custom plugin sources, shared tools, tracked project configuration by default, and every ambiguous artifact. +- Report removed, preserved, acknowledged, and blocked items, followed by a qualified binary-removal assessment. ## Motivation `cargo uninstall symposium` knows which package binaries Cargo installed. It does not know that Symposium also wrote hook registrations, MCP server entries, generated skills, cache data, or workspace state. -Today an unguarded hook registration can survive removal of the `cargo-agents` binary. Every later hook event then asks the shell to run a command that no longer exists. The user sees errors such as: +Today an unguarded hook registration can survive removal of the `cargo-agents` binary. Every later hook event then asks the shell to run a command that no longer exists: ```text /usr/bin/bash: line 1: cargo-agents: command not found PostToolUse:Bash hook error ``` -This can affect every project when the surviving registration is global. Project-scoped installation creates a second problem: cleanup cannot find every workspace by scanning an unknown number of directories, and it must not guess which similar-looking entries belong to Symposium. +This is especially visible after a global installation because the stale registration runs in every project. Project-scoped installation creates a different problem: cleanup cannot find arbitrary historical workspaces by scanning the user's filesystem, and a similar-looking entry may belong to another program. -The cleanup contract therefore needs more than a list of paths. Symposium needs durable evidence of what it created, a bounded way to find project scopes later, and hook registrations that fail closed when local ownership state or the binary disappears. +The cleanup contract therefore needs durable discovery evidence, type-specific identity checks, bounded project lookup, and generated commands that become harmless when their binary is absent. -## Change in a nutshell +## User workflow -Introduce a central managed-mutation layer. Agent adapters and other subsystems use this layer whenever they add, update, or remove external state. The layer assigns a stable managed ID, validates that the target is safe to modify, performs the mutation atomically, and stores a durable ownership receipt. +The recommended sequence is: -Hook registrations also receive a small derived activation permit. The adapter-generated command performs two checks before ordinary hook startup: +1. Quit every running Claude, Gemini, Copilot, Codex, Kiro, Goose, OpenCode, or other agent process that may have loaded Symposium-managed configuration. +2. From an ordinary terminal, run `cargo agents uninstall`. +3. Resolve or acknowledge any reported blockers and rerun until the command reports no live integrations in the applicable scopes. +4. Run `cargo uninstall symposium`. +5. Start the agents again. -1. an outer, adapter-specific guard exits successfully if `cargo-agents` is not installed; and -2. `cargo-agents hook --managed-id ...` performs a direct permit lookup and quietly exits if this checkout is not locally activated. +Quitting first avoids an agent rewriting a settings file from its in-memory copy after cleanup has verified and removed an entry. Restarting after binary removal makes every agent reload the cleaned configuration. -`cargo agents uninstall` uses receipts, exact legacy signatures, and a bounded set of known locations to build a cleanup plan. It removes only artifacts for which the applicable ownership check succeeds. It preserves and reports anything external or ambiguous. +`cargo agents uninstall` does not remove its own executable. A running process cannot portably and reliably remove the package that supplied it, and Cargo already owns package removal. -The intended user workflow is: +## Goals -```console -$ cargo agents uninstall -# restart any running coding agents -$ cargo uninstall symposium -``` +This RFD aims to: + +- remove integrations Symposium can prove it manages in global and recorded project scopes; +- remove Symposium-private files only after external integrations are absent or explicitly transferred to the user; +- preserve user-authored and third-party state; +- make interrupted cleanup resumable; +- make copied, moved, and deleted projects safe without filesystem-wide scanning; +- keep the ordinary hook path bounded and free of Cargo subprocesses; +- give humans and automation precise, non-overclaiming results; and +- make new instances of existing managed artifact types participate automatically. + +## Non-goals + +This RFD does not: + +- make a global hook safe to run arbitrary untrusted workspace configuration; +- discover every project ever touched by a pre-receipt Symposium release; +- uninstall shared packages from Cargo or another package manager; +- reverse arbitrary plugin `install_commands`; +- delete user configuration or custom plugin sources; or +- restart or terminate agent processes. + +## Threat model and security boundary -The command cannot safely restart the parent coding agent that may be hosting the terminal. It prints that step after successful cleanup instead of attempting to terminate or replace another process. +Receipts, project permits, and global retirement tombstones primarily provide deterministic cleanup, collision resistance, and a local activation boundary for **project-scoped registrations**. They are not capabilities and they are not secrets. + +A project hook may run only when its managed ID and exact registration-owning root match one active permit. Copying a committed ID to another checkout grants no authority because that checkout owns a different configuration path. The hot path resolves the owning root from the process working directory and adapter-relative configuration path; it does not invoke Cargo. + +A **global** hook is intentionally valid in every working directory. Symposium currently recommends global hook installation, and ordinary hook startup may discover and load workspace plugin configuration. Therefore this design does not claim that permits protect global-hook users from a hostile repository. Changing that trust model would require gating workspace plugin activation itself and belongs in a separate security design. + +The uninstall safety invariant is narrower: + +> Symposium deletes external state only when bounded discovery evidence and artifact-specific identity evidence both agree that Symposium manages it. ## Detailed plans -### Terminology +### Terminology and identity -This RFD uses three related terms: +- A **managed ID** is a stable UUID for one logical registration. All event entries belonging to one agent-and-scope hook registration share that ID. +- An **ownership receipt** is durable discovery evidence describing one intended managed mutation, its target, adapter, scope, and lifecycle. +- A **static signature** is a versioned, secret-free structural description of a registration form emitted by a released Symposium version. +- A **dynamic fingerprint** is a receipt-recorded, secret-free identity for an instance whose command or URL came from plugin configuration. +- An **activation permit** is a small positive record for a project hook. It binds a managed ID to exactly one normalized registration-owning root. +- A **retirement tombstone** is a small positive record that temporarily disables a global hook while its registration is being removed. +- A **blocker ID** is a stable, domain-separated hash of artifact kind, adapter, normalized target, and structural locator. -- A **managed ID** is a non-secret UUID assigned to one logical Symposium integration. It remains stable while that integration is updated. -- An **ownership receipt** is the authoritative durable record of the artifacts written for that managed ID and the evidence needed to verify them later. -- An **activation permit** is a tiny, derived hot-path record that says where a hook registration is locally allowed to run. It is the concrete record for the receipt-backed lease and is not sufficient evidence for deletion. +A managed ID is a correlation key, not proof of ownership and not permission to execute. Deletion always requires the expected target and type-specific identity. Project execution always requires a root match. -The receipt answers “may Symposium modify or remove this artifact?” The permit answers “may this hook run in this checkout?” Keeping those questions separate lets uninstall remain conservative without making every hook invocation parse the full ownership store. +A blocker ID is `blk_` followed by the lowercase hexadecimal first 16 bytes of SHA-256 over the domain `symposium-blocker-v1` and length-prefixed UTF-8 fields for artifact kind, adapter, normalized target, and canonical adapter-specific structural locator. The acknowledgement stores the full tuple rather than trusting the truncated display ID alone. -### Command contract +### Path and scope identity -`cargo agents uninstall` cleans all known scopes in one operation. It has no initial `--global`, `--project`, or `--force` mode. Selective cleanup would make a successful result too easy to misread as permission to remove the binary while active integrations remain elsewhere. +Symposium defines one normalization function for receipt targets, permit roots, and runtime comparison: -The command is: +1. make the path absolute; +2. canonicalize the existing portion of the path; +3. remove the Windows verbatim-path prefix when present; +4. apply platform filesystem case rules on Windows; and +5. compare path components rather than string prefixes. -- non-interactive by default; -- safe to rerun after success or partial failure; -- best-effort across independent artifacts; -- strict about its final result; and -- usable both inside and outside a Cargo workspace. +Every generated invocation carries `--managed-id `, but it does not carry an authoritative scope. The binary classifies scope from the directly addressed local receipt, project permit, or global tombstone. An ID or scope-like text in the command is never sufficient. -One failure does not prevent cleanup of unrelated artifacts. The command aggregates blockers and exits unsuccessfully if any managed item could not be removed or safely classified. A successful exit means all discovered managed artifacts are removed or already absent. The verdict cannot prove the absence of an unrecorded pre-receipt project file; when pre-receipt state is detected, the report includes that transition limitation and the sync guidance separately from the successful known-scope verdict. +When local state for the ID is missing or unavailable, preflight enters a bounded degraded classifier. It uses the same registration-owning-root walk defined below and checks the adapter's known global target for an exact released registration signature containing that ID. Exactly one match is required: a project match remains inactive and may show the one-time sync hint; a global match runs and reports degraded health; zero or multiple matches deny plugin dispatch and report ambiguity. This fallback opens only those adapter files and does not invoke Cargo. -The global `--quiet` and `--json` options apply. The subcommand adds `--dry-run`. +For a project registration, the hook preflight starts at the process working directory and performs a depth-bounded ancestor walk. The **registration-owning root** is the nearest ancestor whose adapter project configuration contains an exact released registration signature for this managed ID. Candidate configuration files without that registration are ignored. Execution requires all of: -### Discovery boundaries +1. trusted local state or exact degraded classification identifies project scope; +2. the nearest registration-owning root exists; +3. the active permit has the same managed ID; and +4. the normalized permit root equals the normalized registration-owning root exactly. -Cleanup examines a bounded set of locations: +The permit root being merely an ancestor of the working directory is not enough. An unrelated nested adapter configuration is ignored, while a nested checkout containing a copied matching registration resolves to its own nearer root and is denied. Sync refuses to create a project permit whose root is a filesystem root or the user's home directory. -1. the active resolved Symposium configuration directory; -2. every supported agent's known global configuration locations; -3. the current workspace, when the command is run inside one; -4. every workspace root named by a valid ownership receipt; and -5. exact historical Symposium cache, state, and registration locations needed for legacy cleanup. +Adapters must establish the documented working directory before invoking a project hook. An adapter that cannot provide that contract cannot use a project-scoped guarded registration. -It does not scan home directories, mounted volumes, Git repositories, or the full filesystem for strings resembling Symposium commands. +Resolved paths, not inode or file IDs, identify targets. This permits a dotfile repository to be recloned without creating an unrecoverable inode conflict. A dev container, WSL environment, and Windows host are separate permit environments even when they expose the same repository through different paths; each requires `cargo agents sync`. -All supported global adapters are inspected, even when an agent is no longer present in `config.toml`. This is necessary because removing an agent from current configuration does not prove that an older registration disappeared. +An acknowledgement stores the same locator tuple plus the artifact's identity fingerprint at acknowledgement time. It remains stable across identical reruns, but a moved locator produces a new blocker ID and changed artifact identity invalidates the old acknowledgement. -A moved or renamed workspace becomes known again when the user runs `cargo agents sync` from its new root. If an old receipt points to a path that no longer exists, uninstall treats that artifact as absent and retires the stale receipt. A checkout whose registration was written or migrated under this design still has the outer binary guard when its recorded location becomes unreachable, so it remains quiet after binary removal. +### Cleanup boundary -Legacy workspace-state files already contain workspace roots and are another exact discovery source during the transition. One unavoidable gap remains: a pre-receipt project hook that is neither in the current workspace nor recoverable from historical workspace state cannot be found automatically. Active legacy hooks migrate when they next invoke the installed binary, and users upgrading from a pre-receipt version are told to run sync in known project-scoped checkouts before removal. Uninstall reports the limited legacy coverage when it detects pre-receipt state; it does not hide the limitation behind a filesystem scan. +The boundary is based on ownership, not on whether a path or command contains the word `symposium`. -One invocation operates on the currently resolved Symposium configuration home. Enumerating multiple historical homes selected by different environment overrides is outside this RFD. -### Removal and preservation rules +| Artifact | Default cleanup behavior | Required identity evidence | +| ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| Static hook registration | Remove its owned entries or dedicated file | Receipt for discovery plus a released static signature | +| Dynamic plugin MCP registration except Goose | Remove its owned structural entry | Receipt plus an exact dynamic fingerprint | +| Goose dynamic MCP registration | Remove one verified marker-delimited YAML block without reserializing surrounding YAML | Receipt, unique marker pair, indentation, and exact dynamic fingerprint | +| Static built-in MCP registration | Remove its owned structural entry | Receipt or bounded legacy discovery plus a released static signature | +| Generated skill or mirror | Remove generated files; remove the directory only when its manifest accounts for every entry | Receipt, marker, and manifest | +| Dedicated generated file | Remove the file | Receipt plus marker or released content signature | +| Symposium-private cache, logs, telemetry, receipts, permits, tombstones, notices, and workspace state | Remove only during finalization | Fixed private root and successful external cleanup | +| `config.toml` | Preserve | User-owned configuration | +| Custom plugin source | Preserve | User-authored or user-selected source | +| Tracked project configuration | Preserve unless `--include-tracked` is given | Git tracking plus ordinary artifact identity | +| User or third-party entry near a managed entry | Preserve | Symposium evidence is absent or conflicts | +| Cargo-installed shared tool | Preserve and report | Package-manager location is not exclusively owned | +| Side effect of arbitrary `install_commands` | Preserve and report when known | The effect cannot be reconstructed safely | +| Unknown pre-receipt checkout | Not automatically discoverable | No recorded root | -The cleanup boundary is based on ownership, not on whether a path or command contains the word `symposium`. -| Artifact | Cleanup behavior | Ownership evidence | -|---|---|---| -| Hook registration | Remove the owned structural entry or dedicated file | Managed ID plus adapter-specific command identity; exact historical signature for legacy entries | -| MCP registration | Remove the owned structural entry | Managed key/ID plus command identity where the host schema permits it | -| Generated skill or mirror | Remove only the generated directory or file | Marker and manifest tied to a receipt | -| Dedicated generated file | Remove the file | Marker or content identity tied to a receipt | -| Symposium-private cache, logs, telemetry data, and state | Remove within its fixed private boundary | Known root plus receipt or exact built-in layout | -| `config.toml` | Preserve | User-owned configuration | -| Custom plugin source | Preserve | User-authored or user-selected source | -| User or third-party entry near a managed entry | Preserve | No valid Symposium ownership evidence | -| Cargo-installed shared tool | Preserve and report as external | Shared package-manager location, not exclusive Symposium ownership | -| Side effect of an arbitrary `install_commands` script | Preserve and report when known | Effect cannot be reconstructed safely | -| Unknown pre-receipt project checkout | Cannot discover automatically; report the transition limitation | No stored workspace root to inspect | +Preserving `config.toml` retains preferences and plugin declarations, but removing their derived hook, MCP, and skill integrations stops Symposium affecting the agents. A future installation may reuse those preferences only after an explicit sync. -Preserving `config.toml` also preserves the user's telemetry preference and plugin declarations. Removing their derived hook, MCP, and skill integrations ensures they no longer affect the user's agents. A later Symposium installation can reuse the preferences after an explicit sync. +### Central managed mutations -If the user edits an artifact but its stable identity and adapter-specific invariants still establish Symposium ownership, cleanup removes it. If another program has replaced the entry, the key is occupied by different content, or the evidence is otherwise ambiguous, cleanup preserves it and reports a conflict. +All code that writes externally visible agent state goes through one managed-mutation layer. Callers declare an artifact type and desired value. The layer supplies: -### Managed mutation layer +- IDs, receipts, and lifecycle transitions; +- adapter-specific identity evidence; +- collision detection by structurally inspecting the current adapter slot: init and sync do not adopt or overwrite an occupied slot without matching `pending` or `applied` ownership evidence, and signature-catalog migration is a separate explicit path; +- safe target validation and path containment; +- structural read-modify-write behavior or Goose's verified byte-extent block edit; +- atomic file replacement; and +- the corresponding cleanup operation. -Managed writes go through one internal interface rather than letting adapters edit external state independently. A mutation declares: +Adding another hook, skill, or MCP server through an existing artifact type therefore does not require uninstall-specific code. A genuinely new side-effect type needs a new ownership adapter because its proof and safe deletion rules differ. -- artifact kind and adapter; -- global or workspace scope; -- logical configuration path and structural locator; -- stable managed ID; -- expected prior ownership state; and -- type-specific evidence for later verification. +### Receipt lifecycle -The layer owns collision checks, receipt transitions, safe path handling, atomic writes, and reporting. Low-level config writers remain private to it or require an explicit managed/unmanaged classification. Tests enforce that adapter registration paths do not bypass this boundary. +Receipts live below a versioned managed-state directory in the resolved Symposium configuration home. They contain paths and identity metadata, never executable instructions, tokens, environment values, or header values. -This makes new skills, MCP registrations, hooks, and generated files participate automatically when they use an existing managed artifact type. A genuinely new side-effect type still needs one ownership adapter defining how it is created, verified, and removed. That definition is made once in the managed layer rather than reimplemented in init, sync, and uninstall. +A receipt moves through: -Registration never overwrites an occupied external key. Updates mutate only an entry whose previous ownership can be verified. The system does not create arbitrary backups of user configuration as a substitute for proving ownership. +1. `pending`: intent is durable, but the external write is not yet confirmed; +2. `applied`: the external artifact matches; +3. `retiring`: this artifact is being removed or has an interrupted removal; and +4. `acknowledged`: the user accepted responsibility for the preserved artifact. -### Receipt storage and lifecycle +Writers store `pending` before the external mutation, verify the mutation, then store `applied`. Recovery reconciles incomplete states by inspecting the target; it never assumes a write succeeded. -Receipts live outside disposable caches beneath the resolved configuration directory, for example: +For project hooks, the derived positive permit is published last. A project registration cannot become active before its receipt and external write are durable. Global hooks have no positive permit: they run unless a retirement tombstone for their managed ID is present. A completed receipt remains available until the whole uninstall reaches finalization, so a running agent, crash, or blocker cannot erase the only discovery evidence. -```text -managed/ - receipts/.json - permits/.json -``` +`cargo agents sync` is the explicit restore path after interrupted cleanup. When the receipt still describes an applied registration, sync changes a repairable `retiring` state back to `applied`, republishes a project permit, or removes a global retirement tombstone as appropriate. `cargo agents status` reports the repairable state and the exact sync command. + +### Static signature catalog + +Symposium maintains a versioned catalog of every released **static** registration form, including current forms rather than only legacy forms. Receipts answer “where should cleanup look?”; signatures answer “is the structure at that location one Symposium released?” + +The catalog covers: + +- static hook commands and their containing structural shape; +- dedicated generated agent files; +- static built-in MCP registrations; and +- generated file markers and manifests where applicable. + +Signatures compare parsed structure, normalized executable identity, fixed arguments, and managed ID placement. They do not match on a broad key name, event name, or the presence of `cargo-agents` alone. + +The hidden hook CLI accepts an optional managed ID. Its presence selects the new preflight; its absence selects legacy behavior. Scope comes from trusted local state or exact degraded classification, never from the invocation. Legacy hooks continue to run exactly as they do today and are not put behind a new runtime verification step. The next `init` or `sync` may migrate an exact historical signature to the guarded form, and uninstall may remove an exact historical signature from a known location. + +### Dynamic MCP identity + +Plugin-provided MCP commands, URLs, arguments, and names are not a finite released signature catalog. Their identity is captured when the managed mutation is written. + + +| Adapter | Structural container | +| -------------------- | ------------------------------ | +| Claude, Gemini, Kiro | `mcpServers.` | +| GitHub Copilot | Top-level MCP server map entry | +| Codex | `mcp_servers.` | +| Goose | `extensions.` | +| OpenCode | `mcp.` | -The exact on-disk representation is an implementation detail, but it is versioned and independently readable. A receipt stores only the data needed for safe discovery and ownership verification: -- schema version and managed ID; -- artifact kind, adapter, and scope; -- logical configuration path and recorded target identity; -- structural locator or generated-file manifest; -- ownership evidence; and -- lifecycle state. +The fingerprint includes the adapter, normalized target, structural container, entry name, transport, and command-plus-arguments or URL. It deliberately excludes environment and header values so receipts do not copy secrets. Removal requires the current entry to match every non-secret identity field; changing one transfers the entry out of automatic cleanup and produces a blocker. -It does not store whole agent configuration files, plugin source contents, environment values, tokens, arbitrary commands, or command output. +If a dynamic MCP receipt is missing, Symposium preserves the entry. A name and command resemblance are not upgraded into proof. Future schemas may reserve a Symposium metadata field or namespace where the host permits one, but this RFD does not assume such a field exists. -Receipts have three lifecycle states: +Goose is an explicit editing exception. Its YAML configuration is not round-tripped through a general serializer because doing so would discard comments and user formatting. New Symposium blocks are enclosed by behavior-neutral managed-ID comment markers. Cleanup locates one unique marker pair at the recorded indentation, parses only the enclosed mapping to verify its dynamic fingerprint, and removes that exact byte extent. Missing markers, duplicate markers, invalid indentation, or any fingerprint mismatch preserve the block. Supporting this verified block editor and its malformed-YAML fixtures is a distinct implementation cost. -- `pending`: published before an external write starts; -- `applied`: the managed artifact was successfully installed; and -- `retiring`: execution has been disabled and removal is in progress. +### Git-tracked project configuration -Installation writes `pending`, performs and verifies the external mutation, then transitions to `applied`. Cleanup transitions to `retiring`, disables any permit, removes and verifies the artifact, then deletes the receipt. A crash can therefore be resumed without assuming that the previous operation completed. +Project hook registrations may live in files a team intentionally commits. Before planning a project mutation, uninstall asks Git whether the containing file is tracked. This query is outside the hook hot path. -Receipts do not expire by age. Long-lived records are small, and age does not prove that an integration is abandoned. Sync refreshes current workspace information; uninstall retires receipts whose targets are confirmed absent. +- Uninstall first walks ancestors for a `.git` file or directory without launching Git. If none exists, ordinary ownership rules apply and Git need not be installed. +- A tracked file is preserved by default and reported as “committed by your project.” +- `--include-tracked` authorizes structural removal of the proven Symposium entry, not deletion or wholesale rewriting of the file. +- Only when a `.git` ancestor exists does uninstall query tracking. If Git is then unavailable or returns an indeterminate result, the file is preserved as a blocker. -### Stable hook IDs and activation permits +The report includes the file and structural locator, whether the integration remains live, and a redacted fragment the user can remove or review. It never prints secret values. -An adapter generates the managed ID when it creates a logical hook registration. All event entries belonging to that registration may share the ID. The ID is embedded only in the Symposium command arguments or in a host schema's supported metadata. It does not change the hook event or plugin payload seen by the agent. +### Activation permits and hook preflight -The activation permit is indexed directly by managed ID. It contains the valid global state or a small set of locally activated workspace roots. It is written only after the corresponding receipt and hook registration are applied. Project sync adds the current root; uninstall retires the permit before touching the registration. +Project and global hooks deliberately fail in opposite directions: -The hook path performs no directory scan, network access, registry refresh, plugin loading, or Cargo metadata query before permit validation. Its decision is: +- Project scope is positive-permit: missing, corrupt, unavailable, or non-matching state denies plugin dispatch. +- Global scope is positive-retirement: only a present retirement tombstone disables dispatch. Missing or unavailable managed state does not make the recommended global installation silently inert. -- valid applied permit for this scope: continue with ordinary hook startup; -- missing, corrupt, or retiring permit: exit successfully without output; or -- structurally invalid managed ID or unsafe permit data: emit a bounded diagnostic and perform no writes. +The binary classifies preflight before ordinary startup: -Receipts remain authoritative. If the derived permit is lost or corrupt, `cargo agents sync` rebuilds it from verified local state. -### Adapter-specific guarded commands +| Trusted classification and state | Behavior | +| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| Project permit with exact managed ID and registration-owning root | Continue to ordinary hook startup | +| Project permit missing, corrupt, unavailable, or non-matching | Do not load plugins or auto-sync; SessionStart may emit one bounded activation or repair hint, other events exit successfully and silently | +| Global retirement tombstone present | Exit successfully and silently | +| Global retirement tombstone present but corrupt | Exit successfully without plugin dispatch and surface a repair warning | +| Global registration proven by receipt or exact degraded signature, no tombstone | Continue to ordinary hook startup and report degraded health when local state was missing | +| Missing state with zero or multiple exact registration matches | Deny plugin dispatch and surface an ambiguity warning | -The outer guard is generated by the agent adapter because host schemas represent shell commands differently. -| Adapter | Managed representation | -|---|---| -| Claude, Gemini, Codex | Owned command entries inside their nested configuration structures | -| GitHub Copilot | Separate Bash and PowerShell command forms in global config or the dedicated project hook file | -| Kiro | A dedicated Symposium agent definition file containing its hook commands | -| Goose, OpenCode | No shell-hook registration; their managed skills and MCP state still use the cleanup system | +An inactive project SessionStart may perform only preflight and emit static `additionalContext` naming `cargo agents sync`. It may not read workspace plugin configuration, refresh a registry, run plugin code, or auto-sync. After the first successful hint for a managed ID and normalized registration-owning root, preflight atomically writes a small notice-suppression record. That root receives no further activation hint unless sync activates it or local managed state is reset. Notice records are capped at 64 roots per managed ID; after the cap, new roots receive neither another stored record nor a repeated hint, and status reports the suppressed count. -Every generated form follows the same semantic contract: +Missing project state that means “correctly inactive” is quiet outside the one-time SessionStart hint. Failure to determine project state receives a repair hint. Missing global state continues to run only after the bounded exact global registration check succeeds, because global retirement, not global activation, is the only property this RFD needs. -1. check for `cargo-agents` using the adapter's actual shell; -2. return status zero with no output if it is absent; -3. invoke `cargo-agents hook --managed-id ...` if present; and -4. preserve the real command's stdout, stderr, and exit status. +When the managed-state directory is writable, corrupt project state or missing global state records a health flag for `cargo agents status` and one best-effort log line. When the store itself is unavailable, no persistent flag can be written by definition: status detects the failure on demand, and a bounded SessionStart warning is the only guaranteed signal. Symposium does not invent a second fallback state directory. Corrupt records inside a writable store are quarantined and recreated by the next explicit sync. -The guard must not turn an installed but failing Symposium hook into success. Command templates are static, managed IDs are validated UUIDs, and adapters apply platform-appropriate quoting. Receipt text is never interpolated into a shell command. +### Integration with `cargo agents status` -Some project hook formats are inherently platform-specific. A checked-in hook copied to an incompatible operating system after Symposium has already been removed may not be executable by that host. Cross-platform translation of an unreachable stale configuration is outside this RFD; newly synced checkouts receive the correct adapter form. +The uninstall work does not add or own a second status command. It exposes a read-only managed-health snapshot from the managed-state layer for the existing or concurrently developed `cargo agents status` command to consume. -### Project checkout activation +The snapshot contains versioned data for inactive, retiring, corrupt, unavailable, and cleanup-in-progress states, with stable reason codes and recommended commands. Presentation, filtering, and the rest of status output remain owned by the status command. -Today a copied project registration can become active as soon as the agent trusts and opens the checkout. Under this RFD, a project hook is inactive unless its managed ID has a permit for that local workspace root. +Status never repairs receipts, permits, or tombstones as a side effect of reading them. It attempts the installation barrier and managed-state lock in shared mode: -After cloning, copying, or moving a project with a checked-in hook registration, the user runs: +- when acquired, it reads one consistent snapshot; +- while uninstall owns the exclusive barrier, it reports `cleanup-in-progress` from lock metadata and does not inspect half-mutated targets; and +- if state is corrupt or unreadable, it returns a diagnostic snapshot rather than panicking. -```console -$ cargo agents sync +The shared types live below the CLI layer. This lets either branch merge first without adding a duplicate `Commands::Status` variant, output schema, or lock implementation. A compatibility test invokes status before, during, and after uninstall to prove both commands coexist. + +### Generated outer guards + +The outer shell guard has one job: an absent binary must produce exit code 0 and no output. Scope-aware permit or tombstone preflight remains inside the binary. + +Machine-local global registrations record the absolute resolved `cargo-agents` path at registration time and try it first. If that path is absent, they fall back to `PATH`. A committed project registration cannot contain another user's absolute path, so its portable template tries the Cargo home convention and then `PATH`. + +The fixtures below are the exact single-line command values before the host's JSON, TOML, or YAML serializer escapes them. The versioned POSIX machine-local global template is: + +```sh +if [ -x ]; then exec hook --managed-id ; elif command -v cargo-agents >/dev/null 2>&1; then exec cargo-agents hook --managed-id ; else exit 0; fi +``` + +The versioned portable POSIX project template is: + +```sh +if [ -x "${CARGO_HOME:-$HOME/.cargo}/bin/cargo-agents" ]; then exec "${CARGO_HOME:-$HOME/.cargo}/bin/cargo-agents" hook --managed-id ; elif command -v cargo-agents >/dev/null 2>&1; then exec cargo-agents hook --managed-id ; else exit 0; fi +``` + +The versioned PowerShell machine-local global template is: + +```powershell +$symposiumBin = ; if (-not (Test-Path -LiteralPath $symposiumBin -PathType Leaf)) { $symposiumCommand = Get-Command cargo-agents -CommandType Application -ErrorAction SilentlyContinue; if ($null -eq $symposiumCommand) { exit 0 }; $symposiumBin = $symposiumCommand.Source }; $global:LASTEXITCODE = $null; & $symposiumBin hook --managed-id ; if ($null -eq $LASTEXITCODE) { exit 1 }; exit $LASTEXITCODE ``` -Sync verifies or adopts the managed registration, records the new root, and publishes its permit. Global hooks are already locally permitted and do not need per-project activation. +The versioned portable PowerShell project template is: + +```powershell +$cargoHome = $env:CARGO_HOME; if ([string]::IsNullOrWhiteSpace($cargoHome)) { $cargoHome = Join-Path $HOME '.cargo' }; $symposiumBin = Join-Path $cargoHome 'bin/cargo-agents.exe'; if (-not (Test-Path -LiteralPath $symposiumBin -PathType Leaf)) { $symposiumCommand = Get-Command cargo-agents -CommandType Application -ErrorAction SilentlyContinue; if ($null -eq $symposiumCommand) { exit 0 }; $symposiumBin = $symposiumCommand.Source }; $global:LASTEXITCODE = $null; & $symposiumBin hook --managed-id ; if ($null -eq $LASTEXITCODE) { exit 1 }; exit $LASTEXITCODE +``` + +The POSIX literal encoder wraps the path in single quotes and replaces each embedded apostrophe with the shell sequence `'\''`. The PowerShell literal encoder wraps the path in single quotes and doubles every embedded apostrophe. The placeholders above are already-encoded literals, never raw paths. + +Copilot publishes and tests both shell forms. Adapter fixtures are versioned as part of the static signature catalog. Fixtures assert the decoded one-line command value and the exact raw host serialization. Every fixture test also asserts: + +- absent absolute candidate and stripped `PATH` produce exit 0 and empty output; +- absolute paths are encoded with the adapter's shell-literal encoder rather than raw placeholder substitution; +- paths containing spaces, apostrophes, quotes, and shell metacharacters are quoted correctly; +- a present but unlaunchable executable produces a nonzero result even when `LASTEXITCODE` was previously unset or zero; +- a present binary receives the event, payload, managed ID, and working directory unchanged; and +- the binary's real nonzero status propagates. + +### Command surface + +```text +cargo agents uninstall [--dry-run] [--include-tracked] + [--acknowledge ]... + [--quiet] [--json] +``` + +`--dry-run` performs the same bounded discovery, path validation, scope normalization, Git classification, and ownership verification as a real run. It writes nothing, creates no tombstone, and deletes nothing. Its output says what would be removed, preserved, acknowledged, or blocked. A dry run with blockers exits 3; an operationally unreliable preview exits 1. + +`--include-tracked` permits structural removal from tracked project configuration after ordinary identity checks. It never permits whole-file deletion or weakens ownership checks. -`cargo agents status` reports a present but inactive project hook and says to run `cargo agents sync`. The hook itself remains silent. Init, sync, installation, configuration, and per-agent documentation must explain the per-checkout activation rule. +`--acknowledge ` is an evidence-preserving transfer of responsibility, not a force delete. It records that the user accepts the reported artifact as user-owned, retires Symposium's claim, preserves the artifact, and prints a redacted fragment for manual removal. A later installation treats the occupied location as a collision rather than reclaiming it. -A managed ID is non-secret and can safely travel in a committed project configuration. Copying it grants no authority because the receiving machine has neither a matching receipt nor a local activation permit. Multiple local clones may share an ID; each root has an independent permit entry. +Acknowledgement cannot produce a ready assessment while the preserved artifact contains an unguarded invocation of `cargo-agents`, or is an MCP server that directly launches it. Such a live binary reference must be removed manually or with `--include-tracked`; otherwise deleting the binary would recreate the original failure. -### Legacy adoption and cleanup +Exit codes are: -Older installations have no managed IDs or receipts. The migration policy is conservative: -- ordinary init or sync adopts only an exact historical Symposium registration and rewrites it to the guarded, ID-bearing form; -- a legacy hook invocation without an ID may run only after verifying its exact on-disk historical registration; -- uninstall can remove an exact historical fragment directly, even if no migration occurred; and -- prefix matches, similar commands, malformed structures, and partially edited entries are preserved and reported. +| Code | Meaning | +| ---- | ----------------------------------------------------------------------------------------- | +| 0 | Planning or cleanup completed with no live blockers in the applicable assessment boundary | +| 1 | Operational failure prevented a reliable plan or verification | +| 2 | Command-line usage error, retaining Clap's conventional exit code | +| 3 | Preview or cleanup completed, but one or more live blockers remain | -Exact historical signatures are versioned test fixtures, not broad substring searches. This prevents a third-party hook that also invokes Cargo or uses a similar key from being mistaken for Symposium state. + +`--quiet` suppresses progress but not errors or the final assessment. The existing global `--json` option emits one versioned document on stdout; diagnostics remain on stderr. + +### Bounded discovery + +Uninstall never crawls the user's home directory or disks. It examines only: + +1. known global configuration targets for supported adapters; +2. the current workspace when one is explicitly available; +3. roots named by ownership receipts; +4. targets or roots named by project permits, notice records, or retirement tombstones; +5. legacy workspace-state files that already contain a root; and +6. fixed Symposium-private directories. + +Legacy coverage is qualified. Today `WorkspaceState::workspace_root` is written by hook-triggered auto-sync, not by every manual `cargo agents sync`. Historical state therefore improves discovery but is not a complete inventory. The command reports this limitation when pre-receipt versions may have created unrecorded project integrations. + +Deleted, moved, or renamed roots cost only a failed bounded lookup. An existing root at a new path becomes a separate scope after `cargo agents sync`. Confirmed-absent records are pruned only during successful finalization. ### Cleanup algorithm -Apply mode performs these phases while holding the installation-wide mutation lock: +The real command follows this order: + +1. Acquire the exclusive uninstall barrier described below. +2. Load receipts, project permits, global tombstones, notice and acknowledgement records, the signature catalog, and bounded legacy roots. +3. Discover candidate artifacts without mutating them. +4. Classify every candidate as removable, already absent, preserved, acknowledged, conflicting, or operationally unverifiable. +5. Build and print the complete plan. +6. For each removable external artifact, perform one ordered transaction: mark only that receipt `retiring`, retire its project permit or create its global tombstone; re-read and revalidate the target under its target lock; perform the narrow removal; verify absence; then retain the receipt with its completed disposition. +7. If any blocker or operational failure remains, retain all discovery-bearing workspace state, receipts, project permits, global tombstones, notices, caches, logs, and telemetry needed for repair or a rerun. +8. Only when every external integration is absent or validly acknowledged, finalize Symposium-private cache, workspace state, telemetry, and logs. +9. Verify private finalization and recompute the assessment. +10. Delete completed receipts, project permits, global tombstones, notice records, acknowledgements, and the now-empty managed-state directory. +11. Release locks and print the result. + +A failure before external mutation restores that artifact to `applied` when its registration still matches, republishing a project permit or deleting a global tombstone as appropriate. A crash after retirement leaves only that artifact inactive and repairable. Successful removals are not rolled back. `cargo agents sync` restores a still-applied retiring registration; rerunning uninstall resumes removal. + +`--dry-run` executes steps 2 through 5 and the same read-only classification used by verification. It takes the installation barrier in shared mode, the managed-state lock in shared mode, and sorted shared locks for every discovered target. It writes no recovery state and performs no finalization. + +Within an external configuration file, cleanup normally uses read, parse, validate, edit only the owned structure, write a sibling temporary file, flush, atomically replace, reopen, and verify. Goose YAML uses the verified marker-delimited block editor described above and never reserializes the surrounding file. If a file changes between read and replacement, the operation replans that target rather than overwriting the concurrent edit. + +Transient filesystem failures receive one initial attempt and at most two bounded retries. Each retry reopens and revalidates the target. Permission failures, ownership conflicts, indeterminate Git state, unsafe links, and content changes are blockers rather than retry loops. -1. Load and validate receipts and the exact legacy signature catalog. -2. Discover known global and workspace locations. -3. Read each target and classify it as owned, already absent, external, or blocked. -4. Build and report a deterministic plan. -5. Mark owned hook receipts and permits as retiring so cached registrations become inert. -6. Remove owned structural fragments and generated artifacts, rechecking ownership immediately before each write. -7. Remove private caches, logs, telemetry events and identifiers, and obsolete workspace state. -8. Verify results, retire successful or absent receipts, and aggregate blockers. +### Concurrency and locks -Configuration entries are removed structurally. Shared JSON, JSONC, TOML, or other host configuration files are never deleted just because Symposium owned one nested entry. +One installation-wide mutex would put routine hook auto-sync behind a long uninstall. Instead, managed mutation uses: -`--dry-run` performs the same discovery, parsing, classification, and ownership verification under the same operation lock, but makes no persistent writes. It prints `Would remove` instead of `Removed`. It exits unsuccessfully when apply mode would encounter a blocker, so automation can use it as a preflight rather than treating it as an alternative cleanup operation. +- a shared/exclusive installation barrier; +- a shared/exclusive managed-state lock; +- a shared/exclusive global-target lock for shared agent configuration; and +- one shared/exclusive target lock per normalized workspace root. -### Concurrency, retries, and crash recovery +Manual init, sync, and repair take the installation barrier in shared mode, then the managed-state and target locks they modify in exclusive mode. Dry-run takes the barrier, managed-state lock, and discovered target locks in shared mode, which prevents it from observing a target mid-mutation. Uninstall takes the installation barrier exclusively for the whole plan-mutate-verify interval, then takes the managed-state and target locks it mutates exclusively. -Init, sync, managed status repair, and uninstall share one OS-backed lock for installation-wide mutation. Uninstall holds it through planning, mutation, and verification. Ordinary permit lookup on the hook hot path is lock-free. A persistent hook-side repair, if required, acquires the same lock and revalidates before writing. +Hook-triggered auto-sync uses a non-blocking try-lock. On contention it skips that cache refresh and allows the hook to continue from already-published state; auto-sync is not a correctness prerequisite. -Agent configuration remains writable by external programs. Every shared-config mutation therefore uses a conservative read-modify-recheck-atomic-replace sequence. A changed file or transient filesystem error is retried with short exponential backoff: one initial attempt and at most two retries. Parse errors, unsupported schemas, invalid receipts, ownership conflicts, and unsafe paths are not transient and are not retried. +The complete order is installation barrier, managed-state lock, global-target lock when needed, then workspace-target locks sorted by normalized path. A dry-run that discovers a target set, acquires those read locks, and then sees a changed managed-state generation retries the snapshot once; a second change is an operational failure rather than an inconsistent preview. -Successful changes are not rolled back when another target fails. Rerunning the command observes removed artifacts as absent and resumes receipts left in `retiring`. This is safer than attempting a broad rollback that could overwrite concurrent user or agent changes. +Lock metadata contains a version, operation, process identifier, and start time for diagnostics. Liveness, not age alone, determines whether a lock is stale. Platform implementations use native advisory locking and are covered by multi-process tests. -### Filesystem and receipt safety +### Filesystem safety -Receipt data is untrusted input. Cleanup validates schema versions, artifact kinds, path boundaries, structural locators, and managed IDs before acting. It never executes a command from a receipt, follows a receipt-provided arbitrary path, logs secret-bearing values, or requests elevated privileges. +Receipts are untrusted input even though Symposium wrote them. Before mutation, cleanup validates: -Managed state is created with user-private permissions where the platform supports them. Cleanup affects only the current user's Symposium and agent configuration locations. +- supported schema version and artifact kind; +- strict UUID and enum forms; +- normalized containment below an allowlisted adapter or Symposium-private root; +- expected file-versus-directory shape; +- component-wise ancestor relationships; +- link policy for the artifact type; and +- current identity evidence. -Symlinks and junctions use artifact-specific rules: +Cleanup refuses to traverse a symlink or junction while deleting a generated tree. If a managed path has been replaced by a link, it is preserved and reported. A generated directory is removed only when its manifest accounts for every remaining entry. -- A shared configuration file may be edited through a symlink only when the logical path is a known agent configuration path and it still resolves to the same target identity recorded at registration. -- Atomic replacement updates the resolved configuration target without replacing the symlink itself. -- A changed target is preserved and reported as a conflict. -- Generated files and directories are never traversed through symlinks or junctions. -- A generated path that has been replaced by a link is preserved and reported. -- A generated directory is removed only when its manifest accounts for its contents; unknown contents prevent directory deletion. +### Minimal startup and telemetry finalization -### Minimal uninstall startup +`uninstall` dispatches before ordinary startup. It may initialize only argument parsing, managed-state path resolution, minimal diagnostics, locking, the cleanup engine, and final reporting. It does not refresh registries, load plugins, run update checks, auto-sync, or initialize normal telemetry recording. -The current binary initializes ordinary logging and state, refreshes registries, and may check for or install an update before dispatching most commands. `uninstall` must be selected before that startup path. +Telemetry finalization uses the telemetry subsystem's supported coordination path. If telemetry cannot be finalized, cleanup retains discovery and recovery state and reports a blocker; it does not silently claim completion. The final uninstall result itself is not recorded as new telemetry. -The uninstall path may parse the CLI, resolve the active configuration directory, acquire its operation lock, and initialize cleanup-specific reporting. It must not: +### Reporting and binary-removal assessment -- stamp ordinary `state.toml`; -- fetch or refresh plugin registries; -- auto-update or re-execute the binary; -- initialize normal telemetry recording; or -- emit an uninstall telemetry event. +Human output groups: -Cleanup removes telemetry event and metric files, pending sets, local identifiers or cohort state, and telemetry-private locks or state. It preserves the telemetry preference in `config.toml`. A telemetry lock that remains busy after the retry budget is a blocker rather than permission to leave data behind silently. +- `Removed`: artifacts verified absent during this run; +- `Already absent`: recorded artifacts already gone; +- `Preserved`: user, shared, tracked, unsupported, or ambiguous state left untouched; +- `Acknowledged`: artifacts whose ownership the user explicitly accepted; +- `Blocked`: live references or failures that prevent the stated assessment; and +- `Next steps`: exact commands or redacted manual edits. -### Human and machine output +The assessment is an enum: -Human output is an action audit, not a count-only summary. It groups entries by scope, then agent, then artifact kind: + +| Value | Meaning | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | +| `ready` | No live Symposium integration remains in every recorded and inspectable scope, and no historical-discovery limitation applies | +| `ready-for-known-scopes` | No live integration remains in known scopes, but pre-receipt project scopes may be unrecorded | +| `blocked` | At least one live binary reference, ownership conflict, or operational verification failure remains | + + +`ready` is decided from a durable coverage-origin field created by the first receipt-aware release. Before ordinary startup overwrites `state.toml.version`, migration reads that previous semver and records exactly one of: + +- `managed-only`: the configuration and managed store are first created by a receipt-aware release and initial bounded discovery finds no existing Symposium integration signature or legacy state; +- `pre-receipt`: the previous version predates receipts or initial discovery finds an exact legacy artifact; or +- `unknown`: provenance is missing or corrupt for a nonempty existing configuration, or an integration signature exists without its expected receipt-aware provenance. + +The origin is never promoted automatically. Only `managed-only` can produce `ready`; `pre-receipt` and `unknown` produce at best `ready-for-known-scopes`. The existing last-touched version stamp is useful only at first migration because later startup replaces it. + +The corresponding human lines are: ```text -Removed - Global - Claude hooks - ~/.claude/settings.json: Symposium hook entries - Workspaces - /work/example - Codex skills - .agents/skills/example-skill - -Preserved - ~/.symposium/config.toml: user configuration - cargo-binstall: shared Cargo tool - -Cleanup complete. It is safe to remove the Symposium binary. -Next: restart running coding agents, then run `cargo uninstall symposium`. +No remaining live Symposium integrations in recorded scopes. ``` -Partial failure uses `Not removed` for blockers, prints the reason and a rerun instruction, and says not to remove the binary yet. `--quiet` suppresses successful detail but still prints blockers and the final verdict. +or: -`--json` writes one versioned document to stdout. Catastrophic diagnostics that prevent creating the document go to stderr. Its top-level fields are: +```text +No remaining Symposium integrations in known scopes. +Older unrecorded project integrations may still exist; see the preserved items above. +``` + +The RFD intentionally avoids “It is safe to remove the Symposium binary,” because uninstall cannot prove the absence of an unknown pre-receipt checkout. + +JSON output contains: ```text schema_version mode -outcome -safe_to_remove_binary +binary_removal_assessment actions preserved +acknowledgements blockers -next_step +next_steps ``` -`mode` is `apply` or `dry-run`; `outcome` is `ready`, `incomplete`, or `preview`. Each action carries a disposition, scope, adapter when applicable, artifact kind, safe display path, managed ID when available, and reason. Non-blocking legacy-coverage notices appear in `preserved` with a machine-readable reason. Counts may be derived by consumers; a count-only summary does not replace the action list. +Every item includes a stable kind, adapter, scope, target, structural locator where applicable, disposition, reason code, and whether it remains a live reference. Secret-bearing fields are redacted. The old boolean `safe_to_remove_binary` is not part of the schema. + +### Performance and cost -### Performance contract +The hook preflight performs no directory-wide scan, network access, registry refresh, plugin loading, Cargo metadata query, or subprocess. It reads one directly addressed project permit or checks one global tombstone path, normalizes the process working directory, and performs depth-bounded reads and signature checks of candidate adapter configurations to find the nearest registration-owning root. -The receipt system is not on most command hot paths. Hook permit validation is the exception and has an explicit budget: steady-state p95 added latency must be no more than the larger of 2 ms or 5% of the existing hook startup baseline on the same machine. +Guarded hooks may become the default only while p95 **added** preflight latency is no more than `max(2 ms, 5% of baseline hook-dispatch latency)`. The baseline is the same generated outer guard and no managed-state preflight on the same host and local filesystem. CI records p50 and p95 for active, inactive, missing-store, and nested-checkout cases on Linux, macOS, and Windows with small and large receipt stores. Store size must not change the number of hot-path reads or path probes. -Benchmarks cover p50 and p95 across supported operating systems and varying receipt counts. The hot path opens the permit file by managed ID and checks the current scope; its work does not grow with the total number of receipts. +The design adds these costs: -### Cost and operational impact -The design adds costs in five places: +| Area | Cost and bound | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| Storage | One receipt per integration, one permit per active project registration, short-lived global tombstones, and bounded notice records | +| Hook startup | One direct state lookup, path normalization, and bounded adapter-configuration signature checks | +| Generated configuration | A one-line absent-binary guard and managed ID; scope is not trusted from command text | +| Project workflow | One explicit `cargo agents sync` after a clone, move, or new container | +| Implementation | State schemas, identity adapters, Goose block editing, safe writers, lock hierarchy, recovery, catalog maintenance, and failure-injection tests | -| Area | Cost and bound | -|---|---| -| Storage | One small receipt per logical managed integration plus a derived permit per hook ID. Growth is linear in recorded integrations and checkouts; no project contents are copied. Retired and confirmed-absent records are removed. | -| Hook startup | One direct permit-file lookup and scope comparison before ordinary startup, bounded by the latency contract above. | -| Generated configuration | Hook command strings gain a static binary-existence guard and `--managed-id `. This is visible in agent configuration but does not change the event or payload delivered to hooks. | -| User workflow | A copied, cloned, or moved project-scoped registration needs one explicit `cargo agents sync` on that machine before it becomes active. | -| Implementation and maintenance | The first implementation adds a state machine, safe atomic writers, platform locking, adapter ownership checks, legacy migration, and failure-injection tests. Later features reuse existing artifact types automatically; only a genuinely new side-effect type needs new ownership logic. | -Receipts disclose local workspace paths to anyone who can already read the user's private Symposium configuration directory. Those paths are not telemetry, are never uploaded, and receive the same restrictive permissions as the rest of managed state. +Receipt growth is linear in recorded integrations and checkouts. Confirmed-absent records are removed at successful finalization. Long-lived receipts disclose normalized local project paths only to principals already able to read the private Symposium configuration directory. They are never telemetry or uploaded. + +Maintaining the static signature catalog is an intentional compatibility cost: every change to a released generated static form adds a fixture rather than replacing old evidence. Dynamic plugin MCP instances do not grow that catalog; their non-secret fingerprints live in receipts. + +### Test strategy + +The feature extends the existing deterministic integration harness; it does not wait for or require the separate agent-interaction test redesign. + +Unit and property tests cover: + +- schema evolution, unknown versions, corrupt and truncated state; +- path normalization across macOS aliases, Windows verbatim prefixes and case behavior; +- traversal, symlink, junction, and manifest attacks; +- receipt lifecycle and crash points; +- exact current and historical signatures; +- dynamic fingerprints with secret fields excluded; and +- assessment and exit-code mapping. + +Adapter contract fixtures cover every supported global and project representation, both Copilot shell forms, exact one-line host serialization, absent binaries, stripped `PATH`, apostrophes and shell metacharacters in paths, launch failures, real error propagation, and payload preservation. + +Integration tests use isolated fake homes, Cargo homes, Git repositories, and workspaces. They cover: + +- global and project init, sync, dry-run, uninstall, rerun, and repair; +- empty or unavailable stores that leave global hooks live, plus global retirement tombstones that disable them; +- clone, move, delete, multiple clones, unrelated nested adapter configuration, nested hostile checkouts, new container semantics, non-matching roots, and refusal of home or filesystem-root permits; +- one-time inactive SessionStart hints without plugin execution; +- tracked, untracked, read-only, externally modified, and concurrently rewritten settings; +- projects without a `.git` ancestor when Git is not installed, and repositories whose tracking query is unavailable; +- Goose marker-block removal with preserved surrounding comments and formatting; +- missing receipts, missing permits, corrupt stores, lost private state, and legacy signatures; +- managed-only, pre-receipt, and unknown coverage origins; +- interrupted cleanup at every lifecycle boundary; +- target-lock contention, shared dry-run snapshots, blocker exit code 3, and hook auto-sync try-lock behavior; +- status snapshots before, during, and after an exclusive uninstall; +- acknowledgement, finalization, structural collision on reinstall, and `--include-tracked`; +- locked telemetry and finalization failures; and +- the original stale-global-hook regression after binary removal. + +Tests assert filesystem and parsed configuration state, retained recovery evidence, stdout, stderr, JSON schema, and exit status. Failure-injection runs prove that discovery state survives every non-final result. ### Documentation changes -Implementation updates: +The implementation updates: -- the installation guide with the staged removal workflow; -- the `uninstall`, `sync`, `status`, configuration, and global command references; -- every relevant agent page with its generated guard and project activation behavior; -- hook, state, module-structure, important-flow, and telemetry design chapters; and -- init and sync output to state that a copied or moved project needs one local sync. +- the command reference with the quit-clean-remove-restart workflow; +- init and sync output to explain that a cloned, moved, or containerized project needs local `cargo agents sync`; +- status documentation for inactive, retiring, corrupt, and unavailable permit states; and +- hook, state, module-structure, important-flow, and telemetry design chapters. -The proposed user-facing command reference is [Proposed: `cargo agents uninstall`](./proposed-cargo-agents-uninstall.md). The ownership and checkout behavior is described in [Proposed: managed integrations](./proposed-managed-integrations.md). +The proposed command reference is `[cargo agents uninstall](./proposed-cargo-agents-uninstall.md)`. The proposed ownership and activation model is [managed integrations](./proposed-managed-integrations.md). ## Frequently asked questions ### Why can Cargo not perform this cleanup? -Cargo tracks installed package binaries and removes those binaries. Symposium's external effects live in agent-specific configuration and workspace paths that Cargo neither owns nor understands. The staged workflow lets Symposium clean its domain while its binary still exists, then lets Cargo remove the package it owns. +Cargo tracks installed package binaries. Symposium's external effects live in agent configuration and workspace paths that Cargo neither owns nor understands. Symposium cleans its domain while its binary exists; Cargo then removes the package it owns. -### Why not make `cargo agents implode` remove the binary too? +### Why must users quit agents first? -A running process cannot portably remove its installed package, restart its parent coding agent, and still provide reliable recovery and diagnostics. Package removal also belongs to Cargo. Keeping the operations separate provides a clear checkpoint: cleanup succeeds, the user restarts agents, then Cargo removes the binary. +Some agents cache settings and write them on exit. Cleaning while one is running creates a race in which the agent can resurrect a verified-absent entry after its receipt has been finalized. Quitting first removes that writer; starting only after Cargo removal reloads the final state. -### Why keep durable state for years? +### Does a copied managed ID activate a hostile clone? -Workspace paths may be needed years later to remove registrations safely. Receipts are small metadata records, contain no project contents or secrets, and do not affect a project's build. They are refreshed when a workspace syncs and removed when uninstall confirms their targets are gone. Expiring them by age would discard the only ownership evidence without proving that an integration disappeared. +Not for a project registration. Preflight finds the nearest adapter configuration that owns the invoked registration and requires its normalized root to equal the permit root exactly. A nested checkout therefore does not inherit its parent's permit. The payload's ID is never authority by itself. A global registration remains global, so this RFD makes no hostile-repository claim for global-hook users. -### What if a project was moved or deleted? +### What if the managed-state directory is deleted? -Sync from the new location records that location. During uninstall, a missing recorded path counts as already absent and its stale receipt is retired. A moved managed checkout that was never synced again cannot be discovered without an unsafe filesystem scan, but its guarded hook quietly exits after the binary is removed. A legacy unguarded checkout that was never recorded is the transition exception described under discovery boundaries; it must first be rediscovered or handled manually. +Project hooks become inactive because their positive permits are gone. Global hooks continue to run because only a positive retirement tombstone disables them; this avoids silently breaking the recommended global workflow after an empty store is recreated. Global startup records degraded health when possible. The next explicit sync can reconstruct static state from exact released signatures; a dynamic entry whose receipt was lost remains preserved because resemblance is not proof. -### Does the first managed release remove every project hook created by older releases? +### Why may an inactive SessionStart say anything? -It removes legacy hooks in global locations, the current workspace, and roots recoverable from historical workspace state. It cannot prove that an unrecorded checkout elsewhere on disk does not contain an old project hook. The report calls out this limited legacy coverage, and the upgrade documentation tells project-scope users to sync dormant checkouts they still use before removing Symposium. This is a transition limitation; registrations created or migrated under this design are recorded and guarded. +If the whole hook returned silently, the very SessionStart that once auto-synced a fresh checkout could never explain how to activate it. The inactive branch emits only static context naming `cargo agents sync`; it cannot load workspace plugins or execute their code. A per-root notice record suppresses the hint after it has been shown once. -### What does `--dry-run` prove? +### Why do legacy hooks run unguarded? -It proves what the command can determine at that moment: the known locations were discovered, current contents were parsed, ownership checks were applied, and blockers were identified. It does not clean anything or guarantee that an external program will not change a file afterward. Apply mode repeats the checks immediately before mutation. +They run unguarded today. Verifying their on-disk project registration on every event would require locating the project and add a new failure mode. Exact historical signatures are instead used during bounded migration and cleanup. -### How are hooks from another program distinguished? +### What if receipts are lost? -New registrations carry a stable managed ID and type-specific command identity tied to a receipt. Legacy cleanup requires an exact historical Symposium structure and command. A matching event name, key prefix, or reference to Cargo is not enough. Ambiguity is preserved as a conflict. +Released static signatures still identify current and historical static hook forms at known locations. Dynamic plugin MCP entries are preserved without their receipt because Symposium cannot reconstruct arbitrary instance identity safely. Deleting private state before uninstall therefore reduces discovery, but it does not authorize guesses. -### Will every future feature need uninstall-specific code? +### Why are both receipts and signatures needed? -Not when it uses an existing managed artifact type. The central writer injects identity and receipts automatically. A new kind of external side effect needs one ownership adapter because safe deletion rules differ between a nested config entry, a generated directory, and a package-manager installation. +Receipts make discovery bounded, especially for projects that no longer exist. Signatures or dynamic fingerprints prove that the current structure is still the artifact Symposium wrote. Neither role substitutes safely for the other. -### Are receipts a new security risk? +### What happens after a project moves? -They create a small new parser and state store, so they require defensive handling. The design bounds that cost: versioned minimal data, private permissions, no stored secrets, no executable receipt content, fixed path boundaries, stable UUID validation, atomic writes, and tests for traversal and link attacks. A forged receipt cannot authorize arbitrary deletion because type-specific verification and allowlisted roots are also required. +The old permit does not match the new root, so plugin dispatch is inactive. SessionStart explains the one-time `cargo agents sync`. Sync records the new normalized root and permit; stale roots remain cheap receipt entries until reconciled or successfully finalized. -### Why preserve globally installed tools? +### Why are containers separate permit environments? -Tools installed into shared locations such as Cargo's binary directory may be used independently of Symposium or by another program. Symposium removes its integrations and reports those tools, but ownership is not exclusive enough to uninstall them. Private copies acquired into Symposium's cache are removed with that cache. +The container can have a different user, configuration home, executable, and path view. Treating a host path as equivalent to a container path would make scope matching ambiguous. Local sync is explicit and cheap. -### What happens to arbitrary installation scripts? +### Can a partial uninstall disable a working installation? -Legacy `install_commands` can perform effects outside any declared boundary. Symposium does not run guessed uninstall commands or replay commands from receipts. Known untracked setup is reported as preserved and does not block binary removal after all live Symposium integrations are gone. A future RFD can add declarative, receipt-backed actions such as managed links or copies. +Only the artifact currently being removed is retired. A project permit is retired or a global tombstone is created immediately before that artifact's mutation. If mutation fails, its matching receipt remains repairable and `cargo agents sync` restores it. Other registrations remain active. Status names the repair command. ### Why is there no `--force`? -Forcing deletion through failed ownership checks would erase the safety property this command exists to provide. The first version reports the exact conflict so the user can resolve it or remove it manually. A later override would need a narrower, evidence-based contract. +A force that bypasses identity checks would permit deletion of another program's state. `--acknowledge` instead gives every resolvable blocker a terminating path: preserve it, transfer ownership explicitly, and show the exact manual edit. Live unguarded binary references still block removal because preserving them would reproduce the bug this command exists to prevent. + +### Why preserve tracked project files by default? + +A developer may not intend to change configuration committed for the whole team. Default preservation makes that repository-level effect visible. `--include-tracked` is the explicit authorization to remove only the proven Symposium structure. + +### Why preserve shared tools? + +A tool in Cargo's binary directory may be used independently or by another program. Symposium removes its integrations and reports the tool, but only private copies below Symposium's own cache are exclusively owned. + +### What happens to arbitrary installation scripts? + +Legacy `install_commands` may create effects outside declared boundaries. Uninstall does not replay guessed inverse commands or execute content from a receipt. Known effects are reported as preserved; a future design may add declarative, receipt-backed actions for managed links or copies. + +### Are receipts a security risk? + +They add a parser and local state store, so they are validated as untrusted data. Receipts are versioned, minimal, non-executable, secret-free, atomically written, privately permissioned, and constrained by allowlisted roots and artifact-specific proof. A forged receipt alone cannot authorize deletion or project execution. + +### Will every future hook, skill, or MCP feature need cleanup code? + +Not when it uses an existing managed artifact type. The central mutation layer injects IDs, receipts, signatures or fingerprints, and lifecycle behavior. A new kind of external side effect needs an adapter because its deletion proof is genuinely new. -### Can a partial failure leave the system worse? +### What assessment should automation trust? -Before removing hook configuration, uninstall retires its permit, making a surviving guarded command a quiet no-op. Successful removals remain complete, failed receipts remain resumable, and the command gives a nonzero result with an explicit warning not to remove the binary yet. +Automation should inspect `binary_removal_assessment` and the exit status. `ready` covers recorded and inspectable scopes, `ready-for-known-scopes` preserves the pre-receipt limitation, and `blocked` means a live reference or unverifiable operation remains. None claims knowledge of an undiscoverable checkout. ## Implementation plan -1. **Introduce managed-state primitives without changing generated integrations.** Add versioned receipt and permit schemas, lifecycle transitions, the installation-wide lock, safe-path validation, and atomic storage. Cover schema evolution, corrupt state, permissions, symlinks/junctions, traversal attempts, crash injection, and concurrent writers with unit and property tests. -2. **Route existing managed writes through the ownership layer.** Refactor hook, MCP, skill, generated-file, cache, and workspace-state mutations to declare their artifact type. Add collision behavior, exact legacy signatures, pending/applied recovery, and adapter contract tests. This step must preserve current user-visible behavior while beginning to publish receipts. -3. **Add guarded hooks and local activation permits.** Generate stable IDs and adapter-specific guards, add the minimal pre-startup permit lookup, teach sync to activate a checkout, and teach status to report inactive copied hooks. Test missing binaries, missing/corrupt/retiring permits, clones, moved roots, multiple clones, shell quoting, legacy migration, real error propagation, and the p50/p95 latency budget. -4. **Build the cleanup planner and engine.** Implement bounded discovery, ownership classification, structural removal, retries, partial progress, dry-run, receipt retirement, private-state cleanup, and telemetry-specific coordination. Test every removal/preservation row, absent paths, unknown contents, external modifications, locked files, interrupted cleanup, and idempotent reruns. -5. **Expose `cargo agents uninstall` through minimal startup and reporting.** Add CLI parsing, human grouping, `--quiet`, the versioned JSON document, exit semantics, and safe/unsafe next steps. Integration tests use isolated fake homes and workspaces and assert exact state as well as output. -6. **Complete platform integration and documentation.** Exercise global and project scope across all supported adapters on Linux, macOS, and Windows, including the original stale-global-hook regression. Update the user and design documentation listed above. These tests extend the existing deterministic integration harness and do not depend on replacing it with the separate agent-interaction suite. +1. **Identity and path primitives.** Define normalized paths, managed IDs, trusted scope classification, exact registration-owning roots, stable blocker-ID derivation, static signatures, dynamic fingerprints, and assessment types. Test platform aliases, case behavior, links, nested checkouts, forbidden roots, acknowledgement invalidation, and every current and historical static registration. +2. **Durable state and lock hierarchy.** Add versioned receipt, project-permit, global-tombstone, notice, coverage-origin, health, and acknowledgement schemas; the read-only managed-health snapshot API; lifecycle recovery; atomic storage; the shared/exclusive installation barrier; managed-state and target locks; and crash/concurrency tests. +3. **Route managed writers.** Refactor hook, MCP, skill, generated-file, cache, and workspace-state writes through the central layer without changing behavior. Implement Goose's marker-delimited YAML block editor. Add structural adapter collisions, post-acknowledgement reinstall collisions, tracked-file classification, pending/applied recovery, formatting preservation, and secret-redaction tests. +4. **Guards and preflight.** Publish exact one-line serialized shell fixtures, record machine-local executable paths, implement positive project permits, positive global retirement tombstones, and bounded signature-based degraded classification, resolve exact registration-owning roots, add one-time inactive SessionStart context, migrate exact legacy signatures on sync, and enforce the numeric latency budget. +5. **Planner, command, and reporting.** Add bounded discovery, shared-lock dry-run, `--include-tracked`, stable acknowledgement, human grouping, versioned JSON through the existing global `--json`, decidable coverage assessments, distinct preview/apply exit codes, and minimal startup. +6. **Ordered cleanup and finalization.** Implement per-artifact project retirement or global tombstone creation, type-specific structural or Goose block mutation, two bounded retries, verification, repair through sync, blocker evidence retention, telemetry coordination, and private-state finalization last. +7. **Status, adapter, platform, and documentation completion.** Connect the shared health snapshot to the existing status command without duplicating its CLI or presentation layer. Exercise status/uninstall concurrency and global and project scope for every supported adapter on Linux, macOS, and Windows, including stripped `PATH`, tracked repositories, interruption, concurrent agents, and the stale-hook regression. For every adapter that supports project-scoped registrations, verify that hooks launched from both the checkout root and nested directories receive a working directory inside that checkout; an adapter that cannot establish this contract must not offer project-scoped guarded registrations. Update the user and design documentation named above. -Each step is independently reviewable and includes its tests. No implementation step requires a rewrite of the existing integration-test infrastructure. +Each step is independently reviewable and includes tests. The work extends the current deterministic integration harness and does not require rewriting it. ## Implementation status diff --git a/md/rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md b/md/rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md index e5272e19..d2cb3d2f 100644 --- a/md/rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md +++ b/md/rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md @@ -1,132 +1,163 @@ # Proposed: `cargo agents uninstall` -Remove the agent integrations and local data managed by Symposium before uninstalling the Symposium package. +Remove agent integrations and private local data managed by Symposium before removing the Symposium package. ## Usage ```console -cargo agents uninstall [--dry-run] +cargo agents uninstall [--dry-run] [--include-tracked] + [--acknowledge ]... + [--quiet] [--json] ``` -The global `--quiet` and `--json` options are also supported. - -The command cleans all known global and project scopes. It can run inside or outside a Cargo workspace and does not prompt. +The command examines all known global and recorded project scopes. It may run inside or outside a Cargo workspace and never scans the entire filesystem. ## Recommended removal workflow -```console -$ cargo agents uninstall -# Restart any running coding agents. -$ cargo uninstall symposium -``` +1. Quit all running coding agents that may have loaded Symposium configuration. +2. From an ordinary terminal, run: + + ```console + cargo agents uninstall + ``` + +3. Resolve or acknowledge reported blockers and rerun as directed. +4. Run: + + ```console + cargo uninstall symposium + ``` -Do not remove the package when `cargo agents uninstall` reports an incomplete cleanup. Resolve the listed blockers and rerun it. +5. Start the coding agents again. -Restarting matters because a running agent may have cached its hook configuration. Symposium cannot safely restart the agent process that launched or contains the current terminal. +Quitting first prevents an agent from writing a cached settings file after cleanup removes its entry. Starting after package removal makes agents reload the cleaned configuration. ## What it removes When ownership can be verified, the command removes: -- Symposium hook registrations from supported agent configurations; -- Symposium MCP server registrations; -- generated and mirrored skill copies; -- generated files and directories; +- Symposium hook registrations; +- Symposium-managed MCP server entries; +- generated or mirrored skills; +- dedicated generated files and directories; - private plugin and installation caches; -- logs, telemetry events and identifiers, and other private runtime state; -- workspace state and hook activation permits; and -- ownership receipts after their artifacts are absent. +- telemetry, logs, and private runtime state; and +- receipts, project permits, global retirement tombstones, and notice records after external cleanup succeeds. -The command checks the active global configuration locations, the current workspace, previously recorded workspaces, and exact locations used by older Symposium versions. It does not search the entire filesystem. +The command uses known global locations, ownership receipts, project permits, tombstones, the current project when available, and recorded historical roots. It removes only the managed entry from a shared configuration file. Goose YAML uses verified marker-delimited block removal so surrounding comments and formatting remain unchanged. ## What it preserves The command preserves: -- Symposium's `config.toml`, including plugin declarations and telemetry preference; -- user-authored custom plugin sources; -- agent configuration that is not owned by Symposium; -- entries another program has replaced or whose ownership is ambiguous; -- shared tools installed into locations such as Cargo's binary directory; and -- side effects from arbitrary legacy installation scripts that cannot be identified safely. +- `config.toml`, including plugin declarations and telemetry preferences; +- custom plugin sources and user-authored skills; +- entries changed or replaced by another program; +- shared tools installed through Cargo or another package manager; +- effects of arbitrary legacy installation scripts that cannot be identified safely; +- tracked project configuration unless `--include-tracked` is supplied; and +- every artifact whose ownership or target safety is ambiguous. -Preserved external tools and known untracked setup are shown in the report. For upgrades from a pre-receipt version, the report also identifies the possibility of an unknown legacy project checkout when detectable. An ownership conflict is a blocker; a deliberately preserved shared tool or legacy-coverage notice is not. +Private caches and discovery state are retained when cleanup has blockers so a rerun or `cargo agents sync` can repair the installation. -## Output +## Dry run -A successful cleanup lists concrete actions and ends with the next steps: +`--dry-run` takes a consistent shared-lock snapshot and performs the same bounded discovery, path validation, Git classification, and ownership verification as cleanup without writing or deleting anything. It reports `Would remove`, `Preserved`, and `Blocked` items. Blockers return status 3; an unreliable preview returns status 1. -```text -Removed +```console +$ cargo agents uninstall --dry-run +Would remove Global Claude hooks ~/.claude/settings.json: Symposium hook entries - Workspaces - /work/example - Codex skills - .agents/skills/example-skill - Symposium state - telemetry events - plugin cache Preserved ~/.symposium/config.toml: user configuration - cargo-binstall: shared Cargo tool -Cleanup complete. It is safe to remove the Symposium binary. -Next: restart running coding agents, then run `cargo uninstall symposium`. +Preview complete. Apply with `cargo agents uninstall`. ``` -If cleanup is incomplete, a `Not removed` section identifies every blocker. Successful removals are retained; rerunning the command safely skips artifacts that are already absent. +Apply mode rechecks every target because an agent or another program may change configuration after the preview. -`--quiet` hides successful detail but retains blockers and the final verdict. +## Tracked files and acknowledgements -## Dry run +A hook inside a Git-tracked project file is preserved by default and reported as “committed by your project.” Use `--include-tracked` to authorize removal of the verified Symposium entry. The surrounding file and unrelated entries remain untouched. -`--dry-run` performs discovery, reads current state, and applies the same ownership checks as cleanup without making persistent changes. Its report uses `Would remove`: +When no ancestor contains a `.git` file or directory, uninstall treats the project as outside Git and does not require a Git executable. -```console -$ cargo agents uninstall --dry-run -Would remove +`--acknowledge ` keeps an artifact but transfers responsibility for it to you. The ID is stable for the same artifact kind, adapter, normalized target, and structural location. The acknowledgement also records current identity, so changing the artifact invalidates it. The report prints its location and a redacted fragment for manual removal. This is not a force option and does not weaken ownership checks. + +An unguarded hook or MCP server that still launches `cargo-agents` cannot be acknowledged into a ready result. Remove that live reference before uninstalling the package. + +## Output + +A completed run reports what was actually removed: + +```text +Removed Global Claude hooks ~/.claude/settings.json: Symposium hook entries + Workspaces + /work/example + Codex skills + .agents/skills/example-skill Preserved ~/.symposium/config.toml: user configuration + cargo-binstall: shared Cargo tool -Preview complete. Apply with `cargo agents uninstall`. +No remaining live Symposium integrations in recorded scopes. +Next: run `cargo uninstall symposium`, then start your coding agents. +``` + +For an installation upgraded from a version that may have unrecorded projects, the final assessment instead says: + +```text +No remaining Symposium integrations in known scopes. +Older unrecorded project integrations may still exist; see the preserved items above. ``` -Dry run is a preflight, not an alternative to uninstall. It exits unsuccessfully if the real cleanup would have a known blocker. Apply mode rechecks files before changing them because another program may modify configuration after the preview. +The command never claims knowledge of a project it cannot discover. + +If cleanup is incomplete, `Blocked` identifies every live reference or operational failure and `Next steps` gives the exact rerun, sync, flag, acknowledgement, or manual edit. Completed removals remain complete. ## JSON output -`--json` writes one versioned document to stdout with: +The existing global `--json` option emits one versioned document containing: -- mode and outcome; -- whether it is safe to remove the binary; -- exact removed, would-remove, or already-absent actions; -- preserved items; +- `mode`; +- `binary_removal_assessment`: `ready`, `ready-for-known-scopes`, or `blocked`; +- exact actions and already-absent artifacts; +- preserved and acknowledged items; - blockers; and -- the next required step. +- next steps. -Paths and ownership reasons are included where safe. Secrets, configuration values, plugin source contents, and command output are not included. +Items include stable reason codes and whether they remain a live binary reference. Paths are included where useful; secret values and plugin source contents are not. ## Exit status -The command exits successfully only when apply mode has no cleanup blockers. In dry-run mode it exits successfully only when the preview finds no blocker. +| Status | Meaning | +|---|---| +| 0 | Preview or cleanup completed with no live blockers in its assessment boundary | +| 1 | An operational failure prevented reliable planning or verification | +| 2 | Command-line usage error | +| 3 | Preview or cleanup completed, but live blockers remain | -Missing artifacts are treated as already absent. Invalid receipts, unsafe paths, ownership conflicts, persistent file locks, unsupported configuration schemas, and failed removals produce a nonzero exit. +Missing artifacts are already absent. Invalid receipts, unsafe paths, ownership conflicts, indeterminate tracked state, persistent locks, unsupported schemas, and failed verification are not silently treated as success. -## Copied and moved workspaces +## Copied, moved, and containerized projects -Project hook registrations are locally inactive until their checkout has been synchronized. After cloning, copying, or moving a workspace that contains Symposium-generated project configuration, run: +Project registrations are inactive until their exact registration-owning root has been synchronized locally. A nested checkout cannot inherit a parent checkout's permit. After cloning, copying, moving, or opening a project in a new container, run: ```console -$ cargo agents sync +cargo agents sync ``` -This records the local workspace and activates its hooks. `cargo agents status` reports a present but inactive hook and recommends sync. A stale project hook remains quiet when it has no local activation record or when the Symposium binary is absent. +An inactive SessionStart may tell you to run that command once per root, but it does not load plugins or execute project-provided code. Other inactive hook events exit successfully without output. Global hook registrations remain global and do not gain an untrusted-project boundary from this mechanism. Deleting the managed store does not disable an exact verifiable global registration; only an uninstall retirement tombstone does. An ambiguous invocation does not run plugins. + +Registrations created before managed receipts and guards are a transition case. Uninstall can inspect the current project and roots retained in historical state, but older manual syncs did not always record a root. Before removing a pre-receipt installation, sync any known dormant project-scoped checkouts. + +## Interrupted cleanup -Registrations created before managed IDs and guards existed are a transition case. Uninstall finds the current workspace and roots retained in historical workspace state, and active legacy hooks migrate when they run. It cannot discover an old project hook in an otherwise unknown checkout without scanning the filesystem. Before removing a pre-receipt installation, sync any known project-scoped checkouts that have not been used recently; the uninstall report identifies limited legacy coverage when detectable. +If cleanup is interrupted, rerun `cargo agents uninstall` to resume. If you decide to keep using Symposium instead, run `cargo agents sync`; it repairs matching registrations left in a retiring state. `cargo agents status` reports corrupt, unavailable, degraded-global, inactive, or repairable state and names the next command. diff --git a/md/rfds/symposium-uninstall/proposed-managed-integrations.md b/md/rfds/symposium-uninstall/proposed-managed-integrations.md index 55f5b710..584c6a1c 100644 --- a/md/rfds/symposium-uninstall/proposed-managed-integrations.md +++ b/md/rfds/symposium-uninstall/proposed-managed-integrations.md @@ -1,53 +1,113 @@ # Proposed: managed integrations -Symposium writes derived state into coding-agent configuration and project directories when it initializes or synchronizes a workspace. Managed integrations make those writes identifiable, locally activatable, and removable without claiming ownership of the surrounding user files. +Symposium writes derived state into coding-agent configuration and project directories during init and sync. Managed integrations make those writes discoverable, identifiable, locally activatable, and removable without claiming ownership of surrounding user files. -## Ownership +## Three separate questions -Every managed integration receives a stable, non-secret ID. Symposium keeps a small ownership receipt in its configuration directory describing the artifact type, scope, location, and evidence required to recognize that integration again. +Managed state deliberately separates: -Receipts do not contain project source, complete agent configuration, plugin source contents, environment variables, tokens, or command output. They remain until the managed artifact is removed or confirmed absent; they do not expire merely because a workspace has not been opened recently. +1. **Discovery:** where should Symposium look? Ownership receipts and known adapter locations answer this. +2. **Identity:** is the current artifact one Symposium manages? Released static signatures, dynamic fingerprints, markers, and manifests answer this. +3. **Execution:** may this project hook run here? A root-bound activation permit answers this. -The receipt allows Symposium to remove its own nested hook or MCP entry while preserving the shared configuration file and adjacent entries from the user or another program. If the current state no longer matches the ownership evidence, Symposium reports a conflict instead of deleting it. +A managed ID connects records across those questions. It is non-secret, may appear in committed configuration, and grants neither deletion authority nor permission to execute by itself. -## Generated files and directories +## Ownership receipts -Generated skills and other dedicated output carry a marker and manifest tied to their receipt. Symposium removes only accounted-for content within known generated boundaries. +Every managed mutation receives a stable ID and a small receipt in Symposium's private configuration directory. The receipt records: -If a generated directory contains an unknown file, or a managed path has been replaced by a symlink or junction, cleanup preserves it and reports the conflict. User-authored skills and custom plugin sources are not generated output and are preserved. +- artifact type and adapter; +- global or project scope; +- normalized target and structural location; +- the non-secret identity evidence needed later; and +- lifecycle state: pending, applied, retiring, or acknowledged. -## Hook activation +Receipts never contain project source, plugin source contents, environment values, HTTP headers, tokens, or command output. They do not expire because a workspace has been idle. Old or missing paths use a small amount of storage and are removed only when cleanup can safely finalize. -Hook registrations carry their managed ID in the `cargo-agents` invocation. A small local activation permit lets the binary decide whether that ID may run in the current scope before loading plugins or inspecting the Cargo workspace. +Receipts make discovery bounded; they do not authorize deletion. A malformed or forged receipt still has to pass path containment, target-shape, link, and artifact-specific identity checks. -Global hooks have a local global permit. Project hooks have a permit for each locally synchronized checkout root. +## Identity evidence -The generated command is also guarded by the shell form supported by that agent. If the `cargo-agents` binary is missing, the command exits successfully without output. If the binary exists but no matching local permit exists, the hook also exits successfully without output. Genuine Symposium hook failures retain their output and exit status. +Static registrations emitted by released Symposium versions have versioned structural signatures. The catalog includes current and historical hook forms, dedicated generated files, and static built-in MCP entries. Receipts say where to inspect; signatures prove the current structure still matches a released form. + +Plugin-provided MCP entries are dynamic. At write time Symposium records a secret-free fingerprint containing the adapter, target, structural container, name, transport, and command-plus-arguments or URL. Environment and header values are excluded. If the non-secret identity changes or the receipt is lost, cleanup preserves the entry. + +Generated skills and other dedicated output use markers and manifests. Unknown contents, or a generated path replaced by a symlink or junction, prevent automatic deletion. + +Goose MCP entries use managed-ID comment markers because its YAML file cannot be reserialized without losing comments and formatting. Cleanup verifies the one marked block and removes its exact byte range; any marker, indentation, or fingerprint mismatch preserves it. + +All managed writers use one central mutation layer, so new hooks, skills, and MCP instances receive the appropriate evidence automatically. Only a new kind of external side effect needs a new ownership adapter. + +Init and sync inspect the current structural slot before writing. An occupied slot without matching pending or applied ownership evidence is a collision, even when its value resembles a released Symposium form. Explicit legacy migration is classified separately. + +## Project hook activation + +A project activation permit binds one managed ID to one normalized registration-owning root. Generated hooks carry the managed ID, but scope comes from trusted local state rather than command text. Before normal project hook startup, Symposium: + +1. obtains the process working directory without invoking Cargo; +2. walks its ancestors to a fixed depth for the nearest adapter project configuration containing an exact released registration for this managed ID; +3. normalizes that registration-owning root with the same function used by sync; and +4. continues only when the permit root equals that root exactly. + +Finding a permit for the ID, or finding only an ancestor match, is never sufficient. An unrelated nested adapter configuration is ignored; a nested checkout containing the copied registration resolves to its own root and cannot inherit a parent's permit. Sync refuses to permit the user's home or a filesystem root. + +If the project is inactive, SessionStart may emit static `additionalContext` telling the user to run `cargo agents sync`. It does not load workspace plugins, auto-sync, refresh registries, or execute project-provided code. A per-root notice record suppresses the hint after it has been shown once. Other inactive events exit successfully and silently. + +Global hooks have no positive activation permit. They run unless uninstall has written a positive retirement tombstone for that managed ID. An empty, deleted, or unavailable managed store therefore does not silently disable the recommended global workflow. A global hook still runs in every project, so tombstones do not make Symposium's global workflow a defense against hostile workspace configuration. + +## Binary guards and failure visibility + +Generated shell commands try the executable path resolved during machine-local registration, then fall back to `PATH`. Portable committed project forms try the conventional Cargo home first. If the binary is absent, the command exits with status 0 and no output. + +That outer guard handles only an absent executable. Its generated command is one line, carries the managed ID, and shell-escapes absolute paths including apostrophes. The binary then checks trusted local state before ordinary startup. If state is missing, it classifies scope only by finding one exact released registration signature at the nearest project or known global adapter target. Project failures deny dispatch; an exactly proven global registration continues and reports degraded health where possible; ambiguous invocations do not run plugins. + +`cargo agents status` consumes a read-only managed-health snapshot and reports inactive, retiring, corrupt, unavailable, degraded-global, and cleanup-in-progress states. It does not repair state while reading it. An explicit sync recreates corrupt derived permits from valid receipts and restores a matching registration interrupted during uninstall. ## After cloning, copying, or moving a project -A managed ID in a project file is not itself permission to execute. Run sync once from the new checkout: +Run once from the new checkout: ```console -$ cargo agents sync +cargo agents sync ``` -Sync verifies the registration, records the local root, and activates it. This prevents a copied or committed hook from becoming active merely because an agent opened the project. +Sync verifies the registration, records the normalized local root, and publishes its permit. Multiple clones may share a managed ID, but every root is activated separately. + +Moving a project does not rewrite an old permit implicitly. The moved checkout remains inactive until sync records its new root. A container, WSL environment, and Windows host are separate permit environments even when they expose the same repository. + +The old record remains small and inert until cleanup confirms that path is absent. Symposium never searches the filesystem for a moved project. -Until then, `cargo agents status` reports that the hook is present but inactive and recommends sync. The hook stays silent so stale configuration does not interrupt the agent workflow. +## Tracked project configuration -Multiple local clones may share the same managed ID. Each root is activated separately. +Some project hook files are intentionally committed for a team. Uninstall checks Git outside the hook path: -## Moving or deleting an old checkout +- when no ancestor has a `.git` file or directory, no Git executable is required; +- tracked configuration is preserved by default; +- `cargo agents uninstall --include-tracked` authorizes removal of only the verified Symposium entry; and +- unavailable or indeterminate Git tracking preserves the file as a blocker. -Syncing after a move records the new root. The old record remains small and inert until cleanup confirms its old path is absent. `cargo agents uninstall` then retires it. +`--acknowledge ` may transfer a preserved artifact to user ownership without deleting it. It cannot make an unguarded hook or direct MCP reference to `cargo-agents` safe for binary removal. -Symposium does not scan the filesystem to locate a project that moved without being synchronized again. A remaining managed registration is still harmless after removal of the binary because it contains the outer guard. +## Legacy registrations -An unrecorded project hook from a version that predates managed IDs is the transition exception: Symposium cannot guard a file it cannot locate. Active legacy hooks migrate automatically while the binary exists, historical workspace state recovers known roots, and users upgrading from an older version should sync any dormant project-scoped checkouts they still use before uninstalling. +Legacy hooks keep their existing runtime behavior. Symposium does not add on-event disk verification that would require locating a Cargo workspace. Exact historical signatures allow known registrations to migrate during init or sync and to be removed during uninstall. + +Historical workspace state recovers some old roots, but previous manual syncs did not always record `workspace_root`. An otherwise unknown pre-receipt project remains undiscoverable without a filesystem scan. The uninstall assessment states that limitation rather than claiming universal coverage. ## Removing Symposium -Run `cargo agents uninstall` while the binary still exists. It uses receipts and exact legacy signatures to remove known managed integrations, private state, and generated data. It preserves user configuration and anything it cannot prove it owns. +First quit all agents that may rewrite cached settings. Then run: + +```console +cargo agents uninstall +``` + +Cleanup retires one artifact immediately before removing it: it retires a project permit or creates a global tombstone. It verifies each removal and keeps its receipt until the entire run can finalize. If any blocker remains, discovery state and private caches stay available for a rerun or repair. + +After the command reports no live integrations in its stated scope, run: + +```console +cargo uninstall symposium +``` -After successful cleanup, restart running coding agents and run `cargo uninstall symposium`. +Then start the agents again. If cleanup was interrupted and Symposium should remain installed, `cargo agents sync` restores registrations whose receipts still match. From cddc53ef9e892a40b5fcb3edb42e62eba5c28030 Mon Sep 17 00:00:00 2001 From: Awesome Rustacean Date: Thu, 20 Aug 2026 18:44:54 +0300 Subject: [PATCH 3/4] docs(rfd): split uninstall design into focused chapters Co-authored-by: Codex --- md/SUMMARY.md | 7 +- md/rfds/symposium-uninstall/README.md | 654 +++--------------- .../README.md} | 4 +- .../cleanup-engine/README.md | 203 ++++++ .../hook-activation/README.md | 183 +++++ .../README.md} | 4 +- .../symposium-uninstall/ownership/README.md | 180 +++++ 7 files changed, 680 insertions(+), 555 deletions(-) rename md/rfds/symposium-uninstall/{proposed-cargo-agents-uninstall.md => cargo-agents-uninstall/README.md} (97%) create mode 100644 md/rfds/symposium-uninstall/cleanup-engine/README.md create mode 100644 md/rfds/symposium-uninstall/hook-activation/README.md rename md/rfds/symposium-uninstall/{proposed-managed-integrations.md => managed-integrations/README.md} (98%) create mode 100644 md/rfds/symposium-uninstall/ownership/README.md diff --git a/md/SUMMARY.md b/md/SUMMARY.md index 51554d13..8a18ca3a 100644 --- a/md/SUMMARY.md +++ b/md/SUMMARY.md @@ -97,8 +97,11 @@ - [User-managed plugins](./rfds/registry-centric-plugins/user-managed-plugins/README.md) - [Predicate caching](./rfds/predicate-caching/README.md) - [Managed Symposium uninstall](./rfds/symposium-uninstall/README.md) - - [Proposed: `cargo agents uninstall`](./rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md) - - [Proposed: managed integrations](./rfds/symposium-uninstall/proposed-managed-integrations.md) + - [Ownership and managed state](./rfds/symposium-uninstall/ownership/README.md) + - [Hook activation](./rfds/symposium-uninstall/hook-activation/README.md) + - [Cleanup engine](./rfds/symposium-uninstall/cleanup-engine/README.md) + - [`cargo agents uninstall`](./rfds/symposium-uninstall/cargo-agents-uninstall/README.md) + - [Managed integrations](./rfds/symposium-uninstall/managed-integrations/README.md) - [Completed](./rfds/completed.md) - [Configuration parsing and normalization](./rfds/config-normalization/README.md) - [RFD Process](./rfds/rfd-process/README.md) diff --git a/md/rfds/symposium-uninstall/README.md b/md/rfds/symposium-uninstall/README.md index e373eba1..9f1b5997 100644 --- a/md/rfds/symposium-uninstall/README.md +++ b/md/rfds/symposium-uninstall/README.md @@ -1,626 +1,178 @@ -# Managed Symposium uninstall +# Managed Symposium uninstall ## TL;DR -- Add `cargo agents uninstall` as an idempotent cleanup command that runs before `cargo uninstall symposium`. -- Ask users to quit every running agent before cleanup, then restart agents only after the binary has been removed. -- Record Symposium-owned writes in durable receipts. Use receipts to discover artifacts and adapter-specific evidence to prove their identity. -- Give project hook registrations stable IDs and root-bound activation permits. A permit for an ID alone is never enough to run a project hook. -- Use positive permits for project hooks and positive retirement tombstones for global hooks. Losing the managed store must not disable the recommended global workflow. -- Preserve user configuration, custom plugin sources, shared tools, tracked project configuration by default, and every ambiguous artifact. -- Report removed, preserved, acknowledged, and blocked items, followed by a qualified binary-removal assessment. +- Add `cargo agents uninstall` to remove Symposium-managed integrations before `cargo uninstall symposium` removes the package. +- Ask users to quit running agents before cleanup and restart them after Cargo removes the binary. +- Record managed writes in durable receipts, then verify current identity before deleting anything. +- Make generated hooks harmless when `cargo-agents` is absent and coordinate hook retirement during cleanup. +- Preserve user-authored, shared, tracked, and ambiguous state by default. +- Report what was removed or preserved and qualify the final removal assessment. ## Motivation -`cargo uninstall symposium` knows which package binaries Cargo installed. It does not know that Symposium also wrote hook registrations, MCP server entries, generated skills, cache data, or workspace state. +`cargo uninstall symposium` removes the package binaries Cargo owns. It does not know that Symposium also wrote agent hooks, MCP entries, generated skills or plugin packages, caches, and workspace state. -Today an unguarded hook registration can survive removal of the `cargo-agents` binary. Every later hook event then asks the shell to run a command that no longer exists: +An unguarded hook can therefore survive the binary: ```text /usr/bin/bash: line 1: cargo-agents: command not found PostToolUse:Bash hook error ``` -This is especially visible after a global installation because the stale registration runs in every project. Project-scoped installation creates a different problem: cleanup cannot find arbitrary historical workspaces by scanning the user's filesystem, and a similar-looking entry may belong to another program. +A stale global hook produces this error in every project. Project-scoped cleanup has a different problem: Symposium cannot scan arbitrary disks to rediscover every workspace, and it must not delete a similar-looking entry that another program or the user owns. -The cleanup contract therefore needs durable discovery evidence, type-specific identity checks, bounded project lookup, and generated commands that become harmless when their binary is absent. +The command needs bounded discovery, current ownership proof, safe hook retirement, and recovery from interrupted cleanup. -## User workflow +## As a user -The recommended sequence is: +The recommended workflow is: -1. Quit every running Claude, Gemini, Copilot, Codex, Kiro, Goose, OpenCode, or other agent process that may have loaded Symposium-managed configuration. -2. From an ordinary terminal, run `cargo agents uninstall`. -3. Resolve or acknowledge any reported blockers and rerun until the command reports no live integrations in the applicable scopes. +1. Quit every agent process that may have loaded Symposium-managed configuration. +2. Run `cargo agents uninstall` from an ordinary terminal. +3. Resolve or acknowledge reported blockers and rerun until no live integration remains in the applicable scopes. 4. Run `cargo uninstall symposium`. -5. Start the agents again. +5. Restart the agents. -Quitting first avoids an agent rewriting a settings file from its in-memory copy after cleanup has verified and removed an entry. Restarting after binary removal makes every agent reload the cleaned configuration. +```console +$ cargo agents uninstall +Removed + claude global hook + codex project skills in /work/reporter -`cargo agents uninstall` does not remove its own executable. A running process cannot portably and reliably remove the package that supplied it, and Cargo already owns package removal. - -## Goals - -This RFD aims to: - -- remove integrations Symposium can prove it manages in global and recorded project scopes; -- remove Symposium-private files only after external integrations are absent or explicitly transferred to the user; -- preserve user-authored and third-party state; -- make interrupted cleanup resumable; -- make copied, moved, and deleted projects safe without filesystem-wide scanning; -- keep the ordinary hook path bounded and free of Cargo subprocesses; -- give humans and automation precise, non-overclaiming results; and -- make new instances of existing managed artifact types participate automatically. - -## Non-goals - -This RFD does not: - -- make a global hook safe to run arbitrary untrusted workspace configuration; -- discover every project ever touched by a pre-receipt Symposium release; -- uninstall shared packages from Cargo or another package manager; -- reverse arbitrary plugin `install_commands`; -- delete user configuration or custom plugin sources; or -- restart or terminate agent processes. - -## Threat model and security boundary - -Receipts, project permits, and global retirement tombstones primarily provide deterministic cleanup, collision resistance, and a local activation boundary for **project-scoped registrations**. They are not capabilities and they are not secrets. - -A project hook may run only when its managed ID and exact registration-owning root match one active permit. Copying a committed ID to another checkout grants no authority because that checkout owns a different configuration path. The hot path resolves the owning root from the process working directory and adapter-relative configuration path; it does not invoke Cargo. - -A **global** hook is intentionally valid in every working directory. Symposium currently recommends global hook installation, and ordinary hook startup may discover and load workspace plugin configuration. Therefore this design does not claim that permits protect global-hook users from a hostile repository. Changing that trust model would require gating workspace plugin activation itself and belongs in a separate security design. - -The uninstall safety invariant is narrower: - -> Symposium deletes external state only when bounded discovery evidence and artifact-specific identity evidence both agree that Symposium manages it. - -## Detailed plans - -### Terminology and identity - -- A **managed ID** is a stable UUID for one logical registration. All event entries belonging to one agent-and-scope hook registration share that ID. -- An **ownership receipt** is durable discovery evidence describing one intended managed mutation, its target, adapter, scope, and lifecycle. -- A **static signature** is a versioned, secret-free structural description of a registration form emitted by a released Symposium version. -- A **dynamic fingerprint** is a receipt-recorded, secret-free identity for an instance whose command or URL came from plugin configuration. -- An **activation permit** is a small positive record for a project hook. It binds a managed ID to exactly one normalized registration-owning root. -- A **retirement tombstone** is a small positive record that temporarily disables a global hook while its registration is being removed. -- A **blocker ID** is a stable, domain-separated hash of artifact kind, adapter, normalized target, and structural locator. - -A managed ID is a correlation key, not proof of ownership and not permission to execute. Deletion always requires the expected target and type-specific identity. Project execution always requires a root match. - -A blocker ID is `blk_` followed by the lowercase hexadecimal first 16 bytes of SHA-256 over the domain `symposium-blocker-v1` and length-prefixed UTF-8 fields for artifact kind, adapter, normalized target, and canonical adapter-specific structural locator. The acknowledgement stores the full tuple rather than trusting the truncated display ID alone. - -### Path and scope identity - -Symposium defines one normalization function for receipt targets, permit roots, and runtime comparison: - -1. make the path absolute; -2. canonicalize the existing portion of the path; -3. remove the Windows verbatim-path prefix when present; -4. apply platform filesystem case rules on Windows; and -5. compare path components rather than string prefixes. - -Every generated invocation carries `--managed-id `, but it does not carry an authoritative scope. The binary classifies scope from the directly addressed local receipt, project permit, or global tombstone. An ID or scope-like text in the command is never sufficient. - -When local state for the ID is missing or unavailable, preflight enters a bounded degraded classifier. It uses the same registration-owning-root walk defined below and checks the adapter's known global target for an exact released registration signature containing that ID. Exactly one match is required: a project match remains inactive and may show the one-time sync hint; a global match runs and reports degraded health; zero or multiple matches deny plugin dispatch and report ambiguity. This fallback opens only those adapter files and does not invoke Cargo. - -For a project registration, the hook preflight starts at the process working directory and performs a depth-bounded ancestor walk. The **registration-owning root** is the nearest ancestor whose adapter project configuration contains an exact released registration signature for this managed ID. Candidate configuration files without that registration are ignored. Execution requires all of: - -1. trusted local state or exact degraded classification identifies project scope; -2. the nearest registration-owning root exists; -3. the active permit has the same managed ID; and -4. the normalized permit root equals the normalized registration-owning root exactly. - -The permit root being merely an ancestor of the working directory is not enough. An unrelated nested adapter configuration is ignored, while a nested checkout containing a copied matching registration resolves to its own nearer root and is denied. Sync refuses to create a project permit whose root is a filesystem root or the user's home directory. - -Adapters must establish the documented working directory before invoking a project hook. An adapter that cannot provide that contract cannot use a project-scoped guarded registration. - -Resolved paths, not inode or file IDs, identify targets. This permits a dotfile repository to be recloned without creating an unrecoverable inode conflict. A dev container, WSL environment, and Windows host are separate permit environments even when they expose the same repository through different paths; each requires `cargo agents sync`. - -An acknowledgement stores the same locator tuple plus the artifact's identity fingerprint at acknowledgement time. It remains stable across identical reruns, but a moved locator produces a new blocker ID and changed artifact identity invalidates the old acknowledgement. - -### Cleanup boundary - -The boundary is based on ownership, not on whether a path or command contains the word `symposium`. - - -| Artifact | Default cleanup behavior | Required identity evidence | -| ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -| Static hook registration | Remove its owned entries or dedicated file | Receipt for discovery plus a released static signature | -| Dynamic plugin MCP registration except Goose | Remove its owned structural entry | Receipt plus an exact dynamic fingerprint | -| Goose dynamic MCP registration | Remove one verified marker-delimited YAML block without reserializing surrounding YAML | Receipt, unique marker pair, indentation, and exact dynamic fingerprint | -| Static built-in MCP registration | Remove its owned structural entry | Receipt or bounded legacy discovery plus a released static signature | -| Generated skill or mirror | Remove generated files; remove the directory only when its manifest accounts for every entry | Receipt, marker, and manifest | -| Dedicated generated file | Remove the file | Receipt plus marker or released content signature | -| Symposium-private cache, logs, telemetry, receipts, permits, tombstones, notices, and workspace state | Remove only during finalization | Fixed private root and successful external cleanup | -| `config.toml` | Preserve | User-owned configuration | -| Custom plugin source | Preserve | User-authored or user-selected source | -| Tracked project configuration | Preserve unless `--include-tracked` is given | Git tracking plus ordinary artifact identity | -| User or third-party entry near a managed entry | Preserve | Symposium evidence is absent or conflicts | -| Cargo-installed shared tool | Preserve and report | Package-manager location is not exclusively owned | -| Side effect of arbitrary `install_commands` | Preserve and report when known | The effect cannot be reconstructed safely | -| Unknown pre-receipt checkout | Not automatically discoverable | No recorded root | - - -Preserving `config.toml` retains preferences and plugin declarations, but removing their derived hook, MCP, and skill integrations stops Symposium affecting the agents. A future installation may reuse those preferences only after an explicit sync. - -### Central managed mutations - -All code that writes externally visible agent state goes through one managed-mutation layer. Callers declare an artifact type and desired value. The layer supplies: - -- IDs, receipts, and lifecycle transitions; -- adapter-specific identity evidence; -- collision detection by structurally inspecting the current adapter slot: init and sync do not adopt or overwrite an occupied slot without matching `pending` or `applied` ownership evidence, and signature-catalog migration is a separate explicit path; -- safe target validation and path containment; -- structural read-modify-write behavior or Goose's verified byte-extent block edit; -- atomic file replacement; and -- the corresponding cleanup operation. - -Adding another hook, skill, or MCP server through an existing artifact type therefore does not require uninstall-specific code. A genuinely new side-effect type needs a new ownership adapter because its proof and safe deletion rules differ. - -### Receipt lifecycle - -Receipts live below a versioned managed-state directory in the resolved Symposium configuration home. They contain paths and identity metadata, never executable instructions, tokens, environment values, or header values. - -A receipt moves through: - -1. `pending`: intent is durable, but the external write is not yet confirmed; -2. `applied`: the external artifact matches; -3. `retiring`: this artifact is being removed or has an interrupted removal; and -4. `acknowledged`: the user accepted responsibility for the preserved artifact. - -Writers store `pending` before the external mutation, verify the mutation, then store `applied`. Recovery reconciles incomplete states by inspecting the target; it never assumes a write succeeded. - -For project hooks, the derived positive permit is published last. A project registration cannot become active before its receipt and external write are durable. Global hooks have no positive permit: they run unless a retirement tombstone for their managed ID is present. A completed receipt remains available until the whole uninstall reaches finalization, so a running agent, crash, or blocker cannot erase the only discovery evidence. - -`cargo agents sync` is the explicit restore path after interrupted cleanup. When the receipt still describes an applied registration, sync changes a repairable `retiring` state back to `applied`, republishes a project permit, or removes a global retirement tombstone as appropriate. `cargo agents status` reports the repairable state and the exact sync command. - -### Static signature catalog - -Symposium maintains a versioned catalog of every released **static** registration form, including current forms rather than only legacy forms. Receipts answer “where should cleanup look?”; signatures answer “is the structure at that location one Symposium released?” - -The catalog covers: - -- static hook commands and their containing structural shape; -- dedicated generated agent files; -- static built-in MCP registrations; and -- generated file markers and manifests where applicable. - -Signatures compare parsed structure, normalized executable identity, fixed arguments, and managed ID placement. They do not match on a broad key name, event name, or the presence of `cargo-agents` alone. - -The hidden hook CLI accepts an optional managed ID. Its presence selects the new preflight; its absence selects legacy behavior. Scope comes from trusted local state or exact degraded classification, never from the invocation. Legacy hooks continue to run exactly as they do today and are not put behind a new runtime verification step. The next `init` or `sync` may migrate an exact historical signature to the guarded form, and uninstall may remove an exact historical signature from a known location. - -### Dynamic MCP identity - -Plugin-provided MCP commands, URLs, arguments, and names are not a finite released signature catalog. Their identity is captured when the managed mutation is written. - - -| Adapter | Structural container | -| -------------------- | ------------------------------ | -| Claude, Gemini, Kiro | `mcpServers.` | -| GitHub Copilot | Top-level MCP server map entry | -| Codex | `mcp_servers.` | -| Goose | `extensions.` | -| OpenCode | `mcp.` | - - -The fingerprint includes the adapter, normalized target, structural container, entry name, transport, and command-plus-arguments or URL. It deliberately excludes environment and header values so receipts do not copy secrets. Removal requires the current entry to match every non-secret identity field; changing one transfers the entry out of automatic cleanup and produces a blocker. - -If a dynamic MCP receipt is missing, Symposium preserves the entry. A name and command resemblance are not upgraded into proof. Future schemas may reserve a Symposium metadata field or namespace where the host permits one, but this RFD does not assume such a field exists. - -Goose is an explicit editing exception. Its YAML configuration is not round-tripped through a general serializer because doing so would discard comments and user formatting. New Symposium blocks are enclosed by behavior-neutral managed-ID comment markers. Cleanup locates one unique marker pair at the recorded indentation, parses only the enclosed mapping to verify its dynamic fingerprint, and removes that exact byte extent. Missing markers, duplicate markers, invalid indentation, or any fingerprint mismatch preserve the block. Supporting this verified block editor and its malformed-YAML fixtures is a distinct implementation cost. - -### Git-tracked project configuration - -Project hook registrations may live in files a team intentionally commits. Before planning a project mutation, uninstall asks Git whether the containing file is tracked. This query is outside the hook hot path. - -- Uninstall first walks ancestors for a `.git` file or directory without launching Git. If none exists, ordinary ownership rules apply and Git need not be installed. -- A tracked file is preserved by default and reported as “committed by your project.” -- `--include-tracked` authorizes structural removal of the proven Symposium entry, not deletion or wholesale rewriting of the file. -- Only when a `.git` ancestor exists does uninstall query tracking. If Git is then unavailable or returns an indeterminate result, the file is preserved as a blocker. - -The report includes the file and structural locator, whether the integration remains live, and a redacted fragment the user can remove or review. It never prints secret values. - -### Activation permits and hook preflight - -Project and global hooks deliberately fail in opposite directions: - -- Project scope is positive-permit: missing, corrupt, unavailable, or non-matching state denies plugin dispatch. -- Global scope is positive-retirement: only a present retirement tombstone disables dispatch. Missing or unavailable managed state does not make the recommended global installation silently inert. - -The binary classifies preflight before ordinary startup: - - -| Trusted classification and state | Behavior | -| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -| Project permit with exact managed ID and registration-owning root | Continue to ordinary hook startup | -| Project permit missing, corrupt, unavailable, or non-matching | Do not load plugins or auto-sync; SessionStart may emit one bounded activation or repair hint, other events exit successfully and silently | -| Global retirement tombstone present | Exit successfully and silently | -| Global retirement tombstone present but corrupt | Exit successfully without plugin dispatch and surface a repair warning | -| Global registration proven by receipt or exact degraded signature, no tombstone | Continue to ordinary hook startup and report degraded health when local state was missing | -| Missing state with zero or multiple exact registration matches | Deny plugin dispatch and surface an ambiguity warning | - - -An inactive project SessionStart may perform only preflight and emit static `additionalContext` naming `cargo agents sync`. It may not read workspace plugin configuration, refresh a registry, run plugin code, or auto-sync. After the first successful hint for a managed ID and normalized registration-owning root, preflight atomically writes a small notice-suppression record. That root receives no further activation hint unless sync activates it or local managed state is reset. Notice records are capped at 64 roots per managed ID; after the cap, new roots receive neither another stored record nor a repeated hint, and status reports the suppressed count. - -Missing project state that means “correctly inactive” is quiet outside the one-time SessionStart hint. Failure to determine project state receives a repair hint. Missing global state continues to run only after the bounded exact global registration check succeeds, because global retirement, not global activation, is the only property this RFD needs. - -When the managed-state directory is writable, corrupt project state or missing global state records a health flag for `cargo agents status` and one best-effort log line. When the store itself is unavailable, no persistent flag can be written by definition: status detects the failure on demand, and a bounded SessionStart warning is the only guaranteed signal. Symposium does not invent a second fallback state directory. Corrupt records inside a writable store are quarantined and recreated by the next explicit sync. - -### Integration with `cargo agents status` - -The uninstall work does not add or own a second status command. It exposes a read-only managed-health snapshot from the managed-state layer for the existing or concurrently developed `cargo agents status` command to consume. - -The snapshot contains versioned data for inactive, retiring, corrupt, unavailable, and cleanup-in-progress states, with stable reason codes and recommended commands. Presentation, filtering, and the rest of status output remain owned by the status command. - -Status never repairs receipts, permits, or tombstones as a side effect of reading them. It attempts the installation barrier and managed-state lock in shared mode: - -- when acquired, it reads one consistent snapshot; -- while uninstall owns the exclusive barrier, it reports `cleanup-in-progress` from lock metadata and does not inspect half-mutated targets; and -- if state is corrupt or unreadable, it returns a diagnostic snapshot rather than panicking. - -The shared types live below the CLI layer. This lets either branch merge first without adding a duplicate `Commands::Status` variant, output schema, or lock implementation. A compatibility test invokes status before, during, and after uninstall to prove both commands coexist. - -### Generated outer guards - -The outer shell guard has one job: an absent binary must produce exit code 0 and no output. Scope-aware permit or tombstone preflight remains inside the binary. - -Machine-local global registrations record the absolute resolved `cargo-agents` path at registration time and try it first. If that path is absent, they fall back to `PATH`. A committed project registration cannot contain another user's absolute path, so its portable template tries the Cargo home convention and then `PATH`. - -The fixtures below are the exact single-line command values before the host's JSON, TOML, or YAML serializer escapes them. The versioned POSIX machine-local global template is: - -```sh -if [ -x ]; then exec hook --managed-id ; elif command -v cargo-agents >/dev/null 2>&1; then exec cargo-agents hook --managed-id ; else exit 0; fi -``` - -The versioned portable POSIX project template is: - -```sh -if [ -x "${CARGO_HOME:-$HOME/.cargo}/bin/cargo-agents" ]; then exec "${CARGO_HOME:-$HOME/.cargo}/bin/cargo-agents" hook --managed-id ; elif command -v cargo-agents >/dev/null 2>&1; then exec cargo-agents hook --managed-id ; else exit 0; fi -``` - -The versioned PowerShell machine-local global template is: - -```powershell -$symposiumBin = ; if (-not (Test-Path -LiteralPath $symposiumBin -PathType Leaf)) { $symposiumCommand = Get-Command cargo-agents -CommandType Application -ErrorAction SilentlyContinue; if ($null -eq $symposiumCommand) { exit 0 }; $symposiumBin = $symposiumCommand.Source }; $global:LASTEXITCODE = $null; & $symposiumBin hook --managed-id ; if ($null -eq $LASTEXITCODE) { exit 1 }; exit $LASTEXITCODE -``` - -The versioned portable PowerShell project template is: - -```powershell -$cargoHome = $env:CARGO_HOME; if ([string]::IsNullOrWhiteSpace($cargoHome)) { $cargoHome = Join-Path $HOME '.cargo' }; $symposiumBin = Join-Path $cargoHome 'bin/cargo-agents.exe'; if (-not (Test-Path -LiteralPath $symposiumBin -PathType Leaf)) { $symposiumCommand = Get-Command cargo-agents -CommandType Application -ErrorAction SilentlyContinue; if ($null -eq $symposiumCommand) { exit 0 }; $symposiumBin = $symposiumCommand.Source }; $global:LASTEXITCODE = $null; & $symposiumBin hook --managed-id ; if ($null -eq $LASTEXITCODE) { exit 1 }; exit $LASTEXITCODE -``` - -The POSIX literal encoder wraps the path in single quotes and replaces each embedded apostrophe with the shell sequence `'\''`. The PowerShell literal encoder wraps the path in single quotes and doubles every embedded apostrophe. The placeholders above are already-encoded literals, never raw paths. - -Copilot publishes and tests both shell forms. Adapter fixtures are versioned as part of the static signature catalog. Fixtures assert the decoded one-line command value and the exact raw host serialization. Every fixture test also asserts: - -- absent absolute candidate and stripped `PATH` produce exit 0 and empty output; -- absolute paths are encoded with the adapter's shell-literal encoder rather than raw placeholder substitution; -- paths containing spaces, apostrophes, quotes, and shell metacharacters are quoted correctly; -- a present but unlaunchable executable produces a nonzero result even when `LASTEXITCODE` was previously unset or zero; -- a present binary receives the event, payload, managed ID, and working directory unchanged; and -- the binary's real nonzero status propagates. - -### Command surface - -```text -cargo agents uninstall [--dry-run] [--include-tracked] - [--acknowledge ]... - [--quiet] [--json] -``` - -`--dry-run` performs the same bounded discovery, path validation, scope normalization, Git classification, and ownership verification as a real run. It writes nothing, creates no tombstone, and deletes nothing. Its output says what would be removed, preserved, acknowledged, or blocked. A dry run with blockers exits 3; an operationally unreliable preview exits 1. - -`--include-tracked` permits structural removal from tracked project configuration after ordinary identity checks. It never permits whole-file deletion or weakens ownership checks. - -`--acknowledge ` is an evidence-preserving transfer of responsibility, not a force delete. It records that the user accepts the reported artifact as user-owned, retires Symposium's claim, preserves the artifact, and prints a redacted fragment for manual removal. A later installation treats the occupied location as a collision rather than reclaiming it. - -Acknowledgement cannot produce a ready assessment while the preserved artifact contains an unguarded invocation of `cargo-agents`, or is an MCP server that directly launches it. Such a live binary reference must be removed manually or with `--include-tracked`; otherwise deleting the binary would recreate the original failure. - -Exit codes are: - - -| Code | Meaning | -| ---- | ----------------------------------------------------------------------------------------- | -| 0 | Planning or cleanup completed with no live blockers in the applicable assessment boundary | -| 1 | Operational failure prevented a reliable plan or verification | -| 2 | Command-line usage error, retaining Clap's conventional exit code | -| 3 | Preview or cleanup completed, but one or more live blockers remain | - - -`--quiet` suppresses progress but not errors or the final assessment. The existing global `--json` option emits one versioned document on stdout; diagnostics remain on stderr. - -### Bounded discovery - -Uninstall never crawls the user's home directory or disks. It examines only: - -1. known global configuration targets for supported adapters; -2. the current workspace when one is explicitly available; -3. roots named by ownership receipts; -4. targets or roots named by project permits, notice records, or retirement tombstones; -5. legacy workspace-state files that already contain a root; and -6. fixed Symposium-private directories. - -Legacy coverage is qualified. Today `WorkspaceState::workspace_root` is written by hook-triggered auto-sync, not by every manual `cargo agents sync`. Historical state therefore improves discovery but is not a complete inventory. The command reports this limitation when pre-receipt versions may have created unrecorded project integrations. - -Deleted, moved, or renamed roots cost only a failed bounded lookup. An existing root at a new path becomes a separate scope after `cargo agents sync`. Confirmed-absent records are pruned only during successful finalization. - -### Cleanup algorithm - -The real command follows this order: - -1. Acquire the exclusive uninstall barrier described below. -2. Load receipts, project permits, global tombstones, notice and acknowledgement records, the signature catalog, and bounded legacy roots. -3. Discover candidate artifacts without mutating them. -4. Classify every candidate as removable, already absent, preserved, acknowledged, conflicting, or operationally unverifiable. -5. Build and print the complete plan. -6. For each removable external artifact, perform one ordered transaction: mark only that receipt `retiring`, retire its project permit or create its global tombstone; re-read and revalidate the target under its target lock; perform the narrow removal; verify absence; then retain the receipt with its completed disposition. -7. If any blocker or operational failure remains, retain all discovery-bearing workspace state, receipts, project permits, global tombstones, notices, caches, logs, and telemetry needed for repair or a rerun. -8. Only when every external integration is absent or validly acknowledged, finalize Symposium-private cache, workspace state, telemetry, and logs. -9. Verify private finalization and recompute the assessment. -10. Delete completed receipts, project permits, global tombstones, notice records, acknowledgements, and the now-empty managed-state directory. -11. Release locks and print the result. - -A failure before external mutation restores that artifact to `applied` when its registration still matches, republishing a project permit or deleting a global tombstone as appropriate. A crash after retirement leaves only that artifact inactive and repairable. Successful removals are not rolled back. `cargo agents sync` restores a still-applied retiring registration; rerunning uninstall resumes removal. - -`--dry-run` executes steps 2 through 5 and the same read-only classification used by verification. It takes the installation barrier in shared mode, the managed-state lock in shared mode, and sorted shared locks for every discovered target. It writes no recovery state and performs no finalization. - -Within an external configuration file, cleanup normally uses read, parse, validate, edit only the owned structure, write a sibling temporary file, flush, atomically replace, reopen, and verify. Goose YAML uses the verified marker-delimited block editor described above and never reserializes the surrounding file. If a file changes between read and replacement, the operation replans that target rather than overwriting the concurrent edit. - -Transient filesystem failures receive one initial attempt and at most two bounded retries. Each retry reopens and revalidates the target. Permission failures, ownership conflicts, indeterminate Git state, unsafe links, and content changes are blockers rather than retry loops. - -### Concurrency and locks - -One installation-wide mutex would put routine hook auto-sync behind a long uninstall. Instead, managed mutation uses: - -- a shared/exclusive installation barrier; -- a shared/exclusive managed-state lock; -- a shared/exclusive global-target lock for shared agent configuration; and -- one shared/exclusive target lock per normalized workspace root. - -Manual init, sync, and repair take the installation barrier in shared mode, then the managed-state and target locks they modify in exclusive mode. Dry-run takes the barrier, managed-state lock, and discovered target locks in shared mode, which prevents it from observing a target mid-mutation. Uninstall takes the installation barrier exclusively for the whole plan-mutate-verify interval, then takes the managed-state and target locks it mutates exclusively. - -Hook-triggered auto-sync uses a non-blocking try-lock. On contention it skips that cache refresh and allows the hook to continue from already-published state; auto-sync is not a correctness prerequisite. - -The complete order is installation barrier, managed-state lock, global-target lock when needed, then workspace-target locks sorted by normalized path. A dry-run that discovers a target set, acquires those read locks, and then sees a changed managed-state generation retries the snapshot once; a second change is an operational failure rather than an inconsistent preview. - -Lock metadata contains a version, operation, process identifier, and start time for diagnostics. Liveness, not age alone, determines whether a lock is stale. Platform implementations use native advisory locking and are covered by multi-process tests. - -### Filesystem safety - -Receipts are untrusted input even though Symposium wrote them. Before mutation, cleanup validates: - -- supported schema version and artifact kind; -- strict UUID and enum forms; -- normalized containment below an allowlisted adapter or Symposium-private root; -- expected file-versus-directory shape; -- component-wise ancestor relationships; -- link policy for the artifact type; and -- current identity evidence. - -Cleanup refuses to traverse a symlink or junction while deleting a generated tree. If a managed path has been replaced by a link, it is preserved and reported. A generated directory is removed only when its manifest accounts for every remaining entry. - -### Minimal startup and telemetry finalization - -`uninstall` dispatches before ordinary startup. It may initialize only argument parsing, managed-state path resolution, minimal diagnostics, locking, the cleanup engine, and final reporting. It does not refresh registries, load plugins, run update checks, auto-sync, or initialize normal telemetry recording. - -Telemetry finalization uses the telemetry subsystem's supported coordination path. If telemetry cannot be finalized, cleanup retains discovery and recovery state and reports a blocker; it does not silently claim completion. The final uninstall result itself is not recorded as new telemetry. - -### Reporting and binary-removal assessment - -Human output groups: - -- `Removed`: artifacts verified absent during this run; -- `Already absent`: recorded artifacts already gone; -- `Preserved`: user, shared, tracked, unsupported, or ambiguous state left untouched; -- `Acknowledged`: artifacts whose ownership the user explicitly accepted; -- `Blocked`: live references or failures that prevent the stated assessment; and -- `Next steps`: exact commands or redacted manual edits. - -The assessment is an enum: - - -| Value | Meaning | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | -| `ready` | No live Symposium integration remains in every recorded and inspectable scope, and no historical-discovery limitation applies | -| `ready-for-known-scopes` | No live integration remains in known scopes, but pre-receipt project scopes may be unrecorded | -| `blocked` | At least one live binary reference, ownership conflict, or operational verification failure remains | - - -`ready` is decided from a durable coverage-origin field created by the first receipt-aware release. Before ordinary startup overwrites `state.toml.version`, migration reads that previous semver and records exactly one of: - -- `managed-only`: the configuration and managed store are first created by a receipt-aware release and initial bounded discovery finds no existing Symposium integration signature or legacy state; -- `pre-receipt`: the previous version predates receipts or initial discovery finds an exact legacy artifact; or -- `unknown`: provenance is missing or corrupt for a nonempty existing configuration, or an integration signature exists without its expected receipt-aware provenance. - -The origin is never promoted automatically. Only `managed-only` can produce `ready`; `pre-receipt` and `unknown` produce at best `ready-for-known-scopes`. The existing last-touched version stamp is useful only at first migration because later startup replaces it. - -The corresponding human lines are: - -```text -No remaining live Symposium integrations in recorded scopes. -``` - -or: - -```text No remaining Symposium integrations in known scopes. -Older unrecorded project integrations may still exist; see the preserved items above. +Next: cargo uninstall symposium ``` -The RFD intentionally avoids “It is safe to remove the Symposium binary,” because uninstall cannot prove the absence of an unknown pre-receipt checkout. +When cleanup cannot prove that an entry is still Symposium's, it preserves the entry: -JSON output contains: +```console +$ cargo agents uninstall --dry-run +Blocked + .claude/settings.json + hook differs from the released Symposium registration -```text -schema_version -mode -binary_removal_assessment -actions -preserved -acknowledgements -blockers -next_steps +No files changed. ``` -Every item includes a stable kind, adapter, scope, target, structural locator where applicable, disposition, reason code, and whether it remains a live reference. Secret-bearing fields are redacted. The old boolean `safe_to_remove_binary` is not part of the schema. +Quitting agents first prevents one from rewriting an old in-memory settings file after cleanup. `cargo agents uninstall` does not remove its own package: a running process cannot do that portably, and Cargo already owns package removal. -### Performance and cost +See the proposed [command reference](./cargo-agents-uninstall/README.md) for flags, output, and recovery instructions. -The hook preflight performs no directory-wide scan, network access, registry refresh, plugin loading, Cargo metadata query, or subprocess. It reads one directly addressed project permit or checks one global tombstone path, normalizes the process working directory, and performs depth-bounded reads and signature checks of candidate adapter configurations to find the nearest registration-owning root. +## Change in a nutshell -Guarded hooks may become the default only while p95 **added** preflight latency is no more than `max(2 ms, 5% of baseline hook-dispatch latency)`. The baseline is the same generated outer guard and no managed-state preflight on the same host and local filesystem. CI records p50 and p95 for active, inactive, missing-store, and nested-checkout cases on Linux, macOS, and Windows with small and large receipt stores. Store size must not change the number of hot-path reads or path probes. +All externally visible Symposium writes go through one managed-mutation layer. -The design adds these costs: +1. Before a write, the layer records a receipt describing the artifact and target. +2. After writing, it records the adapter-specific signature, fingerprint, marker, or manifest needed to recognize the artifact later. +3. Uninstall uses the receipt to find the target and current identity evidence to decide whether it may remove it. +4. It retires and removes one external artifact at a time, retaining recovery evidence until all external work succeeds. +5. It removes Symposium-private state last and reports a qualified result. +Generated hook commands add a small outer guard so an absent binary exits successfully without output. Project hooks also require a local permit for the exact registration-owning root. Global hooks instead run unless uninstall has written a retirement tombstone for that registration. -| Area | Cost and bound | -| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| Storage | One receipt per integration, one permit per active project registration, short-lived global tombstones, and bounded notice records | -| Hook startup | One direct state lookup, path normalization, and bounded adapter-configuration signature checks | -| Generated configuration | A one-line absent-binary guard and managed ID; scope is not trusted from command text | -| Project workflow | One explicit `cargo agents sync` after a clone, move, or new container | -| Implementation | State schemas, identity adapters, Goose block editing, safe writers, lock hierarchy, recovery, catalog maintenance, and failure-injection tests | +## Safety contract +The design has these invariants: -Receipt growth is linear in recorded integrations and checkouts. Confirmed-absent records are removed at successful finalization. Long-lived receipts disclose normalized local project paths only to principals already able to read the private Symposium configuration directory. They are never telemetry or uploaded. +- Symposium deletes external state only when bounded discovery evidence and current artifact-specific identity evidence agree. +- A managed ID correlates state; it is neither ownership proof nor permission by itself. +- User-authored configuration, custom plugin sources, shared tools, tracked project configuration, and ambiguous artifacts are preserved by default. +- Project-hook activation requires the managed ID and normalized registration-owning root to match one local permit exactly. +- Global tombstones coordinate cleanup; they do not protect global-hook users from hostile workspace configuration. +- Only the artifact being mutated is retired. Other integrations remain active. +- Discovery and repair state survives every blocked or interrupted run. +- Private caches, logs, telemetry, workspace state, and completed receipts are finalized only after external integrations are absent or explicitly transferred to the user. -Maintaining the static signature catalog is an intentional compatibility cost: every change to a released generated static form adds a fixture rather than replacing old evidence. Dynamic plugin MCP instances do not grow that catalog; their non-secret fingerprints live in receipts. +This RFD does not scan the filesystem for unknown historical projects, reverse arbitrary plugin installation scripts, uninstall shared packages, delete custom plugin sources, or restart agent processes. -### Test strategy +## Cleanup boundary -The feature extends the existing deterministic integration harness; it does not wait for or require the separate agent-interaction test redesign. +The boundary is based on ownership rather than names such as `symposium` or `cargo-agents`. -Unit and property tests cover: +| Artifact family | Default behavior | +| --- | --- | +| Verified hook and MCP registrations | Remove only the owned structural entry or dedicated file | +| Verified generated skills, plugin packages, mirrors, and files | Remove managed content; remove a directory only when its manifest accounts for every entry | +| Symposium-private state | Remove during successful finalization | +| `config.toml` and custom or external plugin sources | Preserve | +| Tracked project configuration | Preserve unless `--include-tracked` authorizes removal of the verified structure | +| Shared tools and arbitrary installation-script effects | Preserve and report | +| Modified, unsupported, or ambiguous artifacts | Preserve and report a blocker when a live integration remains | +| Unknown pre-receipt project | Not automatically discoverable | -- schema evolution, unknown versions, corrupt and truncated state; -- path normalization across macOS aliases, Windows verbatim prefixes and case behavior; -- traversal, symlink, junction, and manifest attacks; -- receipt lifecycle and crash points; -- exact current and historical signatures; -- dynamic fingerprints with secret fields excluded; and -- assessment and exit-code mapping. +Reading an external plugin package never gives Symposium ownership of its source. A compiled or copied package, generated mirror, or registered path written by Symposium is a managed artifact and uses the same receipt-backed rules. -Adapter contract fixtures cover every supported global and project representation, both Copilot shell forms, exact one-line host serialization, absent binaries, stripped `PATH`, apostrophes and shell metacharacters in paths, launch failures, real error propagation, and payload preservation. +## Results and recovery -Integration tests use isolated fake homes, Cargo homes, Git repositories, and workspaces. They cover: +The command reports removed, already absent, preserved, acknowledged, and blocked items. Its final assessment is: -- global and project init, sync, dry-run, uninstall, rerun, and repair; -- empty or unavailable stores that leave global hooks live, plus global retirement tombstones that disable them; -- clone, move, delete, multiple clones, unrelated nested adapter configuration, nested hostile checkouts, new container semantics, non-matching roots, and refusal of home or filesystem-root permits; -- one-time inactive SessionStart hints without plugin execution; -- tracked, untracked, read-only, externally modified, and concurrently rewritten settings; -- projects without a `.git` ancestor when Git is not installed, and repositories whose tracking query is unavailable; -- Goose marker-block removal with preserved surrounding comments and formatting; -- missing receipts, missing permits, corrupt stores, lost private state, and legacy signatures; -- managed-only, pre-receipt, and unknown coverage origins; -- interrupted cleanup at every lifecycle boundary; -- target-lock contention, shared dry-run snapshots, blocker exit code 3, and hook auto-sync try-lock behavior; -- status snapshots before, during, and after an exclusive uninstall; -- acknowledgement, finalization, structural collision on reinstall, and `--include-tracked`; -- locked telemetry and finalization failures; and -- the original stale-global-hook regression after binary removal. +| Assessment | Meaning | +| --- | --- | +| `ready` | No live integration remains in recorded and inspectable scopes, and no historical-discovery limitation applies | +| `ready-for-known-scopes` | Known scopes are clean, but a pre-receipt project may be unrecorded | +| `blocked` | A live binary reference, ownership conflict, or operational verification failure remains | -Tests assert filesystem and parsed configuration state, retained recovery evidence, stdout, stderr, JSON schema, and exit status. Failure-injection runs prove that discovery state survives every non-final result. +The command does not say that binary removal is universally safe when it cannot know about an old unrecorded checkout. -### Documentation changes +Interrupted cleanup is resumable. Rerunning uninstall continues removal; `cargo agents sync` restores a still-applied integration left in a repairable retiring state. The existing `cargo agents status` command consumes the managed-state health snapshot and reports inactive, retiring, corrupt, unavailable, and cleanup-in-progress states. -The implementation updates: +## Detailed design -- the command reference with the quit-clean-remove-restart workflow; -- init and sync output to explain that a cloned, moved, or containerized project needs local `cargo agents sync`; -- status documentation for inactive, retiring, corrupt, and unavailable permit states; and -- hook, state, module-structure, important-flow, and telemetry design chapters. +The technical contracts are split by responsibility: -The proposed command reference is `[cargo agents uninstall](./proposed-cargo-agents-uninstall.md)`. The proposed ownership and activation model is [managed integrations](./proposed-managed-integrations.md). +- [Ownership and managed state](./ownership/README.md) explains receipts, signatures, fingerprints, lifecycle, collision handling, path safety, legacy evidence, and generated plugin packages. +- [Hook activation](./hook-activation/README.md) explains outer guards, project permits, global tombstones, root matching, degraded state, status integration, and the hook-path performance budget. +- [Cleanup engine](./cleanup-engine/README.md) explains command modes, bounded discovery, planning, mutation ordering, locks, retries, acknowledgements, finalization, reporting, and exit codes. -## Frequently asked questions - -### Why can Cargo not perform this cleanup? - -Cargo tracks installed package binaries. Symposium's external effects live in agent configuration and workspace paths that Cargo neither owns nor understands. Symposium cleans its domain while its binary exists; Cargo then removes the package it owns. - -### Why must users quit agents first? - -Some agents cache settings and write them on exit. Cleaning while one is running creates a race in which the agent can resurrect a verified-absent entry after its receipt has been finalized. Quitting first removes that writer; starting only after Cargo removal reloads the final state. - -### Does a copied managed ID activate a hostile clone? - -Not for a project registration. Preflight finds the nearest adapter configuration that owns the invoked registration and requires its normalized root to equal the permit root exactly. A nested checkout therefore does not inherit its parent's permit. The payload's ID is never authority by itself. A global registration remains global, so this RFD makes no hostile-repository claim for global-hook users. - -### What if the managed-state directory is deleted? - -Project hooks become inactive because their positive permits are gone. Global hooks continue to run because only a positive retirement tombstone disables them; this avoids silently breaking the recommended global workflow after an empty store is recreated. Global startup records degraded health when possible. The next explicit sync can reconstruct static state from exact released signatures; a dynamic entry whose receipt was lost remains preserved because resemblance is not proof. - -### Why may an inactive SessionStart say anything? - -If the whole hook returned silently, the very SessionStart that once auto-synced a fresh checkout could never explain how to activate it. The inactive branch emits only static context naming `cargo agents sync`; it cannot load workspace plugins or execute their code. A per-root notice record suppresses the hint after it has been shown once. +The proposed user documentation is: -### Why do legacy hooks run unguarded? +- [`cargo agents uninstall`](./cargo-agents-uninstall/README.md) +- [Managed integrations](./managed-integrations/README.md) -They run unguarded today. Verifying their on-disk project registration on every event would require locating the project and add a new failure mode. Exact historical signatures are instead used during bounded migration and cleanup. - -### What if receipts are lost? - -Released static signatures still identify current and historical static hook forms at known locations. Dynamic plugin MCP entries are preserved without their receipt because Symposium cannot reconstruct arbitrary instance identity safely. Deleting private state before uninstall therefore reduces discovery, but it does not authorize guesses. - -### Why are both receipts and signatures needed? - -Receipts make discovery bounded, especially for projects that no longer exist. Signatures or dynamic fingerprints prove that the current structure is still the artifact Symposium wrote. Neither role substitutes safely for the other. - -### What happens after a project moves? - -The old permit does not match the new root, so plugin dispatch is inactive. SessionStart explains the one-time `cargo agents sync`. Sync records the new normalized root and permit; stale roots remain cheap receipt entries until reconciled or successfully finalized. +## Frequently asked questions -### Why are containers separate permit environments? +### Why does Cargo not perform this cleanup? -The container can have a different user, configuration home, executable, and path view. Treating a host path as equivalent to a container path would make scope matching ambiguous. Local sync is explicit and cheap. +Cargo tracks installed package binaries. Symposium's effects also live in agent configuration and workspace paths that Cargo neither owns nor understands. Symposium cleans its domain while the binary exists; Cargo then removes the package it owns. -### Can a partial uninstall disable a working installation? +### Why is there no `--force`? -Only the artifact currently being removed is retired. A project permit is retired or a global tombstone is created immediately before that artifact's mutation. If mutation fails, its matching receipt remains repairable and `cargo agents sync` restores it. Other registrations remain active. Status names the repair command. +A force that bypasses identity checks could delete another program's state. `--acknowledge` instead preserves the artifact, transfers responsibility to the user, and shows the exact manual action. A preserved unguarded reference to `cargo-agents` remains a blocker until it is removed. -### Why is there no `--force`? +## Implementation plan -A force that bypasses identity checks would permit deletion of another program's state. `--acknowledge` instead gives every resolvable blocker a terminating path: preserve it, transfer ownership explicitly, and show the exact manual edit. Live unguarded binary references still block removal because preserving them would reproduce the bug this command exists to prevent. +### Step 1: Add ownership primitives -### Why preserve tracked project files by default? +Add normalized paths, managed IDs, versioned receipts, signatures, fingerprints, lifecycle recovery, coverage origin, and artifact-specific identity adapters. -A developer may not intend to change configuration committed for the whole team. Default preservation makes that repository-level effect visible. `--include-tracked` is the explicit authorization to remove only the proven Symposium structure. +- [ ] PR: ownership and managed-state primitives, with schema, path, collision, legacy, and failure-injection tests -### Why preserve shared tools? +### Step 2: Route managed writers -A tool in Cargo's binary directory may be used independently or by another program. Symposium removes its integrations and reports the tool, but only private copies below Symposium's own cache are exclusively owned. +Route hook, MCP, skill, generated-file, generated-plugin-package, cache, and workspace-state writes through the managed-mutation layer without changing their behavior. -### What happens to arbitrary installation scripts? +- [ ] PR: central managed writers, Goose block editing, and formatting- and secret-preservation tests -Legacy `install_commands` may create effects outside declared boundaries. Uninstall does not replay guessed inverse commands or execute content from a receipt. Known effects are reported as preserved; a future design may add declarative, receipt-backed actions for managed links or copies. +### Step 3: Guard and activate hooks -### Are receipts a security risk? +Publish the exact shell fixtures, implement project permits and global tombstones, resolve registration-owning roots, add bounded degraded classification and SessionStart guidance, and connect the shared health snapshot to status. -They add a parser and local state store, so they are validated as untrusted data. Receipts are versioned, minimal, non-executable, secret-free, atomically written, privately permissioned, and constrained by allowlisted roots and artifact-specific proof. A forged receipt alone cannot authorize deletion or project execution. +- [ ] PR: guarded hooks, activation state, adapter working-directory contracts, migration, and latency tests -### Will every future hook, skill, or MCP feature need cleanup code? +### Step 4: Add the planner and cleanup engine -Not when it uses an existing managed artifact type. The central mutation layer injects IDs, receipts, signatures or fingerprints, and lifecycle behavior. A new kind of external side effect needs an adapter because its deletion proof is genuinely new. +Add bounded discovery, dry-run, tracked-file policy, acknowledgements, ordered mutation, retries, recovery, locks, telemetry coordination, finalization, human output, JSON, and exit codes. -### What assessment should automation trust? +- [ ] PR: uninstall command and deterministic end-to-end failure and concurrency tests -Automation should inspect `binary_removal_assessment` and the exit status. `ready` covers recorded and inspectable scopes, `ready-for-known-scopes` preserves the pre-receipt limitation, and `blocked` means a live reference or unverifiable operation remains. None claims knowledge of an undiscoverable checkout. +### Step 5: Complete adapters, platforms, and documentation -## Implementation plan +Exercise global and project scope for every supported adapter on Linux, macOS, and Windows, including stripped `PATH`, tracked repositories, interruptions, concurrent agents, and the original stale-hook regression. Update the command, hook, state, module-structure, important-flow, and telemetry documentation. -1. **Identity and path primitives.** Define normalized paths, managed IDs, trusted scope classification, exact registration-owning roots, stable blocker-ID derivation, static signatures, dynamic fingerprints, and assessment types. Test platform aliases, case behavior, links, nested checkouts, forbidden roots, acknowledgement invalidation, and every current and historical static registration. -2. **Durable state and lock hierarchy.** Add versioned receipt, project-permit, global-tombstone, notice, coverage-origin, health, and acknowledgement schemas; the read-only managed-health snapshot API; lifecycle recovery; atomic storage; the shared/exclusive installation barrier; managed-state and target locks; and crash/concurrency tests. -3. **Route managed writers.** Refactor hook, MCP, skill, generated-file, cache, and workspace-state writes through the central layer without changing behavior. Implement Goose's marker-delimited YAML block editor. Add structural adapter collisions, post-acknowledgement reinstall collisions, tracked-file classification, pending/applied recovery, formatting preservation, and secret-redaction tests. -4. **Guards and preflight.** Publish exact one-line serialized shell fixtures, record machine-local executable paths, implement positive project permits, positive global retirement tombstones, and bounded signature-based degraded classification, resolve exact registration-owning roots, add one-time inactive SessionStart context, migrate exact legacy signatures on sync, and enforce the numeric latency budget. -5. **Planner, command, and reporting.** Add bounded discovery, shared-lock dry-run, `--include-tracked`, stable acknowledgement, human grouping, versioned JSON through the existing global `--json`, decidable coverage assessments, distinct preview/apply exit codes, and minimal startup. -6. **Ordered cleanup and finalization.** Implement per-artifact project retirement or global tombstone creation, type-specific structural or Goose block mutation, two bounded retries, verification, repair through sync, blocker evidence retention, telemetry coordination, and private-state finalization last. -7. **Status, adapter, platform, and documentation completion.** Connect the shared health snapshot to the existing status command without duplicating its CLI or presentation layer. Exercise status/uninstall concurrency and global and project scope for every supported adapter on Linux, macOS, and Windows, including stripped `PATH`, tracked repositories, interruption, concurrent agents, and the stale-hook regression. For every adapter that supports project-scoped registrations, verify that hooks launched from both the checkout root and nested directories receive a working directory inside that checkout; an adapter that cannot establish this contract must not offer project-scoped guarded registrations. Update the user and design documentation named above. +- [ ] PR: adapter and platform completion, documentation, and compatibility with the existing status command -Each step is independently reviewable and includes tests. The work extends the current deterministic integration harness and does not require rewriting it. +Each step leaves the codebase working and extends the existing deterministic integration harness. It does not depend on rewriting that harness. ## Implementation status diff --git a/md/rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md b/md/rfds/symposium-uninstall/cargo-agents-uninstall/README.md similarity index 97% rename from md/rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md rename to md/rfds/symposium-uninstall/cargo-agents-uninstall/README.md index d2cb3d2f..7e532a45 100644 --- a/md/rfds/symposium-uninstall/proposed-cargo-agents-uninstall.md +++ b/md/rfds/symposium-uninstall/cargo-agents-uninstall/README.md @@ -1,4 +1,6 @@ -# Proposed: `cargo agents uninstall` +# `cargo agents uninstall` + +> Proposed user documentation for the [Managed Symposium uninstall RFD](../README.md). It does not describe released behavior yet. Remove agent integrations and private local data managed by Symposium before removing the Symposium package. diff --git a/md/rfds/symposium-uninstall/cleanup-engine/README.md b/md/rfds/symposium-uninstall/cleanup-engine/README.md new file mode 100644 index 00000000..0e13c3b5 --- /dev/null +++ b/md/rfds/symposium-uninstall/cleanup-engine/README.md @@ -0,0 +1,203 @@ +# Cleanup engine + +## TL;DR + +- Uninstall discovers only known locations and recorded project roots. +- Dry-run and apply share the same planning and ownership classification. +- External artifacts are retired, mutated, and verified one at a time. +- Blocked or interrupted runs retain all discovery and repair evidence. +- Symposium-private state is finalized only after external integrations are absent or acknowledged. + +## Command surface + +```text +cargo agents uninstall [--dry-run] [--include-tracked] + [--acknowledge ]... + [--quiet] [--json] +``` + +`--dry-run` performs the same discovery, path validation, Git classification, and ownership verification as apply. It creates no receipt state or tombstone and changes no target. + +`--include-tracked` permits structural removal from tracked project configuration after the ordinary identity checks. It never authorizes whole-file deletion. + +`--acknowledge` preserves a blocker and transfers responsibility to the user. It does not weaken deletion proof. + +`--quiet` suppresses progress, but not errors or the final assessment. The existing global `--json` flag emits one versioned document on stdout while diagnostics remain on stderr. + +| Exit code | Meaning | +| --- | --- | +| `0` | Planning or cleanup completed reliably with no live blockers | +| `1` | An operational failure prevented reliable planning or verification | +| `2` | Command-line usage error | +| `3` | Planning or cleanup completed reliably, but live blockers remain | + +A dry run with blockers exits `3`. + +## Bounded discovery + +Uninstall never crawls the user's home directory or disks. It examines only: + +1. known global configuration targets for supported adapters; +2. the current workspace when one is explicitly available; +3. targets and roots named by receipts; +4. roots or targets named by permits, notices, tombstones, or acknowledgements; +5. legacy workspace-state files that already contain a root; and +6. fixed Symposium-private directories. + +A deleted, moved, or renamed root costs one failed bounded lookup. A project at a new path becomes a separate scope after `cargo agents sync`. + +Historical workspace state improves discovery but is not a complete inventory because earlier versions did not record every manually synchronized root. The final assessment reports that limitation instead of claiming knowledge of an unknown pre-receipt checkout. + +## Planning + +Every candidate receives one disposition: + +| Disposition | Meaning | +| --- | --- | +| Removable | Discovery and current identity evidence agree | +| Already absent | The recorded artifact no longer exists | +| Preserved | Policy excludes it from automatic cleanup | +| Acknowledged | The user accepted responsibility for the preserved artifact | +| Conflicting | The current artifact no longer matches Symposium's evidence | +| Operationally unverifiable | A reliable ownership or absence decision could not be made | + +The engine builds and prints the complete plan before the first mutation. Ownership decisions use the rules in [Ownership and managed state](../ownership/README.md). + +Dry-run stops after planning. It takes shared locks for a consistent snapshot and never writes recovery state. + +## Tracked project configuration + +Before planning a project-file mutation, uninstall walks ancestors for a `.git` file or directory without launching Git. If none exists, ordinary ownership rules apply and Git need not be installed. + +When a repository exists: + +- tracked configuration is preserved by default and reported as committed by the project; +- `--include-tracked` authorizes removal of only the proven Symposium structure; +- an unavailable or indeterminate Git tracking query preserves the file as a blocker; and +- reports name the file and structural locator without printing secrets. + +Acknowledgement may transfer an entry to the user, but preserving a live unguarded `cargo-agents` invocation or an MCP server that launches it cannot produce a clean assessment. That reference must be removed manually or through `--include-tracked` before the package is removed. + +## Applying the plan + +Apply follows these phases: + +1. Acquire the exclusive installation barrier. +2. Load and reconcile receipts, activation records, acknowledgements, signatures, and bounded legacy roots. +3. Discover, classify, and print the complete plan. +4. For each removable external artifact: + 1. mark only its receipt `retiring`; + 2. retire its project permit or create its global tombstone; + 3. lock, reread, and revalidate the target; + 4. remove only the verified structure; + 5. verify absence; and + 6. retain the completed receipt until finalization. +5. If blockers remain, keep every receipt, activation record, cache, workspace record, log, and telemetry file needed for repair or a rerun. +6. Otherwise finalize telemetry and Symposium-private state. +7. Recompute the assessment, delete completed lifecycle records, release locks, and report. + +Retirement is per artifact and occurs immediately before that mutation. Uninstall never disables all hooks as an initial global phase. Private discovery state is never removed while a live blocker remains. + +Within a shared configuration file, cleanup parses and revalidates the current structure, edits only the owned entry, writes a sibling temporary file, flushes, atomically replaces, reopens, and verifies. Goose delegates to its verified marker-delimited block editor. A concurrent content change replans the target rather than overwriting it. + +## Failure and recovery + +A failure before external mutation restores that artifact to `applied` when its registration still matches, republishing its project permit or removing its global tombstone as appropriate. + +A crash after retirement leaves only that artifact inactive and repairable. Successful removals are not rolled back. Rerunning uninstall resumes cleanup; `cargo agents sync` restores a still-applied retiring registration. + +Transient filesystem failures receive one initial attempt and at most two bounded retries. Every retry reopens and revalidates the target. Permission failures, identity conflicts, unsafe links, indeterminate Git state, and concurrent content changes become blockers rather than unbounded retry loops. + +The command is idempotent: a verified absence is an `Already absent` result, and a rerun does not recreate removed state. + +## Concurrency and locks + +Managed mutation uses this lock order: + +```text +installation barrier + → managed-state lock + → global target when needed + → workspace targets sorted by normalized path +``` + +- Uninstall holds the installation barrier exclusively for plan, mutation, and verification. +- Dry-run holds the barrier and discovered targets in shared mode so it cannot observe a target mid-mutation. +- Init, sync, and repair use shared installation access with exclusive locks for state and targets they change. +- Hook-triggered auto-sync uses a non-blocking try-lock. On contention it skips that cache refresh and continues from already-published state. + +After dry-run locks its discovered targets, it checks the managed-state generation. One change retries the snapshot; a second is an operational failure rather than an inconsistent preview. + +Platform implementations use native advisory locking and multi-process tests. Lock diagnostics may include operation, process, and start metadata, but age alone never proves a lock is stale. + +## Blockers and acknowledgements + +A blocker ID is stable over: + +```text +artifact type + adapter + normalized target + structural locator +``` + +The acknowledgement stores that complete locator and the artifact's current identity instead of trusting the display ID alone. A moved locator creates a new blocker, and a changed artifact invalidates the acknowledgement. + +Acknowledgement: + +- preserves the artifact; +- records that the user accepts responsibility; +- retires Symposium's ownership claim; +- prints a redacted manual edit; and +- makes a later installation treat the occupied slot as a structural collision. + +Acknowledgement records can be deleted at successful finalization because collision detection inspects the current occupied entry. There is no `--force`: bypassing identity checks would permit deletion of user or third-party state. + +## Minimal startup and finalization + +Uninstall dispatches before ordinary startup. It initializes only argument parsing, managed-state path resolution, minimal diagnostics, locking, cleanup, and reporting. It does not refresh registries, load plugins, run update checks, auto-sync, or initialize ordinary telemetry recording. + +Telemetry finalization uses the telemetry subsystem's supported coordination path. If telemetry cannot be finalized, the command retains discovery and recovery state and reports a blocker. The uninstall result itself is not recorded as new telemetry. + +Only after every external integration is absent or validly acknowledged does cleanup remove private caches, workspace state, telemetry, logs, receipts, permits, tombstones, notices, acknowledgements, and the empty managed-state directory. It verifies finalization before reporting success. + +## Reporting + +Human output groups: + +- `Removed`; +- `Already absent`; +- `Preserved`; +- `Acknowledged`; +- `Blocked`; and +- `Next steps`. + +Every machine-readable item has a stable kind, adapter, scope, target, structural locator where applicable, disposition, reason code, and live-reference flag. Secret-bearing fields are redacted. The proposed [command reference](../cargo-agents-uninstall/README.md) defines the complete JSON shape and human examples. + +## Removal assessment + +| Assessment | Meaning | +| --- | --- | +| `ready` | No live integration remains in recorded and inspectable scopes, and no historical limitation applies | +| `ready-for-known-scopes` | Known scopes are clean, but a pre-receipt project may be unrecorded | +| `blocked` | A live reference, ownership conflict, or operational verification failure remains | + +The durable coverage origin defined by the ownership model makes this decidable: + +- only `managed-only` can produce `ready`; +- `pre-receipt` and `unknown` produce at best `ready-for-known-scopes`; and +- any live blocker produces `blocked`. + +The origin is never promoted automatically. Output says that known scopes are clean rather than claiming universal safety when an unknown checkout may exist. + +## Acceptance tests + +The feature extends the existing deterministic integration harness. Tests cover: + +- bounded global, current-workspace, receipt, and historical discovery; +- dry-run and apply classification parity, output, and exit codes; +- tracked, untracked, read-only, and indeterminate-Git configuration; +- interruption at every lifecycle boundary and idempotent reruns; +- target contention, shared previews, concurrent edits, and auto-sync try-locks; +- stable acknowledgements, changed artifacts, and reinstall collisions; +- two bounded retries and permanent failures; +- telemetry and private-state finalization failures; +- `ready`, `ready-for-known-scopes`, and `blocked` assessments; and +- the original stale-global-hook regression after package removal. diff --git a/md/rfds/symposium-uninstall/hook-activation/README.md b/md/rfds/symposium-uninstall/hook-activation/README.md new file mode 100644 index 00000000..4f5fe11c --- /dev/null +++ b/md/rfds/symposium-uninstall/hook-activation/README.md @@ -0,0 +1,183 @@ +# Hook activation + +## TL;DR + +- Every new hook command makes an absent `cargo-agents` binary a successful no-op. +- Project hooks run only with a local permit for the exact registration-owning root. +- Global hooks run unless uninstall has written a retirement tombstone. +- Missing or damaged state is either correctly inactive or visible through managed-health reporting. +- Legacy hooks retain their current runtime behavior until an exact signature is migrated. + +## Motivation + +Hook cleanup needs runtime coordination for two reasons. + +First, an agent may invoke a registration after Cargo has removed `cargo-agents`. The generated command must treat that expected absence as success rather than reproducing the original hook error. + +Second, a project registration may be committed and copied. Its managed ID is therefore public and cannot authorize execution in another checkout. Project activation needs local state bound to the exact checkout that owns the registration. + +These controls do not establish general workspace trust. A global hook is intentionally valid in every working directory and may discover workspace plugin configuration. Protecting global-hook users from hostile repositories would require a separate design that gates workspace plugin activation itself. + +## Runtime flow + +```text +agent invokes hook + ↓ +outer guard locates cargo-agents + ↓ +preflight classifies project or global scope + ↓ +permit or tombstone decision + ↓ +ordinary hook startup and plugin dispatch +``` + +The outer guard handles binary absence. The in-process preflight handles scope and retirement before ordinary startup loads plugins, refreshes registries, or auto-syncs. + +## Scope classification + +Every new invocation carries `--managed-id `, but does not carry an authoritative scope. Preflight classifies scope from a directly addressed receipt, project permit, or global tombstone. Text in the invocation is never sufficient. + +When state for the ID is missing or unavailable, preflight performs a bounded degraded classification. It checks the registration-owning-root walk and the adapter's known global target for an exact released signature containing that ID: + +- one project match remains inactive and may show the sync hint; +- one global match runs and reports degraded health; and +- zero or multiple matches deny plugin dispatch and report ambiguity. + +The fallback opens only known adapter configuration files and does not invoke Cargo. + +## Project activation + +A project permit binds: + +```text +managed ID + normalized registration-owning root +``` + +Preflight starts at the process working directory and walks a bounded number of ancestors. The registration-owning root is the nearest ancestor whose adapter project configuration contains an exact released registration for this managed ID. A nearer configuration without that registration is ignored. + +Execution requires: + +1. trusted state or exact degraded classification identifies project scope; +2. a registration-owning root exists; +3. the permit carries the same managed ID; and +4. the normalized permit root equals the normalized owning root. + +The permit root being merely an ancestor of the working directory is not enough. A nested checkout containing the copied registration resolves to its own root and is denied. Sync refuses to permit the filesystem root or the user's home directory. + +Adapters must invoke project hooks with a working directory inside the checkout. An adapter that cannot provide this contract cannot offer project-scoped guarded registrations. Adapter tests cover launches from the checkout root and nested directories. + +A moved checkout, a new clone, a dev container, WSL, and the Windows host are separate permit environments when they expose different normalized roots. Each needs `cargo agents sync`. + +## Global activation + +Project and global registrations deliberately fail in opposite directions: + +| Scope | Active when | +| --- | --- | +| Project | A positive permit matches the ID and owning root | +| Global | No valid retirement tombstone exists for the ID | + +Losing a project permit makes a copied registration inactive. Losing the managed-state directory must not silently disable the recommended global installation, so missing global state is not an activation denial. A global registration runs after receipt proof or one exact degraded signature match and records degraded health where possible. + +Uninstall writes a global tombstone immediately before mutating that registration. A valid tombstone makes the hook exit successfully and quietly. A corrupt tombstone denies dispatch and surfaces a repair warning. + +## Preflight outcomes + +| Classification and state | Behavior | +| --- | --- | +| Project permit matches ID and owning root | Continue to ordinary hook startup | +| Project permit missing or non-matching in a readable store | Remain inactive; SessionStart may give one sync hint | +| Project state corrupt or unavailable | Deny plugin dispatch and surface repair guidance | +| Valid global tombstone present | Exit successfully and quietly | +| Corrupt global tombstone present | Deny plugin dispatch and surface repair guidance | +| Global registration proven, no tombstone | Continue; report degraded health when state was missing | +| Missing state with zero or multiple exact matches | Deny plugin dispatch and report ambiguity | + +Correctly inactive project events are quiet except for the bounded SessionStart guidance described below. An inability to determine state is not silently treated as correct inactivity. + +## Inactive SessionStart + +An inactive project SessionStart may perform preflight and emit static `additionalContext` naming `cargo agents sync`. It may not read workspace plugin configuration, refresh a registry, run plugin code, or auto-sync. + +A per-root notice record suppresses repeated guidance. Records are capped at 64 roots per managed ID; beyond the cap, new roots receive no stored or repeated hint and status reports the suppressed count. Other inactive events return success without output. + +If a writable managed store contains corrupt state, preflight records a health flag and one best-effort log line. Corrupt records are quarantined and recreated by the next explicit sync. When the store itself is unavailable, status detects that condition on demand and SessionStart provides the only guaranteed warning; Symposium does not invent a second fallback state directory. + +## Status and repair + +The managed-state layer exposes one read-only, versioned health snapshot for the existing or concurrently developed `cargo agents status` command. It reports inactive, retiring, corrupt, unavailable, and cleanup-in-progress states with stable reason codes and recommended commands. + +Status does not repair state as a side effect. It reads under the installation barrier in shared mode. While uninstall owns the exclusive barrier, status reports cleanup in progress rather than inspecting half-mutated targets. Corrupt or unreadable state produces a diagnostic snapshot rather than a panic. + +`cargo agents sync` is the explicit repair path. If a retiring receipt still matches an applied registration, sync restores it to `applied`, republishes its project permit, or removes its global tombstone. + +## Generated outer guards + +The outer guard has one job: an absent binary exits with status 0 and no output. Scope-aware preflight stays inside the binary. + +Machine-local global registrations record and try the resolved absolute `cargo-agents` path first, then fall back to `PATH`. A committed project registration cannot contain another user's absolute path, so its portable form tries the Cargo home convention before `PATH`. + +The following are the versioned single-line command values before host serialization. + +POSIX machine-local global: + +```sh +if [ -x ]; then exec hook --managed-id ; elif command -v cargo-agents >/dev/null 2>&1; then exec cargo-agents hook --managed-id ; else exit 0; fi +``` + +POSIX portable project: + +```sh +if [ -x ${CARGO_HOME:-$HOME/.cargo}/bin/cargo-agents ]; then exec ${CARGO_HOME:-$HOME/.cargo}/bin/cargo-agents hook --managed-id ; elif command -v cargo-agents >/dev/null 2>&1; then exec cargo-agents hook --managed-id ; else exit 0; fi +``` + +PowerShell machine-local global: + +```powershell +$symposiumBin = ; if (-not (Test-Path -LiteralPath $symposiumBin -PathType Leaf)) { $symposiumCommand = Get-Command cargo-agents -CommandType Application -ErrorAction SilentlyContinue; if ($null -eq $symposiumCommand) { exit 0 }; $symposiumBin = $symposiumCommand.Source }; $global:LASTEXITCODE = $null; & $symposiumBin hook --managed-id ; if ($null -eq $LASTEXITCODE) { exit 1 }; exit $LASTEXITCODE +``` + +PowerShell portable project: + +```powershell +$cargoHome = $env:CARGO_HOME; if ([string]::IsNullOrWhiteSpace($cargoHome)) { $cargoHome = Join-Path $HOME '.cargo' }; $symposiumBin = Join-Path $cargoHome 'bin/cargo-agents.exe'; if (-not (Test-Path -LiteralPath $symposiumBin -PathType Leaf)) { $symposiumCommand = Get-Command cargo-agents -CommandType Application -ErrorAction SilentlyContinue; if ($null -eq $symposiumCommand) { exit 0 }; $symposiumBin = $symposiumCommand.Source }; $global:LASTEXITCODE = $null; & $symposiumBin hook --managed-id ; if ($null -eq $LASTEXITCODE) { exit 1 }; exit $LASTEXITCODE +``` + +The POSIX literal encoder single-quotes the path and escapes embedded apostrophes as `'\''`. The PowerShell encoder single-quotes the path and doubles embedded apostrophes. Placeholders above are already encoded literals, never raw paths. + +Copilot publishes both shell forms. Versioned adapter fixtures assert the decoded command and exact JSON, TOML, or YAML serialization. They also cover absent binaries, stripped `PATH`, spaces and shell metacharacters, launch failures, status propagation, payload preservation, and an unchanged working directory. + +## Legacy registrations + +A hook invocation without a managed ID follows legacy behavior. Legacy hooks already run unguarded; adding on-disk verification to their hot path would introduce a new failure mode. Exact historical signatures are instead used during bounded migration and cleanup. The next `init` or `sync` may rewrite one into the guarded form. + +## Agent plugin boundary + +Project permits govern Symposium-dispatched hooks. A native agent-plugin directory is loaded by the agent and does not pass through hook preflight. Its scope must be enforced by project-scoped placement, a workspace-scoped agent registration, or a project-safe fallback. + +## Performance + +Preflight performs no directory-wide scan, network access, registry refresh, plugin loading, Cargo metadata query, or subprocess. It reads directly addressed state, normalizes the working directory, and performs bounded adapter-configuration signature checks. + +Guarded hooks may become the default only while p95 added preflight latency is no more than: + +```text +max(2 ms, 5% of baseline hook-dispatch latency) +``` + +The baseline uses the same outer guard without managed-state preflight. CI records p50 and p95 for active, inactive, missing-store, and nested-checkout cases on Linux, macOS, and Windows. Receipt-store size must not change the number of hot-path reads or path probes. + +## Acceptance tests + +Tests cover: + +- positive project permits and global retirement tombstones; +- clones, moves, monorepos, unrelated nested configuration, and nested checkouts; +- missing, corrupt, unavailable, and ambiguous state; +- one-time and dismissed SessionStart guidance without plugin execution; +- status snapshots before, during, and after cleanup; +- exact POSIX and PowerShell fixtures for every supported adapter; +- adapter working-directory contracts from root and nested directories; +- legacy runtime behavior and migration; and +- the numeric latency budget. diff --git a/md/rfds/symposium-uninstall/proposed-managed-integrations.md b/md/rfds/symposium-uninstall/managed-integrations/README.md similarity index 98% rename from md/rfds/symposium-uninstall/proposed-managed-integrations.md rename to md/rfds/symposium-uninstall/managed-integrations/README.md index 584c6a1c..53d03b93 100644 --- a/md/rfds/symposium-uninstall/proposed-managed-integrations.md +++ b/md/rfds/symposium-uninstall/managed-integrations/README.md @@ -1,4 +1,6 @@ -# Proposed: managed integrations +# Managed integrations + +> Proposed user documentation for the [Managed Symposium uninstall RFD](../README.md). It does not describe released behavior yet. Symposium writes derived state into coding-agent configuration and project directories during init and sync. Managed integrations make those writes discoverable, identifiable, locally activatable, and removable without claiming ownership of surrounding user files. diff --git a/md/rfds/symposium-uninstall/ownership/README.md b/md/rfds/symposium-uninstall/ownership/README.md new file mode 100644 index 00000000..c6efac60 --- /dev/null +++ b/md/rfds/symposium-uninstall/ownership/README.md @@ -0,0 +1,180 @@ +# Ownership and managed state + +## TL;DR + +- Every externally visible Symposium write records durable discovery evidence. +- Cleanup verifies the current artifact independently before modifying it. +- Receipts are versioned, private, non-executable, and secret-free. +- Changed or ambiguous artifacts are preserved. +- New instances of an existing managed artifact type inherit cleanup behavior automatically. + +## Motivation + +A receipt proves that Symposium previously wrote or intended to write something at a location. It does not prove that the current occupant is still Symposium's artifact. + +For example, Symposium may record a Claude hook in `~/.claude/settings.json`. If the user later replaces that hook, the receipt still finds the slot, but deleting the replacement would erase user state. + +Cleanup therefore separates two questions: + +| Evidence | Question | +| --- | --- | +| Receipt or bounded legacy record | Where should Symposium look? | +| Signature, fingerprint, marker, or manifest | Is the current artifact still the one Symposium manages? | + +Both must agree before external state is removed. + +## Ownership model + +- A **managed ID** is a stable UUID for one logical registration. All event entries belonging to one agent-and-scope hook registration share it. +- An **ownership receipt** records one intended managed mutation, including its target, adapter, scope, artifact type, identity evidence, and lifecycle. +- A **static signature** is a versioned structural description of a registration form emitted by a released Symposium version. +- A **dynamic fingerprint** records the non-secret identity of an instance derived from plugin configuration. + +A managed ID is a correlation key. It is not proof of ownership, permission to execute, or an authoritative scope declaration. + +## Path identity + +Receipt targets, project roots, and runtime comparisons use one normalization function: + +1. make the path absolute; +2. canonicalize the existing portion; +3. strip the Windows verbatim-path prefix when present; +4. apply Windows filesystem case rules; and +5. compare components rather than string prefixes. + +Resolved paths, not inode or file IDs, identify targets. Re-cloning a dotfile repository therefore does not create an unrecoverable inode conflict. Paths are validated again immediately before every mutation. + +## Managed writes + +All code that changes externally visible agent state goes through one managed-mutation layer. Callers declare an artifact type and desired value; the layer supplies receipts, lifecycle, identity evidence, collision checks, safe target validation, atomic replacement, and cleanup behavior. + +A normal write is: + +```text +record pending intent + ↓ +write external artifact + ↓ +verify current structure + ↓ +record applied state +``` + +The receipt is durable before the external mutation. Recovery inspects the target instead of assuming that an interrupted write succeeded. + +Adding a hook, skill, MCP server, or generated plugin package through an existing artifact type needs no uninstall-specific code. A genuinely new side-effect type needs an ownership adapter because its identity and safe deletion rules differ. + +## Receipt lifecycle + +A receipt has four states: + +| State | Meaning | +| --- | --- | +| `pending` | Intent is durable, but the external write is not confirmed | +| `applied` | The external artifact matches its recorded identity | +| `retiring` | Removal started or was interrupted | +| `acknowledged` | The user accepted responsibility for the preserved artifact | + +Project permits are published only after the corresponding write is `applied`. Completed receipts remain until the whole uninstall reaches finalization, so a crash or blocker cannot erase the only discovery evidence. + +`cargo agents sync` can restore a still-applied registration left in `retiring`. Acknowledgement transfers ownership without deleting the artifact; a later change to that artifact invalidates the acknowledgement. + +## Identity by artifact type + +| Artifact | Identity evidence and mutation | +| --- | --- | +| Static hook or built-in MCP registration | Receipt plus exact released signature; remove only the owned structural entry or dedicated file | +| Dynamic MCP or registered plugin path | Receipt plus exact non-secret fingerprint; remove only the recorded structural entry | +| Goose MCP block | Receipt, unique marker pair, indentation, and fingerprint; remove the verified byte extent | +| Generated file | Receipt plus marker or released content signature | +| Generated skill, plugin package, or mirror | Receipt, marker, and manifest; remove the directory only when the manifest accounts for every entry | +| Symposium-private state | Containment below the fixed private root and successful external finalization | + +`config.toml`, custom plugin sources, and externally authored plugin packages remain user-owned. Reading a `plugin.json` never authorizes deletion of its source. Compiled packages, copies, path registrations, and enablement entries written by Symposium are managed artifacts. + +A `.symposium` marker helps identify a generated package, but it is never sufficient by itself for recursive deletion. The receipt finds the target, and the marker plus manifest must account for its current contents. + +## Static signatures + +Symposium keeps a versioned catalog of every released static form, including current and historical forms: + +- generated hook commands and their containing structure; +- dedicated generated agent files; +- static built-in MCP registrations; and +- generated markers and manifests. + +Signatures compare parsed structure, normalized executable identity, fixed arguments, and managed-ID placement. They do not match on a broad key name, event name, or the presence of `cargo-agents` alone. + +Changing a released generated form adds a signature fixture rather than replacing old evidence. This is the compatibility cost that allows cleanup after receipts are lost or an older release is removed. + +## Dynamic fingerprints + +Plugin-provided MCP names, commands, arguments, transports, and URLs are not a finite catalog. Their identity is captured when written. + +| Adapter | Structural container | +| --- | --- | +| Claude, Gemini, Kiro | `mcpServers.` | +| GitHub Copilot | Top-level MCP server map entry | +| Codex | `mcp_servers.` | +| Goose | `extensions.` | +| OpenCode | `mcp.` | + +The fingerprint includes the adapter, normalized target, structural container, entry name, transport, and command plus arguments or URL. It excludes environment values, headers, tokens, and other secrets. + +Removal requires every recorded non-secret identity field to match. A changed field transfers the entry out of automatic cleanup and produces a conflict. Without the receipt, a dynamic entry is preserved because name or command resemblance is not proof. + +Goose is an editing exception. Its YAML is not round-tripped through a serializer that would discard comments and formatting. Symposium writes behavior-neutral managed-ID comment markers, verifies one unique marker pair and the enclosed mapping, then removes that exact byte extent. Missing, duplicate, malformed, or mismatched markers preserve the block. + +## Collisions and user changes + +Init and sync inspect the current adapter slot before writing. They do not adopt or overwrite an occupied location without matching `pending` or `applied` evidence. Migration from an exact released signature is a separate path. + +Collision detection is structural and does not depend on a retained acknowledgement. Reinstallation therefore treats a preserved occupied location as a collision even after finalization removes old managed records. + +An external change between read and replacement causes that target to be replanned. Symposium never overwrites the concurrent edit using stale identity evidence. + +## Filesystem safety + +Receipts are treated as untrusted input. Before mutation, Symposium validates: + +- the schema version, artifact kind, UUIDs, and enums; +- containment below an allowlisted adapter or private root; +- the expected file or directory shape; +- component-wise ancestor relationships; +- the artifact's link policy; and +- current identity evidence. + +Cleanup never follows a symlink or junction while deleting a generated tree. If a managed target has been replaced by a link, it is preserved. A directory is removed only when its manifest accounts for every remaining entry. + +## Missing receipts and historical state + +At known locations, exact released signatures can identify static current and legacy forms even when a receipt is missing. The next `init` or `sync` may migrate such a registration to the managed form; uninstall may remove it after the same exact identity check. + +Dynamic entries without receipts remain preserved. Unknown pre-receipt project roots cannot be rediscovered by scanning the user's filesystem. + +The first receipt-aware release records a durable coverage origin: + +- `managed-only` when no earlier or unexplained integration exists; +- `pre-receipt` when an older release or exact legacy artifact is found; or +- `unknown` when provenance is missing or corrupt for existing state. + +The origin is never promoted automatically. The cleanup engine uses it to qualify the final assessment. + +## Storage and cost + +Receipts live below a versioned directory in the resolved Symposium configuration home. They contain paths and identity metadata, never executable instructions, tokens, environment values, or headers. Atomic writes and private permissions protect the store. + +Storage grows linearly with recorded integrations and checkouts. Completed records are removed during successful finalization. Recorded project paths are visible only to principals already able to read Symposium's private configuration and are never uploaded as telemetry. + +## Acceptance tests + +Tests cover: + +- schema evolution, truncation, corruption, and every lifecycle crash point; +- path normalization, containment, symlinks, junctions, and manifests; +- current and historical static signatures; +- dynamic fingerprints with secret fields excluded; +- collisions, concurrent changes, and acknowledgement invalidation; +- Goose block editing with surrounding formatting preserved; +- generated skill and plugin-package directories; and +- missing receipts and historical coverage. From 368a1a344cd0c9a4b269a003f5b38ea1860d4f9f Mon Sep 17 00:00:00 2001 From: Awesome Rustacean Date: Sun, 23 Aug 2026 01:43:56 +0300 Subject: [PATCH 4/4] docs(rfd): refine managed uninstall proposal --- md/rfds/symposium-uninstall/README.md | 249 ++++++++++++------ .../cargo-agents-uninstall/README.md | 136 +++++----- .../cleanup-engine/README.md | 151 +++++------ .../hook-activation/README.md | 104 ++++---- .../managed-integrations/README.md | 106 ++++---- .../symposium-uninstall/ownership/README.md | 119 ++++----- 6 files changed, 438 insertions(+), 427 deletions(-) diff --git a/md/rfds/symposium-uninstall/README.md b/md/rfds/symposium-uninstall/README.md index 9f1b5997..252ade88 100644 --- a/md/rfds/symposium-uninstall/README.md +++ b/md/rfds/symposium-uninstall/README.md @@ -2,35 +2,42 @@ ## TL;DR -- Add `cargo agents uninstall` to remove Symposium-managed integrations before `cargo uninstall symposium` removes the package. -- Ask users to quit running agents before cleanup and restart them after Cargo removes the binary. -- Record managed writes in durable receipts, then verify current identity before deleting anything. -- Make generated hooks harmless when `cargo-agents` is absent and coordinate hook retirement during cleanup. -- Preserve user-authored, shared, tracked, and ambiguous state by default. -- Report what was removed or preserved and qualify the final removal assessment. +- Add `cargo agents uninstall` to remove Symposium-managed integrations before Cargo removes the package. +- Use receipts for bounded discovery and current artifact identity for deletion authority. +- Make new hooks harmless when `cargo-agents` is absent and retire integrations one at a time. +- Preserve user-authored, tracked, shared, modified, and ambiguous state by default. +- Report what is clean in known scopes without claiming knowledge of unknown pre-receipt projects. ## Motivation -`cargo uninstall symposium` removes the package binaries Cargo owns. It does not know that Symposium also wrote agent hooks, MCP entries, generated skills or plugin packages, caches, and workspace state. +Today, `cargo uninstall symposium` removes the binaries Cargo installed. It does not remove hooks, MCP entries, generated skills, caches, or workspace state written by Symposium. -An unguarded hook can therefore survive the binary: +An unguarded global hook can therefore survive the binary and fail in every project: ```text /usr/bin/bash: line 1: cargo-agents: command not found PostToolUse:Bash hook error ``` -A stale global hook produces this error in every project. Project-scoped cleanup has a different problem: Symposium cannot scan arbitrary disks to rediscover every workspace, and it must not delete a similar-looking entry that another program or the user owns. +Current init code can unregister known hook forms when an agent is removed from Symposium's configuration. It cannot inventory every managed side effect, prove who owns the current entry, or rediscover every project after the package is gone. -The command needs bounded discovery, current ownership proof, safe hook retirement, and recovery from interrupted cleanup. +Three cases define the required behavior: -## As a user +| Case | Required property | +| --- | --- | +| A global hook survives the binary | An absent binary is a quiet success | +| The user or another program changes an entry | Preserve the current entry | +| A project moves, disappears, or predates receipts | Use bounded discovery and qualify the result | + +The design cannot prove the absence of an unknown pre-receipt project without scanning arbitrary disks. It reports that limit instead. + +## Change in a nutshell -The recommended workflow is: +The normal workflow is: -1. Quit every agent process that may have loaded Symposium-managed configuration. -2. Run `cargo agents uninstall` from an ordinary terminal. -3. Resolve or acknowledge reported blockers and rerun until no live integration remains in the applicable scopes. +1. Quit agents that may have loaded Symposium-managed configuration. +2. Run `cargo agents uninstall`. +3. Resolve reported blockers and rerun until known scopes are clean. 4. Run `cargo uninstall symposium`. 5. Restart the agents. @@ -44,136 +51,206 @@ No remaining Symposium integrations in known scopes. Next: cargo uninstall symposium ``` -When cleanup cannot prove that an entry is still Symposium's, it preserves the entry: +`--dry-run` performs the same discovery and ownership checks without changing state: ```console $ cargo agents uninstall --dry-run +Would remove + claude global hook + Blocked .claude/settings.json - hook differs from the released Symposium registration + hook differs from a released Symposium registration No files changed. ``` -Quitting agents first prevents one from rewriting an old in-memory settings file after cleanup. `cargo agents uninstall` does not remove its own package: a running process cannot do that portably, and Cargo already owns package removal. +A blocker is preserved. The user may restore Symposium with `cargo agents sync`, remove the entry manually, authorize a verified tracked-file edit with `--include-tracked`, or acknowledge responsibility for a preserved artifact. Interrupted cleanup is safe to rerun. -See the proposed [command reference](./cargo-agents-uninstall/README.md) for flags, output, and recovery instructions. +## Detailed plans -## Change in a nutshell +### Decision sought -All externally visible Symposium writes go through one managed-mutation layer. +Accepting this RFD means agreeing that: -1. Before a write, the layer records a receipt describing the artifact and target. -2. After writing, it records the adapter-specific signature, fingerprint, marker, or manifest needed to recognize the artifact later. -3. Uninstall uses the receipt to find the target and current identity evidence to decide whether it may remove it. -4. It retires and removes one external artifact at a time, retaining recovery evidence until all external work succeeds. -5. It removes Symposium-private state last and reports a qualified result. +1. All new Symposium-owned integration writes record durable discovery evidence. +2. Discovery evidence and current identity evidence must both support deletion. +3. New hooks use absent-binary guards and scope-aware activation. +4. Cleanup is bounded, ownership-preserving, and resumable. +5. Cargo, not Symposium, removes the installed package. -Generated hook commands add a small outer guard so an absent binary exits successfully without output. Project hooks also require a local permit for the exact registration-owning root. Global hooks instead run unless uninstall has written a retirement tombstone for that registration. +Exact lifecycle and evidence rules, shell fixtures, adapter behavior, and cleanup algorithms live in the supporting contracts. Internal Rust type and module organization remain implementation choices. -## Safety contract +### Design model -The design has these invariants: +Four concepts answer different questions: -- Symposium deletes external state only when bounded discovery evidence and current artifact-specific identity evidence agree. -- A managed ID correlates state; it is neither ownership proof nor permission by itself. -- User-authored configuration, custom plugin sources, shared tools, tracked project configuration, and ambiguous artifacts are preserved by default. -- Project-hook activation requires the managed ID and normalized registration-owning root to match one local permit exactly. -- Global tombstones coordinate cleanup; they do not protect global-hook users from hostile workspace configuration. -- Only the artifact being mutated is retired. Other integrations remain active. -- Discovery and repair state survives every blocked or interrupted run. -- Private caches, logs, telemetry, workspace state, and completed receipts are finalized only after external integrations are absent or explicitly transferred to the user. +| Term | Question answered | +| --- | --- | +| Managed ID | Which records belong to one logical registration? | +| Receipt | Where should Symposium look? | +| Signature, fingerprint, marker, or manifest | Is the current artifact still Symposium-managed? | +| Project permit or global tombstone | May this hook execute here now? | -This RFD does not scan the filesystem for unknown historical projects, reverse arbitrary plugin installation scripts, uninstall shared packages, delete custom plugin sources, or restart agent processes. +The [ownership contract](./ownership/README.md) is authoritative for receipts and identity. The [hook-activation contract](./hook-activation/README.md) defines execution. The [cleanup-engine contract](./cleanup-engine/README.md) defines planning, mutation, recovery, output, and exit status. -## Cleanup boundary +### Ownership and managed writes -The boundary is based on ownership rather than names such as `symposium` or `cargo-agents`. +The managed-mutation layer records a pending receipt before an external write and records it as applied only after verification. A new hook, skill, MCP server, or plugin package using an existing artifact type inherits this behavior. A genuinely new side-effect type needs an ownership adapter. -| Artifact family | Default behavior | -| --- | --- | -| Verified hook and MCP registrations | Remove only the owned structural entry or dedicated file | -| Verified generated skills, plugin packages, mirrors, and files | Remove managed content; remove a directory only when its manifest accounts for every entry | -| Symposium-private state | Remove during successful finalization | -| `config.toml` and custom or external plugin sources | Preserve | -| Tracked project configuration | Preserve unless `--include-tracked` authorizes removal of the verified structure | -| Shared tools and arbitrary installation-script effects | Preserve and report | -| Modified, unsupported, or ambiguous artifacts | Preserve and report a blocker when a live integration remains | -| Unknown pre-receipt project | Not automatically discoverable | +Static integrations use released structural signatures. Dynamic MCP entries and plugin paths use secret-free fingerprints captured at write time. Generated directories require markers and manifests that account for every entry. Goose uses a verified marker-delimited byte block to preserve surrounding YAML formatting. The [ownership contract](./ownership/README.md#identity-by-artifact-type) specifies each form. + +Receipts discover candidates; they do not authorize deletion. `config.toml`, custom and external plugin sources, shared tools, user content, and any mismatched artifact remain user-owned. A `.symposium` marker alone never permits recursive deletion. + +### Hook safety and activation -Reading an external plugin package never gives Symposium ownership of its source. A compiled or copied package, generated mirror, or registered path written by Symposium is a managed artifact and uses the same receipt-backed rules. +New hook commands use versioned POSIX and PowerShell guards. An absent binary exits successfully without output. The exact command and serialization fixtures remain in the [hook contract](./hook-activation/README.md#generated-outer-guards). -## Results and recovery +A project hook runs only when a local permit matches its managed ID and exact registration-owning root. A copied ID grants no authority, and a nested checkout cannot inherit a parent's permit. A clone, moved checkout, container, WSL environment, or Windows host must run `cargo agents sync` for its own normalized root. -The command reports removed, already absent, preserved, acknowledged, and blocked items. Its final assessment is: +Global hooks use the opposite failure direction. A registration proven global runs unless uninstall has written its retirement tombstone; losing the managed-state store does not disable every global hook. Corrupt or ambiguous state still denies plugin dispatch and surfaces repair guidance. + +This activation model protects project-scoped Symposium dispatch. It does not make global hooks a hostile-workspace boundary, and native agent-plugin directories that bypass hook preflight need their own scope mechanism. Legacy hooks remain unguarded until init or sync migrates an exact released signature. + +### Cleanup and recovery + +Uninstall examines known adapter targets, the current workspace when available, recorded targets and project roots, bounded historical state, and fixed Symposium-private directories. It never crawls the user's home or disks. + +The engine plans the complete run before mutation. It then retires, revalidates, removes, and verifies one external artifact at a time. If a blocker or interruption occurs, receipts and repair state remain. Private caches, telemetry, logs, and lifecycle records are removed only after external integrations are absent or validly acknowledged. + +Tracked project configuration is preserved unless `--include-tracked` authorizes removal of the verified Symposium structure. `--acknowledge` transfers responsibility without weakening identity checks. It cannot turn a live unguarded `cargo-agents` reference into a clean result. + +The final assessment is: | Assessment | Meaning | | --- | --- | -| `ready` | No live integration remains in recorded and inspectable scopes, and no historical-discovery limitation applies | +| `ready` | Recorded and inspectable scopes are clean, with no historical-discovery limitation | | `ready-for-known-scopes` | Known scopes are clean, but a pre-receipt project may be unrecorded | -| `blocked` | A live binary reference, ownership conflict, or operational verification failure remains | +| `blocked` | A live reference, ownership conflict, or operational verification failure remains | + +The durable coverage origin, `managed-only`, `pre-receipt`, or `unknown`, bounds that assessment and is never promoted automatically. The existing `cargo agents status` command currently reports plugin enablement; this proposal extends it with read-only managed-health states. Sync, not status, performs repair. + +### Scope and compatibility + +This RFD covers hook and MCP registrations, generated skills and plugin packages, generated files and mirrors, caches, workspace records, logs, telemetry, and managed lifecycle state. + +It does not reverse arbitrary installation scripts, uninstall shared packages, delete external plugin sources, restart agents, remove tracked project entries without explicit authorization, scan for unknown projects, or remove the package binary. -The command does not say that binary removal is universally safe when it cannot know about an old unrecorded checkout. +The static signature catalog retains current and historical released forms. Dynamic entries without receipts are preserved. Historical workspace state recovers some old roots, but users upgrading from pre-receipt versions should sync any known dormant checkout before uninstalling. -Interrupted cleanup is resumable. Rerunning uninstall continues removal; `cargo agents sync` restores a still-applied integration left in a repairable retiring state. The existing `cargo agents status` command consumes the managed-state health snapshot and reports inactive, retiring, corrupt, unavailable, and cleanup-in-progress states. +### Drawbacks and limitations -## Detailed design +The strongest objection is proportionality: receipts, signatures, activation state, locks, and recovery machinery are substantial additions for uninstall cleanup. A smaller change could guard missing binaries and document manual removal. -The technical contracts are split by responsibility: +That smaller design would stop the visible error but would not safely discover project integrations, identify dynamic entries, coordinate interrupted cleanup, or distinguish user replacements from Symposium output. This RFD accepts the larger mechanism because those are core cleanup requirements, not optional polish. -- [Ownership and managed state](./ownership/README.md) explains receipts, signatures, fingerprints, lifecycle, collision handling, path safety, legacy evidence, and generated plugin packages. -- [Hook activation](./hook-activation/README.md) explains outer guards, project permits, global tombstones, root matching, degraded state, status integration, and the hook-path performance budget. -- [Cleanup engine](./cleanup-engine/README.md) explains command modes, bounded discovery, planning, mutation ordering, locks, retries, acknowledgements, finalization, reporting, and exit codes. +Other costs remain: -The proposed user documentation is: +- Receipts consume small linear storage and retain local paths until finalization. +- Every released static form becomes a compatibility fixture; every new artifact type needs an adapter. +- Project activation adds bounded hook latency and cannot ship until its benchmark gate passes. +- Some pre-receipt installations can reach only `ready-for-known-scopes`. +- Users must quit agents before cleanup and sync projects after moves or clones. -- [`cargo agents uninstall`](./cargo-agents-uninstall/README.md) -- [Managed integrations](./managed-integrations/README.md) +### Rationale and alternatives + +| Alternative | Why not chosen | +| --- | --- | +| Keep current behavior and document manual cleanup | Leaves stale hooks noisy and gives users no bounded inventory or ownership check | +| Add only an absent-binary hook guard | Stops the error but leaves managed configuration and private state behind | +| Delete by name, command, or marker | Resemblance does not prove current ownership, especially for dynamic or replaced entries | +| Scan the home directory or disks | Unbounded, privacy-invasive, and still incomplete across containers or removed media | +| Require positive permits for global hooks | Losing the state store would silently disable the recommended global installation | +| Retire every integration before mutation | A blocker would leave an otherwise usable installation completely inactive | +| Add `--force` | Bypassing identity checks could delete third-party state; acknowledgement gives a terminating path without claiming ownership | +| Add `cargo agents implode` to remove everything | Self-removal is not portable and duplicates Cargo's ownership of installed binaries | + +### Prior art + +1. **[`cargo uninstall`](https://doc.rust-lang.org/cargo/commands/cargo-uninstall.html).** Cargo removes packages recorded under its installation root. Symposium therefore cleans only the external state it understands before Cargo removes the package. + +2. **[`rustup self uninstall`](https://rust-lang.github.io/rustup/installation/).** Cleanup happens while the owning executable remains available. Symposium also requires current identity proof because it edits shared agent configuration. + +3. **[Homebrew Cask `uninstall` and `zap`](https://docs.brew.sh/Cask-Cookbook#stanza-zap).** Deeper cleanup is explicit and structured, while user-created files stay out of scope. + +4. **[Kubernetes finalizers](https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers/).** Durable retiring state preserves recovery evidence until owned cleanup finishes. + +These examples inform the workflow, but none establishes ownership of Symposium artifacts. Adapter-specific discovery and identity rules remain necessary. + +### Questions and future work + +| Class | Items | +| --- | --- | +| Required before acceptance | None | +| Implementation gates | Adapter working-directory conformance and hook latency benchmark | +| Bounded implementation choices | Rust type/module layout and exact JSON nesting within the documented stable fields | +| Future work | Native agent-plugin activation and new artifact adapters | + +If an adapter cannot establish a working directory inside the checkout or meet the latency gate, it cannot enable project-scoped guarded hooks. Native agent-plugin support may change which artifacts Symposium writes, but not the receipt-plus-identity ownership rule. + +### Proposed documentation + +- [`cargo agents uninstall`](./cargo-agents-uninstall/README.md) defines the proposed workflow, flags, output, and recovery guidance. +- [Managed integrations](./managed-integrations/README.md) explains receipts, identity, activation, and lifecycle for users. + +These remain proposed pages until implementation lands. ## Frequently asked questions ### Why does Cargo not perform this cleanup? -Cargo tracks installed package binaries. Symposium's effects also live in agent configuration and workspace paths that Cargo neither owns nor understands. Symposium cleans its domain while the binary exists; Cargo then removes the package it owns. +Cargo tracks installed package binaries. Symposium writes agent configuration and project artifacts that Cargo neither owns nor understands. Symposium cleans its domain first; Cargo then removes the package. + +### Why is a receipt not enough? -### Why is there no `--force`? +A receipt proves that Symposium intended to write at a location. The user or another program may later replace that entry. Current identity evidence must still match before cleanup mutates it. -A force that bypasses identity checks could delete another program's state. `--acknowledge` instead preserves the artifact, transfers responsibility to the user, and shows the exact manual action. A preserved unguarded reference to `cargo-agents` remains a blocker until it is removed. +### Why must agents be quit first? -## Implementation plan +An agent may keep settings in memory and rewrite an old file on exit. Quitting first removes that race. Restarting after package removal reloads the cleaned configuration. -### Step 1: Add ownership primitives +## Implementation plan and status -Add normalized paths, managed IDs, versioned receipts, signatures, fingerprints, lifecycle recovery, coverage origin, and artifact-specific identity adapters. +Implementation has not begun. The steps are dependency-ordered. -- [ ] PR: ownership and managed-state primitives, with schema, path, collision, legacy, and failure-injection tests +### Step 1: Establish ownership primitives + +Add path identity, managed IDs, versioned receipts, lifecycle recovery, coverage origin, static signatures, dynamic fingerprints, and artifact validation. This step has no dependency and changes no production write or runtime behavior. + +Verify schema evolution, crash recovery, path safety, secret exclusion, signatures, fingerprints, collisions, and generated-tree manifests. + +- [ ] PR: ownership and managed-state primitives ### Step 2: Route managed writers -Route hook, MCP, skill, generated-file, generated-plugin-package, cache, and workspace-state writes through the managed-mutation layer without changing their behavior. +Depends on Step 1. Route hook, MCP, skill, generated-file, plugin-package, cache, and workspace-state writes through managed mutation. Writes gain receipts, but their external behavior remains unchanged; exact legacy migration does not change legacy hook execution. + +Verify lifecycle recovery, concurrent edits, collision handling, Goose and surrounding-format preservation, and unchanged adapter behavior apart from managed identity. + +- [ ] PR: managed writers and legacy migration -- [ ] PR: central managed writers, Goose block editing, and formatting- and secret-preservation tests +### Step 3: Add guarded hook activation -### Step 3: Guard and activate hooks +Depends on Step 2. Add exact shell fixtures, project permits, global tombstones, root resolution, degraded classification, SessionStart guidance, and managed health in status. New and migrated registrations change behavior here; unmigrated legacy registrations do not. -Publish the exact shell fixtures, implement project permits and global tombstones, resolve registration-owning roots, add bounded degraded classification and SessionStart guidance, and connect the shared health snapshot to status. +Verify every adapter's working-directory contract, scope isolation, missing and corrupt state, stripped `PATH`, shell escaping, one-time guidance, repair, status, and the platform latency budget. -- [ ] PR: guarded hooks, activation state, adapter working-directory contracts, migration, and latency tests +- [ ] PR: guarded hooks, activation state, and status health -### Step 4: Add the planner and cleanup engine +### Step 4: Add uninstall planning and cleanup -Add bounded discovery, dry-run, tracked-file policy, acknowledgements, ordered mutation, retries, recovery, locks, telemetry coordination, finalization, human output, JSON, and exit codes. +Depends on Steps 1-3. Add minimal-startup dispatch, bounded discovery, dry-run, tracked-file policy, acknowledgements, ordered mutation, recovery, locks, telemetry finalization, reporting, and exit codes. This step introduces `cargo agents uninstall`. -- [ ] PR: uninstall command and deterministic end-to-end failure and concurrency tests +Verify preview/apply parity, tracked and read-only files, acknowledgement invalidation, every interruption boundary, idempotent reruns, contention, retry limits, finalization failures, all assessments, and the original stale-hook regression. -### Step 5: Complete adapters, platforms, and documentation +- [ ] PR: uninstall command and cleanup engine -Exercise global and project scope for every supported adapter on Linux, macOS, and Windows, including stripped `PATH`, tracked repositories, interruptions, concurrent agents, and the original stale-hook regression. Update the command, hook, state, module-structure, important-flow, and telemetry documentation. +### Step 5: Complete compatibility and documentation -- [ ] PR: adapter and platform completion, documentation, and compatibility with the existing status command +Depends on Step 4. Run every supported adapter and both scopes on Linux, macOS, and Windows, then publish the proposed pages and update shipped hook, state, telemetry, module-structure, and important-flow documentation. -Each step leaves the codebase working and extends the existing deterministic integration harness. It does not depend on rewriting that harness. +Verify exact adapter fixtures, concurrent agents, tracked repositories, stripped `PATH`, the deterministic integration harness, mdBook, formatting, clippy, and workspace tests. -## Implementation status +- [ ] PR: adapter rollout, platform completion, and documentation -This RFD describes proposed behavior. Implementation has not begun. +The plan extends the existing integration harness; it does not require rewriting it. diff --git a/md/rfds/symposium-uninstall/cargo-agents-uninstall/README.md b/md/rfds/symposium-uninstall/cargo-agents-uninstall/README.md index 7e532a45..c4d169c6 100644 --- a/md/rfds/symposium-uninstall/cargo-agents-uninstall/README.md +++ b/md/rfds/symposium-uninstall/cargo-agents-uninstall/README.md @@ -2,69 +2,43 @@ > Proposed user documentation for the [Managed Symposium uninstall RFD](../README.md). It does not describe released behavior yet. -Remove agent integrations and private local data managed by Symposium before removing the Symposium package. +This command removes agent integrations and private local data managed by Symposium. The [cleanup-engine contract](../cleanup-engine/README.md) defines its internal behavior. -## Usage - -```console -cargo agents uninstall [--dry-run] [--include-tracked] - [--acknowledge ]... - [--quiet] [--json] -``` - -The command examines all known global and recorded project scopes. It may run inside or outside a Cargo workspace and never scans the entire filesystem. - -## Recommended removal workflow +## Recommended workflow -1. Quit all running coding agents that may have loaded Symposium configuration. -2. From an ordinary terminal, run: +1. Quit coding agents that may have loaded Symposium configuration. +2. Run: ```console cargo agents uninstall ``` -3. Resolve or acknowledge reported blockers and rerun as directed. +3. Resolve reported blockers and rerun as directed. 4. Run: ```console cargo uninstall symposium ``` -5. Start the coding agents again. +5. Start the agents again. -Quitting first prevents an agent from writing a cached settings file after cleanup removes its entry. Starting after package removal makes agents reload the cleaned configuration. +Quitting first prevents an agent from restoring cached settings after cleanup. Restarting last reloads the cleaned configuration. -## What it removes - -When ownership can be verified, the command removes: - -- Symposium hook registrations; -- Symposium-managed MCP server entries; -- generated or mirrored skills; -- dedicated generated files and directories; -- private plugin and installation caches; -- telemetry, logs, and private runtime state; and -- receipts, project permits, global retirement tombstones, and notice records after external cleanup succeeds. - -The command uses known global locations, ownership receipts, project permits, tombstones, the current project when available, and recorded historical roots. It removes only the managed entry from a shared configuration file. Goose YAML uses verified marker-delimited block removal so surrounding comments and formatting remain unchanged. - -## What it preserves +## Usage -The command preserves: +```console +cargo agents uninstall [--dry-run] [--include-tracked] + [--acknowledge ]... + [--quiet] [--json] +``` -- `config.toml`, including plugin declarations and telemetry preferences; -- custom plugin sources and user-authored skills; -- entries changed or replaced by another program; -- shared tools installed through Cargo or another package manager; -- effects of arbitrary legacy installation scripts that cannot be identified safely; -- tracked project configuration unless `--include-tracked` is supplied; and -- every artifact whose ownership or target safety is ambiguous. +The command examines known global and recorded project scopes. It works inside or outside a Cargo workspace and never scans the entire filesystem. -Private caches and discovery state are retained when cleanup has blockers so a rerun or `cargo agents sync` can repair the installation. +`--quiet` suppresses progress, not errors or the final assessment. The existing global `--json` selects machine-readable output. -## Dry run +## Preview cleanup -`--dry-run` takes a consistent shared-lock snapshot and performs the same bounded discovery, path validation, Git classification, and ownership verification as cleanup without writing or deleting anything. It reports `Would remove`, `Preserved`, and `Blocked` items. Blockers return status 3; an unreliable preview returns status 1. +`--dry-run` performs the same bounded discovery, path validation, Git classification, and ownership checks as cleanup without writing or deleting. It reports `Would remove`, `Preserved`, and `Blocked` items. ```console $ cargo agents uninstall --dry-run @@ -79,21 +53,48 @@ Preserved Preview complete. Apply with `cargo agents uninstall`. ``` -Apply mode rechecks every target because an agent or another program may change configuration after the preview. +A preview with blockers exits 3; an unreliable preview exits 1. Apply rechecks every target because configuration may change after preview. + +## What cleanup removes + +When ownership is verified, cleanup removes: + +- hook registrations and managed MCP entries; +- generated or mirrored skills, files, and directories; +- private plugin and installation caches; +- telemetry, logs, and runtime state; and +- cleanup records after external cleanup succeeds. + +From a shared configuration file, it removes only the managed entry. Goose YAML uses verified block removal so surrounding comments and formatting remain unchanged. + +Cleanup preserves: -## Tracked files and acknowledgements +- `config.toml`, custom plugin sources, and user-authored skills; +- entries changed or replaced by another program; +- tools installed through Cargo or another package manager; +- arbitrary legacy script effects that cannot be identified safely; +- tracked project configuration unless `--include-tracked` is used; and +- ambiguous artifacts. + +If blockers remain, private discovery state stays available for repair or rerun. + +## Tracked files + +A hook in Git-tracked project configuration is preserved by default as “committed by your project.” `--include-tracked` authorizes removal of only the verified Symposium entry, not the surrounding file or unrelated entries. + +If no ancestor contains a `.git` file or directory, uninstall treats the project as outside Git and does not require a Git executable. -A hook inside a Git-tracked project file is preserved by default and reported as “committed by your project.” Use `--include-tracked` to authorize removal of the verified Symposium entry. The surrounding file and unrelated entries remain untouched. +## Acknowledging a blocker -When no ancestor contains a `.git` file or directory, uninstall treats the project as outside Git and does not require a Git executable. +`--acknowledge ` preserves an artifact and transfers responsibility to you. The report gives its location and a redacted manual edit. This is not `--force` and never weakens ownership checks. -`--acknowledge ` keeps an artifact but transfers responsibility for it to you. The ID is stable for the same artifact kind, adapter, normalized target, and structural location. The acknowledgement also records current identity, so changing the artifact invalidates it. The report prints its location and a redacted fragment for manual removal. This is not a force option and does not weaken ownership checks. +The ID stays stable for the same artifact kind, adapter, normalized target, and structural location. Changing the artifact invalidates the acknowledgement. An unguarded hook or MCP server that still launches `cargo-agents` cannot be acknowledged into a ready result. Remove that live reference before uninstalling the package. -## Output +## Results -A completed run reports what was actually removed: +A successful run reports actual removals: ```text Removed @@ -113,53 +114,40 @@ No remaining live Symposium integrations in recorded scopes. Next: run `cargo uninstall symposium`, then start your coding agents. ``` -For an installation upgraded from a version that may have unrecorded projects, the final assessment instead says: +For an upgraded installation that may contain unrecorded projects, the result instead says: ```text No remaining Symposium integrations in known scopes. Older unrecorded project integrations may still exist; see the preserved items above. ``` -The command never claims knowledge of a project it cannot discover. - -If cleanup is incomplete, `Blocked` identifies every live reference or operational failure and `Next steps` gives the exact rerun, sync, flag, acknowledgement, or manual edit. Completed removals remain complete. - -## JSON output - -The existing global `--json` option emits one versioned document containing: - -- `mode`; -- `binary_removal_assessment`: `ready`, `ready-for-known-scopes`, or `blocked`; -- exact actions and already-absent artifacts; -- preserved and acknowledged items; -- blockers; and -- next steps. +Incomplete cleanup lists every live reference or operational failure under `Blocked`; `Next steps` gives the exact flag, sync, acknowledgement, rerun, or manual edit. Completed removals remain complete. -Items include stable reason codes and whether they remain a live binary reference. Paths are included where useful; secret values and plugin source contents are not. +## JSON and exit status -## Exit status +The existing global `--json` emits one versioned document with `mode`, `binary_removal_assessment`, actions, preserved and acknowledged items, blockers, and next steps. The assessment is `ready`, `ready-for-known-scopes`, or `blocked`. Items carry stable reason codes and a live-reference flag. Useful paths are included; secret values and plugin source contents are omitted. | Status | Meaning | -|---|---| +| --- | --- | | 0 | Preview or cleanup completed with no live blockers in its assessment boundary | | 1 | An operational failure prevented reliable planning or verification | | 2 | Command-line usage error | | 3 | Preview or cleanup completed, but live blockers remain | -Missing artifacts are already absent. Invalid receipts, unsafe paths, ownership conflicts, indeterminate tracked state, persistent locks, unsupported schemas, and failed verification are not silently treated as success. +Missing artifacts are already absent. Invalid receipts, unsafe paths, identity conflicts, indeterminate tracking, persistent locks, unsupported schemas, and failed verification do not become silent success. -## Copied, moved, and containerized projects +## Cloned, moved, or containerized projects -Project registrations are inactive until their exact registration-owning root has been synchronized locally. A nested checkout cannot inherit a parent checkout's permit. After cloning, copying, moving, or opening a project in a new container, run: +Project registrations activate only for a locally synchronized registration-owning root. After cloning, copying, moving, or opening a project in a new container, run: ```console cargo agents sync ``` -An inactive SessionStart may tell you to run that command once per root, but it does not load plugins or execute project-provided code. Other inactive hook events exit successfully without output. Global hook registrations remain global and do not gain an untrusted-project boundary from this mechanism. Deleting the managed store does not disable an exact verifiable global registration; only an uninstall retirement tombstone does. An ambiguous invocation does not run plugins. +An inactive SessionStart may show that command once without loading plugins or executing project code. Other inactive events exit successfully without output. Global hooks remain global; this activation model is not an untrusted-workspace boundary for them. -Registrations created before managed receipts and guards are a transition case. Uninstall can inspect the current project and roots retained in historical state, but older manual syncs did not always record a root. Before removing a pre-receipt installation, sync any known dormant project-scoped checkouts. +Older project registrations are a transition case. Historical state finds some roots, not every manual pre-receipt sync. Before removing an old installation, sync any known dormant project checkouts. ## Interrupted cleanup -If cleanup is interrupted, rerun `cargo agents uninstall` to resume. If you decide to keep using Symposium instead, run `cargo agents sync`; it repairs matching registrations left in a retiring state. `cargo agents status` reports corrupt, unavailable, degraded-global, inactive, or repairable state and names the next command. +Rerun `cargo agents uninstall` to resume. If Symposium should remain installed, run `cargo agents sync` to restore matching registrations left retiring. `cargo agents status` reports inactive, retiring (with repair guidance), corrupt, unavailable, degraded-global, or cleanup-in-progress state and names the next command. diff --git a/md/rfds/symposium-uninstall/cleanup-engine/README.md b/md/rfds/symposium-uninstall/cleanup-engine/README.md index 0e13c3b5..6316e2f6 100644 --- a/md/rfds/symposium-uninstall/cleanup-engine/README.md +++ b/md/rfds/symposium-uninstall/cleanup-engine/README.md @@ -1,12 +1,12 @@ # Cleanup engine -## TL;DR +> Normative contract for uninstall discovery, planning, mutation, recovery, reporting, and finalization proposed by this RFD. -- Uninstall discovers only known locations and recorded project roots. -- Dry-run and apply share the same planning and ownership classification. -- External artifacts are retired, mutated, and verified one at a time. -- Blocked or interrupted runs retain all discovery and repair evidence. -- Symposium-private state is finalized only after external integrations are absent or acknowledged. +## Summary + +One rule controls the algorithm: never discard evidence needed to finish or repair cleanup. + +A normal run discovers known targets, prints its plan, retires and removes each external artifact separately, verifies absence, then deletes private state. If one target is blocked or the process stops halfway, completed work stays complete while receipts and discovery state remain for a rerun. ## Command surface @@ -16,37 +16,33 @@ cargo agents uninstall [--dry-run] [--include-tracked] [--quiet] [--json] ``` -`--dry-run` performs the same discovery, path validation, Git classification, and ownership verification as apply. It creates no receipt state or tombstone and changes no target. - -`--include-tracked` permits structural removal from tracked project configuration after the ordinary identity checks. It never authorizes whole-file deletion. - -`--acknowledge` preserves a blocker and transfers responsibility to the user. It does not weaken deletion proof. - -`--quiet` suppresses progress, but not errors or the final assessment. The existing global `--json` flag emits one versioned document on stdout while diagnostics remain on stderr. +- `--dry-run` uses the apply planner but creates no receipt, tombstone, or target change. +- `--include-tracked` permits removal of a proven Symposium structure from tracked configuration, never whole-file deletion. +- `--acknowledge` preserves a blocker and transfers responsibility without weakening deletion proof. +- `--quiet` suppresses progress, not errors or the final assessment. +- The existing global `--json` emits one versioned stdout document; diagnostics stay on stderr. | Exit code | Meaning | | --- | --- | -| `0` | Planning or cleanup completed reliably with no live blockers | +| `0` | Preview or cleanup completed reliably with no live blockers | | `1` | An operational failure prevented reliable planning or verification | | `2` | Command-line usage error | -| `3` | Planning or cleanup completed reliably, but live blockers remain | - -A dry run with blockers exits `3`. +| `3` | Preview or cleanup completed reliably, but live blockers remain | ## Bounded discovery -Uninstall never crawls the user's home directory or disks. It examines only: +Uninstall never crawls the home directory or disks. It examines only: -1. known global configuration targets for supported adapters; -2. the current workspace when one is explicitly available; -3. targets and roots named by receipts; -4. roots or targets named by permits, notices, tombstones, or acknowledgements; -5. legacy workspace-state files that already contain a root; and +1. known global targets for supported adapters; +2. the current workspace when explicitly available; +3. targets and roots in receipts; +4. roots or targets in permits, notices, tombstones, or acknowledgements; +5. legacy workspace-state files that contain a root; and 6. fixed Symposium-private directories. A deleted, moved, or renamed root costs one failed bounded lookup. A project at a new path becomes a separate scope after `cargo agents sync`. -Historical workspace state improves discovery but is not a complete inventory because earlier versions did not record every manually synchronized root. The final assessment reports that limitation instead of claiming knowledge of an unknown pre-receipt checkout. +Historical workspace state is incomplete because earlier versions did not record every manually synchronized root. The final assessment reports this limit instead of claiming knowledge of an unknown pre-receipt checkout. ## Planning @@ -58,32 +54,32 @@ Every candidate receives one disposition: | Already absent | The recorded artifact no longer exists | | Preserved | Policy excludes it from automatic cleanup | | Acknowledged | The user accepted responsibility for the preserved artifact | -| Conflicting | The current artifact no longer matches Symposium's evidence | -| Operationally unverifiable | A reliable ownership or absence decision could not be made | +| Conflicting | The artifact no longer matches Symposium's evidence | +| Operationally unverifiable | Ownership or absence cannot be decided reliably | -The engine builds and prints the complete plan before the first mutation. Ownership decisions use the rules in [Ownership and managed state](../ownership/README.md). +The engine prints the complete plan before mutation. Ownership follows [Ownership and managed state](../ownership/README.md). -Dry-run stops after planning. It takes shared locks for a consistent snapshot and never writes recovery state. +Dry-run stops after planning. It takes shared locks for a consistent snapshot and writes no recovery state. A dry run with blockers exits `3`. ## Tracked project configuration -Before planning a project-file mutation, uninstall walks ancestors for a `.git` file or directory without launching Git. If none exists, ordinary ownership rules apply and Git need not be installed. +Before planning a project-file mutation, uninstall checks ancestors for a `.git` file or directory without launching Git. If none exists, Git is not needed. When a repository exists: -- tracked configuration is preserved by default and reported as committed by the project; -- `--include-tracked` authorizes removal of only the proven Symposium structure; -- an unavailable or indeterminate Git tracking query preserves the file as a blocker; and -- reports name the file and structural locator without printing secrets. +- tracked configuration is preserved as project-committed by default; +- `--include-tracked` authorizes removal only of proven Symposium structure; +- unavailable or indeterminate Git tracking preserves the file as a blocker; and +- reports identify the file and structural locator without secrets. -Acknowledgement may transfer an entry to the user, but preserving a live unguarded `cargo-agents` invocation or an MCP server that launches it cannot produce a clean assessment. That reference must be removed manually or through `--include-tracked` before the package is removed. +Acknowledgement may transfer an entry to the user. It cannot make a live unguarded `cargo-agents` hook or MCP invocation safe for package removal. That reference must be removed manually or with `--include-tracked`. ## Applying the plan -Apply follows these phases: +Apply runs these phases: 1. Acquire the exclusive installation barrier. -2. Load and reconcile receipts, activation records, acknowledgements, signatures, and bounded legacy roots. +2. Reconcile receipts, activation records, acknowledgements, signatures, and bounded legacy roots. 3. Discover, classify, and print the complete plan. 4. For each removable external artifact: 1. mark only its receipt `retiring`; @@ -91,44 +87,44 @@ Apply follows these phases: 3. lock, reread, and revalidate the target; 4. remove only the verified structure; 5. verify absence; and - 6. retain the completed receipt until finalization. -5. If blockers remain, keep every receipt, activation record, cache, workspace record, log, and telemetry file needed for repair or a rerun. + 6. keep the completed receipt until finalization. +5. If blockers remain, keep every receipt, activation record, cache, workspace record, log, and telemetry file needed for repair or rerun. 6. Otherwise finalize telemetry and Symposium-private state. 7. Recompute the assessment, delete completed lifecycle records, release locks, and report. -Retirement is per artifact and occurs immediately before that mutation. Uninstall never disables all hooks as an initial global phase. Private discovery state is never removed while a live blocker remains. +Retirement happens per artifact immediately before mutation; uninstall never disables all hooks up front. Discovery state remains while any live blocker exists. -Within a shared configuration file, cleanup parses and revalidates the current structure, edits only the owned entry, writes a sibling temporary file, flushes, atomically replaces, reopens, and verifies. Goose delegates to its verified marker-delimited block editor. A concurrent content change replans the target rather than overwriting it. +Shared configuration uses parse, revalidation, owned-entry editing, sibling temporary write, flush, atomic replacement, reopen, and absence verification. Goose uses its verified marker-delimited block editor. Concurrent change replans the target instead of overwriting it. ## Failure and recovery -A failure before external mutation restores that artifact to `applied` when its registration still matches, republishing its project permit or removing its global tombstone as appropriate. +Failure before external mutation restores that artifact to `applied` when the registration still matches, republishing its project permit or removing its global tombstone. -A crash after retirement leaves only that artifact inactive and repairable. Successful removals are not rolled back. Rerunning uninstall resumes cleanup; `cargo agents sync` restores a still-applied retiring registration. +A crash after retirement leaves only that artifact inactive and repairable. Successful removals are not rolled back. Rerun uninstall to continue; `cargo agents sync` restores a matching applied registration. -Transient filesystem failures receive one initial attempt and at most two bounded retries. Every retry reopens and revalidates the target. Permission failures, identity conflicts, unsafe links, indeterminate Git state, and concurrent content changes become blockers rather than unbounded retry loops. +Transient filesystem failures get one initial attempt and at most two bounded retries. Each retry reopens and revalidates. Permission failures, identity conflicts, unsafe links, indeterminate Git state, and concurrent changes become blockers, not retry loops. -The command is idempotent: a verified absence is an `Already absent` result, and a rerun does not recreate removed state. +The command is idempotent: verified absence is `Already absent`, and reruns do not recreate removed state. ## Concurrency and locks -Managed mutation uses this lock order: +Managed mutation uses this order: ```text installation barrier - → managed-state lock - → global target when needed - → workspace targets sorted by normalized path + -> managed-state lock + -> global target when needed + -> workspace targets sorted by normalized path ``` -- Uninstall holds the installation barrier exclusively for plan, mutation, and verification. -- Dry-run holds the barrier and discovered targets in shared mode so it cannot observe a target mid-mutation. -- Init, sync, and repair use shared installation access with exclusive locks for state and targets they change. -- Hook-triggered auto-sync uses a non-blocking try-lock. On contention it skips that cache refresh and continues from already-published state. +- Uninstall holds the installation barrier exclusively through planning, mutation, and verification. +- Dry-run holds the barrier and discovered targets in shared mode. +- Init, sync, and repair share installation access and exclusively lock changed state and targets. +- Hook auto-sync uses a non-blocking try-lock; contention skips that cache refresh and uses published state. -After dry-run locks its discovered targets, it checks the managed-state generation. One change retries the snapshot; a second is an operational failure rather than an inconsistent preview. +After dry-run locks its targets, it checks the managed-state generation. One change retries the snapshot; a second returns an operational failure. -Platform implementations use native advisory locking and multi-process tests. Lock diagnostics may include operation, process, and start metadata, but age alone never proves a lock is stale. +Platforms use native advisory locking and multi-process tests. Diagnostics may include operation, process, and start metadata, but age alone never proves a lock stale. ## Blockers and acknowledgements @@ -138,66 +134,53 @@ A blocker ID is stable over: artifact type + adapter + normalized target + structural locator ``` -The acknowledgement stores that complete locator and the artifact's current identity instead of trusting the display ID alone. A moved locator creates a new blocker, and a changed artifact invalidates the acknowledgement. +The acknowledgement stores that locator and the artifact's current identity, not only its display ID. Moving the locator creates a new blocker; changing the artifact invalidates acknowledgement. -Acknowledgement: +Acknowledgement preserves the artifact, records user responsibility, retires Symposium's claim, prints a redacted manual edit, and makes later installation treat the occupied slot as a structural collision. -- preserves the artifact; -- records that the user accepts responsibility; -- retires Symposium's ownership claim; -- prints a redacted manual edit; and -- makes a later installation treat the occupied slot as a structural collision. - -Acknowledgement records can be deleted at successful finalization because collision detection inspects the current occupied entry. There is no `--force`: bypassing identity checks would permit deletion of user or third-party state. +Successful finalization may delete acknowledgements because collision detection inspects the occupied entry. There is no `--force`: bypassing identity checks could delete user or third-party state. ## Minimal startup and finalization -Uninstall dispatches before ordinary startup. It initializes only argument parsing, managed-state path resolution, minimal diagnostics, locking, cleanup, and reporting. It does not refresh registries, load plugins, run update checks, auto-sync, or initialize ordinary telemetry recording. +Uninstall dispatches before ordinary startup. It initializes only arguments, managed-state path resolution, minimal diagnostics, locking, cleanup, and reporting. It does not refresh registries, load plugins, check for updates, auto-sync, or initialize ordinary telemetry recording. -Telemetry finalization uses the telemetry subsystem's supported coordination path. If telemetry cannot be finalized, the command retains discovery and recovery state and reports a blocker. The uninstall result itself is not recorded as new telemetry. +Telemetry finalization uses the subsystem's supported coordination path. Failure retains discovery and recovery state and becomes a blocker. The uninstall result is not recorded as new telemetry. -Only after every external integration is absent or validly acknowledged does cleanup remove private caches, workspace state, telemetry, logs, receipts, permits, tombstones, notices, acknowledgements, and the empty managed-state directory. It verifies finalization before reporting success. +Only after every external integration is absent or validly acknowledged does cleanup remove private caches, workspace state, telemetry, logs, receipts, permits, tombstones, notices, acknowledgements, and the empty managed-state directory. It verifies finalization before success. ## Reporting -Human output groups: - -- `Removed`; -- `Already absent`; -- `Preserved`; -- `Acknowledged`; -- `Blocked`; and -- `Next steps`. +Human output groups `Removed`, `Already absent`, `Preserved`, `Acknowledged`, `Blocked`, and `Next steps`. -Every machine-readable item has a stable kind, adapter, scope, target, structural locator where applicable, disposition, reason code, and live-reference flag. Secret-bearing fields are redacted. The proposed [command reference](../cargo-agents-uninstall/README.md) defines the complete JSON shape and human examples. +Machine-readable items carry stable kind, adapter, scope, target, structural locator where applicable, disposition, reason code, and live-reference flag. Secret fields are redacted. The [command reference](../cargo-agents-uninstall/README.md) shows human and JSON output. ## Removal assessment | Assessment | Meaning | | --- | --- | -| `ready` | No live integration remains in recorded and inspectable scopes, and no historical limitation applies | +| `ready` | No live integration remains in recorded and inspectable scopes, with no historical limit | | `ready-for-known-scopes` | Known scopes are clean, but a pre-receipt project may be unrecorded | | `blocked` | A live reference, ownership conflict, or operational verification failure remains | -The durable coverage origin defined by the ownership model makes this decidable: +Coverage origin makes this decidable: - only `managed-only` can produce `ready`; - `pre-receipt` and `unknown` produce at best `ready-for-known-scopes`; and - any live blocker produces `blocked`. -The origin is never promoted automatically. Output says that known scopes are clean rather than claiming universal safety when an unknown checkout may exist. +Origin is never promoted automatically. Output says known scopes are clean instead of claiming universal safety. -## Acceptance tests +## Verification -The feature extends the existing deterministic integration harness. Tests cover: +The deterministic integration harness covers: -- bounded global, current-workspace, receipt, and historical discovery; -- dry-run and apply classification parity, output, and exit codes; +- bounded global, workspace, receipt, and historical discovery; +- dry-run/apply parity, output, and exit codes; - tracked, untracked, read-only, and indeterminate-Git configuration; - interruption at every lifecycle boundary and idempotent reruns; -- target contention, shared previews, concurrent edits, and auto-sync try-locks; +- contention, shared previews, concurrent edits, and auto-sync try-locks; - stable acknowledgements, changed artifacts, and reinstall collisions; - two bounded retries and permanent failures; - telemetry and private-state finalization failures; -- `ready`, `ready-for-known-scopes`, and `blocked` assessments; and +- all three assessments; and - the original stale-global-hook regression after package removal. diff --git a/md/rfds/symposium-uninstall/hook-activation/README.md b/md/rfds/symposium-uninstall/hook-activation/README.md index 4f5fe11c..559ef21b 100644 --- a/md/rfds/symposium-uninstall/hook-activation/README.md +++ b/md/rfds/symposium-uninstall/hook-activation/README.md @@ -1,50 +1,40 @@ # Hook activation -## TL;DR +> Normative contract for generated hook guards, project activation, global retirement, degraded behavior, and hook-path cost proposed by this RFD. -- Every new hook command makes an absent `cargo-agents` binary a successful no-op. -- Project hooks run only with a local permit for the exact registration-owning root. -- Global hooks run unless uninstall has written a retirement tombstone. -- Missing or damaged state is either correctly inactive or visible through managed-health reporting. -- Legacy hooks retain their current runtime behavior until an exact signature is migrated. +## Summary -## Motivation +Two failures shape this contract: -Hook cleanup needs runtime coordination for two reasons. +- after `cargo uninstall symposium`, a stale hook must not fail because `cargo-agents` is absent; +- a committed project hook copied to another checkout must not run merely because its public managed ID is known. -First, an agent may invoke a registration after Cargo has removed `cargo-agents`. The generated command must treat that expected absence as success rather than reproducing the original hook error. +The outer shell guard solves the first. A local permit for the exact registration-owning root solves the second. Global hooks instead run until uninstall writes a retirement tombstone. -Second, a project registration may be committed and copied. Its managed ID is therefore public and cannot authorize execution in another checkout. Project activation needs local state bound to the exact checkout that owns the registration. - -These controls do not establish general workspace trust. A global hook is intentionally valid in every working directory and may discover workspace plugin configuration. Protecting global-hook users from hostile repositories would require a separate design that gates workspace plugin activation itself. +This is not general workspace trust. A global hook intentionally runs in every working directory and may discover workspace plugin configuration. Gating workspace plugins for global users requires a separate design. ## Runtime flow ```text agent invokes hook - ↓ -outer guard locates cargo-agents - ↓ -preflight classifies project or global scope - ↓ -permit or tombstone decision - ↓ -ordinary hook startup and plugin dispatch + -> outer guard locates cargo-agents + -> preflight classifies project or global scope + -> permit or tombstone decision + -> ordinary startup and plugin dispatch ``` -The outer guard handles binary absence. The in-process preflight handles scope and retirement before ordinary startup loads plugins, refreshes registries, or auto-syncs. +The outer guard handles binary absence. In-process preflight decides scope and retirement before plugin loading, registry refresh, or auto-sync. ## Scope classification -Every new invocation carries `--managed-id `, but does not carry an authoritative scope. Preflight classifies scope from a directly addressed receipt, project permit, or global tombstone. Text in the invocation is never sufficient. +New invocations carry `--managed-id `, not an authoritative scope. Preflight uses a directly addressed receipt, project permit, or global tombstone. Invocation text alone is insufficient. -When state for the ID is missing or unavailable, preflight performs a bounded degraded classification. It checks the registration-owning-root walk and the adapter's known global target for an exact released signature containing that ID: +When state for the ID is missing or unavailable, bounded degraded classification checks only: -- one project match remains inactive and may show the sync hint; -- one global match runs and reports degraded health; and -- zero or multiple matches deny plugin dispatch and report ambiguity. +- the registration-owning-root walk; and +- the adapter's known global target. -The fallback opens only known adapter configuration files and does not invoke Cargo. +It requires an exact released signature containing that ID. One project match stays inactive and may show the sync hint. One global match runs and reports degraded health. Zero or multiple matches deny plugin dispatch and report ambiguity. This path opens only known adapter files and never invokes Cargo. ## Project activation @@ -54,71 +44,71 @@ A project permit binds: managed ID + normalized registration-owning root ``` -Preflight starts at the process working directory and walks a bounded number of ancestors. The registration-owning root is the nearest ancestor whose adapter project configuration contains an exact released registration for this managed ID. A nearer configuration without that registration is ignored. +Starting at the process working directory, preflight walks a bounded number of ancestors. The owning root is the nearest ancestor whose adapter project configuration contains an exact released registration for this managed ID. A nearer configuration without that registration is ignored. -Execution requires: +Execution requires all four conditions: 1. trusted state or exact degraded classification identifies project scope; 2. a registration-owning root exists; 3. the permit carries the same managed ID; and -4. the normalized permit root equals the normalized owning root. +4. normalized permit and owning roots are equal. -The permit root being merely an ancestor of the working directory is not enough. A nested checkout containing the copied registration resolves to its own root and is denied. Sync refuses to permit the filesystem root or the user's home directory. +An ancestor permit alone is insufficient. A nested checkout containing the copied registration resolves to its own root and is denied. Sync refuses to permit the filesystem root or the user's home directory. -Adapters must invoke project hooks with a working directory inside the checkout. An adapter that cannot provide this contract cannot offer project-scoped guarded registrations. Adapter tests cover launches from the checkout root and nested directories. +Adapters must invoke project hooks with a working directory inside the checkout. An adapter unable to meet this contract cannot offer project-scoped guarded registrations. Tests cover launches from the checkout root and nested directories. -A moved checkout, a new clone, a dev container, WSL, and the Windows host are separate permit environments when they expose different normalized roots. Each needs `cargo agents sync`. +A move, clone, dev container, WSL environment, or Windows host is a separate permit environment when its normalized root differs. Each needs `cargo agents sync`. ## Global activation -Project and global registrations deliberately fail in opposite directions: +Project and global registrations fail in opposite directions: | Scope | Active when | | --- | --- | | Project | A positive permit matches the ID and owning root | | Global | No valid retirement tombstone exists for the ID | -Losing a project permit makes a copied registration inactive. Losing the managed-state directory must not silently disable the recommended global installation, so missing global state is not an activation denial. A global registration runs after receipt proof or one exact degraded signature match and records degraded health where possible. +Losing a project permit disables that checkout. Losing the managed-state directory does not disable the recommended global installation. A global registration runs after receipt proof or one exact degraded signature match, recording degraded health where possible. -Uninstall writes a global tombstone immediately before mutating that registration. A valid tombstone makes the hook exit successfully and quietly. A corrupt tombstone denies dispatch and surfaces a repair warning. +Uninstall writes a global tombstone immediately before mutating its registration. A valid tombstone exits successfully and quietly. A corrupt tombstone denies dispatch and surfaces repair guidance. ## Preflight outcomes | Classification and state | Behavior | | --- | --- | -| Project permit matches ID and owning root | Continue to ordinary hook startup | -| Project permit missing or non-matching in a readable store | Remain inactive; SessionStart may give one sync hint | +| Project permit matches ID and owning root | Continue to ordinary startup | +| Project permit missing or non-matching in a readable store | Stay inactive; SessionStart may give one sync hint | | Project state corrupt or unavailable | Deny plugin dispatch and surface repair guidance | -| Valid global tombstone present | Exit successfully and quietly | -| Corrupt global tombstone present | Deny plugin dispatch and surface repair guidance | +| Valid global tombstone | Exit successfully and quietly | +| Corrupt global tombstone | Deny plugin dispatch and surface repair guidance | | Global registration proven, no tombstone | Continue; report degraded health when state was missing | | Missing state with zero or multiple exact matches | Deny plugin dispatch and report ambiguity | -Correctly inactive project events are quiet except for the bounded SessionStart guidance described below. An inability to determine state is not silently treated as correct inactivity. +Correct inactivity is quiet except for bounded SessionStart guidance. Unavailable or indeterminate state is not silently treated as correct inactivity. ## Inactive SessionStart -An inactive project SessionStart may perform preflight and emit static `additionalContext` naming `cargo agents sync`. It may not read workspace plugin configuration, refresh a registry, run plugin code, or auto-sync. +An inactive project SessionStart may run preflight and emit static `additionalContext` naming `cargo agents sync`. It may not read workspace plugin configuration, refresh a registry, run plugin code, or auto-sync. -A per-root notice record suppresses repeated guidance. Records are capped at 64 roots per managed ID; beyond the cap, new roots receive no stored or repeated hint and status reports the suppressed count. Other inactive events return success without output. +A per-root notice suppresses repeat guidance. Records are capped at 64 roots per managed ID. Beyond that cap, new roots receive no stored or repeated hint, and status reports the suppressed count. Other inactive events return success without output. -If a writable managed store contains corrupt state, preflight records a health flag and one best-effort log line. Corrupt records are quarantined and recreated by the next explicit sync. When the store itself is unavailable, status detects that condition on demand and SessionStart provides the only guaranteed warning; Symposium does not invent a second fallback state directory. +If a writable managed store contains corrupt state, preflight records a health flag and one best-effort log line. The next explicit sync quarantines corrupt records and recreates derived state. If the store itself is unavailable, status detects it on demand and SessionStart provides the only guaranteed warning; Symposium creates no fallback state directory. ## Status and repair -The managed-state layer exposes one read-only, versioned health snapshot for the existing or concurrently developed `cargo agents status` command. It reports inactive, retiring, corrupt, unavailable, and cleanup-in-progress states with stable reason codes and recommended commands. +Today `cargo agents status` reports plugin enablement for the active workspace. This proposal adds a read-only, versioned managed-health snapshot. The same command can then report inactive, retiring, corrupt, unavailable, degraded-global, and cleanup-in-progress states with stable reason codes and commands. -Status does not repair state as a side effect. It reads under the installation barrier in shared mode. While uninstall owns the exclusive barrier, status reports cleanup in progress rather than inspecting half-mutated targets. Corrupt or unreadable state produces a diagnostic snapshot rather than a panic. +Status never repairs state. It reads under the shared installation barrier. While uninstall holds the barrier exclusively, status reports cleanup in progress instead of inspecting partial mutation. Corrupt or unreadable state produces a diagnostic snapshot, not a panic. -`cargo agents sync` is the explicit repair path. If a retiring receipt still matches an applied registration, sync restores it to `applied`, republishes its project permit, or removes its global tombstone. +`cargo agents sync` is the repair path. If a retiring receipt still matches an applied registration, sync returns it to `applied`, republishes its project permit, or removes its global tombstone. ## Generated outer guards -The outer guard has one job: an absent binary exits with status 0 and no output. Scope-aware preflight stays inside the binary. +The guard's only job is to make an absent binary exit 0 without output. Scope-aware preflight remains inside the binary. -Machine-local global registrations record and try the resolved absolute `cargo-agents` path first, then fall back to `PATH`. A committed project registration cannot contain another user's absolute path, so its portable form tries the Cargo home convention before `PATH`. +Machine-local global registrations try the absolute path resolved at registration, then `PATH`. Portable committed project forms cannot contain another user's path, so they try the Cargo home convention, then `PATH`. -The following are the versioned single-line command values before host serialization. +These are the versioned single-line command values before host serialization. POSIX machine-local global: @@ -144,31 +134,31 @@ PowerShell portable project: $cargoHome = $env:CARGO_HOME; if ([string]::IsNullOrWhiteSpace($cargoHome)) { $cargoHome = Join-Path $HOME '.cargo' }; $symposiumBin = Join-Path $cargoHome 'bin/cargo-agents.exe'; if (-not (Test-Path -LiteralPath $symposiumBin -PathType Leaf)) { $symposiumCommand = Get-Command cargo-agents -CommandType Application -ErrorAction SilentlyContinue; if ($null -eq $symposiumCommand) { exit 0 }; $symposiumBin = $symposiumCommand.Source }; $global:LASTEXITCODE = $null; & $symposiumBin hook --managed-id ; if ($null -eq $LASTEXITCODE) { exit 1 }; exit $LASTEXITCODE ``` -The POSIX literal encoder single-quotes the path and escapes embedded apostrophes as `'\''`. The PowerShell encoder single-quotes the path and doubles embedded apostrophes. Placeholders above are already encoded literals, never raw paths. +The POSIX encoder single-quotes paths and escapes apostrophes as `'\''`. The PowerShell encoder single-quotes paths and doubles apostrophes. Placeholders above are encoded literals, never raw paths. -Copilot publishes both shell forms. Versioned adapter fixtures assert the decoded command and exact JSON, TOML, or YAML serialization. They also cover absent binaries, stripped `PATH`, spaces and shell metacharacters, launch failures, status propagation, payload preservation, and an unchanged working directory. +Copilot publishes both shell forms. Versioned adapter fixtures assert decoded commands and exact JSON, TOML, or YAML serialization. They cover absent binaries, stripped `PATH`, spaces and metacharacters, launch failures, status propagation, payload preservation, and unchanged working directories. ## Legacy registrations -A hook invocation without a managed ID follows legacy behavior. Legacy hooks already run unguarded; adding on-disk verification to their hot path would introduce a new failure mode. Exact historical signatures are instead used during bounded migration and cleanup. The next `init` or `sync` may rewrite one into the guarded form. +An invocation without a managed ID keeps legacy behavior. On-disk hot-path verification would create a new failure mode. Exact historical signatures are used during bounded migration and cleanup; the next `init` or `sync` may rewrite them into guarded form. ## Agent plugin boundary -Project permits govern Symposium-dispatched hooks. A native agent-plugin directory is loaded by the agent and does not pass through hook preflight. Its scope must be enforced by project-scoped placement, a workspace-scoped agent registration, or a project-safe fallback. +Project permits govern Symposium-dispatched hooks. A native agent-plugin directory bypasses hook preflight. Its scope must come from project placement, a workspace-scoped agent registration, or a project-safe fallback. ## Performance Preflight performs no directory-wide scan, network access, registry refresh, plugin loading, Cargo metadata query, or subprocess. It reads directly addressed state, normalizes the working directory, and performs bounded adapter-configuration signature checks. -Guarded hooks may become the default only while p95 added preflight latency is no more than: +Guarded hooks become the default only while p95 added preflight latency is at most: ```text max(2 ms, 5% of baseline hook-dispatch latency) ``` -The baseline uses the same outer guard without managed-state preflight. CI records p50 and p95 for active, inactive, missing-store, and nested-checkout cases on Linux, macOS, and Windows. Receipt-store size must not change the number of hot-path reads or path probes. +The baseline uses the same outer guard without managed-state preflight. CI records p50 and p95 for active, inactive, missing-store, and nested-checkout cases on Linux, macOS, and Windows. Receipt-store size must not change hot-path read or path-probe counts. -## Acceptance tests +## Verification Tests cover: diff --git a/md/rfds/symposium-uninstall/managed-integrations/README.md b/md/rfds/symposium-uninstall/managed-integrations/README.md index 53d03b93..77ba13e1 100644 --- a/md/rfds/symposium-uninstall/managed-integrations/README.md +++ b/md/rfds/symposium-uninstall/managed-integrations/README.md @@ -2,114 +2,102 @@ > Proposed user documentation for the [Managed Symposium uninstall RFD](../README.md). It does not describe released behavior yet. -Symposium writes derived state into coding-agent configuration and project directories during init and sync. Managed integrations make those writes discoverable, identifiable, locally activatable, and removable without claiming ownership of surrounding user files. +Symposium writes derived state into agent configuration and project directories during init and sync. Managed integrations make those writes discoverable, identifiable, locally activatable, and removable without claiming ownership of surrounding user files. -## Three separate questions +The [ownership](../ownership/README.md), [hook-activation](../hook-activation/README.md), and [cleanup-engine](../cleanup-engine/README.md) pages are authoritative. -Managed state deliberately separates: +## Mental model -1. **Discovery:** where should Symposium look? Ownership receipts and known adapter locations answer this. -2. **Identity:** is the current artifact one Symposium manages? Released static signatures, dynamic fingerprints, markers, and manifests answer this. -3. **Execution:** may this project hook run here? A root-bound activation permit answers this. +Consider a project hook written to `.claude/settings.json`: -A managed ID connects records across those questions. It is non-secret, may appear in committed configuration, and grants neither deletion authority nor permission to execute by itself. +1. A **receipt** records where Symposium wrote it. +2. A **released signature** proves the current entry still has the form Symposium wrote. +3. A **project permit** says this local checkout may execute it. -## Ownership receipts +These answer different questions: -Every managed mutation receives a stable ID and a small receipt in Symposium's private configuration directory. The receipt records: +| Question | Evidence | +| --- | --- | +| Where should cleanup look? | Receipt and known adapter locations | +| Is this still Symposium's artifact? | Signature, fingerprint, marker, or manifest | +| May this project hook run here? | Root-bound permit | -- artifact type and adapter; -- global or project scope; -- normalized target and structural location; -- the non-secret identity evidence needed later; and -- lifecycle state: pending, applied, retiring, or acknowledged. +The shared managed ID only connects these records. It is public, may be committed, and grants neither deletion nor execution authority. -Receipts never contain project source, plugin source contents, environment values, HTTP headers, tokens, or command output. They do not expire because a workspace has been idle. Old or missing paths use a small amount of storage and are removed only when cleanup can safely finalize. +## Receipts and identity -Receipts make discovery bounded; they do not authorize deletion. A malformed or forged receipt still has to pass path containment, target-shape, link, and artifact-specific identity checks. +Every managed mutation gets a stable ID and a small private receipt containing artifact type, adapter, scope, normalized target, structural location, non-secret identity evidence, and lifecycle state: `pending`, `applied`, `retiring`, or `acknowledged`. -## Identity evidence +Receipts contain no project or plugin source, environment values, headers, tokens, or command output. They do not expire with inactivity. Missing or moved paths consume little storage and remain until cleanup can finalize safely. -Static registrations emitted by released Symposium versions have versioned structural signatures. The catalog includes current and historical hook forms, dedicated generated files, and static built-in MCP entries. Receipts say where to inspect; signatures prove the current structure still matches a released form. +A receipt discovers a target; it does not authorize deletion. Cleanup also requires artifact-specific evidence: -Plugin-provided MCP entries are dynamic. At write time Symposium records a secret-free fingerprint containing the adapter, target, structural container, name, transport, and command-plus-arguments or URL. Environment and header values are excluded. If the non-secret identity changes or the receipt is lost, cleanup preserves the entry. +- released structural signatures for static hooks, files, and built-in MCP entries; +- secret-free fingerprints for dynamic plugin-provided MCP entries; +- markers and manifests for generated output; and +- a unique verified marker-delimited byte range for Goose YAML. -Generated skills and other dedicated output use markers and manifests. Unknown contents, or a generated path replaced by a symlink or junction, prevent automatic deletion. +Changed entries, unknown generated contents, and targets replaced by links are preserved. Dynamic fingerprints exclude environment values, headers, and tokens; loss of the receipt therefore preserves a dynamic entry. -Goose MCP entries use managed-ID comment markers because its YAML file cannot be reserialized without losing comments and formatting. Cleanup verifies the one marked block and removes its exact byte range; any marker, indentation, or fingerprint mismatch preserves it. +All writers use one managed-mutation layer. New instances of existing hook, skill, MCP, or generated-package types gain this evidence automatically. Only a new kind of external side effect needs a new ownership adapter. -All managed writers use one central mutation layer, so new hooks, skills, and MCP instances receive the appropriate evidence automatically. Only a new kind of external side effect needs a new ownership adapter. - -Init and sync inspect the current structural slot before writing. An occupied slot without matching pending or applied ownership evidence is a collision, even when its value resembles a released Symposium form. Explicit legacy migration is classified separately. +An occupied structural slot is a collision unless matching pending or applied evidence exists. Init and sync do not adopt lookalike entries. Exact legacy migration is separate. ## Project hook activation -A project activation permit binds one managed ID to one normalized registration-owning root. Generated hooks carry the managed ID, but scope comes from trusted local state rather than command text. Before normal project hook startup, Symposium: - -1. obtains the process working directory without invoking Cargo; -2. walks its ancestors to a fixed depth for the nearest adapter project configuration containing an exact released registration for this managed ID; -3. normalizes that registration-owning root with the same function used by sync; and -4. continues only when the permit root equals that root exactly. +A project permit binds a managed ID to one normalized registration-owning root. Before plugin startup, Symposium walks upward from the hook's working directory to the nearest adapter configuration containing that exact released registration and requires its root to equal the permit root. -Finding a permit for the ID, or finding only an ancestor match, is never sufficient. An unrelated nested adapter configuration is ignored; a nested checkout containing the copied registration resolves to its own root and cannot inherit a parent's permit. Sync refuses to permit the user's home or a filesystem root. +Knowing the ID or finding only an ancestor permit is insufficient. Nested checkouts cannot inherit a parent's permit. Sync refuses to permit a filesystem root or the user's home directory. -If the project is inactive, SessionStart may emit static `additionalContext` telling the user to run `cargo agents sync`. It does not load workspace plugins, auto-sync, refresh registries, or execute project-provided code. A per-root notice record suppresses the hint after it has been shown once. Other inactive events exit successfully and silently. +If a project is inactive, SessionStart may show `cargo agents sync` once for that root. It does not load workspace plugins, refresh registries, auto-sync, or run project code. Other inactive events exit successfully without output. -Global hooks have no positive activation permit. They run unless uninstall has written a positive retirement tombstone for that managed ID. An empty, deleted, or unavailable managed store therefore does not silently disable the recommended global workflow. A global hook still runs in every project, so tombstones do not make Symposium's global workflow a defense against hostile workspace configuration. +## Global hooks and binary guards -## Binary guards and failure visibility +Global hooks do not use positive permits. They run unless uninstall writes a retirement tombstone. Deleting or losing the managed-state directory therefore does not silently disable a verifiable global registration. -Generated shell commands try the executable path resolved during machine-local registration, then fall back to `PATH`. Portable committed project forms try the conventional Cargo home first. If the binary is absent, the command exits with status 0 and no output. +This is cleanup coordination, not hostile-workspace protection: a global hook still runs in every project. -That outer guard handles only an absent executable. Its generated command is one line, carries the managed ID, and shell-escapes absolute paths including apostrophes. The binary then checks trusted local state before ordinary startup. If state is missing, it classifies scope only by finding one exact released registration signature at the nearest project or known global adapter target. Project failures deny dispatch; an exactly proven global registration continues and reports degraded health where possible; ambiguous invocations do not run plugins. +Generated shell commands first try the registered executable location, then `PATH`. Portable project forms try the Cargo home convention first. If `cargo-agents` is absent, the guard exits 0 without output. Missing internal state then permits only one exact degraded global match; project failure or ambiguous classification denies plugin dispatch. -`cargo agents status` consumes a read-only managed-health snapshot and reports inactive, retiring, corrupt, unavailable, degraded-global, and cleanup-in-progress states. It does not repair state while reading it. An explicit sync recreates corrupt derived permits from valid receipts and restores a matching registration interrupted during uninstall. +`cargo agents status` continues to report workspace plugin enablement and gains read-only managed health: inactive, retiring, corrupt, unavailable, degraded-global, and cleanup-in-progress. Repair remains explicit through `cargo agents sync`. -## After cloning, copying, or moving a project +## After cloning or moving -Run once from the new checkout: +Run from the new checkout: ```console cargo agents sync ``` -Sync verifies the registration, records the normalized local root, and publishes its permit. Multiple clones may share a managed ID, but every root is activated separately. +Sync verifies the registration, records the normalized local root, and publishes its permit. Multiple clones may share an ID, but each root activates separately. -Moving a project does not rewrite an old permit implicitly. The moved checkout remains inactive until sync records its new root. A container, WSL environment, and Windows host are separate permit environments even when they expose the same repository. - -The old record remains small and inert until cleanup confirms that path is absent. Symposium never searches the filesystem for a moved project. +A moved checkout, container, WSL environment, and Windows host are separate permit environments when their normalized paths differ. Old records stay small and inert until cleanup confirms their paths absent; Symposium never scans for moved projects. ## Tracked project configuration -Some project hook files are intentionally committed for a team. Uninstall checks Git outside the hook path: +Some project hook files are intentionally committed. Uninstall checks Git outside the hook path: -- when no ancestor has a `.git` file or directory, no Git executable is required; +- no `.git` ancestor means no Git executable is needed; - tracked configuration is preserved by default; -- `cargo agents uninstall --include-tracked` authorizes removal of only the verified Symposium entry; and -- unavailable or indeterminate Git tracking preserves the file as a blocker. +- `cargo agents uninstall --include-tracked` permits removal only of verified Symposium structure; and +- unavailable or indeterminate tracking preserves the file as a blocker. -`--acknowledge ` may transfer a preserved artifact to user ownership without deleting it. It cannot make an unguarded hook or direct MCP reference to `cargo-agents` safe for binary removal. +`--acknowledge ` can transfer a preserved artifact to the user. It cannot make a live unguarded hook or direct MCP reference to `cargo-agents` safe for binary removal. ## Legacy registrations -Legacy hooks keep their existing runtime behavior. Symposium does not add on-event disk verification that would require locating a Cargo workspace. Exact historical signatures allow known registrations to migrate during init or sync and to be removed during uninstall. +Legacy hooks keep their current runtime behavior. Exact historical signatures support bounded migration and cleanup without adding disk verification to the hot path. -Historical workspace state recovers some old roots, but previous manual syncs did not always record `workspace_root`. An otherwise unknown pre-receipt project remains undiscoverable without a filesystem scan. The uninstall assessment states that limitation rather than claiming universal coverage. +Historical workspace state finds some old roots, but earlier manual syncs did not always record them. Unknown pre-receipt projects remain undiscoverable without a filesystem scan, so uninstall reports known-scope coverage rather than claiming universal cleanup. ## Removing Symposium -First quit all agents that may rewrite cached settings. Then run: +Quit running agents, then run: ```console cargo agents uninstall ``` -Cleanup retires one artifact immediately before removing it: it retires a project permit or creates a global tombstone. It verifies each removal and keeps its receipt until the entire run can finalize. If any blocker remains, discovery state and private caches stay available for a rerun or repair. - -After the command reports no live integrations in its stated scope, run: - -```console -cargo uninstall symposium -``` +Cleanup retires and verifies artifacts one at a time. Blockers keep discovery state available for rerun or repair. After the command reports no live integrations in its stated scope, run `cargo uninstall symposium`, then restart the agents. -Then start the agents again. If cleanup was interrupted and Symposium should remain installed, `cargo agents sync` restores registrations whose receipts still match. +See the [`cargo agents uninstall` guide](../cargo-agents-uninstall/README.md) for flags, examples, exit codes, and interrupted-cleanup recovery. diff --git a/md/rfds/symposium-uninstall/ownership/README.md b/md/rfds/symposium-uninstall/ownership/README.md index c6efac60..e52b4105 100644 --- a/md/rfds/symposium-uninstall/ownership/README.md +++ b/md/rfds/symposium-uninstall/ownership/README.md @@ -1,36 +1,28 @@ # Ownership and managed state -## TL;DR +> Normative contract for discovery evidence, artifact identity, managed-write lifecycle, and safe mutation proposed by this RFD. -- Every externally visible Symposium write records durable discovery evidence. -- Cleanup verifies the current artifact independently before modifying it. -- Receipts are versioned, private, non-executable, and secret-free. -- Changed or ambiguous artifacts are preserved. -- New instances of an existing managed artifact type inherit cleanup behavior automatically. +## Summary -## Motivation +Suppose Symposium records a Claude hook in `~/.claude/settings.json`, then the user replaces that hook. The receipt still identifies the slot, but no longer proves that Symposium owns its contents. Cleanup must preserve the replacement. -A receipt proves that Symposium previously wrote or intended to write something at a location. It does not prove that the current occupant is still Symposium's artifact. - -For example, Symposium may record a Claude hook in `~/.claude/settings.json`. If the user later replaces that hook, the receipt still finds the slot, but deleting the replacement would erase user state. - -Cleanup therefore separates two questions: +This model therefore keeps two forms of evidence: | Evidence | Question | | --- | --- | | Receipt or bounded legacy record | Where should Symposium look? | | Signature, fingerprint, marker, or manifest | Is the current artifact still the one Symposium manages? | -Both must agree before external state is removed. +Both must agree before external state is removed. Receipts are versioned, private, non-executable, and secret-free. Changed or ambiguous artifacts are preserved. ## Ownership model -- A **managed ID** is a stable UUID for one logical registration. All event entries belonging to one agent-and-scope hook registration share it. -- An **ownership receipt** records one intended managed mutation, including its target, adapter, scope, artifact type, identity evidence, and lifecycle. -- A **static signature** is a versioned structural description of a registration form emitted by a released Symposium version. +- A **managed ID** is a stable UUID for one logical registration. All event entries in one agent-and-scope hook registration share it. +- An **ownership receipt** records one intended mutation: target, adapter, scope, artifact type, identity evidence, and lifecycle. +- A **static signature** is a versioned structural description of a form emitted by a released Symposium version. - A **dynamic fingerprint** records the non-secret identity of an instance derived from plugin configuration. -A managed ID is a correlation key. It is not proof of ownership, permission to execute, or an authoritative scope declaration. +A managed ID only correlates records. It proves neither ownership, execution permission, nor scope. ## Path identity @@ -38,78 +30,71 @@ Receipt targets, project roots, and runtime comparisons use one normalization fu 1. make the path absolute; 2. canonicalize the existing portion; -3. strip the Windows verbatim-path prefix when present; +3. strip the Windows verbatim-path prefix; 4. apply Windows filesystem case rules; and -5. compare components rather than string prefixes. +5. compare components, not string prefixes. -Resolved paths, not inode or file IDs, identify targets. Re-cloning a dotfile repository therefore does not create an unrecoverable inode conflict. Paths are validated again immediately before every mutation. +Resolved paths, not inode or file IDs, identify targets. Re-cloning a dotfile repository therefore does not create an unrecoverable inode conflict. Every mutation revalidates the path. ## Managed writes -All code that changes externally visible agent state goes through one managed-mutation layer. Callers declare an artifact type and desired value; the layer supplies receipts, lifecycle, identity evidence, collision checks, safe target validation, atomic replacement, and cleanup behavior. - -A normal write is: +All externally visible agent-state writes go through one managed-mutation layer. Callers declare an artifact type and desired value; the layer provides receipts, lifecycle, identity evidence, collision checks, target validation, atomic replacement, and cleanup behavior. ```text record pending intent - ↓ -write external artifact - ↓ -verify current structure - ↓ -record applied state + -> write external artifact + -> verify current structure + -> record applied state ``` -The receipt is durable before the external mutation. Recovery inspects the target instead of assuming that an interrupted write succeeded. +The receipt becomes durable before the external mutation. Recovery inspects the target rather than assuming an interrupted write succeeded. -Adding a hook, skill, MCP server, or generated plugin package through an existing artifact type needs no uninstall-specific code. A genuinely new side-effect type needs an ownership adapter because its identity and safe deletion rules differ. +New hooks, skills, MCP servers, or generated plugin packages using an existing artifact type inherit cleanup automatically. A new external side-effect type needs an ownership adapter because its identity and deletion rules differ. ## Receipt lifecycle -A receipt has four states: - | State | Meaning | | --- | --- | -| `pending` | Intent is durable, but the external write is not confirmed | -| `applied` | The external artifact matches its recorded identity | +| `pending` | Intent is durable; the external write is unconfirmed | +| `applied` | The artifact matches its recorded identity | | `retiring` | Removal started or was interrupted | -| `acknowledged` | The user accepted responsibility for the preserved artifact | +| `acknowledged` | The user accepted responsibility for a preserved artifact | -Project permits are published only after the corresponding write is `applied`. Completed receipts remain until the whole uninstall reaches finalization, so a crash or blocker cannot erase the only discovery evidence. +Project permits are published only after the write is `applied`. Completed receipts remain until the whole uninstall finalizes, preserving discovery evidence after a crash or blocker. -`cargo agents sync` can restore a still-applied registration left in `retiring`. Acknowledgement transfers ownership without deleting the artifact; a later change to that artifact invalidates the acknowledgement. +`cargo agents sync` can restore a matching applied registration left `retiring`. Acknowledgement transfers ownership without deletion; changing the artifact invalidates it. ## Identity by artifact type -| Artifact | Identity evidence and mutation | +| Artifact | Required identity evidence and mutation | | --- | --- | | Static hook or built-in MCP registration | Receipt plus exact released signature; remove only the owned structural entry or dedicated file | | Dynamic MCP or registered plugin path | Receipt plus exact non-secret fingerprint; remove only the recorded structural entry | | Goose MCP block | Receipt, unique marker pair, indentation, and fingerprint; remove the verified byte extent | | Generated file | Receipt plus marker or released content signature | -| Generated skill, plugin package, or mirror | Receipt, marker, and manifest; remove the directory only when the manifest accounts for every entry | +| Generated skill, plugin package, or mirror | Receipt, marker, and manifest; remove the directory only when every entry is accounted for | | Symposium-private state | Containment below the fixed private root and successful external finalization | -`config.toml`, custom plugin sources, and externally authored plugin packages remain user-owned. Reading a `plugin.json` never authorizes deletion of its source. Compiled packages, copies, path registrations, and enablement entries written by Symposium are managed artifacts. +`config.toml`, custom plugin sources, and externally authored packages remain user-owned. Reading `plugin.json` never authorizes source deletion. Compiled packages, copies, path registrations, and enablement entries written by Symposium are managed. -A `.symposium` marker helps identify a generated package, but it is never sufficient by itself for recursive deletion. The receipt finds the target, and the marker plus manifest must account for its current contents. +A `.symposium` marker is not enough for recursive deletion. The receipt finds the target; the marker and manifest must account for its contents. ## Static signatures -Symposium keeps a versioned catalog of every released static form, including current and historical forms: +Symposium keeps a versioned catalog of every released static form, current and historical: - generated hook commands and their containing structure; - dedicated generated agent files; - static built-in MCP registrations; and - generated markers and manifests. -Signatures compare parsed structure, normalized executable identity, fixed arguments, and managed-ID placement. They do not match on a broad key name, event name, or the presence of `cargo-agents` alone. +Signatures compare parsed structure, normalized executable identity, fixed arguments, and managed-ID placement. They never match only a broad key, event name, or the presence of `cargo-agents`. -Changing a released generated form adds a signature fixture rather than replacing old evidence. This is the compatibility cost that allows cleanup after receipts are lost or an older release is removed. +Changing a released form adds a fixture; it does not replace old evidence. This compatibility cost permits cleanup after receipts are lost or an older release is removed. ## Dynamic fingerprints -Plugin-provided MCP names, commands, arguments, transports, and URLs are not a finite catalog. Their identity is captured when written. +Plugin-provided MCP values are not a finite catalog, so Symposium records their identity when written. | Adapter | Structural container | | --- | --- | @@ -119,54 +104,54 @@ Plugin-provided MCP names, commands, arguments, transports, and URLs are not a f | Goose | `extensions.` | | OpenCode | `mcp.` | -The fingerprint includes the adapter, normalized target, structural container, entry name, transport, and command plus arguments or URL. It excludes environment values, headers, tokens, and other secrets. +The fingerprint includes adapter, normalized target, structural container, entry name, transport, and command plus arguments or URL. It excludes environment values, headers, tokens, and other secrets. -Removal requires every recorded non-secret identity field to match. A changed field transfers the entry out of automatic cleanup and produces a conflict. Without the receipt, a dynamic entry is preserved because name or command resemblance is not proof. +Every recorded field must match. A changed field creates a conflict. A dynamic entry without its receipt is preserved because resemblance is not proof. -Goose is an editing exception. Its YAML is not round-tripped through a serializer that would discard comments and formatting. Symposium writes behavior-neutral managed-ID comment markers, verifies one unique marker pair and the enclosed mapping, then removes that exact byte extent. Missing, duplicate, malformed, or mismatched markers preserve the block. +Goose is an editing exception: its YAML is not round-tripped through a serializer. Symposium writes behavior-neutral managed-ID comment markers, verifies one unique marker pair and the enclosed mapping, then removes that exact byte extent. Missing, duplicate, malformed, or mismatched markers preserve the block. -## Collisions and user changes +## Collisions and concurrent changes -Init and sync inspect the current adapter slot before writing. They do not adopt or overwrite an occupied location without matching `pending` or `applied` evidence. Migration from an exact released signature is a separate path. +Init and sync do not adopt or overwrite an occupied slot without matching `pending` or `applied` evidence. Exact released-signature migration is a separate path. -Collision detection is structural and does not depend on a retained acknowledgement. Reinstallation therefore treats a preserved occupied location as a collision even after finalization removes old managed records. +Collision detection inspects the occupied structure, not a retained acknowledgement. Reinstallation therefore detects a preserved collision even after finalization removes old records. -An external change between read and replacement causes that target to be replanned. Symposium never overwrites the concurrent edit using stale identity evidence. +If the target changes between read and replacement, Symposium replans it instead of overwriting the edit. ## Filesystem safety -Receipts are treated as untrusted input. Before mutation, Symposium validates: +Receipts are untrusted input. Before mutation, Symposium validates: -- the schema version, artifact kind, UUIDs, and enums; +- schema version, artifact kind, UUIDs, and enums; - containment below an allowlisted adapter or private root; -- the expected file or directory shape; +- expected file or directory shape; - component-wise ancestor relationships; - the artifact's link policy; and - current identity evidence. -Cleanup never follows a symlink or junction while deleting a generated tree. If a managed target has been replaced by a link, it is preserved. A directory is removed only when its manifest accounts for every remaining entry. +Cleanup never follows a symlink or junction while deleting a generated tree. A target replaced by a link is preserved. A directory is removed only when its manifest accounts for every remaining entry. ## Missing receipts and historical state -At known locations, exact released signatures can identify static current and legacy forms even when a receipt is missing. The next `init` or `sync` may migrate such a registration to the managed form; uninstall may remove it after the same exact identity check. +At known locations, exact released signatures can identify static current and legacy forms without a receipt. The next `init` or `sync` may migrate them; uninstall may remove them after the same identity check. -Dynamic entries without receipts remain preserved. Unknown pre-receipt project roots cannot be rediscovered by scanning the user's filesystem. +Dynamic entries without receipts stay preserved. Unknown pre-receipt project roots cannot be rediscovered without a filesystem scan. The first receipt-aware release records a durable coverage origin: -- `managed-only` when no earlier or unexplained integration exists; -- `pre-receipt` when an older release or exact legacy artifact is found; or -- `unknown` when provenance is missing or corrupt for existing state. +- `managed-only`: no earlier or unexplained integration exists; +- `pre-receipt`: an older release or exact legacy artifact exists; or +- `unknown`: provenance for existing state is missing or corrupt. -The origin is never promoted automatically. The cleanup engine uses it to qualify the final assessment. +The origin is never promoted automatically. Cleanup uses it to qualify the final assessment. ## Storage and cost -Receipts live below a versioned directory in the resolved Symposium configuration home. They contain paths and identity metadata, never executable instructions, tokens, environment values, or headers. Atomic writes and private permissions protect the store. +Receipts live below a versioned directory in the resolved Symposium configuration home. They contain paths and identity metadata, never executable instructions, tokens, environment values, or headers. Writes are atomic and permissions private. -Storage grows linearly with recorded integrations and checkouts. Completed records are removed during successful finalization. Recorded project paths are visible only to principals already able to read Symposium's private configuration and are never uploaded as telemetry. +Storage grows linearly with integrations and checkouts. Successful finalization removes completed records. Recorded paths are visible only to principals already able to read Symposium's private configuration and are never uploaded as telemetry. -## Acceptance tests +## Verification Tests cover: @@ -175,6 +160,6 @@ Tests cover: - current and historical static signatures; - dynamic fingerprints with secret fields excluded; - collisions, concurrent changes, and acknowledgement invalidation; -- Goose block editing with surrounding formatting preserved; +- Goose editing with surrounding formatting preserved; - generated skill and plugin-package directories; and - missing receipts and historical coverage.