Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
467f54b
spec: proposal for version aware cli + update flow
Cesar-M-Diaz Jul 30, 2026
abc061f
docs(openspec): harden add-update-flow contracts
Cesar-M-Diaz Jul 31, 2026
cef2b30
docs: finalize update flow specification
Cesar-M-Diaz Aug 3, 2026
8d16ac9
spec: address update flow review gaps
Cesar-M-Diaz Aug 3, 2026
922b716
feat(update): implement approved update flow
Cesar-M-Diaz Aug 25, 2026
121655a
fix(update): match equivalent Codex TOML headers
Cesar-M-Diaz Aug 25, 2026
bcae55a
fix(update): report unsupported Pi scopes
Cesar-M-Diaz Aug 27, 2026
49d2eef
fix(update): close review blockers for safe native and fallback updates
Cesar-M-Diaz Aug 31, 2026
d545491
fix(update): respect Windows chmod, separator, and homedir semantics
Cesar-M-Diaz Aug 31, 2026
4e94454
fix(update): close CodeRabbit review findings on PR #56
Cesar-M-Diaz Aug 31, 2026
3b8a1c9
fix(update): close batch-3 review findings on PR #56
Cesar-M-Diaz Sep 1, 2026
cdfe58e
fix(update): close coderabbit follow-up findings on PR #56
Cesar-M-Diaz Sep 1, 2026
973f393
refactor
Cesar-M-Diaz Sep 2, 2026
09972c4
fix(update): harden transactional update safety
Cesar-M-Diaz Sep 2, 2026
58d393f
fix(update): invoke Pi from the installed source and harden update pl…
Cesar-M-Diaz Sep 3, 2026
ecf76f2
chore(test): pass NSOLID_TEST_CORE_ALREADY_BUILT to the test child af…
Cesar-M-Diaz Sep 3, 2026
276750c
fix(update): make update-flow security checks pass on Windows
Cesar-M-Diaz Sep 3, 2026
4daac21
fix(update): fail closed on unprovable tree termination, validate Ant…
Cesar-M-Diaz Sep 4, 2026
65ef773
refactor(update): centralize owned filesystem safety
Cesar-M-Diaz Sep 4, 2026
b3159ae
refactor(update): make the journal the single source of truth
Cesar-M-Diaz Sep 6, 2026
32d64a8
fix(update): constrain Claude rollback roots to the approved plugin c…
Cesar-M-Diaz Sep 6, 2026
8a8bff7
refactor(update): simplify execution contracts and harden transaction…
Cesar-M-Diaz Sep 7, 2026
f13e2e0
fix journal test config
Cesar-M-Diaz Sep 8, 2026
cd2800b
fix(update): handle platform fixture paths and fail closed on unprove…
Cesar-M-Diaz Sep 8, 2026
499f1ed
test(update): make fallback fixtures portable and assertions fail closed
Cesar-M-Diaz Sep 8, 2026
d0106ee
test(update): align fallback fault injection and assertions with Windows
Cesar-M-Diaz Sep 8, 2026
2637ccd
fix(update): serialize journal claims and validate rollback ownership
Cesar-M-Diaz Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ jobs:
- run: pnpm test
env:
NSOLID_TEST_CORE_ALREADY_BUILT: '1'
NSOLID_TEST_REQUIRE_BUILD: '1'
- run: node scripts/test-marketplace-install.js
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ packages/pi-plugin/skills/
# OpenCode
.opencode/sessions/
.opencode/logs/
.codegraph/
.opencode/plans
.opencode/reviews

.codegraph/
.codegraph
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,29 @@ nsolid-plugin setup --harness <harness>
nsolid-plugin install --harness <harness>
```

### Check and update

The updater plans changes before executing them and never starts OAuth. Version reporting is read-only:

```bash
nsolid-plugin version
nsolid-plugin --version
nsolid-plugin update --check
nsolid-plugin update --all --check --json
```

The default `update` scope is the globally installed CLI. Use `--harness claude|codex|opencode|antigravity|pi` for one harness or `--all` for the CLI plus every detected installation. Mutating updates require an interactive confirmation or `--yes`; non-interactive automation should use `--yes --json`.

Ownership remains with each tool. Claude uses its detected plugin ID and scope, Codex refreshes the detected marketplace and transactionally removes/adds that same ID, and Antigravity backs up its staged root and matching import manifest before reinstalling the fixed GitHub source. Pi runs its native unpinned package update once for the detected user/project scopes. OpenCode and tracked fallback installs use the exact published package's internal refresh entrypoint; they do not invoke `opencode plugin`.

The updater preserves credentials, user-owned configuration, unrelated MCP entries, and native marketplace identities. It refuses ambiguous or unsupported sources, never silently downgrades a CLI newer than the registry, and rolls back owned fallback/Antigravity/Codex state when validation fails. The public `nsolid-plugin install` command and programmatic `install()` behavior remain unchanged.

Without a global install, use `npx -y nsolid-plugin setup --harness <harness>` and `npx -y nsolid-plugin install --harness <harness>`.

When the CLI is launched from a workspace checkout, a local path, `npx`, Volta, or another wrapper, it cannot prove which (if any) global installation it belongs to, so `update` reports the launch as `unsupported`: a read-only `update --check` exits `0` (and does not probe npm/pnpm global roots), a mutating `update` exits `2` without modifying anything, and both print safe exact-version manual install commands for npm and pnpm.

A fallback refresh that would add MCP servers while no valid NodeSource credentials are available fails before any change with `MCP_RECONCILIATION_REQUIRED`; run `nsolid-plugin setup --harness opencode` (or the tracked harness) to authenticate, then retry the update. The refresh never applies a skills-only partial update in that state.

Use direct CLI install as the primary install path for OpenCode. For Claude Code, Codex CLI, and Antigravity CLI, prefer the native plugin commands below and keep `nsolid-plugin install` for fallback or repair. For Pi Agent, skills come from `nsolid-pi-plugin`; the CLI writes Pi MCP config only.

### Claude Code
Expand Down Expand Up @@ -215,10 +236,17 @@ pnpm plugin:sync # Regenerate manifests/conf
pnpm plugin:materialize # Copy root skills into the Pi package for pack/release
pnpm plugin:root # Refresh root marketplace/plugin manifests from bundle.json
pnpm plugin:root:check # Fail if committed root manifests drift from bundle.json
pnpm release:prepare -- patch # Prepare the next release version atomically
pnpm release:check # Check synchronized versions and generated payload
pnpm release:check --release # Also require payload changes to bump the release version
```

