diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e424e982e..d037b96ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,8 @@ jobs: workspaces: . -> target key: js-checks - run: npx --yes zx@8 scripts/ci.mjs js-checks + - run: node --test scripts/verify-fixed-versions.test.mjs + - run: pnpm --filter=publish exec tsx src/ci/verify-fixed-versions.ts rust-lint: runs-on: [self-hosted, agentos-builder] diff --git a/CLAUDE.md b/CLAUDE.md index 41f540918..89a190bf6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -124,6 +124,7 @@ Every bound that protects a shared resource — memory/heap, CPU/wall-clock, fd/ ### Release Tracks - **secure-exec runtime** — `@secure-exec/*` npm packages and `secure-exec-*` crates; releases keep npm/crates in sync, previews are npm-only. See "Release-previewing" and "Publishing" for details. +- **GATE — secure-exec product versions are always `0.0.1` in the committed tree.** Every package returned by `scripts/publish/src/lib/packages.ts` `discoverPackages(repoRoot)` and the root `Cargo.toml [workspace.package]` version stays pinned at `0.0.1`; the real version is applied ONLY transiently in the CI publish checkout by `bump-versions`, never committed. `scripts/publish/src/ci/verify-fixed-versions.ts` enforces this in CI. `@agentos-software/*` registry packages under `registry/{software,agent}/*` are exempt and stay independently versioned. - **`@agentos-software/*` registry packages** — generic VM software from secure-exec `registry/software/*` plus agent adapters from secure-exec `registry/agent/*`; versioned independently of secure-exec runtime packages. - **agent-os product/API** — `@rivet-dev/agentos*`, AgentOs APIs, sidecar wrapper, docs, quickstarts, and examples; see agent-os `CLAUDE.md` for its pinning workflow. diff --git a/Cargo.toml b/Cargo.toml index 2ebcf6760..919bdf45f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,23 +18,23 @@ members = [ ] [workspace.package] -version = "0.3.4-rc.1" +version = "0.0.1" edition = "2021" license = "Apache-2.0" repository = "https://github.com/rivet-dev/secure-exec" [workspace.dependencies] -secure-exec-bridge = { path = "crates/bridge", version = "0.3.4-rc.1" } -secure-exec-build-support = { path = "crates/build-support", version = "0.3.4-rc.1" } -secure-exec-kernel = { path = "crates/kernel", version = "0.3.4-rc.1" } -secure-exec-sidecar-protocol = { path = "crates/sidecar-protocol", version = "0.3.4-rc.1" } -secure-exec-sidecar-core = { path = "crates/sidecar-core", version = "0.3.4-rc.1" } -secure-exec-execution = { path = "crates/execution", version = "0.3.4-rc.1" } -secure-exec-vm-config = { path = "crates/vm-config", version = "0.3.4-rc.1" } -secure-exec-v8-runtime = { path = "crates/v8-runtime", version = "0.3.4-rc.1" } -secure-exec-client = { path = "crates/secure-exec-client", version = "0.3.4-rc.1" } -secure-exec-sidecar = { path = "crates/sidecar", version = "0.3.4-rc.1" } -secure-exec-vfs = { path = "crates/secure-exec-vfs", version = "0.3.4-rc.1" } -vfs = { package = "secure-exec-vfs-core", path = "crates/vfs", version = "0.3.4-rc.1" } +secure-exec-bridge = { path = "crates/bridge", version = "0.0.1" } +secure-exec-build-support = { path = "crates/build-support", version = "0.0.1" } +secure-exec-kernel = { path = "crates/kernel", version = "0.0.1" } +secure-exec-sidecar-protocol = { path = "crates/sidecar-protocol", version = "0.0.1" } +secure-exec-sidecar-core = { path = "crates/sidecar-core", version = "0.0.1" } +secure-exec-execution = { path = "crates/execution", version = "0.0.1" } +secure-exec-vm-config = { path = "crates/vm-config", version = "0.0.1" } +secure-exec-v8-runtime = { path = "crates/v8-runtime", version = "0.0.1" } +secure-exec-client = { path = "crates/secure-exec-client", version = "0.0.1" } +secure-exec-sidecar = { path = "crates/sidecar", version = "0.0.1" } +secure-exec-vfs = { path = "crates/secure-exec-vfs", version = "0.0.1" } +vfs = { package = "secure-exec-vfs-core", path = "crates/vfs", version = "0.0.1" } vbare = "0.0.4" vbare-compiler = { package = "rivet-vbare-compiler", version = "0.0.5" } diff --git a/crates/sidecar-browser/src/wire_dispatch.rs b/crates/sidecar-browser/src/wire_dispatch.rs index 72a95e3d8..4cd8e79d2 100644 --- a/crates/sidecar-browser/src/wire_dispatch.rs +++ b/crates/sidecar-browser/src/wire_dispatch.rs @@ -199,6 +199,17 @@ where "link_package is not available in the converged browser runtime", ) } + RequestRoute::ProvidedCommands(payload) => { + // Provided command metadata is retained by the native sidecar's + // host-backed package projection, which the converged browser + // runtime does not provide. + let _ = payload; + rejected( + &request, + "unsupported", + "provided_commands is not available in the converged browser runtime", + ) + } RequestRoute::UnsupportedHostCallbackDirection => { unsupported_host_callback_direction_dispatch(&request) } diff --git a/crates/sidecar-core/src/frames.rs b/crates/sidecar-core/src/frames.rs index b1fa295d9..6d21cc83d 100644 --- a/crates/sidecar-core/src/frames.rs +++ b/crates/sidecar-core/src/frames.rs @@ -1,15 +1,16 @@ use secure_exec_sidecar_protocol::protocol::{ AuthenticateRequest, AuthenticatedResponse, BoundUdpSnapshotResponse, EventFrame, EventPayload, LayerCreatedResponse, LayerSealedResponse, ListenerSnapshotResponse, OverlayCreatedResponse, - OwnershipScope, PackageLinkedResponse, ProcessExitedEvent, ProcessKilledResponse, - ProcessOutputEvent, ProcessSnapshotEntry, ProcessSnapshotResponse, ProcessStartedResponse, - ProjectedCommand, ProtocolSchema, RejectedResponse, RequestFrame, RequestId, ResponseFrame, - ResponsePayload, RootFilesystemBootstrappedResponse, RootFilesystemEntry, - RootFilesystemSnapshotResponse, SessionOpenedResponse, SignalHandlerRegistration, - SignalStateResponse, SnapshotExportedResponse, SnapshotImportedResponse, SocketStateEntry, - StdinClosedResponse, StdinWrittenResponse, StreamChannel, StructuredEvent, - VmConfiguredResponse, VmCreatedResponse, VmDisposedResponse, VmLifecycleEvent, - VmLifecycleState, ZombieTimerCountResponse, PROTOCOL_VERSION, + OwnershipScope, PackageCommands, PackageLinkedResponse, ProcessExitedEvent, + ProcessKilledResponse, ProcessOutputEvent, ProcessSnapshotEntry, ProcessSnapshotResponse, + ProcessStartedResponse, ProjectedCommand, ProtocolSchema, ProvidedCommandsResponse, + RejectedResponse, RequestFrame, RequestId, ResponseFrame, ResponsePayload, + RootFilesystemBootstrappedResponse, RootFilesystemEntry, RootFilesystemSnapshotResponse, + SessionOpenedResponse, SignalHandlerRegistration, SignalStateResponse, + SnapshotExportedResponse, SnapshotImportedResponse, SocketStateEntry, StdinClosedResponse, + StdinWrittenResponse, StreamChannel, StructuredEvent, VmConfiguredResponse, VmCreatedResponse, + VmDisposedResponse, VmLifecycleEvent, VmLifecycleState, ZombieTimerCountResponse, + PROTOCOL_VERSION, }; use std::collections::HashMap; @@ -172,9 +173,17 @@ pub fn package_linked_response( ) -> ResponseFrame { respond( request, - ResponsePayload::PackageLinked(PackageLinkedResponse { - projected_commands, - }), + ResponsePayload::PackageLinked(PackageLinkedResponse { projected_commands }), + ) +} + +pub fn provided_commands_response( + request: &RequestFrame, + packages: Vec, +) -> ResponseFrame { + respond( + request, + ResponsePayload::ProvidedCommands(ProvidedCommandsResponse { packages }), ) } diff --git a/crates/sidecar-core/src/lib.rs b/crates/sidecar-core/src/lib.rs index c4347328e..929a33e18 100644 --- a/crates/sidecar-core/src/lib.rs +++ b/crates/sidecar-core/src/lib.rs @@ -31,13 +31,13 @@ pub use frames::{ authenticated_response, bound_udp_snapshot_response, event, layer_created_response, layer_sealed_response, listener_snapshot_response, overlay_created_response, package_linked_response, process_exited_event, process_killed_response, process_output_event, - process_snapshot_response, process_started_response, reject, respond, response_with_ownership, - root_filesystem_bootstrapped_response, root_filesystem_snapshot_response, - session_opened_response, signal_state_response, snapshot_exported_response, - snapshot_imported_response, stdin_closed_response, stdin_written_response, - unsupported_guest_kernel_call_detail, unsupported_guest_kernel_call_event, - validate_authenticate_versions, vm_configured_response, vm_created_response, - vm_disposed_response, vm_lifecycle_event, zombie_timer_count_response, + process_snapshot_response, process_started_response, provided_commands_response, reject, + respond, response_with_ownership, root_filesystem_bootstrapped_response, + root_filesystem_snapshot_response, session_opened_response, signal_state_response, + snapshot_exported_response, snapshot_imported_response, stdin_closed_response, + stdin_written_response, unsupported_guest_kernel_call_detail, + unsupported_guest_kernel_call_event, validate_authenticate_versions, vm_configured_response, + vm_created_response, vm_disposed_response, vm_lifecycle_event, zombie_timer_count_response, AuthenticateVersionError, DispatchResult, UNSUPPORTED_GUEST_KERNEL_CALL_EVENT, }; pub use guest_fs::{ diff --git a/crates/sidecar-core/src/router.rs b/crates/sidecar-core/src/router.rs index a60435ef7..f167fc77a 100644 --- a/crates/sidecar-core/src/router.rs +++ b/crates/sidecar-core/src/router.rs @@ -6,8 +6,9 @@ use secure_exec_sidecar_protocol::protocol::{ GetProcessSnapshotRequest, GetResourceSnapshotRequest, GetSignalStateRequest, GetZombieTimerCountRequest, GuestFilesystemCallRequest, GuestKernelCallRequest, ImportSnapshotRequest, KillProcessRequest, LinkPackageRequest, OpenSessionRequest, - OwnershipScope, RegisterHostCallbacksRequest, RequestFrame, RequestPayload, ResizePtyRequest, - SealLayerRequest, SnapshotRootFilesystemRequest, VmFetchRequest, WriteStdinRequest, + OwnershipScope, ProvidedCommandsRequest, RegisterHostCallbacksRequest, RequestFrame, + RequestPayload, ResizePtyRequest, SealLayerRequest, SnapshotRootFilesystemRequest, + VmFetchRequest, WriteStdinRequest, }; use secure_exec_sidecar_protocol::wire as generated_wire; @@ -54,6 +55,7 @@ pub enum RequestRoute { GetSignalState(GetSignalStateRequest), GetZombieTimerCount(GetZombieTimerCountRequest), LinkPackage(LinkPackageRequest), + ProvidedCommands(ProvidedCommandsRequest), Ext(ExtEnvelope), UnsupportedHostCallbackDirection, } @@ -100,6 +102,7 @@ pub fn route_request_payload(request: &RequestFrame) -> RequestRoute { RequestPayload::GetSignalState(payload) => RequestRoute::GetSignalState(payload), RequestPayload::GetZombieTimerCount(payload) => RequestRoute::GetZombieTimerCount(payload), RequestPayload::LinkPackage(payload) => RequestRoute::LinkPackage(payload), + RequestPayload::ProvidedCommands(payload) => RequestRoute::ProvidedCommands(payload), RequestPayload::HostFilesystemCall(_) | RequestPayload::PersistenceLoad(_) | RequestPayload::PersistenceFlush(_) => RequestRoute::UnsupportedHostCallbackDirection, @@ -162,6 +165,7 @@ pub fn request_dispatch_mode(request: &RequestFrame) -> RequestDispatchMode { | RequestPayload::GetSignalState(_) | RequestPayload::GetZombieTimerCount(_) | RequestPayload::LinkPackage(_) + | RequestPayload::ProvidedCommands(_) | RequestPayload::HostFilesystemCall(_) | RequestPayload::PersistenceLoad(_) | RequestPayload::PersistenceFlush(_) => RequestDispatchMode::Immediate, diff --git a/crates/sidecar-protocol/protocol/secure_exec_sidecar_v1.bare b/crates/sidecar-protocol/protocol/secure_exec_sidecar_v1.bare index 6755dfbf9..33667b0d4 100644 --- a/crates/sidecar-protocol/protocol/secure_exec_sidecar_v1.bare +++ b/crates/sidecar-protocol/protocol/secure_exec_sidecar_v1.bare @@ -221,6 +221,17 @@ type LinkPackageRequest struct { package: PackageDescriptor } +type PackageCommands struct { + packageName: str + commands: list +} + +type ProvidedCommandsRequest void + +type ProvidedCommandsResponse struct { + packages: list +} + type ProjectedCommand struct { name: str guestPath: str @@ -456,7 +467,8 @@ type RequestPayload union { GuestKernelCallRequest | ResizePtyRequest | GetResourceSnapshotRequest | - LinkPackageRequest + LinkPackageRequest | + ProvidedCommandsRequest } type RequestFrame struct { @@ -742,7 +754,8 @@ type ResponsePayload union { GuestKernelResultResponse | PtyResizedResponse | ResourceSnapshotResponse | - PackageLinkedResponse + PackageLinkedResponse | + ProvidedCommandsResponse } type ResponseFrame struct { diff --git a/crates/sidecar-protocol/src/protocol.rs b/crates/sidecar-protocol/src/protocol.rs index 8ffe32142..d903a8656 100644 --- a/crates/sidecar-protocol/src/protocol.rs +++ b/crates/sidecar-protocol/src/protocol.rs @@ -497,6 +497,9 @@ fn to_generated_request_payload( RequestPayload::LinkPackage(inner) => { generated_protocol::RequestPayload::LinkPackageRequest(inner.clone()) } + RequestPayload::ProvidedCommands(_) => { + generated_protocol::RequestPayload::ProvidedCommandsRequest + } }) } @@ -602,6 +605,9 @@ fn from_generated_request_payload( generated_protocol::RequestPayload::LinkPackageRequest(inner) => { RequestPayload::LinkPackage(inner) } + generated_protocol::RequestPayload::ProvidedCommandsRequest => { + RequestPayload::ProvidedCommands(ProvidedCommandsRequest {}) + } }) } @@ -753,6 +759,9 @@ fn to_generated_response_payload( ResponsePayload::PackageLinked(inner) => { generated_protocol::ResponsePayload::PackageLinkedResponse(inner.clone()) } + ResponsePayload::ProvidedCommands(inner) => { + generated_protocol::ResponsePayload::ProvidedCommandsResponse(inner.clone()) + } }) } @@ -891,6 +900,9 @@ fn from_generated_response_payload( generated_protocol::ResponsePayload::PackageLinkedResponse(inner) => { ResponsePayload::PackageLinked(inner) } + generated_protocol::ResponsePayload::ProvidedCommandsResponse(inner) => { + ResponsePayload::ProvidedCommands(inner) + } }) } @@ -1257,6 +1269,7 @@ pub enum RequestPayload { ResizePty(ResizePtyRequest), GetResourceSnapshot(GetResourceSnapshotRequest), LinkPackage(LinkPackageRequest), + ProvidedCommands(ProvidedCommandsRequest), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -1295,6 +1308,7 @@ pub enum ResponsePayload { PtyResized(PtyResizedResponse), ResourceSnapshot(ResourceSnapshotResponse), PackageLinked(PackageLinkedResponse), + ProvidedCommands(ProvidedCommandsResponse), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -1399,12 +1413,17 @@ pub type GuestFilesystemCallRequest = crate::wire::GuestFilesystemCallRequest; pub type GuestKernelCallRequest = crate::wire::GuestKernelCallRequest; pub type ResizePtyRequest = crate::wire::ResizePtyRequest; pub type PackageDescriptor = crate::wire::PackageDescriptor; +pub type PackageCommands = crate::wire::PackageCommands; pub type ProjectedCommand = crate::wire::ProjectedCommand; pub type LinkPackageRequest = crate::wire::LinkPackageRequest; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct ProvidedCommandsRequest {} + pub type GuestKernelResultResponse = crate::wire::GuestKernelResultResponse; pub type PtyResizedResponse = crate::wire::PtyResizedResponse; pub type PackageLinkedResponse = crate::wire::PackageLinkedResponse; +pub type ProvidedCommandsResponse = crate::wire::ProvidedCommandsResponse; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct SnapshotRootFilesystemRequest {} @@ -1598,6 +1617,7 @@ impl_bare_newtype_union_enum!( ResizePty(ResizePtyRequest) = 29, GetResourceSnapshot(GetResourceSnapshotRequest) = 30, LinkPackage(LinkPackageRequest) = 31, + ProvidedCommands(ProvidedCommandsRequest) = 32, } ); @@ -1640,6 +1660,7 @@ impl_bare_newtype_union_enum!( PtyResized(PtyResizedResponse) = 31, ResourceSnapshot(ResourceSnapshotResponse) = 32, PackageLinked(PackageLinkedResponse) = 33, + ProvidedCommands(ProvidedCommandsResponse) = 34, } ); @@ -2220,6 +2241,7 @@ enum ExpectedResponseKind { GuestKernelResult, PtyResized, PackageLinked, + ProvidedCommands, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -2265,6 +2287,7 @@ impl ExpectedResponseKind { Self::GuestKernelResult => "guest_kernel_result", Self::PtyResized => "pty_resized", Self::PackageLinked => "package_linked", + Self::ProvidedCommands => "provided_commands_response", } } @@ -2322,6 +2345,7 @@ impl RequestPayload { | Self::GuestKernelCall(_) | Self::ResizePty(_) | Self::LinkPackage(_) + | Self::ProvidedCommands(_) | Self::HostFilesystemCall(_) => OwnershipRequirement::Vm, Self::Ext(_) => OwnershipRequirement::Any, } @@ -2361,6 +2385,7 @@ impl RequestPayload { Self::GuestKernelCall(_) => ExpectedResponseKind::GuestKernelResult, Self::ResizePty(_) => ExpectedResponseKind::PtyResized, Self::LinkPackage(_) => ExpectedResponseKind::PackageLinked, + Self::ProvidedCommands(_) => ExpectedResponseKind::ProvidedCommands, } } } @@ -2413,7 +2438,8 @@ impl ResponsePayload { | Self::PermissionDecision(_) | Self::GuestKernelResult(_) | Self::PtyResized(_) - | Self::PackageLinked(_) => OwnershipRequirement::Vm, + | Self::PackageLinked(_) + | Self::ProvidedCommands(_) => OwnershipRequirement::Vm, Self::ExtResult(_) => OwnershipRequirement::Any, } } @@ -2454,6 +2480,7 @@ impl ResponsePayload { Self::GuestKernelResult(_) => "guest_kernel_result", Self::PtyResized(_) => "pty_resized", Self::PackageLinked(_) => "package_linked", + Self::ProvidedCommands(_) => "provided_commands_response", } } } diff --git a/crates/sidecar/src/service.rs b/crates/sidecar/src/service.rs index 6b38e7f61..62ec2aa6d 100644 --- a/crates/sidecar/src/service.rs +++ b/crates/sidecar/src/service.rs @@ -1214,6 +1214,9 @@ where self.get_zombie_timer_count(&request, payload).await } RequestRoute::LinkPackage(payload) => self.link_package(&request, payload).await, + RequestRoute::ProvidedCommands(payload) => { + self.provided_commands(&request, payload).await + } RequestRoute::UnsupportedHostCallbackDirection => { Ok(unsupported_host_callback_direction_dispatch(&request)) } diff --git a/crates/sidecar/src/state.rs b/crates/sidecar/src/state.rs index 5b98ffb55..599538880 100644 --- a/crates/sidecar/src/state.rs +++ b/crates/sidecar/src/state.rs @@ -290,6 +290,7 @@ pub(crate) struct VmConfiguration { pub(crate) instructions: Vec, pub(crate) projected_modules: Vec, pub(crate) command_permissions: BTreeMap, + pub(crate) provided_commands: BTreeMap>, /// Guest JavaScript host-environment config (platform / module resolution / /// builtin allow-list). Set at `create_vm` from `CreateVmConfig.jsRuntime` /// and preserved across `configure_vm`. `None` => full Node.js emulation. @@ -310,6 +311,7 @@ impl Default for VmConfiguration { instructions: Vec::new(), projected_modules: Vec::new(), command_permissions: BTreeMap::new(), + provided_commands: BTreeMap::new(), js_runtime: None, snapshot_userland_code: None, loopback_exempt_ports: Vec::new(), @@ -339,6 +341,7 @@ pub(crate) struct VmState { pub(crate) configuration: VmConfiguration, pub(crate) layers: VmLayerStore, pub(crate) command_guest_paths: BTreeMap, + pub(crate) provided_commands: BTreeMap>, pub(crate) command_permissions: BTreeMap, pub(crate) toolkits: BTreeMap, pub(crate) active_processes: BTreeMap, diff --git a/crates/sidecar/src/vm.rs b/crates/sidecar/src/vm.rs index ecadeeeb1..a5f2829b9 100644 --- a/crates/sidecar/src/vm.rs +++ b/crates/sidecar/src/vm.rs @@ -11,9 +11,10 @@ use crate::bridge::{bridge_permissions, MountPluginContext}; use crate::protocol::{ ConfigureVmRequest, CreateLayerRequest, CreateOverlayRequest, DisposeReason, EventFrame, ExportSnapshotRequest, ImportSnapshotRequest, LinkPackageRequest, MountDescriptor, - MountPluginDescriptor, ProjectedCommand, RootFilesystemDescriptor, RootFilesystemEntry, - RootFilesystemEntryEncoding, RootFilesystemLowerDescriptor, SealLayerRequest, - SnapshotRootFilesystemRequest, VmLifecycleState, + MountPluginDescriptor, PackageCommands, ProjectedCommand, ProvidedCommandsRequest, + RootFilesystemDescriptor, RootFilesystemEntry, RootFilesystemEntryEncoding, + RootFilesystemLowerDescriptor, SealLayerRequest, SnapshotRootFilesystemRequest, + VmLifecycleState, }; use crate::service::{ audit_fields, dirname, emit_security_audit_event, emit_structured_event, kernel_error, @@ -46,10 +47,10 @@ use secure_exec_kernel::socket_table::{SocketReadiness, SocketReadinessKind}; use secure_exec_sidecar_core::permissions::{allow_all_policy, deny_all_policy}; use secure_exec_sidecar_core::{ layer_created_response, layer_sealed_response, overlay_created_response, - package_linked_response, protocol_root_filesystem_mode, root_filesystem_bootstrapped_response, - root_filesystem_protocol_descriptor_from_config, root_filesystem_snapshot_response, - snapshot_exported_response, snapshot_imported_response, vm_configured_response, - vm_created_response, vm_disposed_response, VmLayerStore, + package_linked_response, protocol_root_filesystem_mode, provided_commands_response, + root_filesystem_bootstrapped_response, root_filesystem_protocol_descriptor_from_config, + root_filesystem_snapshot_response, snapshot_exported_response, snapshot_imported_response, + vm_configured_response, vm_created_response, vm_disposed_response, VmLayerStore, }; use secure_exec_vm_config as vm_config; use std::collections::{BTreeMap, BTreeSet, VecDeque}; @@ -348,6 +349,7 @@ where }, layers: VmLayerStore::default(), command_guest_paths, + provided_commands: BTreeMap::new(), command_permissions: BTreeMap::new(), toolkits: BTreeMap::new(), active_processes: BTreeMap::new(), @@ -445,6 +447,13 @@ where let _ = fs::remove_dir_all(&old); } let package_descriptors = package_descriptors_from_wire(&payload.packages)?; + let mut provided_commands = BTreeMap::new(); + for descriptor in &package_descriptors { + provided_commands.insert( + descriptor.name.clone(), + crate::package_projection::derive_commands(&descriptor.dir)?, + ); + } let snapshot_userland_code = resolve_agent_snapshot_bundle(&package_descriptors)?; let (packages_staging_root, packages_mount) = build_packages_projection(&vm_id, &package_descriptors, &payload.packages_mount_at)?; @@ -475,11 +484,13 @@ where // command by name -> its `/opt/agentos/bin/` entrypoint so both the // kernel command table (via `execution_commands` below) and the sidecar // entrypoint resolver (`resolve_guest_command_entrypoint`) can find it. - for descriptor in &package_descriptors { - for command in crate::package_projection::derive_commands(&descriptor.dir)? { + for commands in provided_commands.values() { + for command in commands { let entrypoint = format!("{}/{command}", crate::package_projection::OPT_AGENTOS_BIN); - vm.command_guest_paths.entry(command).or_insert(entrypoint); + vm.command_guest_paths + .entry(command.clone()) + .or_insert(entrypoint); } } refresh_guest_command_path_env(&mut vm.guest_env, &vm.command_guest_paths); @@ -506,11 +517,13 @@ where instructions: payload.instructions.clone(), projected_modules: payload.projected_modules.clone(), command_permissions: payload.command_permissions.clone().into_iter().collect(), + provided_commands: provided_commands.clone(), // jsRuntime is create-time only; preserve what create_vm stored. js_runtime: vm.configuration.js_runtime.clone(), snapshot_userland_code: snapshot_userland_code.clone(), loopback_exempt_ports: payload.loopback_exempt_ports.clone(), }; + vm.provided_commands = provided_commands; Ok(()) }); match reconfigure_result { @@ -588,6 +601,12 @@ where })?; let descriptor = crate::package_projection::read_package_manifest(&payload.package.dir)?; let commands = crate::package_projection::link_package(&descriptor, &staging_root)?; + let package_commands = crate::package_projection::derive_commands(&descriptor.dir)?; + vm.provided_commands + .insert(descriptor.name.clone(), package_commands.clone()); + vm.configuration + .provided_commands + .insert(descriptor.name.clone(), package_commands); for command in &commands { let entrypoint = projected_command_guest_path(command); vm.command_guest_paths @@ -618,6 +637,34 @@ where }) } + pub(crate) async fn provided_commands( + &mut self, + request: &crate::protocol::RequestFrame, + _payload: ProvidedCommandsRequest, + ) -> Result { + let (connection_id, session_id, vm_id) = self.vm_scope_for(&request.ownership)?; + self.require_owned_vm(&connection_id, &session_id, &vm_id)?; + + let packages = self + .vms + .get(&vm_id) + .map(|vm| { + vm.provided_commands + .iter() + .map(|(package_name, commands)| PackageCommands { + package_name: package_name.clone(), + commands: commands.clone(), + }) + .collect() + }) + .unwrap_or_default(); + + Ok(DispatchResult { + response: provided_commands_response(request, packages), + events: Vec::new(), + }) + } + pub(crate) async fn create_layer( &mut self, request: &crate::protocol::RequestFrame, diff --git a/packages/browser/package.json b/packages/browser/package.json index dca3036ef..a7e0626a2 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -1,6 +1,6 @@ { "name": "@secure-exec/browser", - "version": "0.3.4-rc.1", + "version": "0.0.1", "type": "module", "license": "Apache-2.0", "main": "./dist/index.js", diff --git a/packages/core/package.json b/packages/core/package.json index 070ba8894..42b9121cf 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@secure-exec/core", - "version": "0.3.4-rc.1", + "version": "0.0.1", "type": "module", "license": "Apache-2.0", "main": "./dist/index.js", diff --git a/packages/core/src/generated-protocol.ts b/packages/core/src/generated-protocol.ts index 05b951188..cfba83225 100644 --- a/packages/core/src/generated-protocol.ts +++ b/packages/core/src/generated-protocol.ts @@ -1166,6 +1166,58 @@ export function writeLinkPackageRequest(bc: bare.ByteCursor, x: LinkPackageReque writePackageDescriptor(bc, x.package) } +export type PackageCommands = { + readonly packageName: string + readonly commands: readonly string[] +} + +export function readPackageCommands(bc: bare.ByteCursor): PackageCommands { + return { + packageName: bare.readString(bc), + commands: read6(bc), + } +} + +export function writePackageCommands(bc: bare.ByteCursor, x: PackageCommands): void { + bare.writeString(bc, x.packageName) + write6(bc, x.commands) +} + +export type ProvidedCommandsRequest = null + +function read12(bc: bare.ByteCursor): readonly PackageCommands[] { + const len = bare.readUintSafe(bc) + if (len === 0) { + return [] + } + const result = [readPackageCommands(bc)] + for (let i = 1; i < len; i++) { + result[i] = readPackageCommands(bc) + } + return result +} + +function write12(bc: bare.ByteCursor, x: readonly PackageCommands[]): void { + bare.writeUintSafe(bc, x.length) + for (let i = 0; i < x.length; i++) { + writePackageCommands(bc, x[i]) + } +} + +export type ProvidedCommandsResponse = { + readonly packages: readonly PackageCommands[] +} + +export function readProvidedCommandsResponse(bc: bare.ByteCursor): ProvidedCommandsResponse { + return { + packages: read12(bc), + } +} + +export function writeProvidedCommandsResponse(bc: bare.ByteCursor, x: ProvidedCommandsResponse): void { + write12(bc, x.packages) +} + export type ProjectedCommand = { readonly name: string readonly guestPath: string @@ -1183,7 +1235,7 @@ export function writeProjectedCommand(bc: bare.ByteCursor, x: ProjectedCommand): bare.writeString(bc, x.guestPath) } -function read12(bc: bare.ByteCursor): readonly ProjectedCommand[] { +function read13(bc: bare.ByteCursor): readonly ProjectedCommand[] { const len = bare.readUintSafe(bc) if (len === 0) { return [] @@ -1195,7 +1247,7 @@ function read12(bc: bare.ByteCursor): readonly ProjectedCommand[] { return result } -function write12(bc: bare.ByteCursor, x: readonly ProjectedCommand[]): void { +function write13(bc: bare.ByteCursor, x: readonly ProjectedCommand[]): void { bare.writeUintSafe(bc, x.length) for (let i = 0; i < x.length; i++) { writeProjectedCommand(bc, x[i]) @@ -1208,15 +1260,15 @@ export type PackageLinkedResponse = { export function readPackageLinkedResponse(bc: bare.ByteCursor): PackageLinkedResponse { return { - projectedCommands: read12(bc), + projectedCommands: read13(bc), } } export function writePackageLinkedResponse(bc: bare.ByteCursor, x: PackageLinkedResponse): void { - write12(bc, x.projectedCommands) + write13(bc, x.projectedCommands) } -function read13(bc: bare.ByteCursor): readonly MountDescriptor[] { +function read14(bc: bare.ByteCursor): readonly MountDescriptor[] { const len = bare.readUintSafe(bc) if (len === 0) { return [] @@ -1228,14 +1280,14 @@ function read13(bc: bare.ByteCursor): readonly MountDescriptor[] { return result } -function write13(bc: bare.ByteCursor, x: readonly MountDescriptor[]): void { +function write14(bc: bare.ByteCursor, x: readonly MountDescriptor[]): void { bare.writeUintSafe(bc, x.length) for (let i = 0; i < x.length; i++) { writeMountDescriptor(bc, x[i]) } } -function read14(bc: bare.ByteCursor): readonly SoftwareDescriptor[] { +function read15(bc: bare.ByteCursor): readonly SoftwareDescriptor[] { const len = bare.readUintSafe(bc) if (len === 0) { return [] @@ -1247,25 +1299,25 @@ function read14(bc: bare.ByteCursor): readonly SoftwareDescriptor[] { return result } -function write14(bc: bare.ByteCursor, x: readonly SoftwareDescriptor[]): void { +function write15(bc: bare.ByteCursor, x: readonly SoftwareDescriptor[]): void { bare.writeUintSafe(bc, x.length) for (let i = 0; i < x.length; i++) { writeSoftwareDescriptor(bc, x[i]) } } -function read15(bc: bare.ByteCursor): PermissionsPolicy | null { +function read16(bc: bare.ByteCursor): PermissionsPolicy | null { return bare.readBool(bc) ? readPermissionsPolicy(bc) : null } -function write15(bc: bare.ByteCursor, x: PermissionsPolicy | null): void { +function write16(bc: bare.ByteCursor, x: PermissionsPolicy | null): void { bare.writeBool(bc, x != null) if (x != null) { writePermissionsPolicy(bc, x) } } -function read16(bc: bare.ByteCursor): readonly ProjectedModuleDescriptor[] { +function read17(bc: bare.ByteCursor): readonly ProjectedModuleDescriptor[] { const len = bare.readUintSafe(bc) if (len === 0) { return [] @@ -1277,14 +1329,14 @@ function read16(bc: bare.ByteCursor): readonly ProjectedModuleDescriptor[] { return result } -function write16(bc: bare.ByteCursor, x: readonly ProjectedModuleDescriptor[]): void { +function write17(bc: bare.ByteCursor, x: readonly ProjectedModuleDescriptor[]): void { bare.writeUintSafe(bc, x.length) for (let i = 0; i < x.length; i++) { writeProjectedModuleDescriptor(bc, x[i]) } } -function read17(bc: bare.ByteCursor): ReadonlyMap { +function read18(bc: bare.ByteCursor): ReadonlyMap { const len = bare.readUintSafe(bc) const result = new Map() for (let i = 0; i < len; i++) { @@ -1299,7 +1351,7 @@ function read17(bc: bare.ByteCursor): ReadonlyMap { return result } -function write17(bc: bare.ByteCursor, x: ReadonlyMap): void { +function write18(bc: bare.ByteCursor, x: ReadonlyMap): void { bare.writeUintSafe(bc, x.size) for (const kv of x) { bare.writeString(bc, kv[0]) @@ -1307,7 +1359,7 @@ function write17(bc: bare.ByteCursor, x: ReadonlyMap } } -function read18(bc: bare.ByteCursor): readonly PackageDescriptor[] { +function read19(bc: bare.ByteCursor): readonly PackageDescriptor[] { const len = bare.readUintSafe(bc) if (len === 0) { return [] @@ -1319,7 +1371,7 @@ function read18(bc: bare.ByteCursor): readonly PackageDescriptor[] { return result } -function write18(bc: bare.ByteCursor, x: readonly PackageDescriptor[]): void { +function write19(bc: bare.ByteCursor, x: readonly PackageDescriptor[]): void { bare.writeUintSafe(bc, x.length) for (let i = 0; i < x.length; i++) { writePackageDescriptor(bc, x[i]) @@ -1343,15 +1395,15 @@ export type ConfigureVmRequest = { export function readConfigureVmRequest(bc: bare.ByteCursor): ConfigureVmRequest { return { - mounts: read13(bc), - software: read14(bc), - permissions: read15(bc), + mounts: read14(bc), + software: read15(bc), + permissions: read16(bc), moduleAccessCwd: read0(bc), instructions: read6(bc), - projectedModules: read16(bc), - commandPermissions: read17(bc), + projectedModules: read17(bc), + commandPermissions: read18(bc), loopbackExemptPorts: bare.readU16Array(bc), - packages: read18(bc), + packages: read19(bc), packagesMountAt: bare.readString(bc), bootstrapCommands: read6(bc), toolShimCommands: read6(bc), @@ -1359,15 +1411,15 @@ export function readConfigureVmRequest(bc: bare.ByteCursor): ConfigureVmRequest } export function writeConfigureVmRequest(bc: bare.ByteCursor, x: ConfigureVmRequest): void { - write13(bc, x.mounts) - write14(bc, x.software) - write15(bc, x.permissions) + write14(bc, x.mounts) + write15(bc, x.software) + write16(bc, x.permissions) write0(bc, x.moduleAccessCwd) write6(bc, x.instructions) - write16(bc, x.projectedModules) - write17(bc, x.commandPermissions) + write17(bc, x.projectedModules) + write18(bc, x.commandPermissions) bare.writeU16Array(bc, x.loopbackExemptPorts) - write18(bc, x.packages) + write19(bc, x.packages) bare.writeString(bc, x.packagesMountAt) write6(bc, x.bootstrapCommands) write6(bc, x.toolShimCommands) @@ -1390,18 +1442,18 @@ export function writeRegisteredHostCallbackExample(bc: bare.ByteCursor, x: Regis writeJsonUtf8(bc, x.input) } -function read19(bc: bare.ByteCursor): u64 | null { +function read20(bc: bare.ByteCursor): u64 | null { return bare.readBool(bc) ? bare.readU64(bc) : null } -function write19(bc: bare.ByteCursor, x: u64 | null): void { +function write20(bc: bare.ByteCursor, x: u64 | null): void { bare.writeBool(bc, x != null) if (x != null) { bare.writeU64(bc, x) } } -function read20(bc: bare.ByteCursor): readonly RegisteredHostCallbackExample[] { +function read21(bc: bare.ByteCursor): readonly RegisteredHostCallbackExample[] { const len = bare.readUintSafe(bc) if (len === 0) { return [] @@ -1413,7 +1465,7 @@ function read20(bc: bare.ByteCursor): readonly RegisteredHostCallbackExample[] { return result } -function write20(bc: bare.ByteCursor, x: readonly RegisteredHostCallbackExample[]): void { +function write21(bc: bare.ByteCursor, x: readonly RegisteredHostCallbackExample[]): void { bare.writeUintSafe(bc, x.length) for (let i = 0; i < x.length; i++) { writeRegisteredHostCallbackExample(bc, x[i]) @@ -1431,19 +1483,19 @@ export function readRegisteredHostCallbackDefinition(bc: bare.ByteCursor): Regis return { description: bare.readString(bc), inputSchema: readJsonUtf8(bc), - timeoutMs: read19(bc), - examples: read20(bc), + timeoutMs: read20(bc), + examples: read21(bc), } } export function writeRegisteredHostCallbackDefinition(bc: bare.ByteCursor, x: RegisteredHostCallbackDefinition): void { bare.writeString(bc, x.description) writeJsonUtf8(bc, x.inputSchema) - write19(bc, x.timeoutMs) - write20(bc, x.examples) + write20(bc, x.timeoutMs) + write21(bc, x.examples) } -function read21(bc: bare.ByteCursor): ReadonlyMap { +function read22(bc: bare.ByteCursor): ReadonlyMap { const len = bare.readUintSafe(bc) const result = new Map() for (let i = 0; i < len; i++) { @@ -1458,7 +1510,7 @@ function read21(bc: bare.ByteCursor): ReadonlyMap): void { +function write22(bc: bare.ByteCursor, x: ReadonlyMap): void { bare.writeUintSafe(bc, x.size) for (const kv of x) { bare.writeString(bc, kv[0]) @@ -1480,7 +1532,7 @@ export function readRegisterHostCallbacksRequest(bc: bare.ByteCursor): RegisterH description: bare.readString(bc), commandAliases: read6(bc), registryCommandAliases: read6(bc), - callbacks: read21(bc), + callbacks: read22(bc), } } @@ -1489,7 +1541,7 @@ export function writeRegisterHostCallbacksRequest(bc: bare.ByteCursor, x: Regist bare.writeString(bc, x.description) write6(bc, x.commandAliases) write6(bc, x.registryCommandAliases) - write21(bc, x.callbacks) + write22(bc, x.callbacks) } export type CreateLayerRequest = null @@ -1781,10 +1833,10 @@ export function readGuestFilesystemCallRequest(bc: bare.ByteCursor): GuestFilesy mode: read2(bc), uid: read2(bc), gid: read2(bc), - atimeMs: read19(bc), - mtimeMs: read19(bc), - len: read19(bc), - offset: read19(bc), + atimeMs: read20(bc), + mtimeMs: read20(bc), + len: read20(bc), + offset: read20(bc), } } @@ -1800,10 +1852,10 @@ export function writeGuestFilesystemCallRequest(bc: bare.ByteCursor, x: GuestFil write2(bc, x.mode) write2(bc, x.uid) write2(bc, x.gid) - write19(bc, x.atimeMs) - write19(bc, x.mtimeMs) - write19(bc, x.len) - write19(bc, x.offset) + write20(bc, x.atimeMs) + write20(bc, x.mtimeMs) + write20(bc, x.len) + write20(bc, x.offset) } export type GuestKernelCallRequest = { @@ -1828,22 +1880,22 @@ export function writeGuestKernelCallRequest(bc: bare.ByteCursor, x: GuestKernelC export type SnapshotRootFilesystemRequest = null -function read22(bc: bare.ByteCursor): GuestRuntimeKind | null { +function read23(bc: bare.ByteCursor): GuestRuntimeKind | null { return bare.readBool(bc) ? readGuestRuntimeKind(bc) : null } -function write22(bc: bare.ByteCursor, x: GuestRuntimeKind | null): void { +function write23(bc: bare.ByteCursor, x: GuestRuntimeKind | null): void { bare.writeBool(bc, x != null) if (x != null) { writeGuestRuntimeKind(bc, x) } } -function read23(bc: bare.ByteCursor): WasmPermissionTier | null { +function read24(bc: bare.ByteCursor): WasmPermissionTier | null { return bare.readBool(bc) ? readWasmPermissionTier(bc) : null } -function write23(bc: bare.ByteCursor, x: WasmPermissionTier | null): void { +function write24(bc: bare.ByteCursor, x: WasmPermissionTier | null): void { bare.writeBool(bc, x != null) if (x != null) { writeWasmPermissionTier(bc, x) @@ -1865,24 +1917,24 @@ export function readExecuteRequest(bc: bare.ByteCursor): ExecuteRequest { return { processId: bare.readString(bc), command: read0(bc), - runtime: read22(bc), + runtime: read23(bc), entrypoint: read0(bc), args: read6(bc), env: read1(bc), cwd: read0(bc), - wasmPermissionTier: read23(bc), + wasmPermissionTier: read24(bc), } } export function writeExecuteRequest(bc: bare.ByteCursor, x: ExecuteRequest): void { bare.writeString(bc, x.processId) write0(bc, x.command) - write22(bc, x.runtime) + write23(bc, x.runtime) write0(bc, x.entrypoint) write6(bc, x.args) write1(bc, x.env) write0(bc, x.cwd) - write23(bc, x.wasmPermissionTier) + write24(bc, x.wasmPermissionTier) } export type WriteStdinRequest = { @@ -1957,11 +2009,11 @@ export type GetProcessSnapshotRequest = null export type GetResourceSnapshotRequest = null -function read24(bc: bare.ByteCursor): u16 | null { +function read25(bc: bare.ByteCursor): u16 | null { return bare.readBool(bc) ? bare.readU16(bc) : null } -function write24(bc: bare.ByteCursor, x: u16 | null): void { +function write25(bc: bare.ByteCursor, x: u16 | null): void { bare.writeBool(bc, x != null) if (x != null) { bare.writeU16(bc, x) @@ -1977,14 +2029,14 @@ export type FindListenerRequest = { export function readFindListenerRequest(bc: bare.ByteCursor): FindListenerRequest { return { host: read0(bc), - port: read24(bc), + port: read25(bc), path: read0(bc), } } export function writeFindListenerRequest(bc: bare.ByteCursor, x: FindListenerRequest): void { write0(bc, x.host) - write24(bc, x.port) + write25(bc, x.port) write0(bc, x.path) } @@ -1996,13 +2048,13 @@ export type FindBoundUdpRequest = { export function readFindBoundUdpRequest(bc: bare.ByteCursor): FindBoundUdpRequest { return { host: read0(bc), - port: read24(bc), + port: read25(bc), } } export function writeFindBoundUdpRequest(bc: bare.ByteCursor, x: FindBoundUdpRequest): void { write0(bc, x.host) - write24(bc, x.port) + write25(bc, x.port) } export type GetSignalStateRequest = { @@ -2199,6 +2251,7 @@ export type RequestPayload = | { readonly tag: "ResizePtyRequest"; readonly val: ResizePtyRequest } | { readonly tag: "GetResourceSnapshotRequest"; readonly val: GetResourceSnapshotRequest } | { readonly tag: "LinkPackageRequest"; readonly val: LinkPackageRequest } + | { readonly tag: "ProvidedCommandsRequest"; readonly val: ProvidedCommandsRequest } export function readRequestPayload(bc: bare.ByteCursor): RequestPayload { const offset = bc.offset @@ -2268,6 +2321,8 @@ export function readRequestPayload(bc: bare.ByteCursor): RequestPayload { return { tag: "GetResourceSnapshotRequest", val: null } case 31: return { tag: "LinkPackageRequest", val: readLinkPackageRequest(bc) } + case 32: + return { tag: "ProvidedCommandsRequest", val: null } default: { bc.offset = offset throw new bare.BareError(offset, "invalid tag") @@ -2432,6 +2487,10 @@ export function writeRequestPayload(bc: bare.ByteCursor, x: RequestPayload): voi writeLinkPackageRequest(bc, x.val) break } + case "ProvidedCommandsRequest": { + bare.writeU8(bc, 32) + break + } } } @@ -2547,14 +2606,14 @@ export function readVmConfiguredResponse(bc: bare.ByteCursor): VmConfiguredRespo return { appliedMounts: bare.readU32(bc), appliedSoftware: bare.readU32(bc), - projectedCommands: read12(bc), + projectedCommands: read13(bc), } } export function writeVmConfiguredResponse(bc: bare.ByteCursor, x: VmConfiguredResponse): void { bare.writeU32(bc, x.appliedMounts) bare.writeU32(bc, x.appliedSoftware) - write12(bc, x.projectedCommands) + write13(bc, x.projectedCommands) } export type HostCallbacksRegisteredResponse = { @@ -2729,7 +2788,7 @@ export function writeGuestDirEntry(bc: bare.ByteCursor, x: GuestDirEntry): void bare.writeU64(bc, x.size) } -function read25(bc: bare.ByteCursor): readonly GuestDirEntry[] { +function read26(bc: bare.ByteCursor): readonly GuestDirEntry[] { const len = bare.readUintSafe(bc) if (len === 0) { return [] @@ -2741,40 +2800,40 @@ function read25(bc: bare.ByteCursor): readonly GuestDirEntry[] { return result } -function write25(bc: bare.ByteCursor, x: readonly GuestDirEntry[]): void { +function write26(bc: bare.ByteCursor, x: readonly GuestDirEntry[]): void { bare.writeUintSafe(bc, x.length) for (let i = 0; i < x.length; i++) { writeGuestDirEntry(bc, x[i]) } } -function read26(bc: bare.ByteCursor): readonly GuestDirEntry[] | null { - return bare.readBool(bc) ? read25(bc) : null +function read27(bc: bare.ByteCursor): readonly GuestDirEntry[] | null { + return bare.readBool(bc) ? read26(bc) : null } -function write26(bc: bare.ByteCursor, x: readonly GuestDirEntry[] | null): void { +function write27(bc: bare.ByteCursor, x: readonly GuestDirEntry[] | null): void { bare.writeBool(bc, x != null) if (x != null) { - write25(bc, x) + write26(bc, x) } } -function read27(bc: bare.ByteCursor): GuestFilesystemStat | null { +function read28(bc: bare.ByteCursor): GuestFilesystemStat | null { return bare.readBool(bc) ? readGuestFilesystemStat(bc) : null } -function write27(bc: bare.ByteCursor, x: GuestFilesystemStat | null): void { +function write28(bc: bare.ByteCursor, x: GuestFilesystemStat | null): void { bare.writeBool(bc, x != null) if (x != null) { writeGuestFilesystemStat(bc, x) } } -function read28(bc: bare.ByteCursor): boolean | null { +function read29(bc: bare.ByteCursor): boolean | null { return bare.readBool(bc) ? bare.readBool(bc) : null } -function write28(bc: bare.ByteCursor, x: boolean | null): void { +function write29(bc: bare.ByteCursor, x: boolean | null): void { bare.writeBool(bc, x != null) if (x != null) { bare.writeBool(bc, x) @@ -2798,9 +2857,9 @@ export function readGuestFilesystemResultResponse(bc: bare.ByteCursor): GuestFil path: bare.readString(bc), content: read0(bc), encoding: read3(bc), - entries: read26(bc), - stat: read27(bc), - exists: read28(bc), + entries: read27(bc), + stat: read28(bc), + exists: read29(bc), target: read0(bc), } } @@ -2810,9 +2869,9 @@ export function writeGuestFilesystemResultResponse(bc: bare.ByteCursor, x: Guest bare.writeString(bc, x.path) write0(bc, x.content) write3(bc, x.encoding) - write26(bc, x.entries) - write27(bc, x.stat) - write28(bc, x.exists) + write27(bc, x.entries) + write28(bc, x.stat) + write29(bc, x.exists) write0(bc, x.target) } @@ -2966,11 +3025,11 @@ export function writeProcessSnapshotStatus(bc: bare.ByteCursor, x: ProcessSnapsh } } -function read29(bc: bare.ByteCursor): i32 | null { +function read30(bc: bare.ByteCursor): i32 | null { return bare.readBool(bc) ? bare.readI32(bc) : null } -function write29(bc: bare.ByteCursor, x: i32 | null): void { +function write30(bc: bare.ByteCursor, x: i32 | null): void { bare.writeBool(bc, x != null) if (x != null) { bare.writeI32(bc, x) @@ -3003,7 +3062,7 @@ export function readProcessSnapshotEntry(bc: bare.ByteCursor): ProcessSnapshotEn args: read6(bc), cwd: bare.readString(bc), status: readProcessSnapshotStatus(bc), - exitCode: read29(bc), + exitCode: read30(bc), } } @@ -3018,10 +3077,10 @@ export function writeProcessSnapshotEntry(bc: bare.ByteCursor, x: ProcessSnapsho write6(bc, x.args) bare.writeString(bc, x.cwd) writeProcessSnapshotStatus(bc, x.status) - write29(bc, x.exitCode) + write30(bc, x.exitCode) } -function read30(bc: bare.ByteCursor): readonly ProcessSnapshotEntry[] { +function read31(bc: bare.ByteCursor): readonly ProcessSnapshotEntry[] { const len = bare.readUintSafe(bc) if (len === 0) { return [] @@ -3033,7 +3092,7 @@ function read30(bc: bare.ByteCursor): readonly ProcessSnapshotEntry[] { return result } -function write30(bc: bare.ByteCursor, x: readonly ProcessSnapshotEntry[]): void { +function write31(bc: bare.ByteCursor, x: readonly ProcessSnapshotEntry[]): void { bare.writeUintSafe(bc, x.length) for (let i = 0; i < x.length; i++) { writeProcessSnapshotEntry(bc, x[i]) @@ -3046,12 +3105,12 @@ export type ProcessSnapshotResponse = { export function readProcessSnapshotResponse(bc: bare.ByteCursor): ProcessSnapshotResponse { return { - processes: read30(bc), + processes: read31(bc), } } export function writeProcessSnapshotResponse(bc: bare.ByteCursor, x: ProcessSnapshotResponse): void { - write30(bc, x.processes) + write31(bc, x.processes) } export type QueueSnapshotEntry = { @@ -3083,7 +3142,7 @@ export function writeQueueSnapshotEntry(bc: bare.ByteCursor, x: QueueSnapshotEnt bare.writeU64(bc, x.fillPercent) } -function read31(bc: bare.ByteCursor): readonly QueueSnapshotEntry[] { +function read32(bc: bare.ByteCursor): readonly QueueSnapshotEntry[] { const len = bare.readUintSafe(bc) if (len === 0) { return [] @@ -3095,7 +3154,7 @@ function read31(bc: bare.ByteCursor): readonly QueueSnapshotEntry[] { return result } -function write31(bc: bare.ByteCursor, x: readonly QueueSnapshotEntry[]): void { +function write32(bc: bare.ByteCursor, x: readonly QueueSnapshotEntry[]): void { bare.writeUintSafe(bc, x.length) for (let i = 0; i < x.length; i++) { writeQueueSnapshotEntry(bc, x[i]) @@ -3136,7 +3195,7 @@ export function readResourceSnapshotResponse(bc: bare.ByteCursor): ResourceSnaps socketConnections: bare.readU64(bc), socketBufferedBytes: bare.readU64(bc), socketDatagramQueueLen: bare.readU64(bc), - queueSnapshots: read31(bc), + queueSnapshots: read32(bc), } } @@ -3155,7 +3214,7 @@ export function writeResourceSnapshotResponse(bc: bare.ByteCursor, x: ResourceSn bare.writeU64(bc, x.socketConnections) bare.writeU64(bc, x.socketBufferedBytes) bare.writeU64(bc, x.socketDatagramQueueLen) - write31(bc, x.queueSnapshots) + write32(bc, x.queueSnapshots) } export type SocketStateEntry = { @@ -3169,7 +3228,7 @@ export function readSocketStateEntry(bc: bare.ByteCursor): SocketStateEntry { return { processId: bare.readString(bc), host: read0(bc), - port: read24(bc), + port: read25(bc), path: read0(bc), } } @@ -3177,15 +3236,15 @@ export function readSocketStateEntry(bc: bare.ByteCursor): SocketStateEntry { export function writeSocketStateEntry(bc: bare.ByteCursor, x: SocketStateEntry): void { bare.writeString(bc, x.processId) write0(bc, x.host) - write24(bc, x.port) + write25(bc, x.port) write0(bc, x.path) } -function read32(bc: bare.ByteCursor): SocketStateEntry | null { +function read33(bc: bare.ByteCursor): SocketStateEntry | null { return bare.readBool(bc) ? readSocketStateEntry(bc) : null } -function write32(bc: bare.ByteCursor, x: SocketStateEntry | null): void { +function write33(bc: bare.ByteCursor, x: SocketStateEntry | null): void { bare.writeBool(bc, x != null) if (x != null) { writeSocketStateEntry(bc, x) @@ -3198,12 +3257,12 @@ export type ListenerSnapshotResponse = { export function readListenerSnapshotResponse(bc: bare.ByteCursor): ListenerSnapshotResponse { return { - listener: read32(bc), + listener: read33(bc), } } export function writeListenerSnapshotResponse(bc: bare.ByteCursor, x: ListenerSnapshotResponse): void { - write32(bc, x.listener) + write33(bc, x.listener) } export type BoundUdpSnapshotResponse = { @@ -3212,12 +3271,12 @@ export type BoundUdpSnapshotResponse = { export function readBoundUdpSnapshotResponse(bc: bare.ByteCursor): BoundUdpSnapshotResponse { return { - socket: read32(bc), + socket: read33(bc), } } export function writeBoundUdpSnapshotResponse(bc: bare.ByteCursor, x: BoundUdpSnapshotResponse): void { - write32(bc, x.socket) + write33(bc, x.socket) } export enum SignalDispositionAction { @@ -3280,7 +3339,7 @@ export function writeSignalHandlerRegistration(bc: bare.ByteCursor, x: SignalHan bare.writeU32(bc, x.flags) } -function read33(bc: bare.ByteCursor): ReadonlyMap { +function read34(bc: bare.ByteCursor): ReadonlyMap { const len = bare.readUintSafe(bc) const result = new Map() for (let i = 0; i < len; i++) { @@ -3295,7 +3354,7 @@ function read33(bc: bare.ByteCursor): ReadonlyMap): void { +function write34(bc: bare.ByteCursor, x: ReadonlyMap): void { bare.writeUintSafe(bc, x.size) for (const kv of x) { bare.writeU32(bc, kv[0]) @@ -3311,13 +3370,13 @@ export type SignalStateResponse = { export function readSignalStateResponse(bc: bare.ByteCursor): SignalStateResponse { return { processId: bare.readString(bc), - handlers: read33(bc), + handlers: read34(bc), } } export function writeSignalStateResponse(bc: bare.ByteCursor, x: SignalStateResponse): void { bare.writeString(bc, x.processId) - write33(bc, x.handlers) + write34(bc, x.handlers) } export type ZombieTimerCountResponse = { @@ -3474,6 +3533,7 @@ export type ResponsePayload = | { readonly tag: "PtyResizedResponse"; readonly val: PtyResizedResponse } | { readonly tag: "ResourceSnapshotResponse"; readonly val: ResourceSnapshotResponse } | { readonly tag: "PackageLinkedResponse"; readonly val: PackageLinkedResponse } + | { readonly tag: "ProvidedCommandsResponse"; readonly val: ProvidedCommandsResponse } export function readResponsePayload(bc: bare.ByteCursor): ResponsePayload { const offset = bc.offset @@ -3547,6 +3607,8 @@ export function readResponsePayload(bc: bare.ByteCursor): ResponsePayload { return { tag: "ResourceSnapshotResponse", val: readResourceSnapshotResponse(bc) } case 33: return { tag: "PackageLinkedResponse", val: readPackageLinkedResponse(bc) } + case 34: + return { tag: "ProvidedCommandsResponse", val: readProvidedCommandsResponse(bc) } default: { bc.offset = offset throw new bare.BareError(offset, "invalid tag") @@ -3726,6 +3788,11 @@ export function writeResponsePayload(bc: bare.ByteCursor, x: ResponsePayload): v writePackageLinkedResponse(bc, x.val) break } + case "ProvidedCommandsResponse": { + bare.writeU8(bc, 34) + writeProvidedCommandsResponse(bc, x.val) + break + } } } @@ -4096,11 +4163,11 @@ export function writeSidecarRequestFrame(bc: bare.ByteCursor, x: SidecarRequestF writeSidecarRequestPayload(bc, x.payload) } -function read34(bc: bare.ByteCursor): JsonUtf8 | null { +function read35(bc: bare.ByteCursor): JsonUtf8 | null { return bare.readBool(bc) ? readJsonUtf8(bc) : null } -function write34(bc: bare.ByteCursor, x: JsonUtf8 | null): void { +function write35(bc: bare.ByteCursor, x: JsonUtf8 | null): void { bare.writeBool(bc, x != null) if (x != null) { writeJsonUtf8(bc, x) @@ -4116,14 +4183,14 @@ export type HostCallbackResultResponse = { export function readHostCallbackResultResponse(bc: bare.ByteCursor): HostCallbackResultResponse { return { invocationId: bare.readString(bc), - result: read34(bc), + result: read35(bc), error: read0(bc), } } export function writeHostCallbackResultResponse(bc: bare.ByteCursor, x: HostCallbackResultResponse): void { bare.writeString(bc, x.invocationId) - write34(bc, x.result) + write35(bc, x.result) write0(bc, x.error) } @@ -4136,14 +4203,14 @@ export type JsBridgeResultResponse = { export function readJsBridgeResultResponse(bc: bare.ByteCursor): JsBridgeResultResponse { return { callId: bare.readString(bc), - result: read34(bc), + result: read35(bc), error: read0(bc), } } export function writeJsBridgeResultResponse(bc: bare.ByteCursor, x: JsBridgeResultResponse): void { bare.writeString(bc, x.callId) - write34(bc, x.result) + write35(bc, x.result) write0(bc, x.error) } diff --git a/packages/core/src/request-payloads.ts b/packages/core/src/request-payloads.ts index 12a33fb64..ef8d00c8e 100644 --- a/packages/core/src/request-payloads.ts +++ b/packages/core/src/request-payloads.ts @@ -89,6 +89,9 @@ export type LiveRequestPayload = type: "link_package"; package: LivePackageDescriptor; } + | { + type: "provided_commands"; + } | { type: "register_host_callbacks"; name: string; @@ -311,6 +314,11 @@ export function toGeneratedRequestPayload( package: toGeneratedPackageDescriptor(payload.package), }, }; + case "provided_commands": + return { + tag: "ProvidedCommandsRequest", + val: null, + }; case "register_host_callbacks": return { tag: "RegisterHostCallbacksRequest", diff --git a/packages/core/src/response-payloads.ts b/packages/core/src/response-payloads.ts index 7c7445eaf..fe8792068 100644 --- a/packages/core/src/response-payloads.ts +++ b/packages/core/src/response-payloads.ts @@ -71,6 +71,11 @@ export interface LiveProjectedCommand { guest_path: string; } +export interface LivePackageCommands { + package_name: string; + commands: string[]; +} + export type LiveResponsePayload = | { type: "authenticated"; @@ -97,6 +102,10 @@ export type LiveResponsePayload = type: "package_linked"; projected_commands: LiveProjectedCommand[]; } + | { + type: "provided_commands_response"; + packages: LivePackageCommands[]; + } | { type: "host_callbacks_registered"; registration: string; @@ -277,6 +286,14 @@ export function fromGeneratedResponsePayload( }), ), }; + case "ProvidedCommandsResponse": + return { + type: "provided_commands_response", + packages: payload.val.packages.map((pkg) => ({ + package_name: pkg.packageName, + commands: [...pkg.commands], + })), + }; case "HostCallbacksRegisteredResponse": return { type: "host_callbacks_registered", diff --git a/packages/core/src/sidecar-process.ts b/packages/core/src/sidecar-process.ts index 8574f3cc7..8dbb2a7de 100644 --- a/packages/core/src/sidecar-process.ts +++ b/packages/core/src/sidecar-process.ts @@ -299,6 +299,11 @@ export interface SidecarProjectedCommand { guestPath: string; } +export interface SidecarPackageCommands { + packageName: string; + commands: string[]; +} + export interface SidecarVmConfiguredResponse { appliedMounts: number; appliedSoftware: number; @@ -559,6 +564,32 @@ export class SidecarProcess { })); } + async providedCommands( + session: AuthenticatedSession, + vm: CreatedVm, + ): Promise { + const response = await this.sendRequest({ + ownership: { + scope: "vm", + connection_id: session.connectionId, + session_id: session.sessionId, + vm_id: vm.vmId, + }, + payload: { + type: "provided_commands", + }, + }); + if (response.payload.type !== "provided_commands_response") { + throw new Error( + `unexpected provided_commands response: ${response.payload.type}`, + ); + } + return response.payload.packages.map((pkg) => ({ + packageName: pkg.package_name, + commands: [...pkg.commands], + })); + } + async registerHostCallbacks( session: AuthenticatedSession, vm: CreatedVm, diff --git a/packages/manifest/package.json b/packages/manifest/package.json index 75665287c..e739e9007 100644 --- a/packages/manifest/package.json +++ b/packages/manifest/package.json @@ -1,6 +1,6 @@ { "name": "@agentos-software/manifest", - "version": "0.3.4-rc.1", + "version": "0.0.1", "type": "module", "license": "Apache-2.0", "main": "./dist/index.js", diff --git a/packages/secure-exec/package.json b/packages/secure-exec/package.json index d600a7710..402c66cc9 100644 --- a/packages/secure-exec/package.json +++ b/packages/secure-exec/package.json @@ -1,6 +1,6 @@ { "name": "secure-exec", - "version": "0.3.4-rc.1", + "version": "0.0.1", "type": "module", "license": "Apache-2.0", "main": "./dist/index.js", diff --git a/packages/sidecar/npm/darwin-arm64/package.json b/packages/sidecar/npm/darwin-arm64/package.json index 9253bf95f..ea936c236 100644 --- a/packages/sidecar/npm/darwin-arm64/package.json +++ b/packages/sidecar/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@secure-exec/sidecar-darwin-arm64", - "version": "0.3.4-rc.1", + "version": "0.0.1", "description": "Secure Exec native sidecar binary for macOS arm64", "license": "Apache-2.0", "repository": { diff --git a/packages/sidecar/npm/darwin-x64/package.json b/packages/sidecar/npm/darwin-x64/package.json index d3c12ab99..40b0dc609 100644 --- a/packages/sidecar/npm/darwin-x64/package.json +++ b/packages/sidecar/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@secure-exec/sidecar-darwin-x64", - "version": "0.3.4-rc.1", + "version": "0.0.1", "description": "Secure Exec native sidecar binary for macOS x64", "license": "Apache-2.0", "repository": { diff --git a/packages/sidecar/npm/linux-arm64-gnu/package.json b/packages/sidecar/npm/linux-arm64-gnu/package.json index e5c5d78f6..871f58c16 100644 --- a/packages/sidecar/npm/linux-arm64-gnu/package.json +++ b/packages/sidecar/npm/linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@secure-exec/sidecar-linux-arm64-gnu", - "version": "0.3.4-rc.1", + "version": "0.0.1", "description": "Secure Exec native sidecar binary for Linux arm64 (glibc)", "license": "Apache-2.0", "repository": { diff --git a/packages/sidecar/npm/linux-x64-gnu/package.json b/packages/sidecar/npm/linux-x64-gnu/package.json index 52279a235..33e177872 100644 --- a/packages/sidecar/npm/linux-x64-gnu/package.json +++ b/packages/sidecar/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@secure-exec/sidecar-linux-x64-gnu", - "version": "0.3.4-rc.1", + "version": "0.0.1", "description": "Secure Exec native sidecar binary for Linux x64 (glibc)", "license": "Apache-2.0", "repository": { diff --git a/packages/sidecar/package.json b/packages/sidecar/package.json index ede13fe9d..04f07cb33 100644 --- a/packages/sidecar/package.json +++ b/packages/sidecar/package.json @@ -1,6 +1,6 @@ { "name": "@secure-exec/sidecar", - "version": "0.3.4-rc.1", + "version": "0.0.1", "description": "Platform-specific resolver for the Secure Exec native sidecar binary", "license": "Apache-2.0", "repository": { diff --git a/packages/typescript/package.json b/packages/typescript/package.json index 10b4c8b00..716f0a394 100644 --- a/packages/typescript/package.json +++ b/packages/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@secure-exec/typescript", - "version": "0.3.4-rc.1", + "version": "0.0.1", "type": "module", "license": "Apache-2.0", "main": "./dist/index.js", diff --git a/registry/tool/sandbox/package.json b/registry/tool/sandbox/package.json index 4633ae1a2..68122fc77 100644 --- a/registry/tool/sandbox/package.json +++ b/registry/tool/sandbox/package.json @@ -1,6 +1,6 @@ { "name": "@secure-exec/sandbox", - "version": "0.3.4-rc.1", + "version": "0.0.1", "type": "module", "license": "Apache-2.0", "main": "./dist/index.js", diff --git a/scripts/publish/src/ci/verify-fixed-versions.ts b/scripts/publish/src/ci/verify-fixed-versions.ts new file mode 100644 index 000000000..71fd93cf5 --- /dev/null +++ b/scripts/publish/src/ci/verify-fixed-versions.ts @@ -0,0 +1,157 @@ +#!/usr/bin/env tsx +import { existsSync, readFileSync } from "node:fs"; +import { dirname, join, relative, resolve, sep } from "node:path"; +import { fileURLToPath } from "node:url"; +import { discoverPackages } from "../lib/packages.js"; + +const EXPECTED_VERSION = "0.0.1"; +const defaultRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../../.."); + +function parseArgs(argv: string[]) { + const options = { root: defaultRoot }; + for (let i = 0; i < argv.length; i++) { + const arg = argv[i]; + if (arg === "--root") { + options.root = argv[++i]; + continue; + } + if (arg.startsWith("--root=")) { + options.root = arg.slice("--root=".length); + continue; + } + throw new Error(`unknown argument: ${arg}`); + } + return { root: resolve(options.root) }; +} + +function toRel(root: string, path: string) { + return relative(root, path).split(sep).join("/"); +} + +function readPackageVersion( + path: string, + relPath: string, + failures: string[], +) { + let manifest: { version?: unknown }; + try { + manifest = JSON.parse(readFileSync(path, "utf8")); + } catch (err) { + failures.push( + `${relPath} could not be parsed: ${err instanceof Error ? err.message : String(err)}`, + ); + return undefined; + } + return manifest.version; +} + +function readWorkspacePackageVersion(root: string, failures: string[]) { + const cargoPath = join(root, "Cargo.toml"); + if (!existsSync(cargoPath)) { + failures.push("Cargo.toml is missing"); + return undefined; + } + + let inWorkspacePackage = false; + for (const line of readFileSync(cargoPath, "utf8").split("\n")) { + const header = line.match(/^\[([^\]]+)\]\s*$/); + if (header) { + inWorkspacePackage = header[1] === "workspace.package"; + continue; + } + if (!inWorkspacePackage) continue; + const version = line.match(/^\s*version\s*=\s*"([^"]+)"/)?.[1]; + if (version) return version; + } + + failures.push("Cargo.toml [workspace.package] is missing version"); + return undefined; +} + +function checkWorkspaceCrateDeps(root: string, failures: string[]) { + const cargoPath = join(root, "Cargo.toml"); + if (!existsSync(cargoPath)) return; + + let inDeps = false; + for (const line of readFileSync(cargoPath, "utf8").split("\n")) { + const header = line.match(/^\[([^\]]+)\]\s*$/); + if (header) { + inDeps = header[1] === "workspace.dependencies"; + continue; + } + if (!inDeps) continue; + if (!/path\s*=\s*"crates\//.test(line)) continue; + + const name = line.match(/^\s*([A-Za-z0-9_-]+)\s*=/)?.[1]; + const version = line.match(/version\s*=\s*"([^"]+)"/)?.[1]; + if (version !== EXPECTED_VERSION) { + failures.push( + `Cargo.toml [workspace.dependencies] ${name ?? "(unknown)"} version is ${version === undefined ? "missing" : `"${version}"`}`, + ); + } + } +} + +export function auditFixedVersions(options: { root?: string } = {}) { + const root = resolve(options.root ?? defaultRoot); + const failures: string[] = []; + + if (!existsSync(root)) { + return { + root, + ok: false, + packageCount: 0, + failures: [`${root} does not exist`], + }; + } + + const packages = discoverPackages(root).sort((a, b) => + a.relDir.localeCompare(b.relDir), + ); + + for (const pkg of packages) { + const pkgJsonPath = join(pkg.dir, "package.json"); + const relPath = toRel(root, pkgJsonPath); + const version = readPackageVersion(pkgJsonPath, relPath, failures); + if (version !== EXPECTED_VERSION) { + failures.push( + `${relPath} version is ${version === undefined ? "missing" : `"${String(version)}"`}`, + ); + } + } + + const cargoVersion = readWorkspacePackageVersion(root, failures); + if (cargoVersion !== undefined && cargoVersion !== EXPECTED_VERSION) { + failures.push( + `Cargo.toml [workspace.package] version is "${cargoVersion}"`, + ); + } + checkWorkspaceCrateDeps(root, failures); + + return { + root, + ok: failures.length === 0, + packageCount: packages.length, + failures, + }; +} + +export function main(argv = process.argv.slice(2)) { + const options = parseArgs(argv); + const result = auditFixedVersions(options); + if (result.ok) { + process.stdout.write( + `verify-fixed-versions: OK (${result.packageCount} packages + Cargo.toml pinned to ${EXPECTED_VERSION})\n`, + ); + return 0; + } + + for (const failure of result.failures) { + process.stderr.write(`verify-fixed-versions: ${failure}\n`); + } + return 1; +} + +if (import.meta.url === `file://${process.argv[1]}`) { + process.exitCode = main(); +} diff --git a/scripts/publish/src/local/cut-release.ts b/scripts/publish/src/local/cut-release.ts index 95341cdaf..3f9837d80 100644 --- a/scripts/publish/src/local/cut-release.ts +++ b/scripts/publish/src/local/cut-release.ts @@ -7,11 +7,8 @@ * 2. Auto-detect or confirm `latest` flag * 3. Validate git working tree is clean * 4. Print release plan and confirm - * 5. Rewrite Cargo.toml + example source files - * 6. Rewrite every publishable package.json version (version-only) - * 7. Optional local type-check fail-fast - * 8. Commit + push - * 9. Trigger the publish.yaml workflow + * 5. Optional local type-check fail-fast + * 6. Trigger the publish.yaml workflow * * Debugging: comment out any step. No `--only-steps`, no phases. */ @@ -24,13 +21,10 @@ import { $ } from "execa"; import { validateClean } from "../lib/git.js"; import { scoped } from "../lib/logger.js"; import { - bumpCargoVersions, - bumpPackageJsons, getLatestGitVersion, listRecentVersions, resolveVersion, shouldTagAsLatest, - updateSourceFiles, } from "../lib/version.js"; const log = scoped("release"); @@ -80,7 +74,7 @@ async function main() { .option("--patch", "Bump patch") .option("--latest", "Mark as latest dist-tag") .option("--no-latest", "Do not mark as latest") - .option("--dry-run", "Do not commit/push/trigger (still mutates source files)") + .option("--dry-run", "Resolve and print the release plan without triggering the workflow") .option("-y, --yes", "Skip interactive confirmation") .option("--skip-checks", "Skip local type-check fail-fast") .parse(); @@ -115,7 +109,7 @@ async function main() { console.log(` Latest: ${latest}`); console.log(` Branch: ${branch.trim()}`); console.log(` Previous: ${latestGit ?? "(none)"}`); - if (opts.dryRun) console.log(" Dry run: no git commit / push / workflow trigger"); + if (opts.dryRun) console.log(" Dry run: no workflow trigger"); console.log(""); if (recent.length > 0) { console.log("Recent versions:"); @@ -126,6 +120,11 @@ async function main() { console.log(""); } + if (opts.dryRun) { + log.info("dry run complete — workflow not triggered"); + return; + } + if (!opts.yes) { const ok = await confirmPrompt("Proceed with release? (yes/no): "); if (!ok) { @@ -134,19 +133,7 @@ async function main() { } } - // 5. Update Cargo.toml + example source files. - log.info("updating Cargo.toml + example source files"); - await bumpCargoVersions(repoRoot, version); - await updateSourceFiles(repoRoot, version); - - // 6. Rewrite package.json version fields via discovery. Uses versionOnly - // mode so `workspace:*` dep specs are preserved — the lockfile depends on - // them. CI runs the full publish-time bump (with dep rewriting + - // optionalDependencies injection) after `pnpm install --frozen-lockfile`. - log.info("rewriting package.json versions"); - await bumpPackageJsons(repoRoot, version, { versionOnly: true }); - - // 7. Local type-check fail-fast. + // 5. Local type-check fail-fast. if (!opts.skipChecks) { log.info("running local core build + type-check (fail-fast)"); await $({ stdio: "inherit", cwd: repoRoot })`pnpm --dir packages/core build`; @@ -156,28 +143,10 @@ async function main() { })`pnpm --dir packages/core check-types`; } - if (opts.dryRun) { - log.info("dry run complete — source files mutated, nothing committed"); - return; - } - - // 8. Commit + push. - log.info("committing version bump"); - await $({ stdio: "inherit", cwd: repoRoot })`git add -A`; - await $({ - stdio: "inherit", - cwd: repoRoot, - shell: true, - })`git commit --allow-empty -m "chore(release): update version to ${version}"`; - - const currentBranch = ( - await $`git rev-parse --abbrev-ref HEAD` - ).stdout.trim(); - await $({ stdio: "inherit", cwd: repoRoot })`git push origin ${currentBranch}`; - - // 9. Trigger the workflow. + // 6. Trigger the workflow. log.info("triggering publish.yaml workflow"); const latestFlag = latest ? "true" : "false"; + const currentBranch = branch.trim(); await $({ stdio: "inherit", cwd: repoRoot, diff --git a/scripts/verify-fixed-versions.test.mjs b/scripts/verify-fixed-versions.test.mjs new file mode 100644 index 000000000..550eec541 --- /dev/null +++ b/scripts/verify-fixed-versions.test.mjs @@ -0,0 +1,77 @@ +import { execFileSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; + +const repoRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); +const gateArgs = [ + "--filter=publish", + "exec", + "tsx", + "src/ci/verify-fixed-versions.ts", +]; + +function runGate(root) { + const args = [...gateArgs]; + if (root) args.push(`--root=${root}`); + return execFileSync("pnpm", args, { cwd: repoRoot, stdio: "pipe" }); +} + +function gateExitCode(root) { + try { + runGate(root); + return 0; + } catch (err) { + return err.status; + } +} + +function writeJson(root, rel, value) { + const path = join(root, rel); + mkdirSync(join(path, ".."), { recursive: true }); + writeFileSync(path, `${JSON.stringify(value, null, "\t")}\n`); +} + +function writeFixture(root, packageVersion) { + writeJson(root, "package.json", { + name: "secure-exec-fixture", + private: true, + packageManager: "pnpm@10.13.1", + }); + writeFileSync( + join(root, "pnpm-workspace.yaml"), + ["packages:", " - packages/*", ""].join("\n"), + ); + writeJson(root, "packages/core/package.json", { + name: "@secure-exec/core", + version: packageVersion, + }); + writeFileSync( + join(root, "Cargo.toml"), + `[workspace.package] +version = "0.0.1" + +[workspace.dependencies] +secure-exec-bridge = { path = "crates/bridge", version = "0.0.1" } +`, + ); +} + +test("passes on the current tree", () => { + runGate(); +}); + +test("fails when a discovered package drifts off 0.0.1", () => { + const root = mkdtempSync(join(tmpdir(), "secure-exec-fixed-versions-")); + try { + writeFixture(root, "0.3.4-rc.1"); + const exitCode = gateExitCode(root); + if (exitCode !== 1) { + throw new Error(`expected gate to exit 1 on a drifted version, got ${exitCode}`); + } + } finally { + rmSync(root, { recursive: true, force: true }); + } +});