diff --git a/md/SUMMARY.md b/md/SUMMARY.md index 0ed860b4..8a18ca3a 100644 --- a/md/SUMMARY.md +++ b/md/SUMMARY.md @@ -96,6 +96,12 @@ - [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) + - [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 new file mode 100644 index 00000000..252ade88 --- /dev/null +++ b/md/rfds/symposium-uninstall/README.md @@ -0,0 +1,256 @@ +# Managed Symposium uninstall + +## TL;DR + +- 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 + +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 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 +``` + +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. + +Three cases define the required behavior: + +| 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 normal workflow is: + +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. + +```console +$ cargo agents uninstall +Removed + claude global hook + codex project skills in /work/reporter + +No remaining Symposium integrations in known scopes. +Next: cargo uninstall symposium +``` + +`--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 a released Symposium registration + +No files changed. +``` + +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. + +## Detailed plans + +### Decision sought + +Accepting this RFD means agreeing that: + +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. + +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. + +### Design model + +Four concepts answer different questions: + +| 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? | + +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. + +### Ownership and managed writes + +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. + +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 + +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). + +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. + +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` | 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 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 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. + +### Drawbacks and limitations + +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. + +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. + +Other costs remain: + +- 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. + +### 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 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? + +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. + +### Why must agents be quit first? + +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. + +## Implementation plan and status + +Implementation has not begun. The steps are dependency-ordered. + +### 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 + +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 + +### Step 3: Add guarded hook activation + +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. + +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, and status health + +### Step 4: Add uninstall planning and cleanup + +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`. + +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. + +- [ ] PR: uninstall command and cleanup engine + +### Step 5: Complete compatibility and documentation + +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. + +Verify exact adapter fixtures, concurrent agents, tracked repositories, stripped `PATH`, the deterministic integration harness, mdBook, formatting, clippy, and workspace tests. + +- [ ] PR: adapter rollout, platform completion, and documentation + +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 new file mode 100644 index 00000000..c4d169c6 --- /dev/null +++ b/md/rfds/symposium-uninstall/cargo-agents-uninstall/README.md @@ -0,0 +1,153 @@ +# `cargo agents uninstall` + +> Proposed user documentation for the [Managed Symposium uninstall RFD](../README.md). It does not describe released behavior yet. + +This command removes agent integrations and private local data managed by Symposium. The [cleanup-engine contract](../cleanup-engine/README.md) defines its internal behavior. + +## Recommended workflow + +1. Quit coding agents that may have loaded Symposium configuration. +2. Run: + + ```console + cargo agents uninstall + ``` + +3. Resolve reported blockers and rerun as directed. +4. Run: + + ```console + cargo uninstall symposium + ``` + +5. Start the agents again. + +Quitting first prevents an agent from restoring cached settings after cleanup. Restarting last reloads the cleaned configuration. + +## Usage + +```console +cargo agents uninstall [--dry-run] [--include-tracked] + [--acknowledge ]... + [--quiet] [--json] +``` + +The command examines known global and recorded project scopes. It works inside or outside a Cargo workspace and never scans the entire filesystem. + +`--quiet` suppresses progress, not errors or the final assessment. The existing global `--json` selects machine-readable output. + +## Preview cleanup + +`--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 +Would remove + Global + Claude hooks + ~/.claude/settings.json: Symposium hook entries + +Preserved + ~/.symposium/config.toml: user configuration + +Preview complete. Apply with `cargo agents uninstall`. +``` + +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: + +- `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. + +## Acknowledging a blocker + +`--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. + +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. + +## Results + +A successful run reports actual removals: + +```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 + +No remaining live Symposium integrations in recorded scopes. +Next: run `cargo uninstall symposium`, then start your coding agents. +``` + +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. +``` + +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. + +## JSON and 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, identity conflicts, indeterminate tracking, persistent locks, unsupported schemas, and failed verification do not become silent success. + +## Cloned, moved, or containerized projects + +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 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. + +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 + +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 new file mode 100644 index 00000000..6316e2f6 --- /dev/null +++ b/md/rfds/symposium-uninstall/cleanup-engine/README.md @@ -0,0 +1,186 @@ +# Cleanup engine + +> Normative contract for uninstall discovery, planning, mutation, recovery, reporting, and finalization proposed by this RFD. + +## 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 + +```text +cargo agents uninstall [--dry-run] [--include-tracked] + [--acknowledge ]... + [--quiet] [--json] +``` + +- `--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` | Preview or cleanup completed reliably with no live blockers | +| `1` | An operational failure prevented reliable planning or verification | +| `2` | Command-line usage error | +| `3` | Preview or cleanup completed reliably, but live blockers remain | + +## Bounded discovery + +Uninstall never crawls the home directory or disks. It examines only: + +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 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 + +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 artifact no longer matches Symposium's evidence | +| Operationally unverifiable | Ownership or absence cannot be decided reliably | + +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 writes no recovery state. A dry run with blockers exits `3`. + +## Tracked project configuration + +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 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. 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 runs these phases: + +1. Acquire the exclusive installation barrier. +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`; + 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. 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 happens per artifact immediately before mutation; uninstall never disables all hooks up front. Discovery state remains while any live blocker exists. + +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 + +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. Rerun uninstall to continue; `cargo agents sync` restores a matching applied registration. + +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: verified absence is `Already absent`, and reruns do not recreate removed state. + +## Concurrency and locks + +Managed mutation uses this order: + +```text +installation barrier + -> managed-state lock + -> global target when needed + -> workspace targets sorted by normalized path +``` + +- 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 targets, it checks the managed-state generation. One change retries the snapshot; a second returns an operational failure. + +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 + +A blocker ID is stable over: + +```text +artifact type + adapter + normalized target + structural locator +``` + +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 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. + +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 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 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 success. + +## Reporting + +Human output groups `Removed`, `Already absent`, `Preserved`, `Acknowledged`, `Blocked`, and `Next steps`. + +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, 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 | + +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`. + +Origin is never promoted automatically. Output says known scopes are clean instead of claiming universal safety. + +## Verification + +The deterministic integration harness covers: + +- 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; +- 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; +- 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 new file mode 100644 index 00000000..559ef21b --- /dev/null +++ b/md/rfds/symposium-uninstall/hook-activation/README.md @@ -0,0 +1,173 @@ +# Hook activation + +> Normative contract for generated hook guards, project activation, global retirement, degraded behavior, and hook-path cost proposed by this RFD. + +## Summary + +Two failures shape this contract: + +- 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. + +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. + +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 startup and plugin dispatch +``` + +The outer guard handles binary absence. In-process preflight decides scope and retirement before plugin loading, registry refresh, or auto-sync. + +## Scope classification + +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, bounded degraded classification checks only: + +- the registration-owning-root walk; and +- the adapter's known global target. + +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 + +A project permit binds: + +```text +managed ID + normalized registration-owning root +``` + +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 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. normalized permit and owning roots are equal. + +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 unable to meet this contract cannot offer project-scoped guarded registrations. Tests cover launches from the checkout root and nested directories. + +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 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 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 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 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 | 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 | + +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 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 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. 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 + +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 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 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 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 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`. + +These 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 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 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 + +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 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 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 hot-path read or path-probe counts. + +## Verification + +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/managed-integrations/README.md b/md/rfds/symposium-uninstall/managed-integrations/README.md new file mode 100644 index 00000000..77ba13e1 --- /dev/null +++ b/md/rfds/symposium-uninstall/managed-integrations/README.md @@ -0,0 +1,103 @@ +# 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 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. + +The [ownership](../ownership/README.md), [hook-activation](../hook-activation/README.md), and [cleanup-engine](../cleanup-engine/README.md) pages are authoritative. + +## Mental model + +Consider a project hook written to `.claude/settings.json`: + +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. + +These answer different questions: + +| 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 | + +The shared managed ID only connects these records. It is public, may be committed, and grants neither deletion nor execution authority. + +## Receipts and identity + +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`. + +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. + +A receipt discovers a target; it does not authorize deletion. Cleanup also requires artifact-specific evidence: + +- 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. + +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. + +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. + +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 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. + +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 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 and binary guards + +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. + +This is cleanup coordination, not hostile-workspace protection: a global hook still runs in every project. + +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` 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 or moving + +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 an ID, but each root activates separately. + +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. Uninstall checks Git outside the hook path: + +- no `.git` ancestor means no Git executable is needed; +- tracked configuration is preserved by default; +- `cargo agents uninstall --include-tracked` permits removal only of verified Symposium structure; and +- unavailable or indeterminate tracking preserves the file as a blocker. + +`--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 current runtime behavior. Exact historical signatures support bounded migration and cleanup without adding disk verification to the hot path. + +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 + +Quit running agents, then run: + +```console +cargo agents uninstall +``` + +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. + +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 new file mode 100644 index 00000000..e52b4105 --- /dev/null +++ b/md/rfds/symposium-uninstall/ownership/README.md @@ -0,0 +1,165 @@ +# Ownership and managed state + +> Normative contract for discovery evidence, artifact identity, managed-write lifecycle, and safe mutation proposed by this RFD. + +## Summary + +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. + +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. 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 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 only correlates records. It proves neither ownership, execution permission, nor scope. + +## 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; +4. apply Windows filesystem case rules; and +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. Every mutation revalidates the path. + +## Managed writes + +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 +``` + +The receipt becomes durable before the external mutation. Recovery inspects the target rather than assuming an interrupted write succeeded. + +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 + +| State | Meaning | +| --- | --- | +| `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 a preserved artifact | + +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 matching applied registration left `retiring`. Acknowledgement transfers ownership without deletion; changing the artifact invalidates it. + +## Identity by artifact type + +| 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 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 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 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, 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 never match only a broad key, event name, or the presence of `cargo-agents`. + +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 values are not a finite catalog, so Symposium records their identity 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 adapter, normalized target, structural container, entry name, transport, and command plus arguments or URL. It excludes environment values, headers, tokens, and other secrets. + +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. 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 concurrent changes + +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 inspects the occupied structure, not a retained acknowledgement. Reinstallation therefore detects a preserved collision even after finalization removes old records. + +If the target changes between read and replacement, Symposium replans it instead of overwriting the edit. + +## Filesystem safety + +Receipts are untrusted input. Before mutation, Symposium validates: + +- schema version, artifact kind, UUIDs, and enums; +- containment below an allowlisted adapter or private root; +- 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. 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 without a receipt. The next `init` or `sync` may migrate them; uninstall may remove them after the same identity check. + +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`: 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. 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. Writes are atomic and permissions private. + +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. + +## Verification + +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 editing with surrounding formatting preserved; +- generated skill and plugin-package directories; and +- missing receipts and historical coverage.