From 096f3db214ec5bb39ae852a1029ea57011ccbdf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yuniel=20Acosta=20P=C3=A9rez?= <33158051+yacosta738@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:21:50 +0100 Subject: [PATCH 1/3] feat(plugins): introduce edge catalog worker and switch official plugin host --- .github/workflows/publish-plugins.yml | 9 +- clients/agent-runtime/src/config/schema.rs | 26 +- clients/agent-runtime/src/plugins/mod.rs | 4 +- clients/web/README.md | 12 + .../src/content/docs/en/guides/plugins.md | 6 +- .../src/content/docs/es/guides/plugins.md | 6 +- ...c67fbb86c62b4fe5b08ee0d7e80000019c8f1f1c75 | 4 + ...120b25b7313ad7c500cc8558930000019c8f1f2a41 | 12 + ...8c6aad029d038feba23955a6ba0000019c8f1f30e5 | 12 + ...c2482b2c17d21412d300257df80000019c8f1f237b | Bin 0 -> 99 bytes ...6209ad92e53b09b33dce6695f60000019c8f1f15d2 | 34 +++ ...ae8d284994813668f4d82fa5bad50f689d5.sqlite | Bin 0 -> 4096 bytes ...284994813668f4d82fa5bad50f689d5.sqlite-shm | Bin 0 -> 32768 bytes ...284994813668f4d82fa5bad50f689d5.sqlite-wal | Bin 0 -> 70072 bytes clients/web/apps/plugins-edge/README.md | 53 ++++ clients/web/apps/plugins-edge/package.json | 19 ++ clients/web/apps/plugins-edge/src/index.ts | 228 ++++++++++++++++ clients/web/apps/plugins-edge/tsconfig.json | 12 + clients/web/apps/plugins-edge/wrangler.toml | 12 + clients/web/apps/plugins/README.md | 3 + clients/web/build.gradle.kts | 1 + clients/web/package.json | 5 +- clients/web/pnpm-lock.yaml | 246 ++++++++++++++++++ 23 files changed, 679 insertions(+), 25 deletions(-) create mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/25ac6cd46124c1845757a0b42f3bbb3071a951c67fbb86c62b4fe5b08ee0d7e80000019c8f1f1c75 create mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/70453d75c6479044f5bbc8e9d4da343a666266120b25b7313ad7c500cc8558930000019c8f1f2a41 create mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/9ed1a0c98364c0785cb2f4cd7a4403361176f08c6aad029d038feba23955a6ba0000019c8f1f30e5 create mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/c7fb9782885d0870868816d9c4fd8d5b3d01f4c2482b2c17d21412d300257df80000019c8f1f237b create mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/ff350587ab323266bbd980dffafaee3a1e079d6209ad92e53b09b33dce6695f60000019c8f1f15d2 create mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/miniflare-R2BucketObject/cd378746ea92fc3d2e2cb85bd872aae8d284994813668f4d82fa5bad50f689d5.sqlite create mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/miniflare-R2BucketObject/cd378746ea92fc3d2e2cb85bd872aae8d284994813668f4d82fa5bad50f689d5.sqlite-shm create mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/miniflare-R2BucketObject/cd378746ea92fc3d2e2cb85bd872aae8d284994813668f4d82fa5bad50f689d5.sqlite-wal create mode 100644 clients/web/apps/plugins-edge/README.md create mode 100644 clients/web/apps/plugins-edge/package.json create mode 100644 clients/web/apps/plugins-edge/src/index.ts create mode 100644 clients/web/apps/plugins-edge/tsconfig.json create mode 100644 clients/web/apps/plugins-edge/wrangler.toml diff --git a/.github/workflows/publish-plugins.yml b/.github/workflows/publish-plugins.yml index 2cef49a4f..d8d74706e 100644 --- a/.github/workflows/publish-plugins.yml +++ b/.github/workflows/publish-plugins.yml @@ -18,7 +18,7 @@ on: catalog_base_url: description: "Catalog base URL for immutable artifact links" required: false - default: "https://corvus.profiletailors.com" + default: "https://plugins.corvus.profiletailors.com" oci_repository: description: "OCI repository (example: ghcr.io/org/corvus-plugins/memory-surreal-graphs)" required: false @@ -80,7 +80,10 @@ jobs: input_plugin_id = os.environ.get("INPUT_PLUGIN_ID", "").strip() input_plugin_version = os.environ.get("INPUT_PLUGIN_VERSION", "").strip() input_catalog_base_url = ( - os.environ.get("INPUT_CATALOG_BASE_URL", "https://corvus.profiletailors.com") + os.environ.get( + "INPUT_CATALOG_BASE_URL", + "https://plugins.corvus.profiletailors.com", + ) .strip() .rstrip("/") ) @@ -91,7 +94,7 @@ jobs: effective_cf_project_name = input_cf_project_name or default_cf_project_name if not input_catalog_base_url: - input_catalog_base_url = "https://corvus.profiletailors.com" + input_catalog_base_url = "https://plugins.corvus.profiletailors.com" if not input_deploy_cf: input_deploy_cf = "true" diff --git a/clients/agent-runtime/src/config/schema.rs b/clients/agent-runtime/src/config/schema.rs index 8151acbc9..20c64d4e1 100644 --- a/clients/agent-runtime/src/config/schema.rs +++ b/clients/agent-runtime/src/config/schema.rs @@ -1023,7 +1023,7 @@ fn default_plugin_revocation_refresh_minutes() -> u64 { } fn default_plugin_revocation_sources() -> Vec { - vec!["https://corvus.profiletailors.com/revocations.json".to_string()] + vec!["https://plugins.corvus.profiletailors.com/revocations.json".to_string()] } impl Default for PluginRevocationConfig { @@ -1067,7 +1067,7 @@ fn default_plugin_allow_publishers() -> Vec { fn default_plugin_sources() -> Vec { vec![PluginSourceConfig { name: "official".to_string(), - url: "https://corvus.profiletailors.com/catalog.json".to_string(), + url: "https://plugins.corvus.profiletailors.com/catalog.json".to_string(), plugin_identity_regex: None, }] } @@ -2220,7 +2220,7 @@ fn env_override_optional(var_name: &str, target: &mut Option) { fn migrate_deprecated_plugin_registry_urls(config: &mut Config) -> bool { const OLD_PLUGIN_HOST: &str = "plugins.corvus.ai"; - const NEW_PLUGIN_HOST: &str = "corvus.profiletailors.com"; + const NEW_PLUGIN_HOST: &str = "plugins.corvus.profiletailors.com"; fn migrate_url_host(raw_url: &str, old_host: &str, new_host: &str) -> Option { let mut parsed = Url::parse(raw_url).ok()?; @@ -2337,7 +2337,7 @@ impl Config { if migrate_deprecated_plugin_registry_urls(&mut config) { tracing::warn!( "Migrated deprecated plugin registry host entries from 'plugins.corvus.ai' \ - to 'corvus.profiletailors.com' in plugins.sources and \ + to 'plugins.corvus.profiletailors.com' in plugins.sources and \ plugins.revocation.source_urls" ); if let Err(error) = config.save() { @@ -2903,19 +2903,19 @@ default_temperature = 0.7 assert!(!plugins.sources.is_empty()); assert_eq!( plugins.sources[0].url, - "https://corvus.profiletailors.com/catalog.json" + "https://plugins.corvus.profiletailors.com/catalog.json" ); assert_eq!( plugins.revocation.source_urls, - vec!["https://corvus.profiletailors.com/revocations.json".to_string()] + vec!["https://plugins.corvus.profiletailors.com/revocations.json".to_string()] ); assert_eq!( default_plugin_sources()[0].url, - "https://corvus.profiletailors.com/catalog.json" + "https://plugins.corvus.profiletailors.com/catalog.json" ); assert_eq!( default_plugin_revocation_sources(), - vec!["https://corvus.profiletailors.com/revocations.json".to_string()] + vec!["https://plugins.corvus.profiletailors.com/revocations.json".to_string()] ); assert!(plugins .allow_publishers @@ -2939,14 +2939,14 @@ default_temperature = 0.7 ]; config.plugins.revocation.source_urls = vec![ "https://plugins.corvus.ai/revocations.json".to_string(), - "https://corvus.profiletailors.com/revocations.json".to_string(), + "https://plugins.corvus.profiletailors.com/revocations.json".to_string(), ]; let changed = migrate_deprecated_plugin_registry_urls(&mut config); assert!(changed); assert_eq!( config.plugins.sources[0].url, - "https://corvus.profiletailors.com/catalog.json" + "https://plugins.corvus.profiletailors.com/catalog.json" ); assert_eq!( config.plugins.sources[1].url, @@ -2954,7 +2954,7 @@ default_temperature = 0.7 ); assert_eq!( config.plugins.revocation.source_urls, - vec!["https://corvus.profiletailors.com/revocations.json".to_string()] + vec!["https://plugins.corvus.profiletailors.com/revocations.json".to_string()] ); } @@ -2965,11 +2965,11 @@ default_temperature = 0.7 assert!(!changed); assert_eq!( config.plugins.sources[0].url, - "https://corvus.profiletailors.com/catalog.json" + "https://plugins.corvus.profiletailors.com/catalog.json" ); assert_eq!( config.plugins.revocation.source_urls, - vec!["https://corvus.profiletailors.com/revocations.json".to_string()] + vec!["https://plugins.corvus.profiletailors.com/revocations.json".to_string()] ); } diff --git a/clients/agent-runtime/src/plugins/mod.rs b/clients/agent-runtime/src/plugins/mod.rs index 237732843..4ca7a547a 100644 --- a/clients/agent-runtime/src/plugins/mod.rs +++ b/clients/agent-runtime/src/plugins/mod.rs @@ -22,7 +22,7 @@ const MAX_ARTIFACT_BYTES: usize = 50 * 1024 * 1024; const MAX_SIGNATURE_BYTES: usize = 64 * 1024; const MAX_CERTIFICATE_BYTES: usize = 512 * 1024; const COSIGN_VERIFY_TIMEOUT: Duration = Duration::from_secs(30); -const OFFICIAL_PLUGIN_CATALOG_HOST: &str = "corvus.profiletailors.com"; +const OFFICIAL_PLUGIN_CATALOG_HOST: &str = "plugins.corvus.profiletailors.com"; const SIGSTORE_GITHUB_OIDC_ISSUER: &str = "https://token.actions.githubusercontent.com"; const OFFICIAL_PLUGIN_IDENTITY_REGEX: &str = r"^https://github\.com/dallay/corvus/\.github/workflows/publish-plugins\.yml@.*$"; @@ -2065,7 +2065,7 @@ mod tests { fn signature_policy_requires_keyless_for_official_remote_source() { let source = PluginSourceConfig { name: "official".to_string(), - url: "https://corvus.profiletailors.com/catalog.json".to_string(), + url: "https://plugins.corvus.profiletailors.com/catalog.json".to_string(), plugin_identity_regex: None, }; diff --git a/clients/web/README.md b/clients/web/README.md index ef93fb9ab..762f78d1c 100644 --- a/clients/web/README.md +++ b/clients/web/README.md @@ -10,6 +10,7 @@ clients/web/ │ ├── docs/ # Documentation (Astro + Starlight) │ ├── marketing/ # Marketing landing and campaign pages (Astro) │ ├── plugins/ # Plugin catalog and revocations (Astro) +│ ├── plugins-edge/ # Plugin distribution API (Cloudflare Worker + R2) │ └── chat/ # ChatGPT-style conversational chat (Vue 3 + Vite) ├── packages/ │ └── shared/ # Shared utilities @@ -37,6 +38,12 @@ clients/web/ - URL configurable with `PLUGINS_URL` (dev default: `http://localhost:9990`) - Publishes official plugin metadata at `/catalog.json` and `/revocations.json` +### plugins-edge + +- Runtime metadata/artifact API served from Cloudflare Worker + R2 +- Intended production source for `/catalog.json`, `/revocations.json`, and `/artifacts/*` +- Default local port: 9797 + ### chat - Framework: Vue 3 + Vite + Tailwind + shadcn-vue style components @@ -61,6 +68,7 @@ pnpm build pnpm build:docs pnpm build:marketing pnpm build:plugins +pnpm build:plugins-edge pnpm build:chat # Compatibility (legacy alias) @@ -70,6 +78,7 @@ pnpm build:landing pnpm dev pnpm dev:marketing pnpm dev:plugins +pnpm dev:plugins-edge pnpm dev:chat # Compatibility (legacy alias) @@ -80,6 +89,9 @@ pnpm format pnpm check pnpm test pnpm test:chat + +# Deploy plugins edge worker +pnpm deploy:plugins-edge ``` ## Biome (Linter & Formatter) diff --git a/clients/web/apps/docs/src/content/docs/en/guides/plugins.md b/clients/web/apps/docs/src/content/docs/en/guides/plugins.md index 2e70c98d1..913e49f78 100644 --- a/clients/web/apps/docs/src/content/docs/en/guides/plugins.md +++ b/clients/web/apps/docs/src/content/docs/en/guides/plugins.md @@ -64,8 +64,8 @@ All new official plugins must satisfy: Relevant configuration defaults: -- Catalog: `https://corvus.profiletailors.com/catalog.json` -- Revocations: `https://corvus.profiletailors.com/revocations.json` +- Catalog: `https://plugins.corvus.profiletailors.com/catalog.json` +- Revocations: `https://plugins.corvus.profiletailors.com/revocations.json` See: @@ -212,4 +212,4 @@ Recommended production rollout: ### Migration from old plugin host On config load, Corvus migrates old `plugins.corvus.ai` host references to -`corvus.profiletailors.com` for both catalog and revocation source URLs. +`plugins.corvus.profiletailors.com` for both catalog and revocation source URLs. diff --git a/clients/web/apps/docs/src/content/docs/es/guides/plugins.md b/clients/web/apps/docs/src/content/docs/es/guides/plugins.md index 7f37ca331..2b5f65438 100644 --- a/clients/web/apps/docs/src/content/docs/es/guides/plugins.md +++ b/clients/web/apps/docs/src/content/docs/es/guides/plugins.md @@ -65,8 +65,8 @@ Todos los plugins oficiales nuevos deben satisfacer: Configuración por defecto relevante: -- Catálogo: `https://corvus.profiletailors.com/catalog.json` -- Revocaciones: `https://corvus.profiletailors.com/revocations.json` +- Catálogo: `https://plugins.corvus.profiletailors.com/catalog.json` +- Revocaciones: `https://plugins.corvus.profiletailors.com/revocations.json` Ver: @@ -215,4 +215,4 @@ Rollout recomendado para producción: ### Migración del host de plugins antiguo Al cargar config, Corvus migra referencias antiguas del host `plugins.corvus.ai` a -`corvus.profiletailors.com` para ambas URLs de fuente de catálogo y revocación. +`plugins.corvus.profiletailors.com` para ambas URLs de fuente de catálogo y revocación. diff --git a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/25ac6cd46124c1845757a0b42f3bbb3071a951c67fbb86c62b4fe5b08ee0d7e80000019c8f1f1c75 b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/25ac6cd46124c1845757a0b42f3bbb3071a951c67fbb86c62b4fe5b08ee0d7e80000019c8f1f1c75 new file mode 100644 index 000000000..69ffa87c8 --- /dev/null +++ b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/25ac6cd46124c1845757a0b42f3bbb3071a951c67fbb86c62b4fe5b08ee0d7e80000019c8f1f1c75 @@ -0,0 +1,4 @@ +{ + "updated_at": "2026-02-22T15:44:45Z", + "revoked": [] +} diff --git a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/70453d75c6479044f5bbc8e9d4da343a666266120b25b7313ad7c500cc8558930000019c8f1f2a41 b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/70453d75c6479044f5bbc8e9d4da343a666266120b25b7313ad7c500cc8558930000019c8f1f2a41 new file mode 100644 index 000000000..dde6414e4 --- /dev/null +++ b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/70453d75c6479044f5bbc8e9d4da343a666266120b25b7313ad7c500cc8558930000019c8f1f2a41 @@ -0,0 +1,12 @@ + Corvus Plugins Catalog

Corvus runtime metadata

Official plugin distribution endpoints

+This host serves machine-readable metadata used by the Corvus runtime to discover and + verify plugins. +

Endpoints

Catalog /catalog.json

Revocations /revocations.json

Immutable artifact /artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm

Immutable manifest /artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json

+Runtime defaults point to these URLs and treat them as official metadata sources. +

+The artifact and manifest paths below are immutable versioned examples. For the latest + release metadata, always use /catalog.json. +

Quick validation

Use these commands after deployment:

curl -fsSL https://corvus.profiletailors.com/catalog.json

curl -fsSL https://corvus.profiletailors.com/revocations.json

curl -fsSL https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm -o memory.surreal.graphs.wasm

curl -fsSL https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json

+Source of truth lives in workflow output from plugin publishing. +
\ No newline at end of file diff --git a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/9ed1a0c98364c0785cb2f4cd7a4403361176f08c6aad029d038feba23955a6ba0000019c8f1f30e5 b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/9ed1a0c98364c0785cb2f4cd7a4403361176f08c6aad029d038feba23955a6ba0000019c8f1f30e5 new file mode 100644 index 000000000..dde6414e4 --- /dev/null +++ b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/9ed1a0c98364c0785cb2f4cd7a4403361176f08c6aad029d038feba23955a6ba0000019c8f1f30e5 @@ -0,0 +1,12 @@ + Corvus Plugins Catalog

Corvus runtime metadata

Official plugin distribution endpoints

+This host serves machine-readable metadata used by the Corvus runtime to discover and + verify plugins. +

Endpoints

Catalog /catalog.json

Revocations /revocations.json

Immutable artifact /artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm

Immutable manifest /artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json

+Runtime defaults point to these URLs and treat them as official metadata sources. +

+The artifact and manifest paths below are immutable versioned examples. For the latest + release metadata, always use /catalog.json. +

Quick validation

Use these commands after deployment:

curl -fsSL https://corvus.profiletailors.com/catalog.json

curl -fsSL https://corvus.profiletailors.com/revocations.json

curl -fsSL https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm -o memory.surreal.graphs.wasm

curl -fsSL https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json

+Source of truth lives in workflow output from plugin publishing. +
\ No newline at end of file diff --git a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/c7fb9782885d0870868816d9c4fd8d5b3d01f4c2482b2c17d21412d300257df80000019c8f1f237b b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/c7fb9782885d0870868816d9c4fd8d5b3d01f4c2482b2c17d21412d300257df80000019c8f1f237b new file mode 100644 index 0000000000000000000000000000000000000000..02b7bdab6c27cbba96ea3f4fac30f6bf62e0fb2c GIT binary patch literal 99 zcmXZMyA8rn5Jkar_kA#|gth}}aVI7qd7U}9o$P*7lCU|@t|AVoG{WYDXN;00+HAlr;ljiVtj n8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O6ovo**AfQj literal 0 HcmV?d00001 diff --git a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/miniflare-R2BucketObject/cd378746ea92fc3d2e2cb85bd872aae8d284994813668f4d82fa5bad50f689d5.sqlite-shm b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/miniflare-R2BucketObject/cd378746ea92fc3d2e2cb85bd872aae8d284994813668f4d82fa5bad50f689d5.sqlite-shm new file mode 100644 index 0000000000000000000000000000000000000000..e4f01fe6f2c0b2ce014d408ef6f472454a04660d GIT binary patch literal 32768 zcmeI)y-fo_5C+h(5AzQ`Bmxsq0wqv@iAiXHm;kCEBqE?I3a}iy221t~HWHadXzlx? z?^ar^Ztq?JGnwB-Ww2>~mFrRV}$eZRl{e!F>Geodcl7v1=||C~+!c|KqDlW{&* zf3n?<+OO38!)%v5$d0my+4`2>{atGw6#@hZ5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7csfxQx_sS`q= zO@Uz?$7!6$W%O+V`0IuRx;TlmxX1}aHSi zE>T2GduraS6Y*$_bwHerc+P;`62JE z=j^QX=J`)Q_VxGQ8h+x9V_%uCG)+FK)vKbJr*`X1CUrQUx9eu?ZOCoYEm`}R+jwFh z*I#eb^L~?=)XdY5kBmXvqwie*{Eju zq&d@BdR2uwiyKpR`E-8HRd#V*s<==r6=&z?m%1|xbDiCNd9iqOc5x~HNMR}eLERt7 zZ>sk6@N{8taAJw3Q3T3a)%%S$#?9fCQrq56IY4L6Y zY~8LqGk1el@2=9n-8WUQQ{SH3#wVgP`$V(3R<7DM(V{8(v8z_2S+k9DgH;+$VUG(- z#f2sNIdROhkIt9!r1xnVo-XDFC!Ppzj&XzID#-5afpbxMXk!2V^s`})vfK0yusfN# zhON7xi`B!phH3L>x+8ST)EkxBYCoI*;#1zKhSAsga%_AF8vS;6Yi%_=%`$@%#r-#G zg$>Dd`|cBWEdR0YZ>u}b@AVf<-FyE#Z?t}MWz@~=bS1bc6bL{70uX=z1Rwwb2tWV= z5P$##cBg>rBk1)DT%5ez%6;$m%foIa`URW<3Irek0SG_<0uX=z1Rwwb2tZ&r3#^|5 zuyKAt^T<~}^QnK`^JC--?B*s%-5>w~2tWV=5P$##AOHafKmYABh~$QRhnO^&)j z00Izz00bZa0SG_<0uX=z1bPDQ+5$rx{Q`HKy086*7v8;se1V=1CLjO-2tWV=5P$## zAOHafKmY=}Szycc2>$)sUq2hYeCC|X7f6pEPT7AH2tWV=5P$##AOHafKmY;|fB*!p zBhWsS9%1bVk7kYz?A^Op+3P~C)Q%tMKF%yRYO6^?8DUWW7_wG;37#Fi8%EY3q$F7iR1|jf<($# zTbEq6VFUcSKUW0yZkvw!=A z%NIzG|0QMrQ6K;T2tWV=5P$##AOHafKmY;|_=pQkWZ514dM#|Vw&V*0jhTnC#ktVs z3uMO5+I#_fhXMfzKmY;|fB*y_009U<00I#BUkJ1hXGRX$V0(H?u)S_h)$Ac`rMB8w z54ihGD6VwCJwH%h6tIwmg2=#6sFaeD&=WE96c25INB6G?7k_>rVGq;6lF(Li%)1N)kCV#Dnb)luL!j9OD`9%6mhSYPXlq*JvQW0ZOY(wcE z3=r>&z%GyM$nh~FoFq1OPGc$rl_K^6I}W@j{PDR^P+tf;a=eY)^Mo5}?D_B>i33HG z*yqN@(C?q^#?X;3fbj)x-}ML{{=(c3F1`D^C6_Od8$ams1r8!(;5PmEVYd)~00bZa z0SG_<0uX=z1R$`J1&X=$zFUs+yN;^d4$2pJ^(W8GUp{}}uP$F;VB}}Ld;vR&0s#m> z00Izz00bZa0SN32f%fXawuaBwOf@#z6GX+4z23PZVZ@a5?HT7f6hS~J<(?PvghYx9 zp@|=BLZie;;nSEg!KK(7KBvSm*dBSYwiz(hH{aMS}+t8KPWa+(2#icvHxc1^*hb}f}_NW?2I3OybB0G00Izz00bZa0SG_<0ua~+f$fR%+y1nH?Rll!oGY~09 literal 0 HcmV?d00001 diff --git a/clients/web/apps/plugins-edge/README.md b/clients/web/apps/plugins-edge/README.md new file mode 100644 index 000000000..0bfe4afe7 --- /dev/null +++ b/clients/web/apps/plugins-edge/README.md @@ -0,0 +1,53 @@ +# Plugins Edge API (Cloudflare Worker + R2) + +This worker serves runtime plugin distribution assets directly from Cloudflare R2. + +It is designed to keep plugin installation metadata (`catalog.json`, `revocations.json`) +and immutable artifacts (`.wasm`, `.sig`, `.pem`) consistent and fast to fetch. + +## Endpoints + +- `GET /catalog.json` +- `GET /revocations.json` +- `GET /artifacts///` + +## Security Defaults + +- Rejects unsupported methods (`405`) +- Rejects malformed artifact paths (`400`) +- Rejects path traversal attempts (`..`, backslashes) +- Applies `X-Content-Type-Options: nosniff` +- Does not expose directory listing routes + +## Cache Strategy + +- `catalog.json`: `public, max-age=300, stale-while-revalidate=60` +- `revocations.json`: `no-store, max-age=0` +- `artifacts/*`: `public, max-age=31536000, immutable` + +## Local Development + +```bash +pnpm --filter @corvus/plugins-edge run dev +``` + +## Deploy + +```bash +pnpm --filter @corvus/plugins-edge run deploy +``` + +## Cloudflare Configuration + +1. Create R2 bucket (example: `corvus-plugins-catalog-prod`). +2. Bind R2 bucket to worker as `PLUGINS_BUCKET`. +3. Configure custom domain route for this worker (`corvus.profiletailors.com`). +4. Upload objects into R2 using these keys: + - `catalog/catalog.json` + - `catalog/revocations.json` + - `artifacts///.wasm` + - `artifacts///.wasm.sig` + - `artifacts///.wasm.pem` + +`CATALOG_OBJECT_KEY` and `REVOCATIONS_OBJECT_KEY` can override defaults via +`wrangler.toml` vars if needed. diff --git a/clients/web/apps/plugins-edge/package.json b/clients/web/apps/plugins-edge/package.json new file mode 100644 index 000000000..28fad1876 --- /dev/null +++ b/clients/web/apps/plugins-edge/package.json @@ -0,0 +1,19 @@ +{ + "name": "@corvus/plugins-edge", + "version": "0.1.8", + "private": true, + "description": "Corvus plugins catalog edge API (Cloudflare Worker + R2)", + "type": "module", + "scripts": { + "build": "tsc --noEmit && mkdir -p dist", + "dev": "wrangler dev --local --port 9797", + "deploy": "wrangler deploy", + "format": "biome format --write src package.json wrangler.toml tsconfig.json README.md", + "check": "biome check src package.json wrangler.toml tsconfig.json README.md", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "typescript": "5.9.3", + "wrangler": "4.67.0" + } +} diff --git a/clients/web/apps/plugins-edge/src/index.ts b/clients/web/apps/plugins-edge/src/index.ts new file mode 100644 index 000000000..b383f5544 --- /dev/null +++ b/clients/web/apps/plugins-edge/src/index.ts @@ -0,0 +1,228 @@ +interface R2Object { + body: ReadableStream | null; + size: number; + httpEtag?: string; + httpMetadata?: { + contentType?: string; + }; +} + +interface R2BucketLike { + get(key: string): Promise; +} + +interface Env { + PLUGINS_BUCKET: R2BucketLike; + CATALOG_OBJECT_KEY?: string; + REVOCATIONS_OBJECT_KEY?: string; +} + +const DEFAULT_CATALOG_KEY = "catalog/catalog.json"; +const DEFAULT_REVOCATIONS_KEY = "catalog/revocations.json"; +const ARTIFACTS_PREFIX = "/artifacts/"; + +const RESPONSE_HEADERS = { + nosniff: "X-Content-Type-Options", + cacheControl: "Cache-Control", + contentType: "Content-Type", + contentLength: "Content-Length", + etag: "ETag", + allowOrigin: "Access-Control-Allow-Origin", + allowMethods: "Access-Control-Allow-Methods", + allowHeaders: "Access-Control-Allow-Headers", +} as const; + +const CACHE_POLICIES = { + catalog: "public, max-age=300, stale-while-revalidate=60", + revocations: "no-store, max-age=0", + artifact: "public, max-age=31536000, immutable", +} as const; + +const METHOD_NOT_ALLOWED_HEADERS = { + Allow: "GET, HEAD, OPTIONS", +}; + +export default { + async fetch(request: Request, env: Env): Promise { + if (request.method === "OPTIONS") { + return new Response(null, { + status: 204, + headers: corsHeaders(), + }); + } + + if (!isSupportedMethod(request.method)) { + return new Response("Method Not Allowed", { + status: 405, + headers: METHOD_NOT_ALLOWED_HEADERS, + }); + } + + const url = new URL(request.url); + const pathname = normalizePathname(url.pathname); + + if (pathname === "/catalog.json") { + return serveObject( + request, + env, + env.CATALOG_OBJECT_KEY || DEFAULT_CATALOG_KEY, + "application/json; charset=utf-8", + CACHE_POLICIES.catalog, + false + ); + } + + if (pathname === "/revocations.json") { + return serveObject( + request, + env, + env.REVOCATIONS_OBJECT_KEY || DEFAULT_REVOCATIONS_KEY, + "application/json; charset=utf-8", + CACHE_POLICIES.revocations, + false + ); + } + + if (pathname.startsWith(ARTIFACTS_PREFIX)) { + const key = safeArtifactKey(pathname); + if (!key) { + return jsonError(400, "Invalid artifact path"); + } + + return serveObject( + request, + env, + key, + guessArtifactContentType(key), + CACHE_POLICIES.artifact, + true + ); + } + + return jsonError(404, "Not found"); + }, +}; + +function isSupportedMethod(method: string): boolean { + return method === "GET" || method === "HEAD"; +} + +function normalizePathname(pathname: string): string { + if (!pathname || pathname === "/") { + return "/"; + } + + const decoded = decodeURIComponent(pathname); + return decoded.replace(/\/{2,}/g, "/"); +} + +function safeArtifactKey(pathname: string): string | null { + if (!pathname.startsWith(ARTIFACTS_PREFIX)) { + return null; + } + + if (pathname.includes("..") || pathname.includes("\\")) { + return null; + } + + const candidate = pathname.slice(1); + if (!candidate) { + return null; + } + + const valid = /^[a-zA-Z0-9._\-/]+$/.test(candidate); + if (!valid) { + return null; + } + + return candidate; +} + +async function serveObject( + request: Request, + env: Env, + key: string, + fallbackContentType: string, + cacheControl: string, + withCors: boolean +): Promise { + const object = await env.PLUGINS_BUCKET.get(key); + if (!object) { + return jsonError(404, "Not found"); + } + + const headers = new Headers(); + headers.set(RESPONSE_HEADERS.nosniff, "nosniff"); + headers.set(RESPONSE_HEADERS.cacheControl, cacheControl); + headers.set( + RESPONSE_HEADERS.contentType, + object.httpMetadata?.contentType || fallbackContentType + ); + headers.set(RESPONSE_HEADERS.contentLength, String(object.size)); + + if (object.httpEtag) { + headers.set(RESPONSE_HEADERS.etag, object.httpEtag); + } + + if (withCors) { + const cors = corsHeaders(); + cors.forEach((value, name) => { + headers.set(name, value); + }); + } + + const ifNoneMatch = request.headers.get("If-None-Match"); + if (ifNoneMatch && object.httpEtag && ifNoneMatch === object.httpEtag) { + return new Response(null, { + status: 304, + headers, + }); + } + + if (request.method === "HEAD") { + return new Response(null, { + status: 200, + headers, + }); + } + + return new Response(object.body, { + status: 200, + headers, + }); +} + +function guessArtifactContentType(key: string): string { + if (key.endsWith(".wasm")) { + return "application/wasm"; + } + if (key.endsWith(".json")) { + return "application/json; charset=utf-8"; + } + if (key.endsWith(".pem")) { + return "application/x-pem-file"; + } + if (key.endsWith(".sig")) { + return "application/octet-stream"; + } + return "application/octet-stream"; +} + +function corsHeaders(): Headers { + const headers = new Headers(); + headers.set(RESPONSE_HEADERS.allowOrigin, "*"); + headers.set(RESPONSE_HEADERS.allowMethods, "GET, HEAD, OPTIONS"); + headers.set(RESPONSE_HEADERS.allowHeaders, "Content-Type, Authorization, If-None-Match"); + return headers; +} + +function jsonError(status: number, message: string): Response { + return new Response(JSON.stringify({ error: message }), { + status, + headers: { + [RESPONSE_HEADERS.contentType]: "application/json; charset=utf-8", + [RESPONSE_HEADERS.nosniff]: "nosniff", + [RESPONSE_HEADERS.cacheControl]: "no-store, max-age=0", + }, + }); +} diff --git a/clients/web/apps/plugins-edge/tsconfig.json b/clients/web/apps/plugins-edge/tsconfig.json new file mode 100644 index 000000000..0369fc434 --- /dev/null +++ b/clients/web/apps/plugins-edge/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "WebWorker"], + "strict": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/clients/web/apps/plugins-edge/wrangler.toml b/clients/web/apps/plugins-edge/wrangler.toml new file mode 100644 index 000000000..e2c5710f9 --- /dev/null +++ b/clients/web/apps/plugins-edge/wrangler.toml @@ -0,0 +1,12 @@ +name = "corvus-plugins-edge" +main = "src/index.ts" +compatibility_date = "2026-02-23" +workers_dev = true + +[vars] +CATALOG_OBJECT_KEY = "catalog/catalog.json" +REVOCATIONS_OBJECT_KEY = "catalog/revocations.json" + +[[r2_buckets]] +binding = "PLUGINS_BUCKET" +bucket_name = "corvus-plugins-catalog-prod" diff --git a/clients/web/apps/plugins/README.md b/clients/web/apps/plugins/README.md index 0178ff2fc..4c7194a0c 100644 --- a/clients/web/apps/plugins/README.md +++ b/clients/web/apps/plugins/README.md @@ -11,6 +11,9 @@ Dedicated site for publishing official runtime plugin metadata: Keep plugin metadata infrastructure separate from the marketing site so deployment, caching, and security controls remain independent. +Production runtime distribution is moving to `apps/plugins-edge` (Cloudflare Worker + R2) +to guarantee atomic publication of catalog and artifact assets. + ## Development ```bash diff --git a/clients/web/build.gradle.kts b/clients/web/build.gradle.kts index bdb3e4ccf..98db2d914 100644 --- a/clients/web/build.gradle.kts +++ b/clients/web/build.gradle.kts @@ -73,6 +73,7 @@ val appConfigs = mapOf( "docs" to WebAppConfig("docs", "dist", 4321), "marketing" to WebAppConfig("marketing", "dist", 9988), "plugins" to WebAppConfig("plugins", "dist", 9990), + "plugins-edge" to WebAppConfig("plugins-edge", "dist", 9797), "chat" to WebAppConfig("chat", "dist", 4323), ) diff --git a/clients/web/package.json b/clients/web/package.json index 06ad99ccd..48bbb66b3 100644 --- a/clients/web/package.json +++ b/clients/web/package.json @@ -9,18 +9,21 @@ "build:docs": "pnpm --filter @corvus/docs run build", "build:marketing": "pnpm --filter @corvus/marketing run build", "build:plugins": "pnpm --filter @corvus/plugins-catalog run build", + "build:plugins-edge": "pnpm --filter @corvus/plugins-edge run typecheck", "build:landing": "pnpm run build:marketing", "build:chat": "pnpm --filter @corvus/chat run build", "dev": "pnpm --filter @corvus/docs run dev", "dev:marketing": "pnpm --filter @corvus/marketing run dev", "dev:plugins": "pnpm --filter @corvus/plugins-catalog run dev", + "dev:plugins-edge": "pnpm --filter @corvus/plugins-edge run dev", "dev:landing": "pnpm run dev:marketing", "dev:chat": "pnpm --filter @corvus/chat run dev", "format": "pnpm -r run format", "check": "pnpm -r run check", "clean": "pnpm -r run clean", "test": "pnpm -r run test", - "test:chat": "pnpm --filter @corvus/chat run test" + "test:chat": "pnpm --filter @corvus/chat run test", + "deploy:plugins-edge": "pnpm --filter @corvus/plugins-edge run deploy" }, "devDependencies": { "@biomejs/biome": "2.4.2", diff --git a/clients/web/pnpm-lock.yaml b/clients/web/pnpm-lock.yaml index f8b81d049..7f6692460 100644 --- a/clients/web/pnpm-lock.yaml +++ b/clients/web/pnpm-lock.yaml @@ -133,6 +133,15 @@ importers: specifier: 6.4.1 version: 6.4.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + apps/plugins-edge: + devDependencies: + typescript: + specifier: 5.9.3 + version: 5.9.3 + wrangler: + specifier: 4.67.0 + version: 4.67.0 + packages/shared: {} packages: @@ -278,6 +287,53 @@ packages: resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==} engines: {node: '>=18'} + '@cloudflare/kv-asset-handler@0.4.2': + resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} + engines: {node: '>=18.0.0'} + + '@cloudflare/unenv-preset@2.14.0': + resolution: {integrity: sha512-XKAkWhi1nBdNsSEoNG9nkcbyvfUrSjSf+VYVPfOto3gLTZVc3F4g6RASCMh6IixBKCG2yDgZKQIHGKtjcnLnKg==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: ^1.20260218.0 + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/workerd-darwin-64@1.20260219.0': + resolution: {integrity: sha512-k+xM+swQBQnkrvwobjRPxyeYwjLSludJusR0PqeHe+h6X9QIRGgw3s1AO38lXQsqzMSgG5709oOXSF19NKVVaQ==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20260219.0': + resolution: {integrity: sha512-EyfQdsG1KcIVAf4qndT00LZly7sLFm1VxMWHBvOFB/EVYF2sE5HZ0dPbe+yrax5p3eS0oLZthR8ynhz4UulMUQ==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20260219.0': + resolution: {integrity: sha512-N0UHXILYYa6htFO/uC92uAqusvynbSbOcHcrVXMKqP9Jy7eqXGMovyKIrNgzYnKIszNB+0lfUYdGI3Wci07LuA==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20260219.0': + resolution: {integrity: sha512-835pjQ9uuAtwPBOAkPf+oxH3mNE5mqWuE3H7hJsul7WZsRD2FDcariyoT2AW6xyOePILrn4uMnmG1KGc9m/8Pg==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20260219.0': + resolution: {integrity: sha512-i7qcuOsuAxqqn1n5Ar3Rh1dHUL9vNmpF9FcdMTT84jIrdm5UNrPZz5grJthPmpB9LTcreT9iiP6qKbzGjnCwPA==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@ctrl/tinycolor@4.2.0': resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} engines: {node: '>=14'} @@ -818,6 +874,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} @@ -864,6 +923,15 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + '@qwik.dev/partytown@0.11.2': resolution: {integrity: sha512-795y49CqBiKiwKAD+QBZlzlqEK275hVcazZ7wBPSfgC23L+vWuA7PJmMpgxojOucZHzYi5rAAQ+IP1I3BKVZxw==} engines: {node: '>=18.0.0'} @@ -1040,6 +1108,13 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} + engines: {node: '>=18'} + + '@speed-highlight/core@1.2.14': + resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==} + '@tailwindcss/node@4.1.18': resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} @@ -1431,6 +1506,9 @@ packages: bcp-47@2.1.0: resolution: {integrity: sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==} + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -1679,6 +1757,9 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -2243,6 +2324,11 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + miniflare@4.20260219.0: + resolution: {integrity: sha512-EIb5wXbWUnnC60XU2aiFOPNd4fgTXzECkwRSOXZ1vdcY9WZaEE9rVf+h+Apw+WkOHRkp3Dr9/ZhQ5y1R+9iZ4Q==} + engines: {node: '>=18.0.0'} + hasBin: true + minimatch@9.0.1: resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} engines: {node: '>=16 || 14 >=14.17'} @@ -2349,6 +2435,9 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -2606,6 +2695,10 @@ packages: style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + svgo@4.0.0: resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} engines: {node: '>=16'} @@ -2693,6 +2786,13 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici@7.18.2: + resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==} + engines: {node: '>=20.18.1'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -3064,6 +3164,21 @@ packages: resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} engines: {node: '>=18'} + workerd@1.20260219.0: + resolution: {integrity: sha512-l4U4iT5H8jNV6+EK23ExnUV2z6JvqQtQPrT8XCm4G8RpwC9EPpYTOO9s/ImMPJKe1WSbQUQoJ4k8Nd83fz8skQ==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.67.0: + resolution: {integrity: sha512-58OoVth7bqm0nqsRgcI67gHbpp0IfR1JIBqDY0XR1FzRu9Qkjn6v2iJAdFf82QcVBFhaMBYQi88WqYGswq5wlQ==} + engines: {node: '>=20.0.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20260219.0 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -3076,6 +3191,18 @@ packages: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.19.0: resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} engines: {node: '>=10.0.0'} @@ -3129,6 +3256,12 @@ packages: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + zod-to-json-schema@3.25.1: resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: @@ -3354,6 +3487,33 @@ snapshots: dependencies: fontkitten: 1.0.2 + '@cloudflare/kv-asset-handler@0.4.2': {} + + '@cloudflare/unenv-preset@2.14.0(unenv@2.0.0-rc.24)(workerd@1.20260219.0)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260219.0 + + '@cloudflare/workerd-darwin-64@1.20260219.0': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20260219.0': + optional: true + + '@cloudflare/workerd-linux-64@1.20260219.0': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20260219.0': + optional: true + + '@cloudflare/workerd-windows-64@1.20260219.0': + optional: true + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@ctrl/tinycolor@4.2.0': {} '@emmetio/abbreviation@2.3.3': @@ -3703,6 +3863,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@mdx-js/mdx@3.1.1': dependencies: '@types/estree': 1.0.8 @@ -3760,6 +3925,18 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@poppinss/colors@4.1.6': + dependencies: + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 + + '@poppinss/exception@1.2.3': {} + '@qwik.dev/partytown@0.11.2': dependencies: dotenv: 16.6.1 @@ -3882,6 +4059,10 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@sindresorhus/is@7.2.0': {} + + '@speed-highlight/core@1.2.14': {} + '@tailwindcss/node@4.1.18': dependencies: '@jridgewell/remapping': 2.3.5 @@ -4389,6 +4570,8 @@ snapshots: is-alphanumerical: 2.0.1 is-decimal: 2.0.1 + blake3-wasm@2.1.5: {} + boolbase@1.0.0: {} boxen@8.0.1: @@ -4604,6 +4787,8 @@ snapshots: entities@7.0.1: {} + error-stack-parser-es@1.0.5: {} + es-module-lexer@1.7.0: {} esast-util-from-estree@2.0.0: @@ -5603,6 +5788,18 @@ snapshots: transitivePeerDependencies: - supports-color + miniflare@4.20260219.0: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.34.5 + undici: 7.18.2 + workerd: 1.20260219.0 + ws: 8.18.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + minimatch@9.0.1: dependencies: brace-expansion: 2.0.2 @@ -5713,6 +5910,8 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-to-regexp@6.3.0: {} + pathe@2.0.3: {} pathval@2.0.1: {} @@ -6084,6 +6283,8 @@ snapshots: dependencies: inline-style-parser: 0.2.7 + supports-color@10.2.2: {} + svgo@4.0.0: dependencies: commander: 11.1.0 @@ -6146,6 +6347,12 @@ snapshots: undici-types@7.16.0: {} + undici@7.18.2: {} + + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -6475,6 +6682,30 @@ snapshots: dependencies: string-width: 7.2.0 + workerd@1.20260219.0: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260219.0 + '@cloudflare/workerd-darwin-arm64': 1.20260219.0 + '@cloudflare/workerd-linux-64': 1.20260219.0 + '@cloudflare/workerd-linux-arm64': 1.20260219.0 + '@cloudflare/workerd-windows-64': 1.20260219.0 + + wrangler@4.67.0: + dependencies: + '@cloudflare/kv-asset-handler': 0.4.2 + '@cloudflare/unenv-preset': 2.14.0(unenv@2.0.0-rc.24)(workerd@1.20260219.0) + blake3-wasm: 2.1.5 + esbuild: 0.27.3 + miniflare: 4.20260219.0 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260219.0 + optionalDependencies: + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -6493,6 +6724,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.2 + ws@8.18.0: {} + ws@8.19.0: {} xxhash-wasm@1.1.0: {} @@ -6538,6 +6771,19 @@ snapshots: yoctocolors@2.1.2: {} + youch-core@0.3.3: + dependencies: + '@poppinss/exception': 1.2.3 + error-stack-parser-es: 1.0.5 + + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.14 + cookie: 1.1.1 + youch-core: 0.3.3 + zod-to-json-schema@3.25.1(zod@3.25.76): dependencies: zod: 3.25.76 From 24a6dbf92bf058ebf12fa53b4b6eaa20873351c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yuniel=20Acosta=20P=C3=A9rez?= <33158051+yacosta738@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:22:25 +0100 Subject: [PATCH 2/3] chore(plugins): ignore local wrangler state artifacts --- clients/web/apps/plugins-edge/.gitignore | 3 ++ ...c67fbb86c62b4fe5b08ee0d7e80000019c8f1f1c75 | 4 --- ...120b25b7313ad7c500cc8558930000019c8f1f2a41 | 12 ------- ...8c6aad029d038feba23955a6ba0000019c8f1f30e5 | 12 ------- ...c2482b2c17d21412d300257df80000019c8f1f237b | Bin 99 -> 0 bytes ...6209ad92e53b09b33dce6695f60000019c8f1f15d2 | 34 ------------------ ...ae8d284994813668f4d82fa5bad50f689d5.sqlite | Bin 4096 -> 0 bytes ...284994813668f4d82fa5bad50f689d5.sqlite-shm | Bin 32768 -> 0 bytes ...284994813668f4d82fa5bad50f689d5.sqlite-wal | Bin 70072 -> 0 bytes 9 files changed, 3 insertions(+), 62 deletions(-) create mode 100644 clients/web/apps/plugins-edge/.gitignore delete mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/25ac6cd46124c1845757a0b42f3bbb3071a951c67fbb86c62b4fe5b08ee0d7e80000019c8f1f1c75 delete mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/70453d75c6479044f5bbc8e9d4da343a666266120b25b7313ad7c500cc8558930000019c8f1f2a41 delete mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/9ed1a0c98364c0785cb2f4cd7a4403361176f08c6aad029d038feba23955a6ba0000019c8f1f30e5 delete mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/c7fb9782885d0870868816d9c4fd8d5b3d01f4c2482b2c17d21412d300257df80000019c8f1f237b delete mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/ff350587ab323266bbd980dffafaee3a1e079d6209ad92e53b09b33dce6695f60000019c8f1f15d2 delete mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/miniflare-R2BucketObject/cd378746ea92fc3d2e2cb85bd872aae8d284994813668f4d82fa5bad50f689d5.sqlite delete mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/miniflare-R2BucketObject/cd378746ea92fc3d2e2cb85bd872aae8d284994813668f4d82fa5bad50f689d5.sqlite-shm delete mode 100644 clients/web/apps/plugins-edge/.wrangler/state/v3/r2/miniflare-R2BucketObject/cd378746ea92fc3d2e2cb85bd872aae8d284994813668f4d82fa5bad50f689d5.sqlite-wal diff --git a/clients/web/apps/plugins-edge/.gitignore b/clients/web/apps/plugins-edge/.gitignore new file mode 100644 index 000000000..cfd84630a --- /dev/null +++ b/clients/web/apps/plugins-edge/.gitignore @@ -0,0 +1,3 @@ +.wrangler/ +dist/ +node_modules/ diff --git a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/25ac6cd46124c1845757a0b42f3bbb3071a951c67fbb86c62b4fe5b08ee0d7e80000019c8f1f1c75 b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/25ac6cd46124c1845757a0b42f3bbb3071a951c67fbb86c62b4fe5b08ee0d7e80000019c8f1f1c75 deleted file mode 100644 index 69ffa87c8..000000000 --- a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/25ac6cd46124c1845757a0b42f3bbb3071a951c67fbb86c62b4fe5b08ee0d7e80000019c8f1f1c75 +++ /dev/null @@ -1,4 +0,0 @@ -{ - "updated_at": "2026-02-22T15:44:45Z", - "revoked": [] -} diff --git a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/70453d75c6479044f5bbc8e9d4da343a666266120b25b7313ad7c500cc8558930000019c8f1f2a41 b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/70453d75c6479044f5bbc8e9d4da343a666266120b25b7313ad7c500cc8558930000019c8f1f2a41 deleted file mode 100644 index dde6414e4..000000000 --- a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/70453d75c6479044f5bbc8e9d4da343a666266120b25b7313ad7c500cc8558930000019c8f1f2a41 +++ /dev/null @@ -1,12 +0,0 @@ - Corvus Plugins Catalog

Corvus runtime metadata

Official plugin distribution endpoints

-This host serves machine-readable metadata used by the Corvus runtime to discover and - verify plugins. -

Endpoints

Catalog /catalog.json

Revocations /revocations.json

Immutable artifact /artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm

Immutable manifest /artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json

-Runtime defaults point to these URLs and treat them as official metadata sources. -

-The artifact and manifest paths below are immutable versioned examples. For the latest - release metadata, always use /catalog.json. -

Quick validation

Use these commands after deployment:

curl -fsSL https://corvus.profiletailors.com/catalog.json

curl -fsSL https://corvus.profiletailors.com/revocations.json

curl -fsSL https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm -o memory.surreal.graphs.wasm

curl -fsSL https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json

-Source of truth lives in workflow output from plugin publishing. -
\ No newline at end of file diff --git a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/9ed1a0c98364c0785cb2f4cd7a4403361176f08c6aad029d038feba23955a6ba0000019c8f1f30e5 b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/9ed1a0c98364c0785cb2f4cd7a4403361176f08c6aad029d038feba23955a6ba0000019c8f1f30e5 deleted file mode 100644 index dde6414e4..000000000 --- a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/9ed1a0c98364c0785cb2f4cd7a4403361176f08c6aad029d038feba23955a6ba0000019c8f1f30e5 +++ /dev/null @@ -1,12 +0,0 @@ - Corvus Plugins Catalog

Corvus runtime metadata

Official plugin distribution endpoints

-This host serves machine-readable metadata used by the Corvus runtime to discover and - verify plugins. -

Endpoints

Catalog /catalog.json

Revocations /revocations.json

Immutable artifact /artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm

Immutable manifest /artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json

-Runtime defaults point to these URLs and treat them as official metadata sources. -

-The artifact and manifest paths below are immutable versioned examples. For the latest - release metadata, always use /catalog.json. -

Quick validation

Use these commands after deployment:

curl -fsSL https://corvus.profiletailors.com/catalog.json

curl -fsSL https://corvus.profiletailors.com/revocations.json

curl -fsSL https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/memory.surreal.graphs.wasm -o memory.surreal.graphs.wasm

curl -fsSL https://corvus.profiletailors.com/artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json

-Source of truth lives in workflow output from plugin publishing. -
\ No newline at end of file diff --git a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/c7fb9782885d0870868816d9c4fd8d5b3d01f4c2482b2c17d21412d300257df80000019c8f1f237b b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/corvus-plugins-catalog-prod/blobs/c7fb9782885d0870868816d9c4fd8d5b3d01f4c2482b2c17d21412d300257df80000019c8f1f237b deleted file mode 100644 index 02b7bdab6c27cbba96ea3f4fac30f6bf62e0fb2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99 zcmXZMyA8rn5Jkar_kA#|gth}}aVI7qd7U}9o$P*7lCU|@t|AVoG{WYDXN;00+HAlr;ljiVtj n8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O6ovo**AfQj diff --git a/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/miniflare-R2BucketObject/cd378746ea92fc3d2e2cb85bd872aae8d284994813668f4d82fa5bad50f689d5.sqlite-shm b/clients/web/apps/plugins-edge/.wrangler/state/v3/r2/miniflare-R2BucketObject/cd378746ea92fc3d2e2cb85bd872aae8d284994813668f4d82fa5bad50f689d5.sqlite-shm deleted file mode 100644 index e4f01fe6f2c0b2ce014d408ef6f472454a04660d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI)y-fo_5C+h(5AzQ`Bmxsq0wqv@iAiXHm;kCEBqE?I3a}iy221t~HWHadXzlx? z?^ar^Ztq?JGnwB-Ww2>~mFrRV}$eZRl{e!F>Geodcl7v1=||C~+!c|KqDlW{&* zf3n?<+OO38!)%v5$d0my+4`2>{atGw6#@hZ5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7csfxQx_sS`q= zO@Uz?$7!6$W%O+V`0IuRx;TlmxX1}aHSi zE>T2GduraS6Y*$_bwHerc+P;`62JE z=j^QX=J`)Q_VxGQ8h+x9V_%uCG)+FK)vKbJr*`X1CUrQUx9eu?ZOCoYEm`}R+jwFh z*I#eb^L~?=)XdY5kBmXvqwie*{Eju zq&d@BdR2uwiyKpR`E-8HRd#V*s<==r6=&z?m%1|xbDiCNd9iqOc5x~HNMR}eLERt7 zZ>sk6@N{8taAJw3Q3T3a)%%S$#?9fCQrq56IY4L6Y zY~8LqGk1el@2=9n-8WUQQ{SH3#wVgP`$V(3R<7DM(V{8(v8z_2S+k9DgH;+$VUG(- z#f2sNIdROhkIt9!r1xnVo-XDFC!Ppzj&XzID#-5afpbxMXk!2V^s`})vfK0yusfN# zhON7xi`B!phH3L>x+8ST)EkxBYCoI*;#1zKhSAsga%_AF8vS;6Yi%_=%`$@%#r-#G zg$>Dd`|cBWEdR0YZ>u}b@AVf<-FyE#Z?t}MWz@~=bS1bc6bL{70uX=z1Rwwb2tWV= z5P$##cBg>rBk1)DT%5ez%6;$m%foIa`URW<3Irek0SG_<0uX=z1Rwwb2tZ&r3#^|5 zuyKAt^T<~}^QnK`^JC--?B*s%-5>w~2tWV=5P$##AOHafKmYABh~$QRhnO^&)j z00Izz00bZa0SG_<0uX=z1bPDQ+5$rx{Q`HKy086*7v8;se1V=1CLjO-2tWV=5P$## zAOHafKmY=}Szycc2>$)sUq2hYeCC|X7f6pEPT7AH2tWV=5P$##AOHafKmY;|fB*!p zBhWsS9%1bVk7kYz?A^Op+3P~C)Q%tMKF%yRYO6^?8DUWW7_wG;37#Fi8%EY3q$F7iR1|jf<($# zTbEq6VFUcSKUW0yZkvw!=A z%NIzG|0QMrQ6K;T2tWV=5P$##AOHafKmY;|_=pQkWZ514dM#|Vw&V*0jhTnC#ktVs z3uMO5+I#_fhXMfzKmY;|fB*y_009U<00I#BUkJ1hXGRX$V0(H?u)S_h)$Ac`rMB8w z54ihGD6VwCJwH%h6tIwmg2=#6sFaeD&=WE96c25INB6G?7k_>rVGq;6lF(Li%)1N)kCV#Dnb)luL!j9OD`9%6mhSYPXlq*JvQW0ZOY(wcE z3=r>&z%GyM$nh~FoFq1OPGc$rl_K^6I}W@j{PDR^P+tf;a=eY)^Mo5}?D_B>i33HG z*yqN@(C?q^#?X;3fbj)x-}ML{{=(c3F1`D^C6_Od8$ams1r8!(;5PmEVYd)~00bZa z0SG_<0uX=z1R$`J1&X=$zFUs+yN;^d4$2pJ^(W8GUp{}}uP$F;VB}}Ld;vR&0s#m> z00Izz00bZa0SN32f%fXawuaBwOf@#z6GX+4z23PZVZ@a5?HT7f6hS~J<(?PvghYx9 zp@|=BLZie;;nSEg!KK(7KBvSm*dBSYwiz(hH{aMS}+t8KPWa+(2#icvHxc1^*hb}f}_NW?2I3OybB0G00Izz00bZa0SG_<0ua~+f$fR%+y1nH?Rll!oGY~09 From 36d6bd7a095b33bf98bfcb218527fce1a81a5698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yuniel=20Acosta=20P=C3=A9rez?= <33158051+yacosta738@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:42:48 +0100 Subject: [PATCH 3/3] fix(plugins): harden edge worker responses and host migration --- clients/agent-runtime/src/config/schema.rs | 47 ++++++++++++++++++---- clients/web/apps/plugins-edge/README.md | 2 +- clients/web/apps/plugins-edge/src/index.ts | 28 ++++++++----- clients/web/apps/plugins/README.md | 2 +- 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/clients/agent-runtime/src/config/schema.rs b/clients/agent-runtime/src/config/schema.rs index 20c64d4e1..d13826d58 100644 --- a/clients/agent-runtime/src/config/schema.rs +++ b/clients/agent-runtime/src/config/schema.rs @@ -2220,12 +2220,20 @@ fn env_override_optional(var_name: &str, target: &mut Option) { fn migrate_deprecated_plugin_registry_urls(config: &mut Config) -> bool { const OLD_PLUGIN_HOST: &str = "plugins.corvus.ai"; + const LEGACY_PLUGIN_HOST: &str = "corvus.profiletailors.com"; const NEW_PLUGIN_HOST: &str = "plugins.corvus.profiletailors.com"; - fn migrate_url_host(raw_url: &str, old_host: &str, new_host: &str) -> Option { + fn host_matches(candidate: &str, host: &str) -> bool { + candidate == host || candidate.ends_with(&format!(".{host}")) + } + + fn migrate_url_host(raw_url: &str, old_hosts: &[&str], new_host: &str) -> Option { let mut parsed = Url::parse(raw_url).ok()?; let host = parsed.host_str()?; - if host == old_host || host.ends_with(&format!(".{old_host}")) { + if old_hosts + .iter() + .any(|old_host| host_matches(host, old_host)) + { parsed.set_host(Some(new_host)).ok()?; return Some(parsed.to_string()); } @@ -2234,18 +2242,17 @@ fn migrate_deprecated_plugin_registry_urls(config: &mut Config) -> bool { let mut changed = false; let mut revocation_changed = false; + let old_hosts = [OLD_PLUGIN_HOST, LEGACY_PLUGIN_HOST]; for source in &mut config.plugins.sources { - if let Some(migrated) = migrate_url_host(&source.url, OLD_PLUGIN_HOST, NEW_PLUGIN_HOST) { + if let Some(migrated) = migrate_url_host(&source.url, &old_hosts, NEW_PLUGIN_HOST) { source.url = migrated; changed = true; } } for source_url in &mut config.plugins.revocation.source_urls { - if let Some(migrated) = - migrate_url_host(source_url.as_str(), OLD_PLUGIN_HOST, NEW_PLUGIN_HOST) - { + if let Some(migrated) = migrate_url_host(source_url.as_str(), &old_hosts, NEW_PLUGIN_HOST) { *source_url = migrated; changed = true; revocation_changed = true; @@ -2336,8 +2343,9 @@ impl Config { if migrate_deprecated_plugin_registry_urls(&mut config) { tracing::warn!( - "Migrated deprecated plugin registry host entries from 'plugins.corvus.ai' \ - to 'plugins.corvus.profiletailors.com' in plugins.sources and \ + "Migrated deprecated plugin registry host entries from \ + 'plugins.corvus.ai' and 'corvus.profiletailors.com' to \ + 'plugins.corvus.profiletailors.com' in plugins.sources and \ plugins.revocation.source_urls" ); if let Err(error) = config.save() { @@ -2973,6 +2981,29 @@ default_temperature = 0.7 ); } + #[test] + fn migrate_deprecated_plugin_registry_urls_rewrites_legacy_profiletailors_host() { + let mut config = Config::default(); + config.plugins.sources = vec![PluginSourceConfig { + name: "official".to_string(), + url: "https://corvus.profiletailors.com/catalog.json".to_string(), + plugin_identity_regex: None, + }]; + config.plugins.revocation.source_urls = + vec!["https://corvus.profiletailors.com/revocations.json".to_string()]; + + let changed = migrate_deprecated_plugin_registry_urls(&mut config); + assert!(changed); + assert_eq!( + config.plugins.sources[0].url, + "https://plugins.corvus.profiletailors.com/catalog.json" + ); + assert_eq!( + config.plugins.revocation.source_urls, + vec!["https://plugins.corvus.profiletailors.com/revocations.json".to_string()] + ); + } + #[test] fn surreal_memory_config_debug_redacts_sensitive_fields() { let cfg = SurrealMemoryConfig { diff --git a/clients/web/apps/plugins-edge/README.md b/clients/web/apps/plugins-edge/README.md index 0bfe4afe7..3cc509df7 100644 --- a/clients/web/apps/plugins-edge/README.md +++ b/clients/web/apps/plugins-edge/README.md @@ -41,7 +41,7 @@ pnpm --filter @corvus/plugins-edge run deploy 1. Create R2 bucket (example: `corvus-plugins-catalog-prod`). 2. Bind R2 bucket to worker as `PLUGINS_BUCKET`. -3. Configure custom domain route for this worker (`corvus.profiletailors.com`). +3. Configure custom domain route for this worker (`plugins.corvus.profiletailors.com`). 4. Upload objects into R2 using these keys: - `catalog/catalog.json` - `catalog/revocations.json` diff --git a/clients/web/apps/plugins-edge/src/index.ts b/clients/web/apps/plugins-edge/src/index.ts index b383f5544..f02c85b53 100644 --- a/clients/web/apps/plugins-edge/src/index.ts +++ b/clients/web/apps/plugins-edge/src/index.ts @@ -38,9 +38,7 @@ const CACHE_POLICIES = { artifact: "public, max-age=31536000, immutable", } as const; -const METHOD_NOT_ALLOWED_HEADERS = { - Allow: "GET, HEAD, OPTIONS", -}; +const ALLOW_METHODS_VALUE = "GET, HEAD, OPTIONS"; export default { async fetch(request: Request, env: Env): Promise { @@ -52,9 +50,11 @@ export default { } if (!isSupportedMethod(request.method)) { + const headers = corsHeaders(); + headers.set("Allow", ALLOW_METHODS_VALUE); return new Response("Method Not Allowed", { status: 405, - headers: METHOD_NOT_ALLOWED_HEADERS, + headers, }); } @@ -68,7 +68,7 @@ export default { env.CATALOG_OBJECT_KEY || DEFAULT_CATALOG_KEY, "application/json; charset=utf-8", CACHE_POLICIES.catalog, - false + true ); } @@ -79,7 +79,7 @@ export default { env.REVOCATIONS_OBJECT_KEY || DEFAULT_REVOCATIONS_KEY, "application/json; charset=utf-8", CACHE_POLICIES.revocations, - false + true ); } @@ -112,8 +112,15 @@ function normalizePathname(pathname: string): string { return "/"; } - const decoded = decodeURIComponent(pathname); - return decoded.replace(/\/{2,}/g, "/"); + try { + const decoded = decodeURIComponent(pathname); + return decoded.replace(/\/{2,}/g, "/"); + } catch (error) { + if (error instanceof URIError) { + return pathname.replace(/\/{2,}/g, "/"); + } + throw error; + } } function safeArtifactKey(pathname: string): string | null { @@ -173,9 +180,12 @@ async function serveObject( const ifNoneMatch = request.headers.get("If-None-Match"); if (ifNoneMatch && object.httpEtag && ifNoneMatch === object.httpEtag) { + const responseHeaders = new Headers(headers); + responseHeaders.delete(RESPONSE_HEADERS.contentLength); + responseHeaders.delete(RESPONSE_HEADERS.contentType); return new Response(null, { status: 304, - headers, + headers: responseHeaders, }); } diff --git a/clients/web/apps/plugins/README.md b/clients/web/apps/plugins/README.md index 4c7194a0c..e57e7d6df 100644 --- a/clients/web/apps/plugins/README.md +++ b/clients/web/apps/plugins/README.md @@ -30,7 +30,7 @@ pnpm --filter @corvus/plugins-catalog run check ## Domain and Port Configuration - Dev default: `http://localhost:9990` -- Prod default: `https://corvus.profiletailors.com` +- Prod default: `https://plugins.corvus.profiletailors.com` Supported variable: