From 06a86e9d67f7ad8482ab9b4788d3f17069385003 Mon Sep 17 00:00:00 2001 From: Tovli Date: Tue, 23 Jun 2026 12:56:36 +0300 Subject: [PATCH 1/3] Update packaged READMEs for local Qwen usage --- .github/workflows/release.yml | 16 +---- Makefile | 8 +-- crates/adapters/el-cloud/README.md | 4 ++ crates/adapters/el-engine-candle/README.md | 15 +++-- crates/adapters/el-ffi/README.md | 63 +++++++++++++++++-- .../adapters/el-grammar-llguidance/README.md | 7 ++- .../adapters/el-provenance-ed25519/README.md | 10 ++- crates/el-core/README.md | 11 +++- crates/el-grammar/README.md | 4 ++ crates/el-memory/README.md | 7 +++ crates/el-provenance/README.md | 12 +++- crates/el-runtime/README.md | 5 ++ crates/el-safety/README.md | 9 ++- crates/el-telemetry/README.md | 5 ++ packaging/flutter/README.md | 24 +++++++ packaging/npm/README.md | 46 ++++++++++++++ 16 files changed, 209 insertions(+), 37 deletions(-) create mode 100644 packaging/flutter/README.md create mode 100644 packaging/npm/README.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8186034..31d5dbe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -276,20 +276,8 @@ jobs: files: ["src/", "android/", "ios/", "README.md"], license: "Apache-2.0" }' > assembly/package.json - - name: Write README.md - run: | - cat > assembly/README.md <<'EOF' - # edge-intelligence-sdk - - Generated Web and React Native bindings for the Edge Intelligence SDK. - - This package contains the WASM/TypeScript browser surface, React Native - TypeScript bindings, and native Android/iOS libraries assembled by the - release pipeline. - - Source, crate documentation, and release notes live in the Edge - Intelligence repository. - EOF + - name: Copy README.md + run: cp packaging/npm/README.md assembly/README.md - name: Assert npm package ships README.md run: bash scripts/assert-release-readmes.sh npm assembly - uses: actions/upload-artifact@v4 diff --git a/Makefile b/Makefile index 2031b88..086c46b 100644 --- a/Makefile +++ b/Makefile @@ -83,13 +83,7 @@ codegen-flutter: ' flutter_rust_bridge: ^$(FRB_VERSION)' \ > $(OUT)/flutter/pubspec.yaml @cp LICENSE $(OUT)/flutter/LICENSE - @printf '%s\n' \ - '# edge_intelligence' \ - '' \ - 'Generated Flutter bindings for the Edge Intelligence SDK.' \ - '' \ - 'The package contains Dart bindings generated with flutter_rust_bridge and native libraries assembled by the release pipeline.' \ - > $(OUT)/flutter/README.md + @cp packaging/flutter/README.md $(OUT)/flutter/README.md @printf '%s\n' \ '# Changelog' \ '' \ diff --git a/crates/adapters/el-cloud/README.md b/crates/adapters/el-cloud/README.md index 82fd053..8949bfa 100644 --- a/crates/adapters/el-cloud/README.md +++ b/crates/adapters/el-cloud/README.md @@ -46,11 +46,15 @@ a clean completion), and errors carry only parse category/position/size — ## Usage +Use `CloudProvider` only as an explicit fallback or comparison path; the local +Qwen2.5 0.5B GGUF remains on device and is not sent to the provider. + ```rust use el_core::{ChatMessage, ChatRequest, CredentialRef, LlmProvider}; use el_cloud::CloudProvider; let provider = CloudProvider::new(); +let _local_qwen_model = "models/qwen2.5-0.5b-instruct-q4_k_m.gguf"; // The credential is resolved at runtime from the platform keystore — never embedded. let req = ChatRequest::new("openai/gpt-4o", vec![ChatMessage::user("Hello!")]) diff --git a/crates/adapters/el-engine-candle/README.md b/crates/adapters/el-engine-candle/README.md index e8f2527..7cc6bb0 100644 --- a/crates/adapters/el-engine-candle/README.md +++ b/crates/adapters/el-engine-candle/README.md @@ -30,17 +30,22 @@ Mismatched shapes are rejected at load time, not silently at inference. ## Usage +This is the direct Rust SDK path for a local Qwen2.5 0.5B instruct model. + ```rust use el_core::{ChatMessage, ChatRequest, LlmProvider}; use el_engine_candle::QwenChatProvider; +const QWEN_0_5B_GGUF: &str = "models/qwen2.5-0.5b-instruct-q4_k_m.gguf"; +const QWEN_0_5B_TOKENIZER: &str = "models/qwen2.5-0.5b-instruct.tokenizer.json"; + // Real on-device chat: a local Qwen2 GGUF + its tokenizer (no network egress). -let provider = QwenChatProvider::from_paths( - "models/qwen2.5-0.5b-instruct-q4_k_m.gguf", - "models/qwen2.5-0.5b-instruct.tokenizer.json", -)?; +let provider = QwenChatProvider::from_paths(QWEN_0_5B_GGUF, QWEN_0_5B_TOKENIZER)?; -let req = ChatRequest::new("local", vec![ChatMessage::user("What is the capital of France?")]) +let req = ChatRequest::new( + "local/qwen2.5-0.5b-instruct-q4_k_m", + vec![ChatMessage::user("Summarize edge inference in one sentence.")], +) .with_max_tokens(128); let reply = provider.chat(&req)?; println!("{}", reply.content); diff --git a/crates/adapters/el-ffi/README.md b/crates/adapters/el-ffi/README.md index 1172d87..0e9f17f 100644 --- a/crates/adapters/el-ffi/README.md +++ b/crates/adapters/el-ffi/README.md @@ -34,19 +34,74 @@ No `unsafe` (`#![forbid(unsafe_code)]`). The crate is `cdylib` + `staticlib` + ## Usage (Rust side) +Use the Qwen2.5 0.5B GGUF as the local model URI when constructing the facade. +Native package bindings use the same model path after copying the file into app +storage. For full Rust ChatML/tokenizer control, use +`el_engine_candle::QwenChatProvider` directly. + ```rust use el_ffi::EdgeLlm; -// Empty path → deterministic toy model; exercises the full binding layer. -let sdk = EdgeLlm::local(String::new())?; -let reply = sdk.ask("hello".into())?; +const QWEN_0_5B_GGUF: &str = "models/qwen2.5-0.5b-instruct-q4_k_m.gguf"; + +let sdk = EdgeLlm::local(QWEN_0_5B_GGUF.into())?; +let reply = sdk.ask("Summarize edge inference in one sentence.".into())?; assert!(!reply.is_empty()); // Streaming (Flutter / closure form): -sdk.ask_stream("hi".into(), |fragment| print!("{fragment}"))?; +sdk.ask_stream("Give me two deployment tips.".into(), |fragment| print!("{fragment}"))?; # Ok::<(), el_ffi::SdkError>(()) ``` +## Usage (npm / web) + +The npm package exposes the wasm-bindgen browser surface. The local web path +currently exercises the generated API shape while Candle-on-wasm is being wired; +native React Native builds load the GGUF through the same package. + +```ts +import init, { EdgeLlm } from "edge-intelligence-sdk"; + +await init(); + +const qwen05b = "/models/qwen2.5-0.5b-instruct-q4_k_m.gguf"; +const sdk = new EdgeLlm(qwen05b); +const reply = sdk.ask_wasm("Summarize edge inference in one sentence."); + +console.log(reply); +``` + +## Usage (React Native) + +```ts +import { EdgeLlm } from "edge-intelligence-sdk"; + +const qwen05b = "/data/user/0/com.example.app/files/models/qwen2.5-0.5b-instruct-q4_k_m.gguf"; +const sdk = EdgeLlm.local(qwen05b); + +const reply = sdk.ask("Summarize edge inference in one sentence."); +let streamed = ""; +sdk.ask_stream_cb("Give me two deployment tips.", { + on_token(token) { + streamed += token; + }, +}); +``` + +## Usage (pub.dev / Flutter) + +```dart +import "package:edge_intelligence/edge_intelligence.dart"; + +final qwen05b = "/path/to/app/models/qwen2.5-0.5b-instruct-q4_k_m.gguf"; +final sdk = await EdgeLlm.local(qwen05b); + +final reply = await sdk.ask("Summarize edge inference in one sentence."); +await for (final token in sdk.askStream("Give me two deployment tips.")) { + stdout.write(token); +} +``` + ## Building the bindings The Rust binding *surfaces* compile on the host; the cross-target builds and diff --git a/crates/adapters/el-grammar-llguidance/README.md b/crates/adapters/el-grammar-llguidance/README.md index b280cc6..b8f44d9 100644 --- a/crates/adapters/el-grammar-llguidance/README.md +++ b/crates/adapters/el-grammar-llguidance/README.md @@ -25,11 +25,16 @@ The HuggingFace `tokenizers::Tokenizer` is bridged to llguidance's `TokEnv` via ## Usage +Use the same Qwen2.5 0.5B tokenizer that the chat provider uses, so schema +masking and model decoding agree on token ids. + ```rust use el_grammar_llguidance::LlguidanceMasker; use el_runtime::GrammarMasker; -let tokenizer = tokenizers::Tokenizer::from_file("tokenizer.json").unwrap(); +let tokenizer = tokenizers::Tokenizer::from_file( + "models/qwen2.5-0.5b-instruct.tokenizer.json", +).unwrap(); let masker = LlguidanceMasker::from_tokenizer(&tokenizer, r#"{"type":"integer"}"#)?; // In the decode loop, wired into the session's Ports: diff --git a/crates/adapters/el-provenance-ed25519/README.md b/crates/adapters/el-provenance-ed25519/README.md index 489f2d8..772214b 100644 --- a/crates/adapters/el-provenance-ed25519/README.md +++ b/crates/adapters/el-provenance-ed25519/README.md @@ -21,16 +21,24 @@ do about it (issue a `LoadPermit`, or hard-stop). Pure-Rust dependency tree, no ## Usage +Use this adapter when a shipped Qwen2.5 0.5B GGUF is accompanied by a detached +ED25519 signature and a trusted provider public key. + ```rust use el_core::{ModelFormat, ModelId, ModelVersion}; use el_provenance::ModelArtifact; use el_provenance_ed25519::Ed25519Verifier; +use std::path::Path; let mut verifier = Ed25519Verifier::new(); verifier.register(/* public_key_id */ 1, trusted_public_key_bytes)?; +let qwen_path = Path::new("models/qwen2.5-0.5b-instruct-q4_k_m.gguf"); +let model_bytes = std::fs::read(qwen_path)?; +let signature_bytes = std::fs::read(qwen_path.with_extension("gguf.sig"))?; + let mut artifact = ModelArtifact::new(ModelId(1), ModelVersion::new(0, 1, 0), ModelFormat::Gguf); -artifact.verify(&verifier, model_bytes, signature_bytes, 1); +artifact.verify(&verifier, &model_bytes, &signature_bytes, 1); // Verified → a LoadPermit; tampered bytes, a forged signature, or an unknown // key id → a hard error with no fallback. diff --git a/crates/el-core/README.md b/crates/el-core/README.md index 67cf956..80b0948 100644 --- a/crates/el-core/README.md +++ b/crates/el-core/README.md @@ -40,6 +40,12 @@ These are enforced by the type system, not by convention: ## Usage +This is the crate-local part of a real local SDK call. In an app, the request +can be served by `el_engine_candle::QwenChatProvider::from_paths(...)` with +`models/qwen2.5-0.5b-instruct-q4_k_m.gguf` and +`models/qwen2.5-0.5b-instruct.tokenizer.json`; `el-core` only defines the +backend-agnostic contract. + ```rust use el_core::{ChatMessage, ChatRequest, LlmProvider, SessionConfig}; @@ -49,7 +55,10 @@ assert!(!cfg.hybrid_mode); // A backend-agnostic request. `model` is a routing hint: // "local"/"" → local engine, "openai/…", "anthropic/…", "ollama/…", "gemini/…" -let req = ChatRequest::new("local", vec![ChatMessage::user("Hello!")]) +let req = ChatRequest::new( + "local/qwen2.5-0.5b-instruct-q4_k_m", + vec![ChatMessage::user("Summarize edge inference in one sentence.")], +) .with_max_tokens(256) .with_temperature(700); // 700 = 0.7 (milli to keep the type Eq-able) diff --git a/crates/el-grammar/README.md b/crates/el-grammar/README.md index 377f2d2..35a5864 100644 --- a/crates/el-grammar/README.md +++ b/crates/el-grammar/README.md @@ -31,6 +31,10 @@ is wired. ## Usage +For Qwen2.5 0.5B, build the DFA over token ids from +`models/qwen2.5-0.5b-instruct.tokenizer.json`, then wire the masker into the +same runtime ports used by `QwenChatProvider`. + ```rust use el_grammar::{Dfa, DfaMasker}; use el_runtime::GrammarMasker; diff --git a/crates/el-memory/README.md b/crates/el-memory/README.md index d2ea895..dcd813f 100644 --- a/crates/el-memory/README.md +++ b/crates/el-memory/README.md @@ -29,9 +29,16 @@ Depends only on `el-core`. No `unsafe` (`#![forbid(unsafe_code)]`). ## Usage +In a Qwen2.5 0.5B local chat run, `el-engine-candle` loads the GGUF/tokenizer +and the runtime uses memory planning and KV descriptors like these during +prefill and decode. + ```rust use el_memory::{Arena, BufferLifetime, KvRegion, MemoryTier, StaticMemoryPlanner, TensorSpec}; +let _qwen_model = "models/qwen2.5-0.5b-instruct-q4_k_m.gguf"; +let _qwen_tokenizer = "models/qwen2.5-0.5b-instruct.tokenizer.json"; + // Two same-tier tensors with disjoint lifetimes share one offset, so the tier // needs max(size), not the sum. let tensors = [ diff --git a/crates/el-provenance/README.md b/crates/el-provenance/README.md index 25c8fa8..a1644a7 100644 --- a/crates/el-provenance/README.md +++ b/crates/el-provenance/README.md @@ -27,9 +27,13 @@ Depends only on `el-core`. No `unsafe` (`#![forbid(unsafe_code)]`). ## Usage +For a packaged Qwen2.5 0.5B model, verify the local GGUF bytes and detached +signature before the runtime can construct a session. + ```rust use el_core::{ModelFormat, ModelId, ModelVersion}; use el_provenance::{ModelArtifact, SignatureVerifier}; +use std::path::Path; // Plug in a real verifier (el-provenance-ed25519) or a test double. struct AlwaysOk; @@ -37,12 +41,16 @@ impl SignatureVerifier for AlwaysOk { fn verify(&self, _bytes: &[u8], _sig: &[u8], _key_id: u32) -> bool { true } } +let qwen_path = Path::new("models/qwen2.5-0.5b-instruct-q4_k_m.gguf"); +let qwen_bytes = std::fs::read(qwen_path)?; +let qwen_signature = std::fs::read(qwen_path.with_extension("gguf.sig"))?; + let mut artifact = ModelArtifact::new(ModelId(1), ModelVersion::new(0, 1, 0), ModelFormat::Gguf); -artifact.verify(&AlwaysOk, b"", b"", /* public_key_id */ 7); +artifact.verify(&AlwaysOk, &qwen_bytes, &qwen_signature, /* public_key_id */ 7); // No verified signature → no permit → no session. let permit = artifact.ensure_loadable()?; // Err(UnverifiedModel | SignatureRejected) otherwise -# Ok::<(), el_core::EdgeError>(()) +# Ok::<(), Box>(()) ``` ## Place in the workspace diff --git a/crates/el-runtime/README.md b/crates/el-runtime/README.md index 727eb12..06e9e9f 100644 --- a/crates/el-runtime/README.md +++ b/crates/el-runtime/README.md @@ -44,6 +44,11 @@ and covered by tests. ## Usage +The snippet uses `NullEngine` so the crate stays self-contained. In the real +Qwen2.5 0.5B path, replace it with the Candle `QwenEngine`/`QwenChatProvider` +loaded from `models/qwen2.5-0.5b-instruct-q4_k_m.gguf` plus the matching +tokenizer. + ```rust use el_core::{SessionConfig, SessionId, StopReason}; use el_provenance::{ModelArtifact, SignatureVerifier}; diff --git a/crates/el-safety/README.md b/crates/el-safety/README.md index 8b572fc..e8bdb34 100644 --- a/crates/el-safety/README.md +++ b/crates/el-safety/README.md @@ -39,6 +39,10 @@ Depends only on `el-core`. No `unsafe` (`#![forbid(unsafe_code)]`). ## Usage +`QwenChatProvider` resolves guard words through the Qwen2.5 0.5B +`tokenizer.json`; this crate receives the resulting token ids and applies the +same deterministic steering regardless of the model backend. + ```rust use el_core::{DeviceTarget, SafetyMode}; use el_safety::{LightweightFilter, SafetyModeSelector, SafetySteerer}; @@ -47,8 +51,9 @@ use el_safety::{LightweightFilter, SafetyModeSelector, SafetySteerer}; let mode = SafetyModeSelector::resolve(SafetyMode::SecDecoding, DeviceTarget::MidRange); assert_eq!(mode, SafetyMode::Lightweight); -// Lightweight bans specific token ids outright. -let filter = LightweightFilter::new(vec![42, 99]); +// Lightweight bans specific Qwen tokenizer ids outright. +let qwen_guard_token_ids = vec![42, 99]; +let filter = LightweightFilter::new(qwen_guard_token_ids); let adj = filter.adjust(&[]); assert_eq!(adj.delta_for(42), LightweightFilter::HARD_BAN); assert_eq!(adj.delta_for(7), 0); diff --git a/crates/el-telemetry/README.md b/crates/el-telemetry/README.md index e8b7723..0790e22 100644 --- a/crates/el-telemetry/README.md +++ b/crates/el-telemetry/README.md @@ -22,12 +22,17 @@ events it observes. ## Usage +The collector sees only numeric events from a session, whether that session is +the local Qwen2.5 0.5B path or another `LlmProvider`; prompts and responses +never enter telemetry. + ```rust use el_core::{DomainEvent, EventEnvelope, SessionId}; use el_telemetry::MetricsCollector; let mut collector = MetricsCollector::new(); +// Example events from a local Qwen2.5 0.5B prefill/decode pass. collector.observe(&EventEnvelope::new( SessionId(1), 0, diff --git a/packaging/flutter/README.md b/packaging/flutter/README.md new file mode 100644 index 0000000..85864cc --- /dev/null +++ b/packaging/flutter/README.md @@ -0,0 +1,24 @@ +# edge_intelligence + +Generated Flutter bindings for the Edge Intelligence SDK. + +The package contains Dart bindings generated with flutter_rust_bridge and native +libraries assembled by the release pipeline. + +## Usage + +Copy the Qwen2.5 0.5B GGUF into app storage, then open the local SDK facade with +that model path. + +```dart +import "dart:io"; +import "package:edge_intelligence/edge_intelligence.dart"; + +final qwen05b = "/path/to/app/models/qwen2.5-0.5b-instruct-q4_k_m.gguf"; +final sdk = await EdgeLlm.local(qwen05b); + +final reply = await sdk.ask("Summarize edge inference in one sentence."); +await for (final token in sdk.askStream("Give me two deployment tips.")) { + stdout.write(token); +} +``` diff --git a/packaging/npm/README.md b/packaging/npm/README.md new file mode 100644 index 0000000..a1de7a2 --- /dev/null +++ b/packaging/npm/README.md @@ -0,0 +1,46 @@ +# edge-intelligence-sdk + +Generated Web and React Native bindings for the Edge Intelligence SDK. + +This package contains the WASM/TypeScript browser surface, React Native +TypeScript bindings, and native Android/iOS libraries assembled by the release +pipeline. + +## Usage + +Copy the Qwen2.5 0.5B GGUF into app storage and pass that local path to the SDK +facade. + +Browser / WASM: + +```ts +import init, { EdgeLlm } from "edge-intelligence-sdk"; + +await init(); + +const qwen05b = "/models/qwen2.5-0.5b-instruct-q4_k_m.gguf"; +const sdk = new EdgeLlm(qwen05b); +const reply = sdk.ask_wasm("Summarize edge inference in one sentence."); + +console.log(reply); +``` + +React Native: + +```ts +import { EdgeLlm } from "edge-intelligence-sdk"; + +const qwen05b = "/data/user/0/com.example.app/files/models/qwen2.5-0.5b-instruct-q4_k_m.gguf"; +const sdk = EdgeLlm.local(qwen05b); + +const reply = sdk.ask("Summarize edge inference in one sentence."); +let streamed = ""; +sdk.ask_stream_cb("Give me two deployment tips.", { + on_token(token) { + streamed += token; + }, +}); +``` + +Source, crate documentation, and release notes live in the Edge Intelligence +repository. From 736945ea7eb430fc8480dabdf29361d3a86ba252 Mon Sep 17 00:00:00 2001 From: Tovli Date: Tue, 23 Jun 2026 13:15:56 +0300 Subject: [PATCH 2/3] bump version to 0.3.10 --- Cargo.toml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2a2d296..2fa495e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ exclude = [ [workspace.package] edition = "2021" -version = "0.3.9" +version = "0.3.10" license = "Apache-2.0" rust-version = "1.96" repository = "Edge-Native LLM SDK" @@ -48,16 +48,16 @@ repository = "Edge-Native LLM SDK" # This is the single source of truth for internal dep version requirements; # member crates reference them with `{ workspace = true }` and add no inline # version fields. `cargo set-version --workspace ` updates every entry. -el-core = { path = "crates/el-core", version = "0.3.9" } -el-memory = { path = "crates/el-memory", version = "0.3.9" } -el-telemetry = { path = "crates/el-telemetry", version = "0.3.9" } -el-provenance = { path = "crates/el-provenance", version = "0.3.9" } -el-safety = { path = "crates/el-safety", version = "0.3.9" } -el-runtime = { path = "crates/el-runtime", version = "0.3.9" } -el-grammar = { path = "crates/el-grammar", version = "0.3.9" } -el-provenance-ed25519 = { path = "crates/adapters/el-provenance-ed25519", version = "0.3.9" } -el-engine-candle = { path = "crates/adapters/el-engine-candle", version = "0.3.9" } -el-cloud = { path = "crates/adapters/el-cloud", version = "0.3.9" } +el-core = { path = "crates/el-core", version = "0.3.10" } +el-memory = { path = "crates/el-memory", version = "0.3.10" } +el-telemetry = { path = "crates/el-telemetry", version = "0.3.10" } +el-provenance = { path = "crates/el-provenance", version = "0.3.10" } +el-safety = { path = "crates/el-safety", version = "0.3.10" } +el-runtime = { path = "crates/el-runtime", version = "0.3.10" } +el-grammar = { path = "crates/el-grammar", version = "0.3.10" } +el-provenance-ed25519 = { path = "crates/adapters/el-provenance-ed25519", version = "0.3.10" } +el-engine-candle = { path = "crates/adapters/el-engine-candle", version = "0.3.10" } +el-cloud = { path = "crates/adapters/el-cloud", version = "0.3.10" } [profile.release] opt-level = 3 From 07d3ab1df99c3b4159d6da714b869254c43b301b Mon Sep 17 00:00:00 2001 From: Tovli Date: Tue, 23 Jun 2026 14:02:43 +0300 Subject: [PATCH 3/3] Gate binding artifact uploads on pull requests --- .github/workflows/bindings.yml | 3 +++ Cargo.lock | 26 +++++++++---------- ...gistry-release-ci-crates-io-npm-pub-dev.md | 7 +++-- tests/check_ci_workflows.py | 10 +++++++ 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/.github/workflows/bindings.yml b/.github/workflows/bindings.yml index 0cad49f..fee636c 100644 --- a/.github/workflows/bindings.yml +++ b/.github/workflows/bindings.yml @@ -95,6 +95,7 @@ jobs: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib run: bash scripts/retry-command.sh make codegen-rn - uses: actions/upload-artifact@v4 + if: github.event_name != 'pull_request' with: name: rn-bindings path: out/rn/ @@ -140,6 +141,7 @@ jobs: - name: Build WASM ESM package run: make build-wasm - uses: actions/upload-artifact@v4 + if: github.event_name != 'pull_request' with: name: web-bindings path: out/web/ @@ -163,6 +165,7 @@ jobs: - name: Codegen — Flutter/Dart run: make codegen-flutter - uses: actions/upload-artifact@v4 + if: github.event_name != 'pull_request' with: name: flutter-bindings path: out/flutter/ diff --git a/Cargo.lock b/Cargo.lock index 0d95d38..bd18a65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -793,7 +793,7 @@ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "el-bench" -version = "0.3.9" +version = "0.3.10" dependencies = [ "el-core", "el-engine-candle", @@ -803,7 +803,7 @@ dependencies = [ [[package]] name = "el-chat" -version = "0.3.9" +version = "0.3.10" dependencies = [ "el-core", "el-engine-candle", @@ -811,7 +811,7 @@ dependencies = [ [[package]] name = "el-cloud" -version = "0.3.9" +version = "0.3.10" dependencies = [ "el-core", "reqwest", @@ -822,11 +822,11 @@ dependencies = [ [[package]] name = "el-core" -version = "0.3.9" +version = "0.3.10" [[package]] name = "el-engine-candle" -version = "0.3.9" +version = "0.3.10" dependencies = [ "candle-core", "candle-transformers", @@ -838,7 +838,7 @@ dependencies = [ [[package]] name = "el-ffi" -version = "0.3.9" +version = "0.3.10" dependencies = [ "el-cloud", "el-core", @@ -852,7 +852,7 @@ dependencies = [ [[package]] name = "el-grammar" -version = "0.3.9" +version = "0.3.10" dependencies = [ "el-core", "el-provenance", @@ -861,21 +861,21 @@ dependencies = [ [[package]] name = "el-memory" -version = "0.3.9" +version = "0.3.10" dependencies = [ "el-core", ] [[package]] name = "el-provenance" -version = "0.3.9" +version = "0.3.10" dependencies = [ "el-core", ] [[package]] name = "el-provenance-ed25519" -version = "0.3.9" +version = "0.3.10" dependencies = [ "ed25519-dalek", "el-core", @@ -884,7 +884,7 @@ dependencies = [ [[package]] name = "el-runtime" -version = "0.3.9" +version = "0.3.10" dependencies = [ "el-core", "el-memory", @@ -894,14 +894,14 @@ dependencies = [ [[package]] name = "el-safety" -version = "0.3.9" +version = "0.3.10" dependencies = [ "el-core", ] [[package]] name = "el-telemetry" -version = "0.3.9" +version = "0.3.10" dependencies = [ "el-core", ] diff --git a/docs/adr/ADR-011-multi-registry-release-ci-crates-io-npm-pub-dev.md b/docs/adr/ADR-011-multi-registry-release-ci-crates-io-npm-pub-dev.md index 8ab3343..d1ad819 100644 --- a/docs/adr/ADR-011-multi-registry-release-ci-crates-io-npm-pub-dev.md +++ b/docs/adr/ADR-011-multi-registry-release-ci-crates-io-npm-pub-dev.md @@ -66,8 +66,11 @@ Run `make codegen-rn` (Android + RN), `make build-ios`, `make build-wasm`, index propagation before the next dependent is submitted. The `bindings.yml` CI (ADR-011, triggered on every PR/push to affected paths) -provides the pre-release confidence gate. `release.yml` only runs on explicit -version tags. +provides the pre-release confidence gate. Pull requests validate that binding +codegen still succeeds without depending on nonessential artifact upload +finalization; pushes to `master` retain the generated binding artifacts for +inspection. `release.yml` only runs on explicit version tags and still uploads +artifacts because later assembly jobs consume them. ## Consequences diff --git a/tests/check_ci_workflows.py b/tests/check_ci_workflows.py index 374ea28..df88ec1 100644 --- a/tests/check_ci_workflows.py +++ b/tests/check_ci_workflows.py @@ -12,6 +12,7 @@ RN_RETRY_COMMAND = "bash scripts/retry-command.sh make codegen-rn" WASM_RETRY_COMMAND = "bash scripts/retry-command.sh curl -fsSL" +BINDINGS_UPLOAD_IF = "if: github.event_name != 'pull_request'" REQUIRED_INSTALLS = [ ( @@ -47,6 +48,15 @@ def check_workflow(path: Path) -> list[str]: if "Install wasm-pack" in text and WASM_RETRY_COMMAND not in text: errors.append(f"{path}: wasm-pack download must run through retry wrapper") + if path.name == "bindings.yml": + step_blocks = text.split("\n - ") + for block in step_blocks: + if "uses: actions/upload-artifact@v4" in block and BINDINGS_UPLOAD_IF not in block: + errors.append( + f"{path}: PR validation artifact uploads must be gated with " + f"{BINDINGS_UPLOAD_IF!r}" + ) + return errors