Skip to content

feat(install): public installer API and portable setup pin - #182

Draft
777genius wants to merge 29 commits into
cursor/uap-installer-p4a-handoff-6c84from
cursor/uap-installer-sdk-adapter-6c84
Draft

777genius wants to merge 29 commits into
cursor/uap-installer-p4a-handoff-6c84from
cursor/uap-installer-sdk-adapter-6c84

Conversation

@777genius

@777genius 777genius commented Sep 14, 2026

Copy link
Copy Markdown
Owner

This is P4 (R1b) on top of #180. It does not fold into #177.

UAP P1–P3 cannot be published from this agent (universal-agent-plugins push is 403). This PR is the Notifications-side portable adapter: a process-local install/uapinstaller facade over the current plugin-kit-ai/install/integrationctl pin, plus the version-bound portable zip from §8.4.

What landed

  • Public-shaped install/uapinstaller Engine: Prepare/Apply/Inspect/Recover/Discover for install and remove. Update/repair return ErrUnsupported until UAP P2.
  • Inspect returns a read-only §5.8 recovery observation and per-client materialization/activation. Recover takes that observation, re-reads it under the mutation lock, and returns plan_changed instead of expanding the confirmed scope.
  • Recover classifies observed journals/receipts as resolved / remaining / unknown on Result.Recovery, including when recovery fails.
  • Confirmed Apply re-reads live target/ownership before mutation and returns plan_changed instead of applying a stale confirmation.
  • Unconfirmed Apply returns cancelled before usecase mutation. Prepare keeps DryRun even when the PersistAuthoritativeObservations seam is enabled.
  • Discover lists Claude/Codex user-scope metadata, executable presence, and current bindings without executing found files or creating state.
  • Last-client remove retains PLUGIN_DATA. A later remove of that retained empty installation returns already_absent without a journal or helper.
  • Sequential Claude then Codex install shares one installation; removing one binding leaves the other.
  • After a terminal remove, a later install of the missing client uses the explicit ClientConfigRoot; sibling bindings and PLUGIN_DATA stay.
  • Windows UNC StateRoot is rejected before mutation.
  • Prepare rejects TempRoot that overlaps PackageRoot, including case, symlink, and Unicode NFC/NFD aliases when os.SameFile shows they are the same directory.
  • The portable adapter treats the same retained-empty installation as already_absent before validate, helper, journal, or kernel mutation, and forwards Codex ExternalUninstalled to the SDK.
  • Missing required components fail Prepare before state is written. Plan includes the helper protocol version. Result.NextActions cover recover/update/reprepare.
  • Install Result.Client copies required MCP/skill components together with materialization/activation/authentication/verification.
  • Confirmed Apply returns recovery_required when Inspect sees a pending journal. Close during Apply returns ErrHandleBusy.
  • Install of a different TreeDigest, including an old-bridge artifact digest, returns ErrUpdateRequired without rewriting state.
  • Host projection seam replaces args of one declared MCP server. Remove preflight verifies the managed artifact before deactivation.
  • Isolated install E2E deletes a disposable MCP cwd instead of the UAP managed package.
  • External sample module install/uapinstaller/example imports the public API with GOWORK=off and no replace.
  • Portable adapter uses that facade, P4a reservation, and existing-installer ownership IDs. Version-bound portable asset agent-notify-portable-${GOOS}-${GOARCH}.zip.

Honest gaps

  • This is the hosted Notifications install/uapinstaller package, not a published UAP install/integrationctl/agentplugins/installer module.
  • Update/repair/group/P5 UI are not published.
  • UAP managedstdio still requires POSIX execute bits at Apply time on Windows.

Tests

  • go test ./install/uapinstaller ./internal/agentnotify/portablesetup ./internal/agentnotify/portableasset ./internal/installruntime
  • cd install/uapinstaller/example && GOWORK=off go run .external import ok
Open in Web Open in Cursor 

Host the P1 Prepare/Apply contract in install/uapinstaller until UAP can
publish agentplugins/installer. Portable setup now uses that API for
canonical snapshots and host seams, while keeping P4a reservation handoff.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.92926% with 471 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.26%. Comparing base (e29dbd1) to head (3914f92).

Files with missing lines Patch % Lines
install/uapinstaller/prepare.go 72.30% 46 Missing and 31 partials ⚠️
internal/agentnotify/portableasset/extract.go 51.31% 38 Missing and 36 partials ⚠️
internal/agentnotify/portableasset/build.go 53.47% 34 Missing and 33 partials ⚠️
install/uapinstaller/apply.go 69.26% 38 Missing and 25 partials ⚠️
install/uapinstaller/inspect.go 70.41% 35 Missing and 15 partials ⚠️
internal/agentnotify/portablesetup/uap.go 57.94% 26 Missing and 19 partials ⚠️
cmd/build-portable-package/main.go 0.00% 35 Missing ⚠️
install/uapinstaller/seams.go 58.66% 18 Missing and 13 partials ⚠️
install/uapinstaller/config.go 82.92% 8 Missing and 6 partials ⚠️
install/uapinstaller/discover.go 81.08% 5 Missing and 2 partials ⚠️
... and 2 more
Additional details and impacted files
@@                            Coverage Diff                            @@
##           cursor/uap-installer-p4a-handoff-6c84     #182      +/-   ##
=========================================================================
+ Coverage                                  65.18%   65.26%   +0.07%     
=========================================================================
  Files                                        170      181      +11     
  Lines                                      17474    18657    +1183     