Run `pnpm plugin:check` in CI and before release. The source tree keeps one canonical skill copy under root `skills/`; package-local `skills/` directories are materialized only for npm package release and cleaned afterward by package sync scripts.

### Release order

`release:prepare` only updates and validates local files; it never publishes or creates Git state. After review, run `pnpm release:check --release`, publish `nsolid-plugin@<version>` first, publish the same-version `nsolid-pi-plugin` second, then commit and push the generated root manifests and the matching semantic Git tag. Run `pnpm plugin:clean` after any interrupted package materialization. The first release that introduces this updater must be bootstrapped manually because an older CLI cannot update itself before the new package and native marketplace payload are published.

## Troubleshooting

### Run the doctor command
Expand Down
75 changes: 75 additions & 0 deletions docs/update-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Update lifecycle

This guide describes the fallback and native update lifecycle implemented by the
update coordinator. A check plan is read-only; a mutation plan captures verified
artifact identity, trusted ownership evidence, and (for fallback) a nonce-bound
transaction manifest before execution.

## States and transitions

1. **Planned** — inventory and version resolution produce an `UpdatePlan`. A
mutable fallback plan also owns a private manifest staging directory. No
live destination is changed during planning.
2. **Awaiting approval** — the coordinator may return a confirmation-required
result. Planning resources are released only when no recovery or execution
evidence requires preservation.
3. **Journal open / mutating** — the fallback parent snapshots authenticated
owned paths and obtains an in-memory journal handle. The child must validate
the manifest, nonce, result containment, and frontier evidence before it
claims mutation authority.
4. **Command finished** — a zero exit code is not enough. The command runner
must report `treeTerminated: true` before the parent can reclaim authority or
roll back. An unconfirmed tree leaves recovery state unresolved.
5. **Committed** — after the parent reclaims the owner handle, journal evidence
and postconditions prove the update. The journal commits and removes only
authenticated backup/quarantine material.
6. **Recovered or preserved** — a confirmed failure goes through journal-owned
restore. A failed proof, concurrent change, or incomplete durable journal
operation preserves the journal, snapshot, quarantine, and affected live
paths for the next recovery decision. Next-run check mode only reports this
state; a non-check plan may restore authenticated owned paths, but the
journal and snapshot remain and block a new mutation.

## Cleanup ownership

- The **coordinator** releases verified npm artifact downloads and directories
recorded by the plan as planning-owned. It never derives a deletion target
from command arguments or child-reported paths.
- The **fallback strategy** owns its execution workspace and fresh child-result
directory. Their cleanup decision is an internal execution outcome:
`{ result, planResources: 'release' | 'preserve' }`. Missing or malformed
authorization preserves planning resources.
- The **journal module** owns authenticated snapshots, backups, stages,
quarantines, recovery, and commit. Child preservation arrays are reporting
only and never authorize deletion or overwrite.
- Native strategy modules retain ownership of their own rollback bundles and
use their existing conservative preservation decisions.

The two fallback execution directories remain separate. Their independent
containment identities and different preservation needs are security evidence;
combining them would not provide a meaningful simplification without weakening
replay, symlink/concurrent replacement, or detached-child recovery checks.

## Public and internal results

`UpdateStrategy.execute` and `executeUpdatePlan` keep their result-only public
interfaces. The coordinator consumes the fallback strategy's internal outcome,
but publishes only `UpdateResult` values inside `UpdateSummary`; `planResources`
is never present in public JSON. Public rollback and preservation fields are
reporting projections, not cleanup authority.

## Safety invariants

- Only explicit whole-tree termination evidence authorizes journal reclaim or
rollback; exit status and timeout absence do not.
- A trusted in-memory manifest, authenticated backup/CAS evidence, genuine
handles, and recorded containment identities authorize filesystem operations.
Mutable journal fields and child assertions do not create authority.
- Nonce-bound child results, result-directory identity checks, frontier proofs,
and pre/post asynchronous checks remain mandatory.
- Concurrent edits, unconfirmed descendants, unauthenticated backups, and
incomplete durable bookkeeping are preserved rather than overwritten or
deleted.
- Physical restoration and durable journal finalization remain distinct facts.
A restored path with pending journal bookkeeping is still an incomplete
recovery and remains reportable.
2 changes: 2 additions & 0 deletions openspec/changes/add-update-flow/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: ns-workflow
created: 2026-07-30
Loading
Loading