From 790f803b4a5996bc989ce719b484ae99bf66c21e Mon Sep 17 00:00:00 2001 From: Erdenebileg Bat-Amgalan Date: Thu, 10 Sep 2026 17:40:47 +0900 Subject: [PATCH] add Paseo runtime action adapter --- .agents/DECISIONS.md | 1 + .agents/PASEO.md | 7 + .agents/PROGRESS.md | 21 +- .../decisions/0018-paseo-action-adapter.md | 71 +++++ .agents/decisions/README.md | 1 + .agents/tasks/phase-9-runtime-wiring.md | 42 ++- Makefile | 10 +- crates/weft-cli/src/tests.rs | 8 + crates/weft-cli/src/wiring.rs | 47 +++- docs/AGENT_PROTOCOL.md | 67 +++-- docs/CHANGELOG.md | 4 + docs/DEPLOYMENT.md | 2 +- docs/DEVELOPMENT.md | 14 +- docs/DOMAIN.md | 37 ++- docs/GETTING_STARTED.md | 5 +- docs/MANUAL.md | 5 + docs/MULTI_AGENT_WORKFLOWS.md | 2 +- docs/PASEO_INTEGRATION.md | 55 +++- docs/ROADMAP.md | 4 +- docs/RUNTIME_README.md | 9 +- docs/RUNTIME_WIRING.md | 25 +- scripts/package-release.sh | 7 +- scripts/paseo-weft-action.sh | 243 +++++++++++++++++- scripts/test-cli-session-resume.sh | 39 ++- scripts/test-paseo-weft-bridge.sh | 117 ++++++++- scripts/test-release.sh | 6 + 26 files changed, 712 insertions(+), 137 deletions(-) create mode 100644 .agents/decisions/0018-paseo-action-adapter.md diff --git a/.agents/DECISIONS.md b/.agents/DECISIONS.md index ec86cae..ac5bdf5 100644 --- a/.agents/DECISIONS.md +++ b/.agents/DECISIONS.md @@ -18,3 +18,4 @@ | Stable latest-release Unix installer asset | Accepted | `.agents/decisions/0015-stable-latest-release-installer.md` | | Local runtime wiring without embedded scheduling | Accepted | `.agents/decisions/0016-runtime-wiring-boundary.md` | | Declarative QA profiles around the canonical gate | Accepted | `.agents/decisions/0017-declarative-qa-profiles.md` | +| Versioned Paseo action adapter with explicit lifecycle authority | Accepted | `.agents/decisions/0018-paseo-action-adapter.md` | diff --git a/.agents/PASEO.md b/.agents/PASEO.md index 5e51b13..7efbc8c 100644 --- a/.agents/PASEO.md +++ b/.agents/PASEO.md @@ -25,6 +25,13 @@ Paseo is Weft's first documented agent launcher integration. The integration is Paseo may stop or archive a session at any point. Resume uses the durable procedure in [AGENT_PROTOCOL.md](AGENT_PROTOCOL.md), optionally in a new workspace and with a new agent. Workspace removal must never precede successful canonical capture when uncommitted work is intended to survive. +The concrete Unix action mapping is documented in +[PASEO_INTEGRATION.md](../docs/PASEO_INTEGRATION.md). Release archives expose +`bin/weft-paseo-action`, a thin adapter for assignment/lease acquisition, +checkpoint, exact-revision materialization, session replacement, observation, +and release. It consumes the bridge-resolved state directory and explicit +caller-owned IDs; it never launches or schedules the agent. + ## Launch guidance Register the repository with `paseo project create`. Prefer worktree isolation for mutating agents and local isolation for read-only review. Select a human-configured Paseo agent profile by its current notes rather than hard-coding a provider/model in the repository. Use completion notifications to trigger inspection; do not poll agents as a scheduler loop. diff --git a/.agents/PROGRESS.md b/.agents/PROGRESS.md index 67354a1..fea102b 100644 --- a/.agents/PROGRESS.md +++ b/.agents/PROGRESS.md @@ -2,7 +2,7 @@ ## Current phase -Phase 9 — runtime wiring in progress. +Phase 9 — runtime wiring and the first native Paseo adapter complete. ## Completed @@ -211,12 +211,25 @@ Phase 9 — runtime wiring in progress. a small operator reference, embedded SBOM, and manifest; project documentation and development helpers remain source-only, while releases publish only the installable archive. +- Replaced stale Paseo helper invocations with the current `weft.cli.v1` + noun/verb grammar and added strict action-argument validation. +- Added a versioned Unix `weft-paseo-action` adapter to the runtime bridge for + explicit assignment/lease acquisition and renewal, Native Git checkpoint, + exact-revision materialization/observation, handoff, session replacement, and + release; it never launches or schedules processes. +- Added bridge metadata and diagnostics for the Paseo adapter, packaged it in + Unix runtime archives, and made the release smoke invoke the archived adapter + with the installed binary. +- Proved the Paseo lifecycle across two isolated workspaces, including expired + lease reclaim and separate-process checkpoint/resume; the full `make check` + gate and v0.2.1 Linux archive smoke are green. ## Next checkpoint -Prove a native runtime adapter that carries the bridge's shared state into an -isolated agent workspace and covers acquisition, checkpoint, session replacement, -and release without making Weft a scheduler. +The first native runtime adapter is proven for Unix Paseo. Future expansion can +add a Windows PowerShell adapter or other runtime-specific lifecycle adapters +only with their own versioned end-to-end proof; OMC, OMG, and OMX remain +bridge-only, and Codex/Claude Code/Gemini CLI remain instruction integrations. ## Known gaps diff --git a/.agents/decisions/0018-paseo-action-adapter.md b/.agents/decisions/0018-paseo-action-adapter.md new file mode 100644 index 0000000..b344b8a --- /dev/null +++ b/.agents/decisions/0018-paseo-action-adapter.md @@ -0,0 +1,71 @@ +# ADR-0018: Paseo action adapter and lifecycle proof + +- **Status:** Accepted +- **Date:** 2026-09-10 + +## Context + +Runtime wiring made Weft discoverable to Paseo, but a bridge by itself still +left each launcher integration to translate durable assignments, leases, +materializations, and revisions into the current CLI grammar. The old helper +scripts also used obsolete flags and commands, so a freshly wired runtime could +not reliably resume work across processes. + +The adapter must preserve Weft's identity, compare-and-swap, canonical-content, +lease, and reconciliation invariants while keeping process launch and +scheduling in Paseo. + +## Decision + +Keep the existing `weft.runtime-bridge.v1` shape backward-readable and add an +optional `adapter` entry. A Paseo setup entry identifies the current +`paseo-action-adapter-v2` integration and the `weft-paseo-action` executable. +Older bridge entries without that field remain parseable, but `weft doctor` +reports the missing adapter until setup refreshes the bridge. + +Provide `weft-paseo-action` as a thin Unix shell adapter. Each action maps to +one current `weft.cli.v1` command and receives the state directory, Change ID, +actor, timestamp, repository identity, workspace identity, operation IDs, and +expected versions explicitly. It does not generate durable identities, launch +or supervise processes, retry ambiguous provider mutations, or alter user-home +configuration. + +The supported lifecycle is assignment/lease acquisition, exact Native Git +checkpoint, exact-revision materialization, observation, handoff, session +replacement with a new lease/workspace, and explicit release. The repository +gate runs the lifecycle proof and a separate-process resume proof. Unix-like +release archives ship the adapter; Windows archives retain the bridge and CLI +contract but do not claim to ship a Bash adapter. + +## Consequences + +- Paseo can call a stable action boundary without duplicating Weft's durable + state machine or using stale command syntax. +- A replacement session can reclaim an expired scope, materialize the exact + recorded revision in a new workspace, and release all durable ownership. +- Callers still supply IDs and expected versions, so malformed or stale actions + fail as explicit usage or concurrency errors. +- OMC, OMG, OMX, Codex, Claude Code, and Gemini CLI remain bridge/instruction + integrations rather than unproven native lifecycle adapters. +- A PowerShell adapter remains future work if Windows Paseo lifecycle parity is + required. + +## Alternatives rejected + +- Embedding a process scheduler or agent runner in Weft: violates the external + scheduler boundary. +- Keeping the obsolete helper grammar as a compatibility alias: hides caller + drift and would make the public v1 command contract ambiguous. +- Inventing IDs or silently recovering stale versions in the adapter: breaks + durable identity and compare-and-swap guarantees. + +## Required proof + +- Current CLI and bridge-schema tests prove setup metadata, idempotency, and + legacy bridge diagnosis. +- `scripts/test-paseo-weft-bridge.sh` proves two isolated workspaces, + acquisition, renewal, checkpoint, dirty observation, handoff, expiry/reclaim, + exact replacement materialization, and release. +- `scripts/test-cli-session-resume.sh` proves the checkpoint survives a separate + process. +- `make check` and the Linux release archive smoke test must remain green. diff --git a/.agents/decisions/README.md b/.agents/decisions/README.md index 038518c..8f37c1a 100644 --- a/.agents/decisions/README.md +++ b/.agents/decisions/README.md @@ -23,3 +23,4 @@ Each ADR records status, context, decision, alternatives, consequences, migratio - [ADR-0015: Stable latest-release Unix installer asset](0015-stable-latest-release-installer.md) - [ADR-0016: Local runtime wiring without embedded scheduling](0016-runtime-wiring-boundary.md) - [ADR-0017: Declarative QA profiles around the canonical gate](0017-declarative-qa-profiles.md) +- [ADR-0018: Paseo action adapter and lifecycle proof](0018-paseo-action-adapter.md) diff --git a/.agents/tasks/phase-9-runtime-wiring.md b/.agents/tasks/phase-9-runtime-wiring.md index b6b3621..4ab2a3b 100644 --- a/.agents/tasks/phase-9-runtime-wiring.md +++ b/.agents/tasks/phase-9-runtime-wiring.md @@ -4,10 +4,12 @@ - **User/operator result:** One `weft setup` invocation initializes local state, wires supported project instruction surfaces, and exposes a runtime-neutral - bridge for existing agent runtimes and orchestrators. + bridge for existing agent runtimes and orchestrators. Paseo can then use the + shipped Unix action adapter to carry durable work across agent sessions. - **In scope:** Idempotent local setup, runtime discovery, managed instruction blocks, a machine-readable manifest, wiring diagnosis, a Codex/Claude - Code/Gemini CLI reference path, and orchestrator-facing bridge semantics. + Code/Gemini CLI reference path, orchestrator-facing bridge semantics, and the + versioned Paseo assignment/lease/checkpoint/materialization/release adapter. - **Out of scope:** Launching, scheduling, monitoring, selecting models for, or taking authority over external agents; remote control planes; credentials. - **Affected domain invariants:** Assignment and lease authority, exact revision @@ -29,6 +31,13 @@ 5. `weft doctor` validates local state and wiring without mutating it. 6. Tests cover setup, repeat setup, existing instruction preservation, malformed marker denial, unavailable runtimes, and machine-readable output. +7. The Paseo adapter uses the current CLI grammar and rejects missing or extra + action arguments instead of silently invoking stale commands. +8. A deterministic proof covers acquisition, renewal, checkpoint, dirty + observation, handoff, expired-lease reclaim, exact session replacement, and + release across two isolated workspaces. +9. Unix release archives contain the executable adapter and their smoke test + verifies its lifecycle entry point. ## Risks @@ -37,6 +46,8 @@ failed setup must not claim a completed wiring. - **Provider divergence/compatibility:** Runtime-specific files are convenience adapters, not proof that a provider accepts or executes Weft operations. +- **Runtime lifecycle:** The Paseo adapter must carry explicit IDs and expected + versions without becoming a launcher, scheduler, or blind retry loop. - **Upgrade/rollback:** The manifest format is versioned and remains local; removal must preserve user-authored instruction content. @@ -51,7 +62,9 @@ 1. Define the runtime-wiring boundary and manifest — ADR and roadmap proof. 2. Implement setup/doctor and managed instruction blocks — focused CLI tests. 3. Add runtime/reference documentation — link checker and JSON contract proof. -4. Run full workspace, clean setup/repeat/denial checks — `make check`. +4. Add the current-grammar Paseo action adapter and lifecycle proof — runtime + scripts and installed archive smoke test. +5. Run full workspace, clean setup/repeat/denial checks — `make check`. ## Validation record @@ -60,17 +73,22 @@ | Focused | `cargo test -p weft-cli wiring` | Passed | Managed-block, runtime detection, and non-mutating doctor coverage | | CLI/JSON contract | `cargo test -p weft-cli setup_` | Passed | Setup, doctor, JSON envelope, and preflight path | | Idempotency and denial | `setup_wires_project_context_idempotently_and_denies_malformed_markers` | Passed | Existing content, repeat bytes, malformed-marker, and no-state denial proof | -| Static/harness | `make check` | Passed | Documentation links, 17 CLI tests, full workspace tests, formatting, strict Clippy | -| Package/deployment | `make package-release VERSION=v0.2.0 && make test-release ARCHIVE=dist/weft-0.2.0-x86_64-unknown-linux-musl.tar.gz` | Passed | Clean installed binary ran setup, doctor, durable Change restart, and uninstall retention | +| Static/harness | `make check` | Passed | Documentation links, 18 CLI tests, full workspace tests, formatting, strict Clippy, and runtime adapter proofs | +| Runtime lifecycle | `bash scripts/test-paseo-weft-bridge.sh` | Passed | Two isolated workspaces proved acquisition, renewal, checkpoint, observation, handoff, expiry/reclaim, replacement, and release. | +| Session resume | `bash scripts/test-cli-session-resume.sh` | Passed | Canonical checkpoint and Change head survived a separate process. | +| Package/deployment | `make package-release VERSION=v0.2.1 && make test-release ARCHIVE=dist/weft-0.2.1-x86_64-unknown-linux-musl.tar.gz` | Passed | The installed archive contained and exercised `bin/weft-paseo-action` with the installed binary, then passed setup, doctor, restart, and uninstall retention. | ## Decision and follow-up - **Decision and alternatives rejected:** ADR-0016; reject embedded scheduling, user-home configuration mutation, and unsupported native-adapter claims. -- **Residual risks:** Native runtime plug-ins and lifecycle hooks require - runtime-specific capability proof before Weft can claim automatic lifecycle - capture. -- **Unavailable evidence:** No runtime process is launched by setup; native - runtime hooks and cross-worktree environment injection remain unproven. -- **Follow-up:** Add native adapters only after each runtime's versioned config - and lifecycle semantics are proved end-to-end. +- **Residual risks:** The adapter is proven as an explicit Unix action boundary; + it does not configure Paseo itself, and Windows has no shipped PowerShell + equivalent yet. OMC, OMG, OMX, Codex, Claude Code, and Gemini CLI still need + their own lifecycle proof before native adapter claims. +- **Unavailable evidence:** No runtime process is launched by setup; Paseo's + host-specific project/workspace registration and Windows action mapping remain + external to this repository proof. +- **Follow-up:** Keep launch/scheduling in Paseo. Add another native adapter only + after its versioned lifecycle and crash/recovery semantics are proved + end-to-end. diff --git a/Makefile b/Makefile index 6f2c991..8c8a27d 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -.PHONY: check docs-check harness-check qa-self-test rust-check phase0-spike phase0-native-git-spike phase0-gitbutler-spike phase3-gitbutler-live package-release test-release test-upgrade-rollback test-release-reproducibility +.PHONY: check docs-check harness-check qa-self-test rust-check agent-runtime-check phase6-paseo-bridge phase0-spike phase0-native-git-spike phase0-gitbutler-spike phase3-gitbutler-live package-release test-release test-upgrade-rollback test-release-reproducibility RUST_HOST := $(shell rustc -vV | sed -n 's/^host: //p') -check: harness-check docs-check qa-self-test rust-check +check: harness-check docs-check qa-self-test rust-check agent-runtime-check harness-check: ./scripts/verify-harness.sh @@ -18,6 +18,12 @@ rust-check: cargo test --workspace --all-targets --target $(RUST_HOST) cargo clippy --workspace --all-targets --target $(RUST_HOST) -- -D warnings +agent-runtime-check: + ./scripts/test-paseo-weft-bridge.sh + ./scripts/test-cli-session-resume.sh + +phase6-paseo-bridge: agent-runtime-check + phase0-native-git-spike: ./scripts/phase0-native-git-spike.sh diff --git a/crates/weft-cli/src/tests.rs b/crates/weft-cli/src/tests.rs index ad2c4b7..31281c8 100644 --- a/crates/weft-cli/src/tests.rs +++ b/crates/weft-cli/src/tests.rs @@ -338,6 +338,14 @@ fn setup_wires_project_context_idempotently_and_denies_malformed_markers() { assert_eq!(setup["data"]["bridge_schema"], "weft.runtime-bridge.v1"); assert_eq!(setup["data"]["initialized"], true); assert_eq!(setup["data"]["runtimes"].as_array().unwrap().len(), 4); + let paseo = setup["data"]["runtimes"] + .as_array() + .unwrap() + .iter() + .find(|runtime| runtime["name"] == "paseo") + .unwrap(); + assert_eq!(paseo["integration"], "paseo-action-adapter-v2"); + assert_eq!(paseo["adapter"], "weft-paseo-action"); let agents_path = project.join("AGENTS.md"); let agents = fs::read_to_string(&agents_path).unwrap(); diff --git a/crates/weft-cli/src/wiring.rs b/crates/weft-cli/src/wiring.rs index e52b7e5..6bbd8c8 100644 --- a/crates/weft-cli/src/wiring.rs +++ b/crates/weft-cli/src/wiring.rs @@ -69,9 +69,22 @@ impl Runtime { } const fn integration(self) -> &'static str { - match self.instruction_file() { - Some(_) => "project-instructions", - None => "runtime-bridge", + match self { + Self::Paseo => "paseo-action-adapter-v2", + Self::Omc | Self::Omg | Self::Omx => "runtime-bridge", + Self::Codex | Self::ClaudeCode | Self::GeminiCli => "project-instructions", + } + } + + const fn adapter(self) -> Option<&'static str> { + match self { + Self::Paseo => Some("weft-paseo-action"), + Self::Codex + | Self::ClaudeCode + | Self::GeminiCli + | Self::Omc + | Self::Omg + | Self::Omx => None, } } @@ -107,6 +120,8 @@ struct RuntimeEntry { detected: bool, integration: String, instruction_file: Option, + #[serde(default)] + adapter: Option, } pub(crate) fn setup( @@ -131,6 +146,7 @@ pub(crate) fn setup( detected: executable_in_paths(runtime.executable(), &path_entries), integration: runtime.integration().to_owned(), instruction_file: runtime.instruction_file().map(ToOwned::to_owned), + adapter: runtime.adapter().map(ToOwned::to_owned), }) .collect(), }; @@ -359,6 +375,17 @@ fn configured_bridge_problems( if bridge.state_dir != display_path(state_dir) { problems.push("runtime bridge points to a different state directory".to_owned()); } + for runtime in &bridge.runtimes { + if runtime.name == "paseo" + && (runtime.integration != "paseo-action-adapter-v2" + || runtime.adapter.as_deref() != Some("weft-paseo-action")) + { + problems.push( + "paseo runtime bridge entry lacks the current weft-paseo-action adapter; run `weft setup`" + .to_owned(), + ); + } + } } fn bridge_view(bridge: &RuntimeBridge, initialized: bool, problems: &[String]) -> Value { @@ -373,7 +400,8 @@ fn bridge_view(bridge: &RuntimeBridge, initialized: bool, problems: &[String]) - "executable": runtime.executable, "detected": runtime.detected, "integration": runtime.integration, - "instruction_file": runtime.instruction_file + "instruction_file": runtime.instruction_file, + "adapter": runtime.adapter })).collect::>(), "problems": problems }) @@ -517,11 +545,12 @@ mod tests { state_dir: state.display().to_string(), protocol: "weft.cli.v1".to_owned(), runtimes: vec![RuntimeEntry { - name: "test".to_owned(), + name: "paseo".to_owned(), executable: "weft-test-runtime-never-present".to_owned(), detected: false, - integration: "runtime-bridge".to_owned(), + integration: "paseo-action-adapter-v2".to_owned(), instruction_file: None, + adapter: None, }], }; fs::create_dir(project.join(".weft")).unwrap(); @@ -540,6 +569,12 @@ mod tests { .iter() .any(|value| value.as_str().unwrap().contains("unavailable on PATH")) ); + assert!(report["problems"].as_array().unwrap().iter().any(|value| { + value + .as_str() + .unwrap() + .contains("lacks the current weft-paseo-action adapter") + })); assert_eq!( fs::read(state.join("metadata.sqlite3")).unwrap(), b"not sqlite" diff --git a/docs/AGENT_PROTOCOL.md b/docs/AGENT_PROTOCOL.md index 62053ab..87ae522 100644 --- a/docs/AGENT_PROTOCOL.md +++ b/docs/AGENT_PROTOCOL.md @@ -2,45 +2,58 @@ Weft coordinates durable Change state; it does not schedule, supervise, or terminate agent processes. Clients may be humans, Codex, Paseo, CI, or another -runtime. Every mutation uses the CLI v1 JSON contract and explicit state path. +runtime. Every mutation uses the `weft.cli.v1` JSON envelope and an explicit +state path. The normative, repository-maintained copy is +[`../.agents/AGENT_PROTOCOL.md`](../.agents/AGENT_PROTOCOL.md). + +## Process contract + +```text +weft --format json --state-dir PATH [options] +``` + +Global options precede the command. JSON mode emits one envelope on stdout and +the exit status is part of the protocol. Mutations carry caller-owned +`--operation-id`, `--actor`, and `--at` values. Head/version-changing commands +also carry the exact observed expected value. Reusing an operation ID with the +same intent replays its recorded result; a different intent is a conflict. ## Provider-neutral operations -| Operation | CLI surface | Exact durable inputs | +| Agent intent | CLI surface | Exact durable checkpoint | | --- | --- | --- | -| Discover/inspect | `status`, `change show`, `history` | Change or provider path as applicable | -| Acquire/renew/release | `change acquire|renew|release` | Change, operation, holder, explicit time/expiry | -| Create revision | `change revise` | Change, expected head, exact base, provider content | -| Assign/handoff | `change assign|handoff` | Change, immutable assignment ID, actor/time | -| Dependencies/stacks/candidates | `dependency`, `stack`, `candidate` | Exact revisions and expected stack version | -| Materialize | `materialization create|transition` | Exact revision and expected lifecycle state | -| Review/validate | `review`, `validation` | Exact revision or candidate target | -| Integrate/reconcile | `integrate`, `reconcile`, `conflict list` | Immutable candidate, operation ID, expected target, receipt/reconciliation IDs | +| Discover provider | `native-git discover`, `gitbutler discover` | Capability and locator evidence | +| Acquire work | `assignment create`, `lease acquire` | Assignment/lease ID and version | +| Inspect exact work | `change show`, `change history`, `candidate show` | Change/revision/candidate ID | +| Materialize | `native-git materialize` or `materialization create` | Exact revision and Materialization ID | +| Publish progress | `native-git capture` or `revision append` | Expected Change head and artifact digest | +| Handoff | `assignment create`, then `assignment release` | Both immutable tenures | +| Review/validate | `review request|submit`, `validation record` | Exact revision or candidate target | +| Compose | `stack create|replace`, `candidate create` | Exact ordered revision inputs | +| Integrate | `integration plan`, provider execute command | Expected target and stable effect ID | +| Recover uncertainty | `integration uncertain`, provider `reconcile-integration` | Reconciliation outcome before closure | +| Release workspace | Provider release, `lease release`, `assignment release` | Observed versions and `--yes` | ## Required error handling -Agents must not reinterpret any of these conditions as success: - -- `stale revision head`, `stale stack version`, or stale materialization state: - re-read durable state and create an explicit next action. -- `lease held` or `lease lost`: stop exclusive work; acquire/recover before retry. -- stale candidate or changed target: plan a new candidate/integration; do not - retarget the existing attempt. -- unsupported provider capability/version: report the explicit error and choose - another supported operation only with operator direction. -- uncertain integration: preserve the running attempt and use reconciliation; - never emit a success receipt from inference. +Agents must not reinterpret stale heads/versions, held or lost leases, stale +candidates, changed targets, unsupported capabilities, provider failures, or +uncertain integration as success. Reload durable state and create an explicit +next action. A provider mutation with an ambiguous result must be recorded and +reconciled; it must never be blindly retried. ## Session resume rule -An agent may resume only from `--state` durable metadata, canonical artifact +An agent may resume only from `--state-dir` durable metadata, canonical artifact content, exact revision/candidate IDs, and provider observations. Dirty workspace files, a prior conversation, or an agent process identifier are never resume -authority. The resumed agent first inspects its Change/history, obtains or -recovers the required lease, materializes an exact revision if needed, and then -records a new revision or outcome through the normal CAS/operation-ID path. +authority. The resumed agent inspects its Change/history, obtains or reclaims +the required lease, materializes an exact revision in a new workspace if needed, +and records the next revision or outcome through the normal CAS and operation-ID +path. ## Compatibility -The protocol is versioned by the CLI JSON `schemaVersion`. Clients must reject -unknown major schema versions and must not depend on provider raw JSON. +Clients must reject unknown major protocol schemas and must not depend on raw +provider JSON. The installed Paseo action adapter uses the same CLI contract; +see [Paseo Integration](PASEO_INTEGRATION.md). diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b9ee1fd..a5b9039 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,11 +7,15 @@ All notable project changes will be documented here. ### Added - Archive-to-archive `v0.1.0` to `v0.2.0` upgrade/rollback proof with checksum/SBOM verification, durable-state compatibility, and complete pre-upgrade snapshot restoration. +- Versioned Unix `weft-paseo-action` lifecycle adapter with isolated-workspace + acquisition, checkpoint, session replacement, and release proof. ### Changed - Runtime releases publish only the installable archive. The archive no longer ships project documentation or development helpers; its embedded root SBOM remains covered by CI and provenance verification. - Runtime releases also publish a stable-name Unix `install.sh` bootstrap asset, enabling the latest published release to be installed without executing a script from the development branch. +- Paseo bridge metadata and helper scripts now use the current `weft.cli.v1` + command grammar, with stale action invocations denied explicitly. ## 0.1.0 - 2026-08-27 diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 487f74a..c6cd18f 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -4,7 +4,7 @@ The first deployable surface is the local `weft` CLI built and smoke-tested on GitHub-hosted Ubuntu 24.04 x86_64. Other GNU/Linux distributions and glibc versions may work but are not supported claims. It is not a daemon and opens no network listener. The caller chooses a state directory and grants access only to identities that coordinate that repository. Native Git requires Git 2.38 or newer. -Tags matching `vMAJOR.MINOR.PATCH` run the repository gate, build a relocatable archive, smoke-test it in a clean temporary prefix, and attach GitHub build provenance. GitHub Releases publish the runtime archives plus a small Unix `install.sh` bootstrap asset under a stable name. The archive contains the binary, install/uninstall helpers, a small operator reference (`README.md`, `GETTING_STARTED.md`, `MANUAL.md`, `USAGE.md`, and `LICENSE`), the embedded `SBOM.cdx.json`, and `MANIFEST.sha256`; it does not contain the repository documentation tree or development scripts. The bootstrapper downloads the matching archive and verifies GitHub's published SHA-256 digest. CI generates and verifies SHA-256 sidecars and a CycloneDX dependency inventory; the SBOM is embedded at the archive root, not published as a separate asset. A tag must point to a commit on `main`. Publication requires explicit authorization. +Tags matching `vMAJOR.MINOR.PATCH` run the repository gate, build a relocatable archive, smoke-test it in a clean temporary prefix, and attach GitHub build provenance. GitHub Releases publish the runtime archives plus a small Unix `install.sh` bootstrap asset under a stable name. Unix-like archives contain the binary, the install/uninstall helpers, the installed `weft-paseo-action` lifecycle adapter, a small operator reference (`README.md`, `GETTING_STARTED.md`, `MANUAL.md`, `USAGE.md`, and `LICENSE`), the embedded `SBOM.cdx.json`, and `MANIFEST.sha256`; Windows archives contain their PowerShell install/uninstall helpers and the same bridge/CLI documentation but no Bash adapter. No archive contains the repository documentation tree or development-only scripts. The bootstrapper downloads the matching archive and verifies GitHub's published SHA-256 digest. CI generates and verifies SHA-256 sidecars and a CycloneDX dependency inventory; the SBOM is embedded at the archive root, not published as a separate asset. A tag must point to a commit on `main`. Publication requires explicit authorization. ## Release preparation diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index eb2ea05..ed40124 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -8,7 +8,10 @@ Run all repository checks from the root: make check ``` -The current gate validates repository documentation, Rust formatting, workspace tests, and Clippy warnings. Provider spikes remain explicit targets because the GitButler spike requires a local GitButler installation and registry access. +The current gate validates repository documentation, Rust formatting, workspace +tests, Clippy warnings, and the deterministic Paseo lifecycle/session-resume +adapter proofs. Provider spikes remain explicit targets because the GitButler +spike requires a local GitButler installation and registry access. ## Rust workspace @@ -51,13 +54,14 @@ Mutations require caller-owned operation IDs, actors, timestamps, and relevant e The initial deployable boundary is the Ubuntu 24.04 x86_64 local CLI archive: ```bash -make package-release VERSION=v0.2.0 -make test-release ARCHIVE=dist/weft-0.2.0-x86_64-unknown-linux-musl.tar.gz +make package-release VERSION=v0.2.1 +make test-release ARCHIVE=dist/weft-0.2.1-x86_64-unknown-linux-musl.tar.gz ``` The smoke test verifies the checksum, installs into a disposable prefix, checks -version/help, initializes state, creates and reads a Change across processes, -uninstalls the binary, and proves state retention. It does not publish anything. +version/help, initializes state, runs the installed Paseo adapter lifecycle, +creates and reads a Change across processes, uninstalls the binary, and proves +state retention. It does not publish anything. ## Work lifecycle diff --git a/docs/DOMAIN.md b/docs/DOMAIN.md index 61e88f3..b6d5dec 100644 --- a/docs/DOMAIN.md +++ b/docs/DOMAIN.md @@ -245,27 +245,24 @@ The reusable domain API is independent of the CLI. The CLI exposes Change lifecy Structured commands support a stable JSON schema, documented field semantics and exit codes, noninteractive operation, explicit confirmation for destructive actions, expected-version inputs, and operation IDs for retryable operations. -Representative commands include: +Representative current CLI command families include: ```bash -weft status --json -weft change create -weft change show --json -weft change revise --expected-head -weft change acquire -weft change handoff -weft dependency add @ -weft stack create -weft candidate create --stack -weft materialization create -weft review request --candidate -weft review submit -weft validation record --candidate -weft conflict list -weft integrate plan -weft integrate run --operation-id --yes -weft reconcile -weft history +weft --format json --state-dir .weft init +weft --format json --state-dir .weft change create \ + --change-id change-1 --operation-id create-1 --actor agent-1 --at 1000 +weft --format json --state-dir .weft change show --change-id change-1 +weft --format json --state-dir .weft revision append ... +weft --format json --state-dir .weft assignment create ... +weft --format json --state-dir .weft lease acquire ... +weft --format json --state-dir .weft candidate create ... +weft --format json --state-dir .weft review request ... +weft --format json --state-dir .weft validation record ... +weft --format json --state-dir .weft integration plan ... +weft --format json --state-dir .weft native-git execute-integration ... +weft --format json --state-dir .weft native-git reconcile-integration ... +weft --format json --state-dir .weft change history --change-id change-1 ``` -Exact syntax may evolve, but the domain operations and machine-readable guarantees are required. +See `docs/CLI.md` and `docs/USAGE.md` for the complete current grammar; the +domain operations and machine-readable guarantees are required. diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index c2d8911..0349526 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -34,7 +34,10 @@ 4. Continue to use Codex, Claude Code, Gemini CLI, Paseo, or your existing orchestrator normally. Weft provides their shared durable coordination state; - the runtime/orchestrator continues to launch and supervise agents. + the runtime/orchestrator continues to launch and supervise agents. On + Unix-like archives, Paseo can call the bundled `bin/weft-paseo-action` for + explicit acquisition, checkpoint, replacement, and release actions; see + [Paseo Integration](PASEO_INTEGRATION.md). 5. For an explicit or machine-managed setup, choose runtime names and consume the JSON bridge: diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 7f99d81..3f0271a 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -30,6 +30,11 @@ Provider repositories, worktrees, credentials, and agent sessions remain outside Weft state. Setup never launches agents, reads credentials, or changes user-home runtime configuration. +On Unix-like archives, Paseo may invoke `bin/weft-paseo-action` using the +explicit environment and action arguments in [Paseo Integration](PASEO_INTEGRATION.md). +The adapter is not a process runner; Paseo remains responsible for launching, +supervising, and replacing agent sessions. + ## Upgrade, rollback, and uninstall Before upgrading, stop concurrent mutating callers and back up the complete diff --git a/docs/MULTI_AGENT_WORKFLOWS.md b/docs/MULTI_AGENT_WORKFLOWS.md index 393147a..c4d67e3 100644 --- a/docs/MULTI_AGENT_WORKFLOWS.md +++ b/docs/MULTI_AGENT_WORKFLOWS.md @@ -7,7 +7,7 @@ does not start, stop, or supervise agents. | Workflow | Durable prerequisite | Orchestrator action | | --- | --- | --- | -| Execute a Change | exact Change and acquired operation lease | Launch an implementer only after `change acquire` succeeds. | +| Execute a Change | exact Change and acquired operation lease | Launch an implementer only after `lease acquire` succeeds. | | Review | exact revision/candidate and review request | Assign reviewer subject; record immutable review submission. | | Resolve conflict | durable conflict record and candidate | Assign resolver by handoff; create a successor revision, never mutate the conflicted one. | | Validate | exact revision/candidate | Run external pipeline and record its execution ID/status. | diff --git a/docs/PASEO_INTEGRATION.md b/docs/PASEO_INTEGRATION.md index 6f79c0a..4eebff9 100644 --- a/docs/PASEO_INTEGRATION.md +++ b/docs/PASEO_INTEGRATION.md @@ -1,4 +1,4 @@ -# Paseo Integration v1 +# Paseo Integration v2 Paseo is an external agent/workspace launcher. Weft remains the source of truth for Change, revision, assignment, lease, candidate, materialization, and @@ -16,25 +16,54 @@ WEFT_ACTOR=paseo-agent-or-human WEFT_NOW_UNIX_MS=milliseconds ``` -The optional `PASEO_WORKSPACE_ID` is passed as a Weft materialization workspace -reference or assignment subject. The action shim refuses to invent missing +An external launcher may use its workspace ID as the adapter's `WORKSPACE_ID` +or assignment subject argument. The action shim refuses to invent missing identities or timestamps. -## Supported requested actions +## Action adapter -`scripts/paseo-weft-action.sh` supports: +The installed `weft-paseo-action` command (and its source copy at +`scripts/paseo-weft-action.sh`) translates one Paseo lifecycle action into one +current `weft.cli.v1` command. Unix-like release archives include the adapter +as `bin/weft-paseo-action`; Windows archives currently expose the same bridge +and CLI contract but require a PowerShell caller to perform this mapping. +Set `WEFT_BIN` only when the adapter should use a specific binary; otherwise it +uses a sibling installed `weft`, a `weft` on `PATH`, or the source workspace's +Cargo binary. -- `acquire`: obtain an exclusive operation lease before delegated work; -- `handoff`: append a durable handoff assignment; -- `history`: inspect durable Change evidence. +Every action keeps IDs, expected versions, operation IDs, actor, and time +explicit. The supported actions are: + +| Action | Arguments | Weft operation | +| --- | --- | --- | +| `assign` | `ASSIGNMENT_ID OPERATION_ID [ROLE]` | `assignment create` | +| `acquire` | `LEASE_ID OPERATION EXPECTED_VERSION EXPIRES_AT OPERATION_ID` | `lease acquire` | +| `renew` | `LEASE_ID EXPECTED_VERSION EXPIRES_AT OPERATION_ID` | `lease renew` | +| `checkpoint` | `REVISION_ID EXPECTED_HEAD BASE_REVISION PROVIDER_REVISION OPERATION_ID` | `native-git capture` | +| `materialize` | `MATERIALIZATION_ID WORKSPACE_ID REVISION_ID PROVIDER_REVISION DESTINATION OPERATION_ID` | `native-git materialize` | +| `observe` | `MATERIALIZATION_ID EXPECTED_VERSION WORKTREE PROVIDER_REVISION OPERATION_ID` | `native-git observe-materialization` | +| `release-materialization` | `MATERIALIZATION_ID EXPECTED_VERSION WORKTREE OPERATION_ID` | `native-git release-materialization` | +| `release-lease` | `LEASE_ID EXPECTED_VERSION OPERATION_ID` | `lease release` | +| `release-assignment` | `ASSIGNMENT_ID EXPECTED_VERSION OPERATION_ID` | `assignment release` | +| `handoff` | `ASSIGNMENT_ID SUBJECT_ID OPERATION_ID [ROLE] [SUBJECT_KIND]` | `assignment create` | +| `history` | none | `change history` | + +The common environment is `WEFT_STATE_DIR`, `WEFT_CHANGE_ID`, `WEFT_ACTOR`, and +`WEFT_NOW_UNIX_MS`. The adapter defaults `WEFT_SUBJECT_KIND` to `agent`, +`WEFT_SUBJECT_ID` to `WEFT_ACTOR`, and `WEFT_ASSIGNMENT_ROLE` to `implementer`. +Native Git actions require `WEFT_REPOSITORY`; checkpoint/capture additionally +requires `WEFT_REPOSITORY_ID` because it creates the durable Repository binding. Paseo decides when to launch, notify, or stop an agent. Weft decides whether a lease can be acquired and records all resulting state. A Paseo outage cannot -block direct `weft --state ...` operation. +block direct `weft --state-dir ...` operation. ## Resume and blocking -On a resumed Paseo session, first invoke `history` and then `acquire`. If the -lease is held, stale, or lost, surface that Weft JSON error as a blocking reason; -do not run an exclusive provider mutation. Providers and candidates are still -reconciled through Weft's normal CLI commands. +On a resumed Paseo session, invoke `history`, acquire a new lease with the +observed scope version, and materialize the exact recorded revision into a new +workspace. A replacement lease has a new ID and points to the expired +predecessor; it never revives the old lease. If the lease is held, stale, or +lost, surface that Weft JSON error as a blocking reason; do not run an +exclusive provider mutation. Providers and candidates are still reconciled +through Weft's normal CLI commands. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 32e6e48..aa5b679 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -71,7 +71,9 @@ configuration, read credentials, or schedule work. Its output is a provider-neut contract that external launchers use to acquire durable authority, pass exact context, checkpoint progress, and release/reconcile work. A runtime-specific native adapter is complete only after an end-to-end proof covers setup, acquisition, -checkpoint, session replacement, and release. +checkpoint, session replacement, and release. The first proven native adapter is +the Unix `weft-paseo-action` mapping; OMC, OMG, and OMX remain bridge-only until +their own lifecycle contracts are proven. ## Phase acceptance diff --git a/docs/RUNTIME_README.md b/docs/RUNTIME_README.md index 1f6092a..eb778e7 100644 --- a/docs/RUNTIME_README.md +++ b/docs/RUNTIME_README.md @@ -5,9 +5,14 @@ humans, agents, and supported Git providers. This archive contains only the runtime and its operator reference. Start with [Getting Started](GETTING_STARTED.md). Use [Usage](USAGE.md) for the -noninteractive CLI contract and [the manual](MANUAL.md) for installation, -upgrade, rollback, and support boundaries. +noninteractive CLI contract, [Paseo Integration](PASEO_INTEGRATION.md) for the +installed `weft-paseo-action` lifecycle adapter, and [the manual](MANUAL.md) for +installation, upgrade, rollback, and support boundaries. +On Unix-like archives, `bin/weft-paseo-action` is a thin, non-scheduling +adapter for Paseo to acquire, checkpoint, replace, and release agent work +through Weft. Windows archives provide the bridge and CLI contract but do not +ship this Bash adapter. It does not start or supervise an agent. `SBOM.cdx.json` is the embedded CycloneDX component inventory. `MANIFEST.sha256` lists SHA-256 digests for every other file in this archive. See `LICENSE` for the distribution status. diff --git a/docs/RUNTIME_WIRING.md b/docs/RUNTIME_WIRING.md index a583051..ef0572a 100644 --- a/docs/RUNTIME_WIRING.md +++ b/docs/RUNTIME_WIRING.md @@ -23,7 +23,7 @@ credential, changes user-home configuration, or schedules an agent. | Codex | Maintains a marked block in `AGENTS.md` | Codex remains the agent runtime. | | Claude Code | Maintains a marked block in `CLAUDE.md` | Claude Code remains the agent runtime. | | Gemini CLI | Maintains a marked block in `GEMINI.md` | Gemini CLI remains the agent runtime. | -| Paseo | Publishes an entry in the runtime bridge | Paseo remains the launcher/supervisor. | +| Paseo | Publishes a bridge entry consumed by the `weft-paseo-action` lifecycle adapter | Paseo remains the launcher/supervisor. | | OMC, OMG, OMX | Publishes an entry in the runtime bridge | Their adapters remain external to Weft. | The instruction block makes the durable protocol visible to supported code-agent @@ -31,9 +31,11 @@ surfaces. It tells agents to use Weft's JSON CLI for shared coordination, to acq authority before shared mutation, and to checkpoint/reconcile rather than trusting ephemeral sessions or provider status. -Bridge-only runtimes are discoverable but are not claimed as native lifecycle -integrations. A native adapter requires its own proof that acquisition, checkpoint, -session replacement, and release work end-to-end. +OMC, OMG, and OMX remain bridge-only runtimes: discovery is not a claim that +their lifecycle hooks are configured. Paseo has a versioned action adapter for +explicit acquisition, checkpoint, replacement, and release; it has no authority +to launch or schedule processes. A different runtime requires its own adapter +and end-to-end proof before Weft can claim native lifecycle integration. ## Select runtimes explicitly @@ -60,11 +62,11 @@ duplicated, or malformed, setup stops without modifying any wiring file. `.weft/runtime-bridge.json` is local configuration and is intentionally ignored by Git. It records the state location, `weft.cli.v1` protocol version, selected -runtimes, their executable names, setup-time availability, and whether Weft wrote a -project instruction block or only a bridge entry. Agents first read the project-local -bridge and pass its `state_dir` as `--state-dir`; this keeps an external shared state -location available from an isolated workspace without embedding an absolute path in -tracked instruction files. +runtimes, their executable names, setup-time availability, integration kind, and +the optional adapter command. Agents first read the project-local bridge and pass +its `state_dir` as `--state-dir`; this keeps an external shared state location +available from an isolated workspace without embedding an absolute path in tracked +instruction files. An orchestrator reads that bridge, then uses the JSON protocol to create or inspect durable Changes, Assignments, Leases, revisions, candidates, and integration @@ -78,8 +80,9 @@ weft --format json doctor ``` Doctor verifies the state-directory shape and SQLite header, parses the project-local -bridge, checks the exact managed instruction blocks, and checks whether each configured -executable is currently visible on `PATH`. Its JSON `healthy` field is the authoritative summary. +bridge, checks the exact managed instruction blocks, validates the current Paseo +adapter entry, and checks whether each configured executable is currently visible on +`PATH`. Its JSON `healthy` field is the authoritative summary. A completed diagnostic may return `healthy: false`; that reports a condition to fix, not an implicit repair. diff --git a/scripts/package-release.sh b/scripts/package-release.sh index 52a50f1..565413e 100755 --- a/scripts/package-release.sh +++ b/scripts/package-release.sh @@ -44,10 +44,13 @@ case "$target" in x86_64-pc-windows-msvc) install -m 0644 packaging/install.ps1 packaging/uninstall.ps1 "$stage/$package/" ;; - *) install -m 0755 packaging/install.sh packaging/uninstall.sh "$stage/$package/" ;; + *) + install -m 0755 packaging/install.sh packaging/uninstall.sh "$stage/$package/" + install -m 0755 scripts/paseo-weft-action.sh "$stage/$package/bin/weft-paseo-action" + ;; esac install -m 0644 docs/RUNTIME_README.md "$stage/$package/README.md" -install -m 0644 docs/GETTING_STARTED.md docs/MANUAL.md docs/USAGE.md LICENSE "$stage/$package/" +install -m 0644 docs/GETTING_STARTED.md docs/MANUAL.md docs/USAGE.md docs/PASEO_INTEGRATION.md LICENSE "$stage/$package/" mkdir -p "$output_dir" archive="$output_dir/$package.tar.gz" diff --git a/scripts/paseo-weft-action.sh b/scripts/paseo-weft-action.sh index b4771d6..7a02017 100755 --- a/scripts/paseo-weft-action.sh +++ b/scripts/paseo-weft-action.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Thin Paseo-to-Weft action bridge; process scheduling remains in Paseo. +# Paseo-to-Weft action adapter v2; process scheduling remains in Paseo. set -euo pipefail : "${WEFT_STATE_DIR:?WEFT_STATE_DIR is required}" @@ -7,24 +7,239 @@ set -euo pipefail : "${WEFT_ACTOR:?WEFT_ACTOR is required}" : "${WEFT_NOW_UNIX_MS:?WEFT_NOW_UNIX_MS is required}" -action="${1:?action is required: acquire|handoff|history}" +state_dir="$WEFT_STATE_DIR" +subject_kind="${WEFT_SUBJECT_KIND:-agent}" +subject_id="${WEFT_SUBJECT_ID:-$WEFT_ACTOR}" +assignment_role="${WEFT_ASSIGNMENT_ROLE:-implementer}" +weft_bin="${WEFT_BIN:-}" +script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" + +if [[ -z "$weft_bin" && -x "$script_dir/weft" ]]; then + weft_bin="$script_dir/weft" +fi + +require_var() { + local name="$1" + if [[ -z "${!name:-}" ]]; then + printf '%s is required\n' "$name" >&2 + exit 2 + fi +} + +weft() { + if [[ -n "$weft_bin" ]]; then + "$weft_bin" --format json --state-dir "$state_dir" "$@" + elif type -P weft >/dev/null 2>&1; then + command weft --format json --state-dir "$state_dir" "$@" + else + cargo run --offline --manifest-path "$script_dir/../Cargo.toml" \ + -p weft-cli -- --format json --state-dir "$state_dir" "$@" + fi +} + +require_repository() { + require_var WEFT_REPOSITORY +} + +require_repository_id() { + require_repository + require_var WEFT_REPOSITORY_ID +} + +usage() { + printf '%s\n' 'usage: weft-paseo-action ACTION [ACTION_ARGUMENTS...]' >&2 + printf '%s\n' 'actions: assign, acquire, renew, checkpoint, materialize, observe, release-materialization, release-lease, release-assignment, handoff, history' >&2 + exit 2 +} + +require_args() { + local minimum="$1" + local maximum="$2" + shift 2 + if (( $# < minimum || $# > maximum )); then + printf 'action %s expects between %s and %s arguments; got %s\n' \ + "$action" "$minimum" "$maximum" "$#" >&2 + exit 2 + fi +} + +action="${1:-}" +[[ -n "$action" ]] || usage +shift case "$action" in + assign) + require_args 2 3 "$@" + assignment_id="$1" + operation_id="$2" + role="${3:-$assignment_role}" + weft assignment create \ + --assignment-id "$assignment_id" \ + --change-id "$WEFT_CHANGE_ID" \ + --subject-kind "$subject_kind" \ + --subject-id "$subject_id" \ + --role "$role" \ + --operation-id "$operation_id" \ + --actor "$WEFT_ACTOR" \ + --at "$WEFT_NOW_UNIX_MS" + ;; acquire) - operation="${2:?operation is required}" - expires="${3:?expiry timestamp is required}" - cargo run --offline -p weft-cli -- --state "$WEFT_STATE_DIR" \ - change acquire "$WEFT_CHANGE_ID" --operation "$operation" \ - --holder "$WEFT_ACTOR" --now "$WEFT_NOW_UNIX_MS" --expires "$expires" --json + require_args 5 5 "$@" + lease_id="$1" + operation="$2" + expected_version="$3" + expires_at="$4" + operation_id="$5" + weft lease acquire \ + --lease-id "$lease_id" \ + --change-id "$WEFT_CHANGE_ID" \ + --operation "$operation" \ + --holder-kind "$subject_kind" \ + --holder-id "$subject_id" \ + --expected-version "$expected_version" \ + --expires-at "$expires_at" \ + --operation-id "$operation_id" \ + --actor "$WEFT_ACTOR" \ + --at "$WEFT_NOW_UNIX_MS" + ;; + renew) + require_args 4 4 "$@" + lease_id="$1" + expected_version="$2" + expires_at="$3" + operation_id="$4" + weft lease renew \ + --lease-id "$lease_id" \ + --expected-version "$expected_version" \ + --expires-at "$expires_at" \ + --operation-id "$operation_id" \ + --actor "$WEFT_ACTOR" \ + --at "$WEFT_NOW_UNIX_MS" + ;; + checkpoint) + require_args 5 5 "$@" + revision_id="$1" + expected_head="$2" + base_revision="$3" + provider_revision="$4" + operation_id="$5" + require_repository_id + weft native-git capture \ + --repository "$WEFT_REPOSITORY" \ + --repository-id "$WEFT_REPOSITORY_ID" \ + --base-revision "$base_revision" \ + --provider-revision "$provider_revision" \ + --change-id "$WEFT_CHANGE_ID" \ + --revision-id "$revision_id" \ + --expected-head "$expected_head" \ + --operation-id "$operation_id" \ + --actor "$WEFT_ACTOR" \ + --at "$WEFT_NOW_UNIX_MS" + ;; + materialize) + require_args 6 6 "$@" + materialization_id="$1" + workspace_id="$2" + revision_id="$3" + provider_revision="$4" + destination="$5" + operation_id="$6" + require_repository + weft native-git materialize \ + --repository "$WEFT_REPOSITORY" \ + --provider-revision "$provider_revision" \ + --change-id "$WEFT_CHANGE_ID" \ + --revision-id "$revision_id" \ + --destination "$destination" \ + --materialization-id "$materialization_id" \ + --workspace-id "$workspace_id" \ + --operation-id "$operation_id" \ + --actor "$WEFT_ACTOR" \ + --at "$WEFT_NOW_UNIX_MS" + ;; + observe) + require_args 5 5 "$@" + materialization_id="$1" + expected_version="$2" + worktree="$3" + provider_revision="$4" + operation_id="$5" + require_repository + weft native-git observe-materialization \ + --repository "$WEFT_REPOSITORY" \ + --worktree "$worktree" \ + --provider-revision "$provider_revision" \ + --materialization-id "$materialization_id" \ + --expected-version "$expected_version" \ + --operation-id "$operation_id" \ + --actor "$WEFT_ACTOR" \ + --at "$WEFT_NOW_UNIX_MS" + ;; + release-materialization) + require_args 4 4 "$@" + materialization_id="$1" + expected_version="$2" + worktree="$3" + operation_id="$4" + require_repository + weft native-git release-materialization \ + --repository "$WEFT_REPOSITORY" \ + --worktree "$worktree" \ + --materialization-id "$materialization_id" \ + --expected-version "$expected_version" \ + --operation-id "$operation_id" \ + --actor "$WEFT_ACTOR" \ + --at "$WEFT_NOW_UNIX_MS" \ + --yes + ;; + release-lease) + require_args 3 3 "$@" + lease_id="$1" + expected_version="$2" + operation_id="$3" + weft lease release \ + --lease-id "$lease_id" \ + --expected-version "$expected_version" \ + --operation-id "$operation_id" \ + --actor "$WEFT_ACTOR" \ + --at "$WEFT_NOW_UNIX_MS" \ + --yes + ;; + release-assignment) + require_args 3 3 "$@" + assignment_id="$1" + expected_version="$2" + operation_id="$3" + weft assignment release \ + --assignment-id "$assignment_id" \ + --expected-version "$expected_version" \ + --operation-id "$operation_id" \ + --actor "$WEFT_ACTOR" \ + --at "$WEFT_NOW_UNIX_MS" \ + --yes ;; handoff) - assignment="${2:?assignment ID is required}" - subject="${3:?handoff subject is required}" - cargo run --offline -p weft-cli -- --state "$WEFT_STATE_DIR" \ - change handoff "$WEFT_CHANGE_ID" --assignment "$assignment" --to "$subject" \ - --actor "$WEFT_ACTOR" --at "$WEFT_NOW_UNIX_MS" --json + require_args 3 5 "$@" + assignment_id="$1" + successor_id="$2" + operation_id="$3" + role="${4:-$assignment_role}" + successor_kind="${5:-$subject_kind}" + weft assignment create \ + --assignment-id "$assignment_id" \ + --change-id "$WEFT_CHANGE_ID" \ + --subject-kind "$successor_kind" \ + --subject-id "$successor_id" \ + --role "$role" \ + --operation-id "$operation_id" \ + --actor "$WEFT_ACTOR" \ + --at "$WEFT_NOW_UNIX_MS" ;; history) - cargo run --offline -p weft-cli -- --state "$WEFT_STATE_DIR" history "$WEFT_CHANGE_ID" --json + require_args 0 0 "$@" + weft change history --change-id "$WEFT_CHANGE_ID" + ;; + *) + printf 'unsupported Paseo Weft action: %s\n' "$action" >&2 + exit 2 ;; - *) printf 'unsupported Paseo Weft action: %s\n' "$action" >&2; exit 2 ;; esac diff --git a/scripts/test-cli-session-resume.sh b/scripts/test-cli-session-resume.sh index 2360fca..19f6c7b 100755 --- a/scripts/test-cli-session-resume.sh +++ b/scripts/test-cli-session-resume.sh @@ -6,6 +6,15 @@ root="$(mktemp -d /tmp/weft-cli-resume.XXXXXX)" trap 'rm -rf "$root"' EXIT repo="$root/repository" state="$root/state" +weft_bin="${WEFT_BIN:-}" + +run_weft() { + if [[ -n "$weft_bin" ]]; then + "$weft_bin" --format json --state-dir "$state" "$@" + else + cargo run --offline -p weft-cli -- --format json --state-dir "$state" "$@" + fi +} git init --quiet "$repo" git -C "$repo" config user.name Weft @@ -17,16 +26,30 @@ git -C "$repo" commit --quiet -m base base="$(git -C "$repo" rev-parse HEAD)" printf 'canonical revision\n' > "$repo/file" git -C "$repo" commit --quiet -am revision +revision="$(git -C "$repo" rev-parse HEAD)" -cargo run --offline -p weft-cli -- --state "$state" change create change-1 --json >/dev/null -cargo run --offline -p weft-cli -- --state "$state" change revise change-1 \ - --repository "$repo" --base "$base" --revision revision-1 \ - --expected-head none --json >/dev/null +run_weft init >/dev/null +run_weft change create \ + --change-id change-1 \ + --operation-id change-create-1 \ + --actor prior-runtime \ + --at 1000 >/dev/null +run_weft native-git capture \ + --repository "$repo" \ + --repository-id repo-1 \ + --base-revision "$base" \ + --provider-revision "$revision" \ + --change-id change-1 \ + --revision-id revision-1 \ + --expected-head none \ + --operation-id revision-capture-1 \ + --actor prior-runtime \ + --at 1010 >/dev/null # This separate process simulates the prior runtime no longer existing. -resumed="$(cargo run --offline -p weft-cli -- --state "$state" change show change-1 --json)" -[[ "$resumed" == *'"headRevisionId":"revision-1"'* ]] -cargo run --offline -p weft-cli -- --state "$state" history change-1 --json \ - | grep -q 'revision-appended' +resumed="$(run_weft change show --change-id change-1)" +[[ "$resumed" == *'"head_revision_id":"revision-1"'* ]] +run_weft change history --change-id change-1 \ + | grep -q 'revision.appended' printf 'cli-session-resume: ok\n' diff --git a/scripts/test-paseo-weft-bridge.sh b/scripts/test-paseo-weft-bridge.sh index 5d5eda3..0c53720 100755 --- a/scripts/test-paseo-weft-bridge.sh +++ b/scripts/test-paseo-weft-bridge.sh @@ -3,13 +3,116 @@ set -euo pipefail root="$(mktemp -d /tmp/weft-paseo-bridge.XXXXXX)" trap 'rm -rf "$root"' EXIT +script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" state="$root/state" +project="$root/project" +repository="$root/repository" +workspace_one="$root/workspace-one" +workspace_two="$root/workspace-two" +weft_bin="${WEFT_BIN:-}" +action_bin="${WEFT_ACTION_BIN:-$script_dir/paseo-weft-action.sh}" + +run_weft() { + if [[ -n "$weft_bin" ]]; then + "$weft_bin" --format json --state-dir "$state" "$@" + else + cargo run --offline -p weft-cli -- --format json --state-dir "$state" "$@" + fi +} + +action() { + local now="$1" + shift + WEFT_BIN="$weft_bin" \ + WEFT_STATE_DIR="$state" \ + WEFT_CHANGE_ID=change-1 \ + WEFT_ACTOR="${WEFT_ACTION_ACTOR:-agent-one}" \ + WEFT_NOW_UNIX_MS="$now" \ + WEFT_SUBJECT_KIND=agent \ + WEFT_SUBJECT_ID="${WEFT_ACTION_SUBJECT:-agent-one}" \ + WEFT_REPOSITORY="${WEFT_ACTION_REPOSITORY:-$repository}" \ + WEFT_REPOSITORY_ID=repo-1 \ + "$action_bin" "$@" +} + +git init --quiet "$repository" +git -C "$repository" config user.name Weft +git -C "$repository" config user.email weft@example.test +git -C "$repository" config commit.gpgsign false +printf 'base\n' > "$repository/file.txt" +git -C "$repository" add file.txt +git -C "$repository" commit --quiet -m base +base="$(git -C "$repository" rev-parse HEAD)" +printf 'first revision\n' > "$repository/file.txt" +git -C "$repository" commit --quiet -am first-revision +revision_one="$(git -C "$repository" rev-parse HEAD)" + +mkdir "$project" +run_weft setup --project-dir "$project" --runtime paseo >/dev/null +run_weft change create \ + --change-id change-1 \ + --operation-id change-create-1 \ + --actor setup-test \ + --at 1 >/dev/null +test -f "$project/.weft/runtime-bridge.json" +grep -q 'weft.runtime-bridge.v1' "$project/.weft/runtime-bridge.json" +grep -q 'weft.cli.v1' "$project/.weft/runtime-bridge.json" +grep -q 'paseo-action-adapter-v2' "$project/.weft/runtime-bridge.json" +grep -q 'weft-paseo-action' "$project/.weft/runtime-bridge.json" +configured_state="$(sed -n 's/^ "state_dir": "\(.*\)",$/\1/p' "$project/.weft/runtime-bridge.json")" +test "$configured_state" = "$state" +state="$configured_state" + +if action 9 history unexpected >/dev/null 2>&1; then + printf '%s\n' 'adapter accepted an unexpected history argument' >&2 + exit 1 +fi +if action 9 acquire lease-usage-only >/dev/null 2>&1; then + printf '%s\n' 'adapter accepted incomplete acquire arguments' >&2 + exit 1 +fi + +action 10 assign assignment-1 assignment-create-1 +action 11 acquire lease-1 implement 0 100 lease-acquire-1 +action 12 renew lease-1 1 200 lease-renew-1 +action 13 checkpoint revision-1 none "$base" "$revision_one" revision-capture-1 +action 14 materialize materialization-1 workspace-1 revision-1 "$revision_one" "$workspace_one" materialization-create-1 +test "$(cat "$workspace_one/file.txt")" = 'first revision' + +printf 'checkpointed progress\n' > "$workspace_one/file.txt" +action 15 observe materialization-1 1 "$workspace_one" "$revision_one" materialization-observe-1 >/dev/null +git -C "$workspace_one" commit --quiet -am checkpointed-progress +revision_two="$(git -C "$workspace_one" rev-parse HEAD)" +action 20 checkpoint revision-2 revision-1 "$revision_one" "$revision_two" revision-capture-2 +action 21 handoff assignment-2 agent-two assignment-create-2 +action 22 release-materialization materialization-1 2 "$workspace_one" materialization-release-1 +action 23 release-assignment assignment-1 1 assignment-release-1 +test ! -e "$workspace_one" + +# A replacement session reclaims the expired scope and materializes the exact +# checkpoint into a different isolated workspace. +WEFT_ACTION_ACTOR=agent-two WEFT_ACTION_SUBJECT=agent-two action 201 acquire lease-2 implement 2 300 lease-acquire-2 +WEFT_ACTION_ACTOR=agent-two WEFT_ACTION_SUBJECT=agent-two action 202 materialize materialization-2 workspace-2 revision-2 "$revision_two" "$workspace_two" materialization-create-2 +test "$(cat "$workspace_two/file.txt")" = 'checkpointed progress' +# The exact materialization is canonical Weft content applied on top of its +# base, so return the provider worktree to that base before safe removal. +git -C "$workspace_two" reset --hard --quiet "$revision_one" +WEFT_ACTION_ACTOR=agent-two WEFT_ACTION_SUBJECT=agent-two action 210 release-materialization materialization-2 1 "$workspace_two" materialization-release-2 +released_lease="$(WEFT_ACTION_ACTOR=agent-two WEFT_ACTION_SUBJECT=agent-two action 211 release-lease lease-2 3 lease-release-2)" +[[ "$released_lease" == *'"lease_id":"lease-2"'* && "$released_lease" == *'"version":4'* ]] +released_assignment="$(WEFT_ACTION_ACTOR=agent-two WEFT_ACTION_SUBJECT=agent-two action 212 release-assignment assignment-2 1 assignment-release-2)" +[[ "$released_assignment" == *'"assignment_id":"assignment-2"'* && "$released_assignment" == *'"active":false'* ]] +test ! -e "$workspace_two" + +history="$(WEFT_ACTION_ACTOR=agent-two WEFT_ACTION_SUBJECT=agent-two action 213 history)" +[[ "$history" == *'revision.appended'* ]] +assignments="$(run_weft assignment list --change-id change-1)" +[[ "$assignments" == *'assignment-1'* && "$assignments" == *'assignment-2'* ]] +[[ "$assignments" == *'"active":false'* ]] +lease="$(run_weft lease show --change-id change-1 --operation implement)" +[[ "$lease" == *'"lease":null'* ]] +materializations="$(run_weft materialization list --change-id change-1)" +[[ "$materializations" == *'materialization-1'* && "$materializations" == *'materialization-2'* ]] +[[ "$materializations" == *'"state":"released"'* ]] -cargo run --offline -p weft-cli -- --state "$state" change create change-1 --json >/dev/null -WEFT_STATE_DIR="$state" WEFT_CHANGE_ID=change-1 WEFT_ACTOR=paseo-agent WEFT_NOW_UNIX_MS=10 \ - ./scripts/paseo-weft-action.sh acquire inspect 100 >/dev/null -WEFT_STATE_DIR="$state" WEFT_CHANGE_ID=change-1 WEFT_ACTOR=paseo-agent WEFT_NOW_UNIX_MS=11 \ - ./scripts/paseo-weft-action.sh handoff handoff-1 next-agent >/dev/null -history="$(WEFT_STATE_DIR="$state" WEFT_CHANGE_ID=change-1 WEFT_ACTOR=paseo-agent WEFT_NOW_UNIX_MS=12 ./scripts/paseo-weft-action.sh history)" -[[ "$history" == *'lease-acquired'* && "$history" == *'assignment-recorded'* ]] printf 'paseo-weft-bridge: ok\n' diff --git a/scripts/test-release.sh b/scripts/test-release.sh index 407fdd4..4eda094 100755 --- a/scripts/test-release.sh +++ b/scripts/test-release.sh @@ -41,7 +41,9 @@ test -f "$package_dir/README.md" test -f "$package_dir/GETTING_STARTED.md" test -f "$package_dir/MANUAL.md" test -f "$package_dir/USAGE.md" +test -f "$package_dir/PASEO_INTEGRATION.md" test -f "$package_dir/LICENSE" +test -x "$package_dir/bin/weft-paseo-action" test ! -e "$package_dir/docs" test ! -e "$package_dir/scripts" verify_manifest "$package_dir" "$package_dir/MANIFEST.sha256" @@ -62,6 +64,7 @@ printf '%s\n' '# Existing project rules' > "$project/AGENTS.md" --project-dir "$project" --runtime codex,claude-code,gemini-cli,paseo >/dev/null test -f "$project/.weft/runtime-bridge.json" grep -q '' "$project/AGENTS.md" +grep -q 'weft-paseo-action' "$project/.weft/runtime-bridge.json" test -f "$project/CLAUDE.md" test -f "$project/GEMINI.md" "$prefix/bin/weft" --format json --state-dir "$state" doctor \ @@ -71,6 +74,9 @@ test -f "$project/GEMINI.md" --actor release-test --at 1 >/dev/null "$prefix/bin/weft" --format json --state-dir "$state" change show \ --change-id release-smoke >/dev/null +WEFT_BIN="$prefix/bin/weft" \ +WEFT_ACTION_BIN="$package_dir/bin/weft-paseo-action" \ + "$script_dir/test-paseo-weft-bridge.sh" >/dev/null PREFIX="$prefix" "$package_dir/uninstall.sh" test ! -e "$prefix/bin/weft" test -f "$state/metadata.sqlite3"