=========================================================================
+ Hits                                       11391    12177     +786     
- Misses                                      4432     4676     +244     
- Partials                                    1651     1804     +153     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

cursoragent and others added 7 commits September 14, 2026 15:39
Co-authored-by: Илия <iliyazelenkog@gmail.com>
Co-authored-by: Илия <iliyazelenkog@gmail.com>
UAP managedstdio treats a launcher as executable only when the file mode
has an execute bit. Windows reports that for PATHEXT names, so installer
tests now build probe.exe instead of a suffix-less probe.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Add a same-release Agent Notify package builder and extractor so install
uses plugin.json, mcp.json, the canonical skill, and the platform binary
from that tag. The zip is a Notifications release asset named
agent-notify-portable-${GOOS}-${GOARCH}.zip, checked into checksums.txt.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Recognize --internal-stdio-v1 with managedstdio.Dispatch before config,
logs, or notification init. Unknown --internal-stdio-* versions exit 126
instead of falling through to the public CLI.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
UAP managedstdio.NewSource rejects files whose Perm() lacks 0111. Go
Windows FileMode does not set execute bits on regular files, including
.exe, so Apply cannot run until that UAP check is Windows-aware.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Co-authored-by: Илия <iliyazelenkog@gmail.com>
The shared 5s request context covered several Commits plus crash replay.
Windows 1.26 CI exceeded that on antivirus-slow temp IO. Keep the 5s
helper for lighter tests and bound this recovery case at 30s.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
cursoragent and others added 16 commits September 14, 2026 19:34
Local Prepare used AcquireLocal, which hashes POSIX 0111 from the
checkout. Windows regular files never carry that bit, so TreeDigest
dropped logical bin/ helpers. SnapshotWithExecutables now marks mcp.json
commands and bin/ files as 100755 independently of host FileMode.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Inspect now returns a read-only recovery observation: state root,
pending directory journals, and unfinished state receipts including
state_committed without a journal. Recover takes that observation,
re-reads it under the mutation lock, and returns plan_changed instead
of expanding the confirmed scope.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Inspect does not create state or invoke the helper runner. Apply rejects
a closed handle, a handle from another Engine, and a second confirmed
Apply. Recover does not call the runner or committed-binding callback.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Install Apply constructs the helper before creating the state file.
Remove Prepare and Apply check the persisted target and managed
artifact digest without running the client. An external example
module imports the public API with GOWORK=off and no replace.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Confirmed Apply returns recovery_required when Inspect sees a pending
journal, instead of letting usecase beginMutation recover silently.
Apply releases the handle mutex while busy so Close and a second Apply
return ErrHandleBusy without dropping the sealed snapshot.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Prepare wraps UAP sticky-source and update-separately failures as
ErrUpdateRequired so a new package cannot upsert an owned revision.
The projection seam still replaces args of one declared MCP server.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
… rewrite

Last-client remove keeps PLUGIN_DATA and reports data_retained. Optional
digest-bound Assess never turns block or unavailable into allow. A recorded
TreeDigest that is not the canonical snapshot, including an old-bridge
artifact digest, returns ErrUpdateRequired without rewriting state.
Isolated E2E deletes a disposable MCP cwd instead of the managed package.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Confirmed Apply re-reads live target/ownership before mutation and returns
plan_changed instead of applying a moved binding. Inspect and Result report
per-client materialization/activation. Discover lists Claude/Codex metadata
without creating state or running a helper.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Read-only discovery now Lstats explicit client paths or PATH candidates
for Claude and Codex. Found files are never executed, state is not
created, and caller-owned ClientExecutables maps are copied by New.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Install Result.Client now copies the required MCP/skill components.
Discover reports current UAP bindings read-only, without creating
state, taking the mutation lock, or executing found files.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Repeated last-client remove of a DataRetained installation is unchanged
without creating a journal or running a helper. Plan now includes the
helper protocol version, Result.NextActions cover recover/update/reprepare,
and missing required components fail Prepare before state is written.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Last-client DataRetained installations with zero live clients return
success without validate, helper, journal, or kernel mutation. Codex
remove now forwards ExternalUninstalled to the installer SDK.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Unconfirmed Apply returns cancelled before usecase mutation. Prepare still
uses DryRun when PersistAuthoritativeObservations is enabled, so planning
does not write state, lock, or journals.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Co-authored-by: Илия <iliyazelenkog@gmail.com>
Install of a second Claude/Codex client shares the installation and
removing one binding leaves the other. Windows UNC StateRoot is rejected
before mutation.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
…igest

Prove ClientConfigRoot is not mixed with CODEX_HOME/CLAUDE_CONFIG_DIR/HOME,
and surface the helper SHA-256 that UAP managedstdio stores.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
cursoragent and others added 4 commits September 14, 2026 23:48
Reservation rollback and lease fencing do several Commits on one context.
A 5s budget expired on Windows 1.25 CI under the larger P4 suite.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Prepare rejects scratch overlap with PackageRoot, including case and
symlink aliases, before creating the snapshot directory.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Scratch must not overlap PackageRoot through filesystem identity, including
NFC/NFD names when the volume aliases them as the same directory.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Recover classifies observed journals/receipts as resolved, remaining, or
unknown even on error. After a terminal remove, a later install uses the
new explicit ClientConfigRoot; sibling bindings and PLUGIN_DATA stay.

Co-authored-by: Илия <iliyazelenkog@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants