mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 12:04:41 +01:00
## Thinking Path > - Paperclip is the open source control plane people use to manage AI-agent companies > - Operators need a predictable installation path that survives beyond an ephemeral `npx` process > - A durable installation needs an owned per-user payload store, stable command shim, safe shell integration, and supported service lifecycle > - Updates must preserve recoverability by backing up data, installing side-by-side, verifying the new payload, and retaining rollback state > - Bootstrap scripts and privileged service operations must fail closed across download, filesystem, ownership, and consent boundaries > - This pull request integrates managed install, update, rollback, service, uninstall, doctor, bootstrap-installer, and runtime-serving support into one workflow > - The benefit is a recoverable, inspectable, and documented installation lifecycle with explicit safety boundaries across Linux, macOS, containers, WSL, npm, npx, and source checkouts ## Linked Issues or Issue Description ### Problem Paperclip lacks a first-class durable installation and lifecycle workflow. Operators currently have to assemble npm/npx installation, PATH setup, background-service management, updates, rollback, diagnostics, and uninstall behavior themselves. That makes upgrades harder to recover, creates inconsistent behavior across platforms, and leaves shell/download/service trust boundaries without one documented implementation. ### Proposed Solution Add a managed per-user install store and stable shim, a verified shell bootstrap installer, service lifecycle commands, install-mode-aware update/rollback behavior, doctor checks, and documentation. Managed updates back up the database, install and smoke-test a side-by-side payload, atomically switch `current`, and retain prior payloads. The shell installer pins registry/download trust boundaries and requires explicit consent for non-interactive privileged actions. ### Alternatives Considered - Keep recommending `npx`: simple for evaluation, but ephemeral and unsuitable for stable services, atomic updates, or rollback. - Require global npm installation only: familiar, but cannot provide the owned side-by-side payload store and retained rollback semantics. - Split the capability across multiple PRs: rejected because install, update, service, uninstall, bootstrap, and serving behavior share contracts and security boundaries that need review together. ### Related Pull Requests - Supersedes #10042 and #10044 with one integrated final diff. - Incorporates and replaces the closed preparatory work in #10032 and #10034. ## What Changed - Added `paperclipai install`, `update`/`upgrade`, rollback, uninstall, service lifecycle, onboarding integration, and managed-install doctor checks. - Added a private managed payload store, verified manifest/marker ownership, exclusive mutation locks, atomic manifest/current/shim writes, retained previous payloads, and provenance validation. - Added npm and GitHub-ref install sources with exact target resolution, registry isolation, database backup, side-by-side verification, atomic activation, service restart coordination, and failure rollback. - Made managed-update backups report actionable service-start and `--no-backup` recovery guidance for unreachable databases, while clean never-onboarded instances skip an empty backup. - Added systemd user and launchd service definitions, status/health/log commands, single-instance coordination, stale-port recovery, and explicit sudo/lingering consent handling. - Added the `scripts/install.sh` bootstrap path with checked two-stage downloads, pinned public npm registry usage, platform checks, dry-run/non-interactive controls, and Docker fixtures. - Added embedded Postgres/native bootstrap integration, hot-restart/systemd-notify serving support, passive update notices, configuration contracts, README/CLI/install documentation, and focused regression tests. - Security re-review should explicitly re-verify: (1) `addManagedPathBlock`/`removeManagedPathBlock` reject symlinked or non-regular rc files, assert current-user ownership, preserve restrictive modes, and replace atomically; (2) managed shim replacement rejects unsafe parents, foreign-owned or multiply linked files, and uses checked atomic replacement; (3) the shell installer and sudo path preserve explicit consent and checked downloads; and (4) installed service/runtime serving remains bound to the validated managed shim and instance configuration. ## Verification - `bash -n scripts/install.sh scripts/clean-install-git.sh scripts/clean-install-npm.sh scripts/test-install-sh-docker.sh` - `pnpm exec vitest run cli/src/__tests__/install-store.test.ts cli/src/__tests__/install-command.test.ts cli/src/__tests__/managed-install-check.test.ts cli/src/__tests__/onboard-service.test.ts cli/src/__tests__/service-health-check.test.ts cli/src/__tests__/service-manager.test.ts cli/src/__tests__/update-command.test.ts cli/src/__tests__/update-notice.test.ts packages/db/src/embedded-postgres-native.test.ts` — 9 files, 66 tests passed - `pnpm --dir cli typecheck` - `pnpm --dir cli build` - Follow-up verification: `pnpm exec vitest run cli/src/__tests__/update-command.test.ts` (14/14), `pnpm --dir cli typecheck`, `pnpm --dir cli build`, and `pnpm --filter @paperclipai/server typecheck`. - `pnpm -r typecheck` - `pnpm build` - Full `pnpm test:run` exercised all suites; an injected static AWS credential changed one unrelated doctor expectation, which passed when those credentials were removed. A second run cleared that case and exposed stale pre-existing adapter-utils `dist` output; rebuilding `@paperclipai/adapter-utils` made the isolated test pass. The updated PR CI is the authoritative clean-workspace full-suite run. ## Risks - Installer/update code writes executable shims, symlinks, shell rc blocks, service definitions, and managed payloads; ownership, regular-file, symlink, hard-link, marker, and path-containment checks fail closed before destructive changes. - The bootstrap installer executes downloaded tooling; downloads are staged and checked before execution, npm traffic is pinned to the public registry, and non-interactive privileged behavior requires explicit consent. - Linux lingering may invoke `sudo`; the command is surfaced and confirmed before execution, and unsupported service managers fall back to foreground-run guidance. - Database migrations remain forward-only; payload rollback does not reverse migrations, so managed updates create a backup before activation unless explicitly disabled. - Service restart and runtime serving touch process/port ownership; lifecycle locks, health/version checks, and stable-shim service definitions reduce split-brain and stale-process risk. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex coding agents using GPT-5.5 and GPT-5.6-sol, with reasoning, repository/API access, shell execution, and test tooling. The runtime did not expose a reliable context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
308 lines
9.6 KiB
Markdown
308 lines
9.6 KiB
Markdown
# Release Automation Setup
|
|
|
|
This document covers the GitHub and npm setup required for the current Paperclip release model:
|
|
|
|
- automatic canaries from `master`
|
|
- manual stable promotion from a chosen source ref
|
|
- npm trusted publishing via GitHub OIDC
|
|
- protected release infrastructure in a public repository
|
|
|
|
Repo-side files that depend on this setup:
|
|
|
|
- `.github/workflows/release.yml`
|
|
- `.github/CODEOWNERS`
|
|
|
|
Note:
|
|
|
|
- the release workflows intentionally use `pnpm install --no-frozen-lockfile`
|
|
- this matches the repo's current policy where `pnpm-lock.yaml` is refreshed by GitHub automation after manifest changes land on `master`
|
|
- the publish jobs then restore `pnpm-lock.yaml` before running `scripts/release.sh`, so the release script still sees a clean worktree
|
|
|
|
## 1. Merge the Repo Changes First
|
|
|
|
Before touching GitHub or npm settings, merge the release automation code so the referenced workflow filenames already exist on the default branch.
|
|
|
|
Required files:
|
|
|
|
- `.github/workflows/release.yml`
|
|
- `.github/CODEOWNERS`
|
|
|
|
## 2. Configure npm Trusted Publishing
|
|
|
|
Do this for every public package that Paperclip publishes.
|
|
|
|
At minimum that includes:
|
|
|
|
- `paperclipai`
|
|
- `@paperclipai/server`
|
|
- `@paperclipai/ui`
|
|
- public packages under `packages/`
|
|
|
|
### 2.1. In npm, open each package settings page
|
|
|
|
For each package:
|
|
|
|
1. open npm as an owner of the package
|
|
2. go to the package settings / publishing access area
|
|
3. add a trusted publisher for the GitHub repository `paperclipai/paperclip`
|
|
|
|
### 2.2. Add one trusted publisher entry per package
|
|
|
|
npm currently allows one trusted publisher configuration per package.
|
|
|
|
Configure:
|
|
|
|
- workflow: `.github/workflows/release.yml`
|
|
|
|
Repository:
|
|
|
|
- `paperclipai/paperclip`
|
|
|
|
Environment name:
|
|
|
|
- leave the npm trusted-publisher environment field blank
|
|
|
|
Why:
|
|
|
|
- the single `release.yml` workflow handles both canary and stable publishing
|
|
- GitHub environments `npm-canary` and `npm-stable` still enforce different approval rules on the GitHub side
|
|
|
|
### 2.2.1. Newly added public packages need a bootstrap phase
|
|
|
|
Trusted publishing is configured on the npm package itself, not at the repo scope.
|
|
That means a brand-new public package must not be auto-enrolled into CI publishing until its npm package exists and its trusted publisher has been configured.
|
|
|
|
Repo policy:
|
|
|
|
1. add every non-private package to [`scripts/release-package-manifest.json`](../scripts/release-package-manifest.json)
|
|
2. set `"publishFromCi": true` only when CI is expected to publish that package
|
|
3. if the package is not ready for CI publishing yet, keep `"publishFromCi": false`
|
|
4. complete the package bootstrap before merging any PR that changes a release-enabled new package
|
|
|
|
Bootstrap sequence for a new package:
|
|
|
|
1. publish the package once from a trusted maintainer machine using normal npm auth
|
|
2. open that package on npm and add the `paperclipai/paperclip` trusted publisher for `.github/workflows/release.yml`
|
|
3. rerun or dry-run the release flow as needed to confirm CI publishing now works
|
|
4. only then enable `"publishFromCi": true`
|
|
|
|
PR CI enforces this by checking changed release-enabled package manifests against npm. That keeps `master` canary publishing healthy while preserving the no-long-lived-token model for normal CI releases.
|
|
|
|
### 2.3. Verify trusted publishing before removing old auth
|
|
|
|
After the workflows are live:
|
|
|
|
1. run a canary publish
|
|
2. confirm npm publish succeeds without any `NPM_TOKEN`
|
|
3. run a stable dry-run
|
|
4. run one real stable publish
|
|
|
|
Only after that should you remove old token-based access.
|
|
|
|
## 3. Remove Legacy npm Tokens
|
|
|
|
After trusted publishing works:
|
|
|
|
1. revoke any repository or organization `NPM_TOKEN` secrets used for publish
|
|
2. revoke any personal automation token that used to publish Paperclip
|
|
3. if npm offers a package-level setting to restrict publishing to trusted publishers, enable it
|
|
|
|
Goal:
|
|
|
|
- no long-lived npm publishing token should remain in GitHub Actions
|
|
|
|
## 4. Create GitHub Environments
|
|
|
|
Create two environments in the GitHub repository:
|
|
|
|
- `npm-canary`
|
|
- `npm-stable`
|
|
|
|
Path:
|
|
|
|
1. GitHub repository
|
|
2. `Settings`
|
|
3. `Environments`
|
|
4. `New environment`
|
|
|
|
## 5. Configure `npm-canary`
|
|
|
|
Recommended settings for `npm-canary`:
|
|
|
|
- environment name: `npm-canary`
|
|
- required reviewers: none
|
|
- wait timer: none
|
|
- deployment branches and tags:
|
|
- selected branches only
|
|
- allow `master`
|
|
|
|
Reasoning:
|
|
|
|
- every push to `master` should be able to publish a canary automatically
|
|
- no human approval should be required for canaries
|
|
|
|
## 6. Configure `npm-stable`
|
|
|
|
Recommended settings for `npm-stable`:
|
|
|
|
- environment name: `npm-stable`
|
|
- required reviewers: at least one maintainer other than the person triggering the workflow when possible
|
|
- prevent self-review: enabled
|
|
- admin bypass: disabled if your team can tolerate it
|
|
- wait timer: optional
|
|
- deployment branches and tags:
|
|
- selected branches only
|
|
- allow `master`
|
|
|
|
Reasoning:
|
|
|
|
- stable publishes should require an explicit human approval gate
|
|
- the workflow is manual, but the environment should still be the real control point
|
|
|
|
## 7. Protect `master`
|
|
|
|
Open the branch protection settings for `master`.
|
|
|
|
Recommended rules:
|
|
|
|
1. require pull requests before merging
|
|
2. require status checks to pass before merging
|
|
3. require review from code owners
|
|
4. dismiss stale approvals when new commits are pushed
|
|
5. restrict who can push directly to `master`
|
|
|
|
At minimum, make sure workflow and release script changes cannot land without review.
|
|
|
|
## 8. Enforce CODEOWNERS Review
|
|
|
|
This repo now includes `.github/CODEOWNERS`, but GitHub only enforces it if branch protection requires code owner reviews.
|
|
|
|
In branch protection for `master`, enable:
|
|
|
|
- `Require review from Code Owners`
|
|
|
|
Then verify the owner entries are correct for your actual maintainer set.
|
|
|
|
Current file:
|
|
|
|
- `.github/CODEOWNERS`
|
|
|
|
If `@cryppadotta` is not the right reviewer identity in the public repo, change it before enabling enforcement.
|
|
|
|
## 9. Protect Release Infrastructure Specifically
|
|
|
|
These files should always trigger code owner review:
|
|
|
|
- `.github/workflows/release.yml`
|
|
- `scripts/release.sh`
|
|
- `scripts/release-lib.sh`
|
|
- `scripts/release-package-map.mjs`
|
|
- `scripts/create-github-release.sh`
|
|
- `scripts/rollback-latest.sh`
|
|
- `doc/RELEASING.md`
|
|
- `doc/PUBLISHING.md`
|
|
|
|
If you want stronger controls, add a repository ruleset that explicitly blocks direct pushes to:
|
|
|
|
- `.github/workflows/**`
|
|
- `scripts/release*`
|
|
|
|
## 10. Do Not Store a Claude Token in GitHub Actions
|
|
|
|
Do not add a personal Claude or Anthropic token for automatic changelog generation.
|
|
|
|
Recommended policy:
|
|
|
|
- stable changelog generation happens locally from a trusted maintainer machine
|
|
- canaries never generate changelogs
|
|
|
|
This keeps LLM spending intentional and avoids a high-value token sitting in Actions.
|
|
|
|
## 11. Verify the Canary Workflow
|
|
|
|
After setup:
|
|
|
|
1. merge a harmless commit to `master`
|
|
2. open the `Release` workflow run triggered by that push
|
|
3. confirm it passes verification
|
|
4. confirm publish succeeds under the `npm-canary` environment
|
|
5. confirm npm now shows a new `canary` release
|
|
6. confirm a git tag named `canary/vYYYY.MDD.P-canary.N` was pushed
|
|
|
|
Install-path check:
|
|
|
|
```bash
|
|
npm install --prefix "$(mktemp -d)" paperclipai@canary --no-audit --no-fund
|
|
```
|
|
|
|
The release script runs this clean-prefix install after publishing every workspace
|
|
package dependency-first and publishing `paperclipai` last. A package that is not
|
|
yet registry-visible stops the train before the channel entrypoint can advance.
|
|
|
|
## 12. Verify the Stable Workflow
|
|
|
|
After at least one good canary exists:
|
|
|
|
1. resolve the target stable version with `./scripts/release.sh stable --date YYYY-MM-DD --print-version`
|
|
2. prepare `releases/vYYYY.MDD.P.md` on the source commit you want to promote
|
|
3. open `Actions` -> `Release`
|
|
4. run it with:
|
|
- `source_ref`: the tested commit SHA or canary tag source commit
|
|
- `stable_date`: leave blank or set the intended UTC date like `2026-03-18`
|
|
do not enter a version like `2026.318.0`; the workflow computes that from the date
|
|
- `dry_run`: `true`
|
|
5. confirm the dry-run succeeds
|
|
6. rerun with `dry_run: false`
|
|
7. approve the `npm-stable` environment when prompted
|
|
8. confirm npm `latest` points to the new stable version
|
|
9. confirm git tag `vYYYY.MDD.P` exists
|
|
10. confirm the GitHub Release was created
|
|
|
|
Implementation note:
|
|
|
|
- the GitHub Actions stable workflow calls `create-github-release.sh` with `PUBLISH_REMOTE=origin`
|
|
- local maintainer usage can still pass `PUBLISH_REMOTE=public-gh` explicitly when needed
|
|
|
|
## 13. Suggested Maintainer Policy
|
|
|
|
Use this policy going forward:
|
|
|
|
- canaries are automatic and cheap
|
|
- stables are manual and approved
|
|
- only stables get public notes and announcements
|
|
- release notes are committed before stable publish
|
|
- rollback uses `npm dist-tag`, not unpublish
|
|
|
|
## 14. Troubleshooting
|
|
|
|
### Trusted publishing fails with an auth error
|
|
|
|
Check:
|
|
|
|
1. the workflow filename on GitHub exactly matches the filename configured in npm
|
|
2. the package has the trusted publisher entry for the correct repository
|
|
3. the job has `id-token: write`
|
|
4. the job is running from the expected repository, not a fork
|
|
|
|
### Stable workflow runs but never asks for approval
|
|
|
|
Check:
|
|
|
|
1. the `publish` job uses environment `npm-stable`
|
|
2. the environment actually has required reviewers configured
|
|
3. the workflow is running in the canonical repository, not a fork
|
|
|
|
### CODEOWNERS does not trigger
|
|
|
|
Check:
|
|
|
|
1. `.github/CODEOWNERS` is on the default branch
|
|
2. branch protection on `master` requires code owner review
|
|
3. the owner identities in the file are valid reviewers with repository access
|
|
|
|
## Related Docs
|
|
|
|
- [doc/RELEASING.md](RELEASING.md)
|
|
- [doc/PUBLISHING.md](PUBLISHING.md)
|
|
- [doc/plans/2026-03-17-release-automation-and-versioning.md](plans/2026-03-17-release-automation-and-versioning.md)
|