diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d84462f4..67ad8c00 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -189,7 +189,7 @@ jobs: with: # Pinned — matches `packageManager` in web/package.json. # Bump together with that field, never alone. - version: 12.9.1 + version: 12.14.0 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a07bc47..1fdad7da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,45 @@ target. ## [Unreleased] +## [3.8.3] — 2026-10-20 + +Tool-call inspection in streamed answers now decides only on arguments +that have settled, and a hit that is recorded carries the whole call. The +thinkwatch-core crates move from v0.75.0 to v0.77.0. The web image is +built with Node.js 24.22.0 and pnpm 12.14.0. + +No setting, schema or API route changes. + +### Changed + +- The thinkwatch-core crates move from v0.75.0 to v0.77.0. +- Since a recorded hit now waits for the complete call, a streamed tool + call that never completes (the upstream stream breaks off, or the client + leaves, in the middle of it) leaves no tool-inspection row. Such a call + was never whole on the client side either. Rules that cut still check + the arguments while they arrive. +- The web image's build stage uses Node.js 24.22.0 and pnpm 12.14.0. The + served image (nginx) is unchanged. +- The development compose file (`deploy/docker-compose.dev.yml`) runs + Zitadel v4.19.5. + +### Fixed + +- **Tool-call inspection no longer judges arguments that are still + arriving.** A streamed tool call was checked against its arguments as + far as they had come in. A host cut off in the middle of its name + (`http://127` before `.0.0.1`) looked external, and a pattern anchored + at the end could match the half-received tail. A hit now counts only + once it is settled; rules that cut still act as early as possible. +- **A recorded hit carries the whole call.** A hit that is only recorded, + and every hit in observe mode, was recorded with the arguments as they + stood at the first match. It is now recorded once the call is complete. + A Chat Completions call with no `finish_reason` or `[DONE]` is checked + when the stream ends. +- **`-T` is an upload only in curl.** The upload rule took any `-T` for + `curl -T`, so `docker compose exec -T` next to a URL was recorded as a + file upload. + ## [3.8.2] — 2026-10-16 Tool-call inspection now checks every choice of an answer, not only the @@ -2450,7 +2489,8 @@ unreleased builds should: stop the gateway, run `db/schema.sql` against PostgreSQL, restart against this tag. The schema is idempotent end-to-end, so the apply is safe to repeat. -[Unreleased]: https://github.com/ThinkWatchProject/ThinkWatch/compare/v3.8.2...HEAD +[Unreleased]: https://github.com/ThinkWatchProject/ThinkWatch/compare/v3.8.3...HEAD +[3.8.3]: https://github.com/ThinkWatchProject/ThinkWatch/releases/tag/v3.8.3 [3.8.2]: https://github.com/ThinkWatchProject/ThinkWatch/releases/tag/v3.8.2 [3.8.1]: https://github.com/ThinkWatchProject/ThinkWatch/releases/tag/v3.8.1 [3.8.0]: https://github.com/ThinkWatchProject/ThinkWatch/releases/tag/v3.8.0 diff --git a/Cargo.lock b/Cargo.lock index 662d81c5..f570a7f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4189,7 +4189,7 @@ dependencies = [ [[package]] name = "think-watch-auth" -version = "3.8.2" +version = "3.8.3" dependencies = [ "anyhow", "argon2", @@ -4219,7 +4219,7 @@ dependencies = [ [[package]] name = "think-watch-common" -version = "3.8.2" +version = "3.8.3" dependencies = [ "aes-gcm", "anyhow", @@ -4262,7 +4262,7 @@ dependencies = [ [[package]] name = "think-watch-gateway" -version = "3.8.2" +version = "3.8.3" dependencies = [ "anyhow", "arc-swap", @@ -4301,7 +4301,7 @@ dependencies = [ [[package]] name = "think-watch-mcp-gateway" -version = "3.8.2" +version = "3.8.3" dependencies = [ "anyhow", "arc-swap", @@ -4330,7 +4330,7 @@ dependencies = [ [[package]] name = "think-watch-server" -version = "3.8.2" +version = "3.8.3" dependencies = [ "anyhow", "arc-swap", @@ -4381,7 +4381,7 @@ dependencies = [ [[package]] name = "think-watch-test-support" -version = "3.8.2" +version = "3.8.3" dependencies = [ "anyhow", "async-stream", @@ -4837,8 +4837,8 @@ dependencies = [ [[package]] name = "tw-bedrock" -version = "0.75.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.75.0#f040a92a2d509d24c21e8a460e25f6cd3db2cd39" +version = "0.77.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.77.0#00c9a892675c75c97e29609d762e63accb37fa34" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -4852,16 +4852,16 @@ dependencies = [ [[package]] name = "tw-breaker" -version = "0.75.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.75.0#f040a92a2d509d24c21e8a460e25f6cd3db2cd39" +version = "0.77.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.77.0#00c9a892675c75c97e29609d762e63accb37fa34" dependencies = [ "serde", ] [[package]] name = "tw-dialect" -version = "0.75.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.75.0#f040a92a2d509d24c21e8a460e25f6cd3db2cd39" +version = "0.77.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.77.0#00c9a892675c75c97e29609d762e63accb37fa34" dependencies = [ "memchr", "serde", @@ -4870,8 +4870,8 @@ dependencies = [ [[package]] name = "tw-guard" -version = "0.75.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.75.0#f040a92a2d509d24c21e8a460e25f6cd3db2cd39" +version = "0.77.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.77.0#00c9a892675c75c97e29609d762e63accb37fa34" dependencies = [ "aho-corasick", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index 6d0a2ee8..48e44b71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ ] [workspace.package] -version = "3.8.2" +version = "3.8.3" edition = "2024" # The MSRV: the newest `rust-version` among the locked dependencies # (sqlx 0.9 and the aws-smithy crates under aws-sigv4). Without it, @@ -70,10 +70,10 @@ opt-level = 3 # never re-exported through a local shim. And the reverse: something only # this side uses (the at-rest crypto, IMDSv2 credentials, the gateway error) # lives here, not in core. -tw-bedrock = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.75.0" } -tw-breaker = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.75.0" } -tw-dialect = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.75.0" } -tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.75.0" } +tw-bedrock = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.77.0" } +tw-breaker = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.77.0" } +tw-dialect = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.77.0" } +tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.77.0" } # Web framework axum = { version = "0.8", features = ["macros", "ws"] } diff --git a/crates/gateway/src/lifecycle/mod.rs b/crates/gateway/src/lifecycle/mod.rs index 14462342..3013224e 100644 --- a/crates/gateway/src/lifecycle/mod.rs +++ b/crates/gateway/src/lifecycle/mod.rs @@ -334,7 +334,12 @@ pub(crate) fn build_chat_pump( out.extend(shaper.finish()); // The converter's last bytes can complete a tool call (the block's // stop), so they are inspected too. - let stop = inspector.as_mut().and_then(|i| i.check(&out)); + // A Chat call with no `finish_reason` or `[DONE]` completes only + // as the stream closes; one that stops it keeps the tail back. + let stop = inspector.as_mut().and_then(|i| { + i.check(&out) + .or_else(|| i.finish().map(|err| (err, 0))) + }); if let Some((err, safe)) = stop { let out = cut(&shaper, None, client, &out[..safe], &err); tell(&mut done_tx, StreamOutcome::UpstreamError { diff --git a/crates/gateway/src/tool_inspection.rs b/crates/gateway/src/tool_inspection.rs index 1deb4bf5..1235de2c 100644 --- a/crates/gateway/src/tool_inspection.rs +++ b/crates/gateway/src/tool_inspection.rs @@ -59,8 +59,16 @@ impl ToolInspection { } /// A wall for an SSE stream in the client's format. `None` when off. + /// + /// Only the rules that can stop the response are checked while a call + /// is still arriving, and only on what has settled (a host cut off at + /// `http://127` is not yet an external one). Everything else waits for + /// the call to complete, so a recorded hit carries the whole call. In + /// observe nothing can stop it, so every rule waits. pub fn stream(&self) -> Option { - self.mode.detects().then(|| Wall::new(self.rules.clone())) + self.mode + .detects() + .then(|| Wall::new(self.rules.clone()).cut_early(self.mode.acts())) } /// Inspect a whole response. Empty when off. @@ -128,6 +136,29 @@ impl StreamInspector { } None } + + /// The stream ran to its end. A Chat call that never got its + /// `finish_reason` or `[DONE]` is complete only now: the client runs it + /// when the stream closes. Returns the refusal when one stops it; the + /// rest of the stream should not go out then. + pub fn finish(&mut self) -> Option { + for v in self.wall.finish() { + let blocked = self.inspection.blocks(&v); + record( + &self.audit, + &self.caller, + &self.provider, + &v, + blocked, + self.inspection.mode, + &self.mask, + ); + if blocked { + return Some(refusal(&v)); + } + } + None + } } /// What the caller is told when a response is cut. Names the tool and the @@ -364,6 +395,59 @@ mod tests { assert_eq!(found[0].rule, "upload-file-to-host"); } + /// A hit that is only recorded waits for the call to complete: the + /// audit row carries the command the client got, not the piece that had + /// arrived when the rule first matched. + #[test] + fn a_recorded_hit_waits_for_the_whole_call() { + let t = ToolInspection::default(); + let mut wall = t.stream().expect("on by default"); + let ev = |name: &str, d: serde_json::Value| format!("event: {name}\ndata: {d}\n\n"); + let delta = |part: &str| { + ev( + "content_block_delta", + serde_json::json!({"type": "content_block_delta", "index": 0, + "delta": {"type": "input_json_delta", "partial_json": part}}), + ) + }; + wall.feed( + ev( + "content_block_start", + serde_json::json!({"type": "content_block_start", "index": 0, + "content_block": {"type": "tool_use", "id": "t1", "name": "bash", "input": {}}}), + ) + .as_bytes(), + ); + assert!( + wall.feed(delta(r#"{"command":"rm -rf / "#).as_bytes()) + .is_empty() + ); + assert!(wall.feed(delta(r#"&& echo done"}"#).as_bytes()).is_empty()); + let found = wall.feed( + ev( + "content_block_stop", + serde_json::json!({"type": "content_block_stop", "index": 0}), + ) + .as_bytes(), + ); + assert_eq!(found.len(), 1, "{found:?}"); + assert_eq!(found[0].rule, "rm-rf-root"); + assert_eq!(found[0].arguments, r#"{"command":"rm -rf / && echo done"}"#); + } + + /// `-T` is an upload only in curl: `docker compose exec -T` turns off + /// the terminal. + #[test] + fn a_dash_t_outside_curl_is_not_an_upload() { + let t = ToolInspection::default(); + assert!( + t.whole(&call( + "docker compose exec -T postgres psql -c 'select 1'; open https://docs.example.com" + )) + .is_empty() + ); + } + #[test] fn the_excerpt_a_hit_reports_can_be_masked() { let key = "sk-ant-api03-AAAAAAAAAAAAAAAAAAAAAAAAAAAA"; diff --git a/deploy/docker-compose.dev.yml b/deploy/docker-compose.dev.yml index 7a635c30..ef8ad2dc 100644 --- a/deploy/docker-compose.dev.yml +++ b/deploy/docker-compose.dev.yml @@ -112,7 +112,7 @@ services: # --- Zitadel (OIDC / SSO provider for development) --- zitadel: - image: ghcr.io/zitadel/zitadel:v4.19.4 + image: ghcr.io/zitadel/zitadel:v4.19.5 command: start-from-init --masterkeyFromEnv --tlsMode disabled environment: ZITADEL_MASTERKEY: "${ZITADEL_MASTERKEY:?ZITADEL_MASTERKEY is required (32 chars)}" diff --git a/deploy/docker/Dockerfile.web b/deploy/docker/Dockerfile.web index 55d7e4d6..d8ce3678 100644 --- a/deploy/docker/Dockerfile.web +++ b/deploy/docker/Dockerfile.web @@ -6,7 +6,7 @@ # `pnpm install` and `pnpm build` under QEMU: the build alone took 260s # against 23s natively, and when Node crashed there with SIGILL the step # never exited — the CI job sat idle until GitHub's six-hour limit. -FROM --platform=$BUILDPLATFORM node:24.21.0-alpine AS builder +FROM --platform=$BUILDPLATFORM node:24.22.0-alpine AS builder # Pin pnpm to a known-good 12.x, matching `packageManager` in # web/package.json. The workspace YAML's `allowBuilds` field requires @@ -15,7 +15,7 @@ FROM --platform=$BUILDPLATFORM node:24.21.0-alpine AS builder # a non-reproducible dist/. pnpm 12 is a native binary: Corepack fetches # the `pnpm` wrapper, whose first run downloads the musl build from the # npm registry and checks it against npm's signature before running it. -RUN corepack enable && corepack prepare pnpm@12.9.1 --activate +RUN corepack enable && corepack prepare pnpm@12.14.0 --activate WORKDIR /app # pnpm-workspace.yaml carries the install-script allowlist (see its diff --git a/deploy/helm/think-watch/Chart.yaml b/deploy/helm/think-watch/Chart.yaml index 40f4b10d..37f927bb 100644 --- a/deploy/helm/think-watch/Chart.yaml +++ b/deploy/helm/think-watch/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: think-watch description: Enterprise AI API Gateway & MCP Management Platform type: application -version: 3.8.2 -appVersion: "3.8.2" +version: 3.8.3 +appVersion: "3.8.3" keywords: - ai - gateway diff --git a/web/package.json b/web/package.json index 9589518f..ba14fbf7 100644 --- a/web/package.json +++ b/web/package.json @@ -1,9 +1,9 @@ { "name": "web", "private": true, - "version": "3.8.2", + "version": "3.8.3", "type": "module", - "packageManager": "pnpm@12.9.1", + "packageManager": "pnpm@12.14.0", "scripts": { "dev": "vite", "build": "node scripts/check-i18n.mjs && tsc -b && vite build", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index a2053250..384327a0 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -7,153 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.9.1 - version: 12.9.1 + specifier: 12.14.0 + version: 12.14.0 packages: - '@pnpm/exe.android-arm64@12.9.1': - resolution: {integrity: sha512-Hgm1XdqMTfBC8PdGLL+NjZWv4xyi7D6XNrN9fb24XGYlyTBmUtdCefKnCLmHujShRD8BEO1zqM30SNcKBDLysw==} + '@pnpm/exe.android-arm64@12.14.0': + resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==} cpu: [arm64] os: [android] - '@pnpm/exe.android-x64@12.9.1': - resolution: {integrity: sha512-WDGCe6jZRVZP3RC/q6hOkZylkPkIQIi1/GcrR/d/YzbihhAHXCczrk+UYnV5TgbsEnAYO7Cx+2rX3nfhHvy24w==} + '@pnpm/exe.android-x64@12.14.0': + resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==} cpu: [x64] os: [android] - '@pnpm/exe.darwin-arm64@12.9.1': - resolution: {integrity: sha512-+7iiEEBgYN9szPWUnAuN6eOVMTI6Hrki5bcuC9n9xl9tTnT53Tpxpo45Hml+W4IrAU0d+Tmd0sPnVIbX59PD6w==} + '@pnpm/exe.darwin-arm64@12.14.0': + resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.9.1': - resolution: {integrity: sha512-SK7TxJiR12GbJZGffRFHItkc+Mx+99v6xMMJ9/X//8NwWV1wo17a2bwfGI8qZpl17YwsrmPEFOOqhzE1GIlrtQ==} + '@pnpm/exe.darwin-x64@12.14.0': + resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==} cpu: [x64] os: [darwin] - '@pnpm/exe.freebsd-x64@12.9.1': - resolution: {integrity: sha512-4AjLk6uvTs2iO6wMit2hcxJ2M32xb7OV5SNFk+w6G8/NzcjcMB7ENEpyeXyLiP0kcnp8uaCGUct2NfrLCJ35eQ==} + '@pnpm/exe.freebsd-x64@12.14.0': + resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==} cpu: [x64] os: [freebsd] - '@pnpm/exe.linux-arm64-musl@12.9.1': - resolution: {integrity: sha512-dZL2ES1ckDWM9v+EtxoOklZ0WNdtayooi9iLiDUubopudX3zXPNR2mlrxBRgjbDgECOndQen2SmyxL5eZiBuKw==} + '@pnpm/exe.linux-arm64-musl@12.14.0': + resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.9.1': - resolution: {integrity: sha512-cx+uF6xqwEtm7B8NV+1ve0xBg9gcuzQZ5R+0WgOWvKG/h978UljS6BgWs1ipIGuGgUdeVhm3PKmBltXIcczLXA==} + '@pnpm/exe.linux-arm64@12.14.0': + resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-ppc64@12.9.1': - resolution: {integrity: sha512-MpjPsy4XR17zAtLziQZfsN+wveWa/bUJggZtwfo3+SuyRmXnSvVkFD4+v8/3RTWyYaM6mwLUTjZDJ+g3urL+ew==} + '@pnpm/exe.linux-ppc64@12.14.0': + resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==} cpu: [ppc64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-riscv64@12.9.1': - resolution: {integrity: sha512-Oo67TKqqH+Bno+L6X7Xyorwp1QG82zKIkL0vbo1x2kcPTDiyPbdM+d6lIs4+hzeB1KHT5FDoGzZj2dad7Ft+eg==} + '@pnpm/exe.linux-riscv64@12.14.0': + resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==} cpu: [riscv64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-s390x@12.9.1': - resolution: {integrity: sha512-QAD5FRCdW36HfD58BV7DoDm6z8yv+GgSqcUV1+ERwYjpVQuG6ZTpf5o0x3KDas8eTLIW7MLYVdDdlAurWeArEg==} + '@pnpm/exe.linux-s390x@12.14.0': + resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==} cpu: [s390x] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.9.1': - resolution: {integrity: sha512-mQDhBsGqcTkrLQM4q5igmRFcr6EAl4UM/FGawY+0CWGcqPpUmYUvDy9LHy80xxkHIWVJ886gvvso2XOjfav1Jw==} + '@pnpm/exe.linux-x64-musl@12.14.0': + resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.9.1': - resolution: {integrity: sha512-+M5N74hM4fc87AAckxx/UIW+dUnWf2eko+HchJs/NZUxehLhekNyMI/GNaNXlvd+kWR+97XXBD51XRkkNiUtrA==} + '@pnpm/exe.linux-x64@12.14.0': + resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.9.1': - resolution: {integrity: sha512-mI28C2wxobrDi6x1OYhBRyG10hnDq/4sEIyy5RzoX6LB4kLuv6K/d25HOpMwlW20cuM4M72k9NZpOdcmUxMbEA==} + '@pnpm/exe.win32-arm64@12.14.0': + resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.9.1': - resolution: {integrity: sha512-SWoDMT0oD7NrDSIoPpkKD09D9fAQKNjwFZ3lezlCUhAfXxMv70zsfrNT2/P+GoCK2kefXF9OysUZkH1l5vvvfA==} + '@pnpm/exe.win32-x64@12.14.0': + resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==} cpu: [x64] os: [win32] - pnpm@12.9.1: - resolution: {integrity: sha512-BrBV//XNINwSeB3hc87TMQzglRvy7GICEaFgRdVETVyTpmMhB2TvKaZTphBFm6A2jw50+E2AxUcjz5Wq57wNbQ==} + pnpm@12.14.0: + resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.android-arm64@12.9.1': + '@pnpm/exe.android-arm64@12.14.0': optional: true - '@pnpm/exe.android-x64@12.9.1': + '@pnpm/exe.android-x64@12.14.0': optional: true - '@pnpm/exe.darwin-arm64@12.9.1': + '@pnpm/exe.darwin-arm64@12.14.0': optional: true - '@pnpm/exe.darwin-x64@12.9.1': + '@pnpm/exe.darwin-x64@12.14.0': optional: true - '@pnpm/exe.freebsd-x64@12.9.1': + '@pnpm/exe.freebsd-x64@12.14.0': optional: true - '@pnpm/exe.linux-arm64-musl@12.9.1': + '@pnpm/exe.linux-arm64-musl@12.14.0': optional: true - '@pnpm/exe.linux-arm64@12.9.1': + '@pnpm/exe.linux-arm64@12.14.0': optional: true - '@pnpm/exe.linux-ppc64@12.9.1': + '@pnpm/exe.linux-ppc64@12.14.0': optional: true - '@pnpm/exe.linux-riscv64@12.9.1': + '@pnpm/exe.linux-riscv64@12.14.0': optional: true - '@pnpm/exe.linux-s390x@12.9.1': + '@pnpm/exe.linux-s390x@12.14.0': optional: true - '@pnpm/exe.linux-x64-musl@12.9.1': + '@pnpm/exe.linux-x64-musl@12.14.0': optional: true - '@pnpm/exe.linux-x64@12.9.1': + '@pnpm/exe.linux-x64@12.14.0': optional: true - '@pnpm/exe.win32-arm64@12.9.1': + '@pnpm/exe.win32-arm64@12.14.0': optional: true - '@pnpm/exe.win32-x64@12.9.1': + '@pnpm/exe.win32-x64@12.14.0': optional: true - pnpm@12.9.1: + pnpm@12.14.0: optionalDependencies: - '@pnpm/exe.android-arm64': 12.9.1 - '@pnpm/exe.android-x64': 12.9.1 - '@pnpm/exe.darwin-arm64': 12.9.1 - '@pnpm/exe.darwin-x64': 12.9.1 - '@pnpm/exe.freebsd-x64': 12.9.1 - '@pnpm/exe.linux-arm64': 12.9.1 - '@pnpm/exe.linux-arm64-musl': 12.9.1 - '@pnpm/exe.linux-ppc64': 12.9.1 - '@pnpm/exe.linux-riscv64': 12.9.1 - '@pnpm/exe.linux-s390x': 12.9.1 - '@pnpm/exe.linux-x64': 12.9.1 - '@pnpm/exe.linux-x64-musl': 12.9.1 - '@pnpm/exe.win32-arm64': 12.9.1 - '@pnpm/exe.win32-x64': 12.9.1 + '@pnpm/exe.android-arm64': 12.14.0 + '@pnpm/exe.android-x64': 12.14.0 + '@pnpm/exe.darwin-arm64': 12.14.0 + '@pnpm/exe.darwin-x64': 12.14.0 + '@pnpm/exe.freebsd-x64': 12.14.0 + '@pnpm/exe.linux-arm64': 12.14.0 + '@pnpm/exe.linux-arm64-musl': 12.14.0 + '@pnpm/exe.linux-ppc64': 12.14.0 + '@pnpm/exe.linux-riscv64': 12.14.0 + '@pnpm/exe.linux-s390x': 12.14.0 + '@pnpm/exe.linux-x64': 12.14.0 + '@pnpm/exe.linux-x64-musl': 12.14.0 + '@pnpm/exe.win32-arm64': 12.14.0 + '@pnpm/exe.win32-x64': 12.14.0 --- lockfileVersion: '9.0'