استخدمه كمكتبة Rust: استدعِ وكيلاً كأي دالة أخرى، أو شغّل أسطولاً كاملاً من خادم صغير واحد.
@@ -342,7 +342,7 @@ println!("{}", reply.reply);
-بعد ذلك: [البدء السريع مع Rust](https://tinyhumans.gitbook.io/openhuman/developing/quickstart)، و[دليل التضمين](https://tinyhumans.gitbook.io/openhuman/developing/embedding)، و[مستندات المطورين](https://tinyhumans.gitbook.io/openhuman/developing).
+بعد ذلك: [البدء السريع مع Rust](https://tinyhumans.gitbook.io/openhuman/developing/quickstart)، و[دليل التضمين](https://tinyhumans.gitbook.io/openhuman/developing/embed)، و[مستندات المطورين](https://tinyhumans.gitbook.io/openhuman/developing).
---
diff --git a/docs/README.de.md b/docs/README.de.md
index 57475cd8a5d..b8173040e83 100644
--- a/docs/README.de.md
+++ b/docs/README.de.md
@@ -117,7 +117,7 @@ Die meisten Agent-Harnesses starten pro Agent einen schweren Prozess und senden
Für Entwickler gebaut
-
Rust-Schnellstart · Einbettungsanleitung · Beispiele
+
Rust-Schnellstart · Einbettungsanleitung · Beispiele
Nutze es als Rust-Bibliothek: Rufe einen Agenten wie jede andere Funktion auf oder betreibe eine ganze Flotte auf einem kleinen Server.
@@ -288,7 +288,7 @@ let reply = agent.run("Summarize what you can see in this directory.").await?;
println!("{}", reply.reply);
```
-Als Nächstes: der [Rust-Schnellstart](https://tinyhumans.gitbook.io/openhuman/developing/quickstart), die [Einbettungsanleitung](https://tinyhumans.gitbook.io/openhuman/developing/embedding) und die [Entwicklerdokumentation](https://tinyhumans.gitbook.io/openhuman/developing).
+Als Nächstes: der [Rust-Schnellstart](https://tinyhumans.gitbook.io/openhuman/developing/quickstart), die [Einbettungsanleitung](https://tinyhumans.gitbook.io/openhuman/developing/embed) und die [Entwicklerdokumentation](https://tinyhumans.gitbook.io/openhuman/developing).
---
diff --git a/docs/README.ja-JP.md b/docs/README.ja-JP.md
index 71b8a49bced..2c86d21dde1 100644
--- a/docs/README.ja-JP.md
+++ b/docs/README.ja-JP.md
@@ -117,7 +117,7 @@ macOS と Linux のスクリプトが何を行うかを事前に確認するに
開発者のために
-
Rust クイックスタート · 組み込みガイド · サンプル
+
Rust クイックスタート · 組み込みガイド · サンプル
Rust ライブラリとして使えます。エージェントを普通の関数のように呼び出すことも、小さなサーバー1台でエージェント群をまるごと動かすこともできます。
@@ -288,7 +288,7 @@ let reply = agent.run("Summarize what you can see in this directory.").await?;
println!("{}", reply.reply);
```
-次は、[Rust クイックスタート](https://tinyhumans.gitbook.io/openhuman/developing/quickstart)、[組み込みガイド](https://tinyhumans.gitbook.io/openhuman/developing/embedding)、[開発者向けドキュメント](https://tinyhumans.gitbook.io/openhuman/developing)をご覧ください。
+次は、[Rust クイックスタート](https://tinyhumans.gitbook.io/openhuman/developing/quickstart)、[組み込みガイド](https://tinyhumans.gitbook.io/openhuman/developing/embed)、[開発者向けドキュメント](https://tinyhumans.gitbook.io/openhuman/developing)をご覧ください。
---
diff --git a/docs/README.ko.md b/docs/README.ko.md
index bbc290e6738..8d1ae352503 100644
--- a/docs/README.ko.md
+++ b/docs/README.ko.md
@@ -117,7 +117,7 @@ macOS와 Linux 스크립트가 무엇을 하는지 미리 보려면 명령 끝
개발자를 위해 만들었습니다
-
Rust 퀵스타트 · 임베딩 가이드 · 예제
+
Rust 퀵스타트 · 임베딩 가이드 · 예제
Rust 라이브러리로 사용하세요. 다른 함수처럼 에이전트를 호출하거나, 작은 서버 하나에서 수많은 에이전트를 실행할 수 있습니다.
@@ -288,7 +288,7 @@ let reply = agent.run("Summarize what you can see in this directory.").await?;
println!("{}", reply.reply);
```
-다음으로는 [Rust 퀵스타트](https://tinyhumans.gitbook.io/openhuman/developing/quickstart), [임베딩 가이드](https://tinyhumans.gitbook.io/openhuman/developing/embedding), [개발자 문서](https://tinyhumans.gitbook.io/openhuman/developing)를 보세요.
+다음으로는 [Rust 퀵스타트](https://tinyhumans.gitbook.io/openhuman/developing/quickstart), [임베딩 가이드](https://tinyhumans.gitbook.io/openhuman/developing/embed), [개발자 문서](https://tinyhumans.gitbook.io/openhuman/developing)를 보세요.
---
diff --git a/docs/README.tr.md b/docs/README.tr.md
index cd63024b1b3..095acf63395 100644
--- a/docs/README.tr.md
+++ b/docs/README.tr.md
@@ -117,7 +117,7 @@ macOS ve Linux betiğinin ne yapacağını önceden görmek için komutun sonuna
Geliştiriciler için tasarlandı
-
Rust hızlı başlangıç · Gömme rehberi · Örnekler
+
Rust hızlı başlangıç · Gömme rehberi · Örnekler
Bir Rust kütüphanesi olarak kullanın: bir ajanı herhangi bir işlev gibi çağırın ya da tüm bir filoyu tek bir küçük sunucudan çalıştırın.
@@ -288,7 +288,7 @@ let reply = agent.run("Summarize what you can see in this directory.").await?;
println!("{}", reply.reply);
```
-Sırada: [Rust hızlı başlangıç](https://tinyhumans.gitbook.io/openhuman/developing/quickstart), [gömme rehberi](https://tinyhumans.gitbook.io/openhuman/developing/embedding) ve [geliştirici dokümanları](https://tinyhumans.gitbook.io/openhuman/developing).
+Sırada: [Rust hızlı başlangıç](https://tinyhumans.gitbook.io/openhuman/developing/quickstart), [gömme rehberi](https://tinyhumans.gitbook.io/openhuman/developing/embed) ve [geliştirici dokümanları](https://tinyhumans.gitbook.io/openhuman/developing).
---
diff --git a/docs/README.ur-pk.md b/docs/README.ur-pk.md
index b125cdf4ecc..6d1bb7a20e1 100644
--- a/docs/README.ur-pk.md
+++ b/docs/README.ur-pk.md
@@ -139,7 +139,7 @@ macOS اور Linux کی اسکرپٹ کیا کرے گی، یہ پہلے دیکھ
ڈیولپرز کے لیے بنایا گیا
-
Rust کوئیک اسٹارٹ · ایمبیڈنگ گائیڈ · مثالیں
+
Rust کوئیک اسٹارٹ · ایمبیڈنگ گائیڈ · مثالیں
اسے Rust لائبریری کے طور پر استعمال کریں: ایجنٹ کو کسی بھی عام فنکشن کی طرح کال کریں، یا ایک چھوٹے سرور سے پورا بیڑا چلائیں۔
@@ -333,7 +333,7 @@ println!("{}", reply.reply);
-اگلا قدم: [Rust کوئیک اسٹارٹ](https://tinyhumans.gitbook.io/openhuman/developing/quickstart)، [ایمبیڈنگ گائیڈ](https://tinyhumans.gitbook.io/openhuman/developing/embedding) اور [ڈیولپر دستاویزات](https://tinyhumans.gitbook.io/openhuman/developing)۔
+اگلا قدم: [Rust کوئیک اسٹارٹ](https://tinyhumans.gitbook.io/openhuman/developing/quickstart)، [ایمبیڈنگ گائیڈ](https://tinyhumans.gitbook.io/openhuman/developing/embed) اور [ڈیولپر دستاویزات](https://tinyhumans.gitbook.io/openhuman/developing)۔
---
diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md
index 66c0f693af6..db8c809d11c 100644
--- a/docs/README.zh-CN.md
+++ b/docs/README.zh-CN.md
@@ -117,7 +117,7 @@ irm https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/instal
为开发者而建
-
Rust 快速入门 · 嵌入指南 · 示例
+
Rust 快速入门 · 嵌入指南 · 示例
把它当作 Rust 库来用:像调用普通函数一样调用智能体,或者在一台小服务器上运行整个集群。
@@ -288,7 +288,7 @@ let reply = agent.run("Summarize what you can see in this directory.").await?;
println!("{}", reply.reply);
```
-接下来看:[Rust 快速入门](https://tinyhumans.gitbook.io/openhuman/developing/quickstart)、[嵌入指南](https://tinyhumans.gitbook.io/openhuman/developing/embedding)和[开发者文档](https://tinyhumans.gitbook.io/openhuman/developing)。
+接下来看:[Rust 快速入门](https://tinyhumans.gitbook.io/openhuman/developing/quickstart)、[嵌入指南](https://tinyhumans.gitbook.io/openhuman/developing/embed)和[开发者文档](https://tinyhumans.gitbook.io/openhuman/developing)。
---
diff --git a/docs/gitbooks/en/developing/embed/api-index.json b/docs/gitbooks/en/developing/embed/api-index.json
index 76919ff831f..2fa0d37e1ad 100644
--- a/docs/gitbooks/en/developing/embed/api-index.json
+++ b/docs/gitbooks/en/developing/embed/api-index.json
@@ -76,6 +76,7 @@
"ModelDefaults",
"OpenError",
"PendingApproval",
+ "PermissionFuture",
"PermissionLevel",
"PickListenPortError",
"ProfileError",
@@ -126,6 +127,7 @@
"TrustedAccess",
"TrustedAutomationSource",
"Turn",
+ "TurnCancellation",
"TurnContext",
"TurnOutcome",
"TurnRequest",
@@ -136,6 +138,8 @@
"absolute",
"agent_progress",
"artifacts",
+ "budget",
+ "cancellation",
"channels",
"chat_surface",
"complete",
@@ -143,20 +147,25 @@
"cron",
"embeddings",
"events",
+ "fanout",
"identity",
"install_backend_transport",
"installed_backend_transport",
"memory",
"modules",
+ "observe",
"process",
"profiles",
"providers",
+ "repository",
+ "routing",
"run_from_args",
"schema_for_rpc_method",
"seams",
"session_store",
"skill_registry",
- "stream"
+ "stream",
+ "structured"
],
"builder_setters": [
{
@@ -435,7 +444,7 @@
"skills": true,
"storage-file": false,
"storage-mongodb": false,
- "storage-sqlite": false,
+ "storage-sqlite": true,
"tinymemes": true,
"voice": false,
"web3": false,
diff --git a/docs/gitbooks/en/developing/embed/api-index.md b/docs/gitbooks/en/developing/embed/api-index.md
index 3e386b0c6d6..9df96592b9c 100644
--- a/docs/gitbooks/en/developing/embed/api-index.md
+++ b/docs/gitbooks/en/developing/embed/api-index.md
@@ -79,6 +79,7 @@ Generate the complete local Rust API reference with `cargo doc -p openhuman-embe
- [`ModelDefaults`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`OpenError`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`PendingApproval`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`PermissionFuture`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`PermissionLevel`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`PickListenPortError`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`ProfileError`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
@@ -129,6 +130,7 @@ Generate the complete local Rust API reference with `cargo doc -p openhuman-embe
- [`TrustedAccess`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`TrustedAutomationSource`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`Turn`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`TurnCancellation`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`TurnContext`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`TurnOutcome`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`TurnRequest`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
@@ -139,6 +141,8 @@ Generate the complete local Rust API reference with `cargo doc -p openhuman-embe
- [`absolute`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`agent_progress`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`artifacts`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`budget`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`cancellation`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`channels`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`chat_surface`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`complete`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
@@ -146,20 +150,25 @@ Generate the complete local Rust API reference with `cargo doc -p openhuman-embe
- [`cron`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`embeddings`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`events`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`fanout`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`identity`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`install_backend_transport`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`installed_backend_transport`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`memory`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`modules`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`observe`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`process`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`profiles`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`providers`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`repository`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`routing`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`run_from_args`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`schema_for_rpc_method`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`seams`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`session_store`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`skill_registry`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`stream`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`structured`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
The compiled capability report describes this build:
@@ -187,7 +196,7 @@ The compiled capability report describes this build:
"skills": true,
"storage-file": false,
"storage-mongodb": false,
- "storage-sqlite": false,
+ "storage-sqlite": true,
"tinymemes": true,
"voice": false,
"web3": false,
diff --git a/docs/gitbooks/en/developing/embed/capability-matrix.md b/docs/gitbooks/en/developing/embed/capability-matrix.md
index aba183fbf7e..c6219821881 100644
--- a/docs/gitbooks/en/developing/embed/capability-matrix.md
+++ b/docs/gitbooks/en/developing/embed/capability-matrix.md
@@ -25,7 +25,7 @@ This matrix comes from the default-feature compiled capability-report example. R
| `skills` | Yes |
| `storage-file` | No |
| `storage-mongodb` | No |
-| `storage-sqlite` | No |
+| `storage-sqlite` | Yes |
| `tinymemes` | Yes |
| `voice` | No |
| `web3` | No |
diff --git a/docs/gitbooks/en/developing/embed/cookbook.md b/docs/gitbooks/en/developing/embed/cookbook.md
index 6b7caefc55b..3462bef8af5 100644
--- a/docs/gitbooks/en/developing/embed/cookbook.md
+++ b/docs/gitbooks/en/developing/embed/cookbook.md
@@ -76,6 +76,14 @@ Lean runtime without background services.
Run: `cargo run -p openhuman-embed --example lean_headless`
+## Linux agent fleet memory and latency
+
+Measure retained runtime-owned agents using loopback inference and two worker threads.
+
+[Source](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/examples/linux_fleet.rs) · offline on Linux; use a fresh constrained cgroup for release measurements.
+
+Run: `cargo run -p openhuman-embed --example linux_fleet`
+
## Connect an actual MCP protocol stub over loopback
Connect an actual MCP protocol stub over loopback.
diff --git a/docs/plans/security-remaining-7328.md b/docs/plans/security-remaining-7328.md
new file mode 100644
index 00000000000..98b6108e1d0
--- /dev/null
+++ b/docs/plans/security-remaining-7328.md
@@ -0,0 +1,465 @@
+# Complete TinySecurity migration: remaining implementation
+
+Trackers: [OpenHuman #7328](https://github.com/tinyhumansai/openhuman/issues/7328),
+[TinySecurity #1](https://github.com/tinyhumansai/tinysecurity/issues/1),
+[TinyBox #27](https://github.com/tinyhumansai/tinybox/issues/27).
+Binding owner design: `vendor/tinysecurity/docs/specs/security-module.md`,
+`docs/specs/immutable-path-scopes.md`, `docs/plans/security-module.md`.
+
+## Delivery boundary and starting evidence
+
+Bootstrap plus immutable path scopes exist. TinySecurity #4 merged at
+`f2b7ebd77c1f1109e08f6f38de87ad594841a0e3`. The module currently advertises
+Evaluate, Check, PolicyInfo and four immutable path members. Its command engine
+only allows argument-free diagnostics. `future.rs` contains reserved payloads,
+not working approvals, scans, egress, audit or sandbox engines. The host's
+`modules/security.rs::module_config` deliberately returns bootstrap defaults.
+Do not treat any reserved type or passing bootstrap test as tracker completion.
+Root is releasing that path milestone as v0.2.3 (run 38080694935). The next owner
+implementer is already executing Task 3's immutable command registry. Finish and
+review that task first; do not restart it or wait for the full migration plan.
+
+Deliver **one further cumulative TinySecurity PR**, against canonical upstream,
+containing all owner work below. Continue the existing OpenHuman #7331 for host
+work. Tasks are commit/review units, not separate TinySecurity PRs. Preserve all
+checkpoint commits; never squash, reset, amend or bypass hooks. Root coordinates
+release/pinning. Owner must merge and release before production host gitlinks,
+registry versions or digests change. Copy every digest from release
+`checksum.toml`, never from development builds.
+
+Work inside this existing superproject worktree; no nested worktrees. For another
+owner repository, use the same superproject branch and that owner's upstream PR.
+Do not place missing TinyBox/TinyMCP/TinyRuntime/TinyFlows capabilities into the
+host or TinySecurity as workarounds. Their upstream changes must merge/release
+before dependent production pins. One TinySecurity PR does not prohibit required
+PRs in those other owners.
+
+## Invariants for every task
+
+- Host normal dependencies name only `tinysecurity-bus`; its normal dependencies
+ remain serde and thiserror, without runtime, transport, crypto or TinyTools.
+ Internals and dependencies are compiled into the native module.
+- Preserve `approval.*`, `security.*`, `sandbox.*`, `encryption.*` RPC names and
+ existing payload compatibility. Add explicit migrations for stored/config data.
+- Init/reinit carries secret credentials, endpoint and module service settings.
+ Invocation args carry authenticated context and scoped policy references,
+ never judge credentials, arbitrary tenant authority or model-provided tiers.
+- Keep immutable PathPolicyId scopes. Expand to immutable full-policy scopes;
+ never serialize tenants through process-global reinit or let one agent change
+ another's policy. Policy activation increments the appropriate generation.
+- Module absence, timeout, malformed reply or fault denies external effects.
+ A fault latches; no reload/retry of that module in the same process. Missing
+ caller configuration before a native call must not poison unrelated tenants.
+- Disabled autonomy leaves discretionary classification/gates/allowlist/action
+ budget inert, preserving credential/system/traversal/NUL floor and access-tier,
+ origin, privacy and mandatory isolation boundaries.
+- Write behavioral tests first, record the actual RED failure, implement, record
+ GREEN and owning-suite results. Keep host characterization until its replacement
+ tests exercise the same behavior through the released native module.
+- Unit tests use explicit clocks/resolvers/storage seams. Unit files are sibling
+ `*_tests.rs`, start `use super::*;`, and are declared with `#[cfg(test)]` plus
+ `#[path = "…_tests.rs"] mod tests;`; never inline or legacy test filenames.
+- No placeholders, empty crates, ignored failures or blanket lint allowances.
+ Advertise a method only when all its engine, storage and failure paths work.
+- Long checks use `scripts/ci-cancel-aware.sh` from the host root. Never export
+ CARGO_TARGET_DIR or build under a temporary directory. Temp test data is fine.
+
+## Test protocol and contract interfaces
+
+Execution order: finish the in-progress command task, then remaining scoped
+contracts/config, redaction, egress, callback/audit infrastructure, approvals,
+judge, sandbox planning and crypto. Characterization/harness work precedes each
+affected engine. Complete policy-widening human review after approval callbacks
+exist; until then reject widening activation rather than install an approval
+stub. Judge consumes completed redaction, audit and approvals. Sandbox/network
+requirements remain hard denies until their engines work. Finish all owner gates
+and the single owner release, then host migration tasks 12–16. This staged order
+prevents intermediate methods from advertising incomplete authorization.
+
+For each owner task, first run its named filter using
+`cargo test --manifest-path vendor/tinysecurity/Cargo.toml -p
`;
+save RED/GREEN output in the controller ledger. Then run the crate suite.
+For host tests use `cargo test -p openhuman-cli --test ` or
+`cargo test -p openhuman ` through the cancellation-aware wrapper.
+New root tests require explicit `[[test]]` entries in
+`crates/openhuman-cli/Cargo.toml`. In-process backend tests call
+`tests/support/tinyhumans_boot.rs::boot()` before use; network services are mocked.
+
+Extend bus types in focused `policy.rs`, `approval.rs`, `redact.rs`, `egress.rs`,
+`sandbox.rs`, `audit.rs`, `crypto.rs`, `callbacks.rs`; re-export from `lib.rs`.
+Replace definitions in `future.rs` with compatibility re-exports rather than
+duplicate types. Preserve existing method constants and fixtures. Define a
+contract-version change and explicit old/new compatibility tests when adding
+required fields or enum variants to strict serde payloads.
+
+Shared interfaces to implement:
+
+- `RegisterPolicy(RegisteredPolicy { path_policy_id, settings, tool_rules,
+ command_policy_id, subject_scope }) -> PolicyId`; immutable typed ID that
+ composes existing immutable path/command registries rather than duplicating
+ them. Scope includes authenticated
+ user/workspace/agent ceiling, not just agent name. Scoped Evaluate/Check bind
+ PolicyId, verified CallerContext and canonical call fingerprint. Existing
+ bootstrap calls retain safe behavior without a scope; they never gain effects.
+- `PolicySettings`: enabled, allowed commands, rate limit, privacy, approvals,
+ auto-approve origin rules, sandbox defaults and audit requirements. Path settings
+ remain in PathPolicy. `PolicyInfo` returns all effective nonsensitive settings,
+ current generation, implemented members and callback availability.
+- `Decision` keeps generation/verdict/cacheable. Extend typed denial reasons for
+ rate, privacy, URL, isolation, expired grant and required persistence failures.
+ Unknown effects deny. Mutable approvals/rates/DNS results are never cacheable.
+- Approval records bind owner, origin, thread/flow, policy scope/generation,
+ call fingerprint, expiry, lifecycle, decision attribution and execution outcome.
+ ApprovalStore supports scoped load and atomic compare-and-set transitions,
+ plus durable flow/tool grants. Callback commits are explicit acknowledgements.
+- Callback clients use SDK bus calls with bounded timeouts and typed errors.
+ Never hold a module/host state mutex while awaiting host prompt/store callbacks.
+ Reentrant Decide during a parked Evaluate must be supported without deadlock.
+
+## Task 1 — Characterization inventory and native harness
+
+**Host files:** `tests/security_policy_characterization.rs`,
+`tests/security_approval_characterization.rs`,
+`tests/security_redaction_characterization.rs`,
+`tests/security_sandbox_characterization.rs`,
+`tests/security_crypto_characterization.rs`, CLI manifest test tables;
+`crates/openhuman-core/src/modules/security_native_tests.rs`.
+**Owner files:** `crates/tinysecurity-module/tests/native_contract.rs`,
+`crates/tinysecurity-bus/tests/fixtures/`, `docs/performance.md`.
+
+1. Pin existing approval RPC/event shapes, TTLs (600s/180s), origin/flow/tool trust,
+ disabled/enabled policy, command syntax, sandbox precedence and encrypted data.
+2. Build one corpus from all host redactors, preserving missed-secret cases as
+ explicit desired regressions rather than blessing their current omissions.
+3. Native harness loads a real cdylib, serves typed callbacks, detects malformed
+ args and exposes deterministic clock/resolver/stub-judge seams for later tasks.
+4. Capture existing in-process and native Evaluate/Check latency p50/p99 and calls
+ per turn; record runner/hardware/workload. Set a measured budget in
+ `docs/performance.md` before host caller migration, then enforce it in CI.
+**Exit:** characterization passes, native harness proves actual dispatch; desired
+missing behaviors are RED, not removed or marked successful.
+
+## Task 2 — Full scoped configuration and activation
+
+**Owner files:** bus `policy.rs`, `names.rs`, `callbacks.rs`, policy
+`src/policy_registry.rs`, `src/policy_registry_tests.rs`, module `adapter.rs`.
+**Host after release:** `config/schema/security.rs`, schema `mod.rs`,
+`config/migrations/security_policy.rs`, migration `mod.rs`,
+`modules/security_config.rs`, `security/live_policy.rs`.
+
+1. RED: concurrently register two agents/tenants with opposite command/privacy
+ rules; scoped evaluations remain isolated during reload and invalid reinit.
+2. Implement validated immutable policy registration and complete PolicyInfo.
+ Config activation is atomic; failure leaves prior policy active. Review widening
+ changes (roots/hosts/tools/classes/auto origins/judge) through human approval
+ under old policy before publishing the new generation; cannot self-approve.
+3. Choose and implement one `[security]` table. Migrate legacy autonomy/sandbox/
+ privacy fields with deterministic precedence and preserve default policy off.
+ Remove unreachable DaemonConfig.security/SecurityConfig duplication.
+4. Remove unused `max_cost_per_day_cents` with a migration notice and fixture;
+ do not advertise an unenforced cost budget. Keep actual judge budget separately.
+5. Host translator derives authenticated scope and all fields once. No operator
+ config/env fallback for SaaS. Secret settings never enter PolicyInfo or logs.
+**Exit:** round-trip old config fixtures and isolated new effective policies pass.
+
+## Task 3 — Command grammar, tool rules and action accounting
+
+**In progress:** root has dispatched this task; resume its result/review instead
+of dispatching a second implementer. The agreed contract is immutable
+`CommandPolicy { enabled, autonomy, allowed_commands, max_actions_per_hour,
+require_approval_for_medium_risk, block_high_risk_commands, action_dir, home_dir,
+execution_mode }`, opaque `CommandPolicyId`, RegisterCommandPolicy,
+ClassifyCommand and CheckCommand. Mode is `HarnessGated | Allowlisted`, selected
+only at trusted registration. Pure classification returns typed class/risk/gate/
+denial and reserves nothing; Check atomically reserves allowed hourly actions.
+Registry survives reinit, clocks are injected and stateful checks are uncached.
+HarnessGated preserves legacy shell check_gated_command behavior without imposing
+Allowlisted's allowlist/risk gate; validate_command_execution uses Allowlisted.
+Later approval/middleware gates derive command-class decisions from verified
+context. Keep these distinctions in characterization and native tests.
+
+**Owner files:** policy `src/command.rs`, `src/command/{classify,scan,env_guard}.rs`,
+`src/rules.rs`, `src/rate.rs`, `src/engine.rs`, sibling tests; bus policy types.
+**Source parity:** host `security/policy/{command_checks,enforcement,types}.rs`,
+`tools/rules/`; TinyBox shell classifier/scanner/environment rules.
+
+1. RED: POSIX compounds/substitution/redirection, quoted heredoc data, expanded
+ heredocs; PowerShell/cmd escaping, paths and PATHEXT executable resolution.
+2. Port classification and scanning to TinySecurity. Known reads remain reads,
+ unknown commands become writes when enabled. Never trust declared class.
+ Floor scanning recognizes protected literals through supported syntax.
+3. Reuse the existing vendored TinyTools ToolRules/ApprovalDirective vocabulary
+ internally via the single TinyAgents-owned copy; translate serde bus records
+ mechanically. Tool visibility/access ceiling and explicit denies dominate.
+4. Deterministic reservation/commit/release accounting enforces hourly actions
+ without duplicate charges on parked/resumed calls. Disabled policy does not
+ reserve. Reinit/cache cannot replenish or bypass active reservations.
+5. Preserve merged path registry tests, adding full-policy binding and real native
+ Windows/APFS/Linux cases; no second path normalizer.
+**Exit:** complete command/rule/rate parity through native Evaluate/Check.
+
+## Task 4 — One redactor and integrated scans
+
+**Owner files:** new `crates/tinysecurity-redact/{Cargo.toml,src/lib.rs}`, internal
+`src/{patterns,structured,prompt}.rs` and sibling tests; bus `redact.rs`;
+module dispatch; workspace members and internal umbrella wiring.
+
+1. RED shared corpus: secrets, PII/identifiers, nested args, key names, escaped JSON,
+ URL userinfo/query, Unix/macOS/Windows home paths, malformed/bounded inputs.
+2. Implement one registry with explicit modes. Add idempotence/no-secret-survives
+ properties and bounded-work fuzz cases; preserve useful nonsensitive errors.
+3. Implement ScanPrompt/ScanToolDefinition verdicts and stable rule IDs, route
+ blocked/suspicious results into policy/approval. Scanner verdict is context,
+ never authorization. Document TinyMCP protocol sanitation and TinySkills
+ package scanning as owner inputs; avoid reproducing those implementations.
+4. Redact before storage, broadcasts, audit and judge. Sensitive log paths on
+ module failure suppress content rather than return original text. Only a
+ documented fixed boot diagnostic floor may exist before module readiness.
+**Exit:** native Redact/scans and every shared-corpus/property test pass.
+
+## Task 5 — URL guard, privacy and actual rebinding protection
+
+**Owner files:** new `crates/tinysecurity-egress/{Cargo.toml,src/lib.rs}`, internal
+`src/{url_guard,resolver,privacy}.rs`, sibling tests; bus `egress.rs`.
+**Host after release:** `tools/impl/network/host.rs`, `modules/browser.rs`,
+`security/egress/`, `web_chat/egress_surface.rs`, `inference/provider/factory.rs`,
+Composio loopback gates and `util/url.rs` callers.
+
+1. RED literals: metadata/link-local, CGNAT, private/mapped IPv6, unusual numeric
+ IPv4 forms, zones, userinfo, unsupported schemes and mixed-address DNS answers.
+2. Resolve in module, validate every candidate, return hostname plus exact approved
+ IPs and bounded validity. Any forbidden candidate denies. Redirects are checked
+ independently. Destination allowlist is scoped policy, not caller authority.
+3. Host HTTP transport disables unchecked automatic redirects and connects only
+ to approved IPs while preserving Host/SNI/TLS hostname. Test a resolver that
+ changes after validation and a real local transport proving no second lookup.
+4. Browser/TinyComputer or proxy routes require a checked egress proxy capable of
+ per-hop pinning; if unsupported, deny protected network operation. URL string
+ validation alone must never be reported as rebinding protection.
+5. LocalOnly denies nonlocal egress; Standard enforces declared destination/data
+ policy; Sensitive denies identifying/credential-bearing raw egress unless an
+ explicit approved transformation removes it and descriptor is rechecked.
+ All inference/embedding/memory/integration/browser paths supply descriptors.
+**Exit:** native URL/privacy tests and real pinned-transport tests pass.
+
+## Task 6 — Audit engine and callback infrastructure
+
+**Owner files:** new `crates/tinysecurity-audit/{Cargo.toml,src/lib.rs}`, internal
+`src/{event,sink,rotation}.rs`, tests; bus `audit.rs`, `callbacks.rs`; module
+`src/callbacks.rs`, callback and adapter tests.
+**Host after release:** `modules/security_host.rs`, `modules/mod.rs`,
+`security/approval/store*.rs`, existing host storage driver wiring.
+
+1. RED: sink/store unavailable, timeout, wrong owner, malformed acknowledgement,
+ callback reentrancy, duplicate events and rotation during Windows file locking.
+2. Implement bounded typed callback clients. Native callback harness verifies
+ bus interface identities, caller ownership and commit acknowledgements.
+3. One ordered audit stream covers policy, approval, judge, shell/execution and
+ sandbox decisions. Redact event summaries; use content-free applied rule IDs.
+4. Required audit failure denies before execution. Optional failure reports
+ sanitized degraded health, never falsely committed. JSONL rotation uses 0600
+ on Unix and restricted Windows ACLs. Host callback sink is configurable.
+**Exit:** persisted native audit and callback failure/security tests pass.
+
+## Task 7 — Durable approvals, grants and expiry
+
+**Owner files:** new `crates/tinysecurity-approval/{Cargo.toml,src/lib.rs}`, internal
+`src/{state,store,grants,reply}.rs`, tests; bus `approval.rs`; module adapters.
+
+1. RED pending→decided→executed, expiry at exact boundary, restart reload,
+ duplicate/replayed Decide, concurrent decision, wrong tenant/context/fingerprint,
+ changed policy, callback failure and terminal outcome acknowledgement.
+2. Persist pending before RequireApproval. Store transitions are CAS/idempotent.
+ Module reloads scoped durable records/grants; expired state never authorizes.
+ TTL defaults 600s, copilot/sub-agent 180s; test injected-clock rollback safely.
+3. Implement allow once/tool/flow and deny; tool grants persist/reload without
+ silently granting different arguments/classes, and flow grants bind reviewed
+ fingerprints. Policy widening invalidates/reviews affected grants.
+4. ParseReply only parses intent; Decide requires authenticated human authority.
+ Cron reads only; external effects deny. SaaS uses authenticated per-user prompt
+ callback, denies if absent; remove unconditional SaaS approval bypass.
+5. Host owns parked futures/cancellation and ApprovalRequested/ApprovalDecided
+ events. Module owns state/TTL/log. Callbacks do not create recursive lock waits.
+**Exit:** restart-surviving real native approval round trip, no unsafe grant replay.
+
+## Task 8 — Rules and optional Jev judge
+
+**Owner files:** new `crates/tinysecurity-auto/{Cargo.toml,src/lib.rs}`, internal
+`src/{rules,judge,budget}.rs`, tests; init JudgeConfig and approval wiring.
+
+1. RED off-default/per-origin opt-in, allowlist/flow grants, auto_approve_all audit,
+ judge allow/low-confidence/timeout/malformed/error/budget exhaustion and Deny.
+2. Use tinyinference-decisions Jev API internally. Init supplies endpoint/credential;
+ host resolves via resolve_backend_credential. No secret invocation fields.
+3. Send redacted intent/reversibility/exfiltration questions; configurable class/
+ origin thresholds and deterministic budget. Only RequireApproval may become
+ Allow; hard denies, floor, privacy and isolation remain unchanged.
+4. All errors/uncertainty fall back to human, unavailable human channel denies.
+ Audit sanitized scores/thresholds/question IDs with auto:jev. Cache cannot
+ issue reusable permission or bypass mutable checks.
+**Exit:** native tests against local stub judge on all three OSes.
+
+## Task 9 — Sandbox planning with complete capability policy
+
+**Owner files:** new `crates/tinysecurity-sandbox/{Cargo.toml,src/lib.rs}`, internal
+`src/{resolve,grants,capabilities}.rs`, tests; bus `sandbox.rs`, module Plan.
+**Owner prerequisites:** TinyBox #27 real jail/namespace/microvm/Docker contracts.
+
+1. RED SaaS + env off, source tiers, agent mode/config precedence, unsupported
+ capabilities, Noop for untrusted code, credential grants, host networking.
+2. Extend request with verified SaaS/env/mode facts and policy scope; resolve
+ backend, resources, network and grants deterministically. Credentials never
+ enter grants. Actual executor suitability still checked immediately at spawn.
+3. SaaS always isolates; env off never overrides. Untrusted MCP/skills/downloads
+ select microvm where available, otherwise an explicitly suitable real backend;
+ unavailable suitable isolation denies. Host-network needs approval.
+4. Implement Firejail/Bubblewrap config via supported namespace mapping or migrate
+ to namespace and remove obsolete firejail_args/empty resource config. Every
+ surviving resource field must map to an enforced TinyBox limit.
+**Exit:** plan matrix and native Plan tests pass, no optimistic unsupported plan.
+
+## Task 10 — Unified crypto, keyring and pairing
+
+**Owner files:** new `crates/tinysecurity-crypto/{Cargo.toml,src/lib.rs}`, internal
+`src/{password,keyring,device,pairing}.rs`, platform backend submodules, tests;
+bus `crypto.rs` and constants; module dispatch and KeyringConsent callbacks.
+**Host sources:** `security/encryption/core.rs`, `security/keyring/{crypto,
+encrypted_store,encrypted_file_backend,backend}.rs`, `security/devices/crypto.rs`,
+`security/pairing.rs` and existing encrypted fixture files.
+
+1. RED decrypt existing Argon2id/AES-GCM and encrypted-file fixtures; malformed
+ ciphertext/version/tag, wrong password, X25519/HKDF tunnel compatibility,
+ pairing TTL/replay/rate limits, keychain consent deny/unavailable and restart.
+2. Port implementation once; native backend owns OS keyring/encrypted fallback.
+ Credential ownership/auth, device sockets, pairing UI and consent UX stay host.
+ Opaque key/session handles bind authenticated owner; never return key material
+ through info/log/audit. Define explicit binary payload size limits.
+3. Validate native Secret Service/file, macOS Keychain and Windows Credential
+ Manager round trips; remove test fixtures from OS keyrings after the test.
+ Document genuinely unsupported runner consent setup, do not simulate OS tests.
+**Exit:** existing data readable, bus crypto complete, real platform backends tested.
+
+## Task 11 — Owner quality, review, single PR and release
+
+**Files:** owner workflows `ci.yml`, `release.yml`, native test matrix, fuzz targets,
+`deny.toml`, docs/performance, MODULE/specs/ADRs/ROADMAP; no manual version bump.
+
+1. All new members must be native-dispatched, listed in PolicyInfo and covered by
+ malformed/timeout/fault tests. Fuzz commands/paths/redactor/URL; per-file line
+ coverage ≥90%; cargo deny, MSRV and warning-free rustdoc.
+2. Full fmt/clippy/build/test all-features and feature-off checks. Benchmark
+ Evaluate/Check p50/p99 against committed budget; batching/cache fixes remain
+ bus-based and cannot cache rates/grants/DNS authorization.
+3. Real Linux/macOS/Windows CI proves loading/digests, approvals/restart, policy,
+ URL pinning, audit permissions, sandbox plans and keyring. Release matrix
+ produces every supported artifact (11 registry platform keys) and checksum.
+4. Broad spec/code review, address feedback, merge ONE remaining TinySecurity PR,
+ dispatch semantic release workflow. Verify published artifacts by real loader
+ allow/deny/callback flow. Only now can production host pins change.
+
+## Task 12 — Host pins and config/command/redaction migration
+
+**Files:** registry security record, `scripts/ci/check-module-pins.mjs`,
+`modules/security{,_config,_host}.rs`, `security/live_policy.rs`, schema/migrations,
+`security/policy/`, network/fs/shell callers and redaction consumers.
+
+1. Root pins released owner gitlink/version/checksums together and validates
+ monotonic/pin/feature/bus-only checks; no local artifact digest admission.
+2. Implement complete scoped translator and client; bind immutable policy IDs
+ to host verified context. Tenant settings are never process-global init policy.
+3. Route tools/in-tool checks to coarse Evaluate/batched Check and registered
+ path scopes. Cache only explicitly cacheable static decisions by generation,
+ complete fingerprint and verified caller scope; invalidate on activation.
+4. Replace host redactors at util/redact, approval/redact, security/core/scrub/pii,
+ core/log_redaction, registry/denials, Composio redact, credential_scrub and URL
+ helpers. Port characterization tests, delete duplicated implementations only
+ after native parity; suppress sensitive logging while module unavailable.
+5. Install pinned URL transport/egress/scans at every Task 5 consumer and
+ agent/bus.rs, session_host/runtime/run_loop.rs, mcp/registry. Verify secret
+ suppression in Sentry/logs/approval/audit and real redirect/rebinding denial.
+
+## Task 13 — Host approval adapter, middleware and frontend
+
+**Files:** `security/approval/{gate,gate_intercept,store,schemas,rpc}.rs` and
+related fragments; `agent/tinyagents/middleware/tinysecurity.rs`, middleware.rs,
+`tools/agent_policy/`, `agent/tool_policy.rs`, middleware/{approval,tool_policy}.rs;
+`app/src/components/settings/panels/ApprovalHistoryPanel.tsx`, locale resources,
+`app/test/e2e/specs/security-approval.spec.ts`.
+
+1. Keep approval RPC/event compatibility; store/prompt facades serve scoped
+ callbacks. Module owns transitions/expiry/flow trust/log, host parks/resumes.
+ Preserve cancellation and exactly-one execution outcome; no auto_approve bypass.
+2. Register one TinySecurityMiddleware before effect-capable middleware. Map
+ module decisions to tinyagents PolicyDecision, bind recorded tool/rule inputs.
+ Delete duplicate agent_policy/ToolPolicyPosture wrappers and three disallowed
+ checks. Keep declarative ToolRules and one scope/dispatch adapter.
+3. UI displays auto:jev verdicts/scores and pending/expiry state with shadcn
+ primitives, useT and real translations. Cover prompt/decide/history/badge via
+ mocked desktop E2E helpers and existing approval component/API suites.
+4. Run i18n:check, i18n:english:check and i18n coverage; no user text/IDs in analytics.
+
+## Task 14 — Every untrusted process uses approved TinyBox execution
+
+**Host files:** sandbox/{ops,types,grants,docker}.rs,
+`agent/host_runtime.rs`, `cron/scheduler/shell_job.rs`, `hooks/host.rs`,
+`runtime/python_server/{server,kompress}.rs`, `runtime/pool/`,
+`tools/impl/system/install_tool.rs`, MCP/runtime/flow host adapters.
+**Owner seams:** TinyMCP transport/stdio/mod.rs; TinyRuntime pool/worker.rs and
+tinyruntime-pyserver/src/server.rs; TinyFlows caps/host/script/runner.rs;
+TinyComputer browser process startup/egress contracts.
+
+1. Replace resolve_* with module Plan→TinyBox BoxSpec/Placement conversion.
+ Real `SandboxCapabilities::is_suitable_for_untrusted_code()` gates spawn;
+ unsupported or Inactive is denial for untrusted tiers. Replace handwritten
+ DockerRuntime docker run with tinybox_docker::OneShot. Keep execution host-side.
+2. Maintain a committed spawner inventory from Command::new/process spawn search.
+ Each cron/MCP stdio/runtime worker+pyserver+compression/command hook/flow script/
+ installer/downloaded skill/browser process row names source trust, plan,
+ executor and test. Launch probes must not become execution bypasses.
+3. Where owner lacks process-launch injection, add owner seam and upstream tests,
+ merge/release/pin first. No direct stdio/worker spawn bypass in composed product.
+4. Real tests attempt protected file access/network from each untrusted family;
+ confirm denial inside the actual OS jail/namespace/AppContainer/Seatbelt.
+ KVM job tests microvm; Docker integration tests verify grants and network.
+ SaaS+env off ordering/boot_guard stays pinned. Explicit unsupported platforms
+ deny instead of silently skipping product behavior.
+
+## Task 15 — Host audit, scan/consequence gates and crypto facades
+
+**Files:** shell audit consumers, `mcp/audit/`, security encryption/keyring/device/
+pairing adapters, web3/seams.rs, x402/seams.rs, TinyComputer/TinySkills adapters.
+
+1. Route policy/approval/judge/shell/sandbox changes into one module audit. MCP
+ protocol telemetry remains TinyMCP-owned; policy events forward to same audit
+ stream. Record this ownership in ADR, remove duplicate policy logs.
+2. Feed TinyComputer consequence/payment gates and TinySkills scan verdicts into
+ policy/approval before execution. Add upstream callback contracts where needed;
+ no documented bypass can substitute for a missing enforcement seam.
+3. Replace crypto/keyring/pairing implementations with bus facades; preserve
+ encryption.* and web3/x402 consumers. Port all fixture/migration tests before
+ deleting old code; device networking/credentials/consent surface remain host.
+4. Remove obsolete aliases, unused redaction/PII helpers, unused_import allowances,
+ config fields and old tests only together with passing replacement coverage.
+
+## Task 16 — Final matrix, documentation and completion evidence
+
+**Files:** `.github/workflows/{security-native,ci-full,test-reusable}.yml`,
+`scripts/ci/security-native-fixture.sh`, dependency/pin checks; AGENTS ownership
+(CLAUDE symlink), `gitbooks/developing/loadable-modules.md`,
+`gitbooks/features/{approval-gate,privacy-and-security}.md`, security/approval/
+sandbox README files, `platform/about_app/`; generated docs via scripts only.
+
+1. Native host matrix loads RELEASED module, tests tampered digest, fault/timeout
+ latched deny with zero retry, two-tenant isolation, approval RPC/event/restart,
+ judge stub, OS command/path semantics, pinned URL transport, audit permissions,
+ crypto/keyring and actual sandbox effects. No mocked native module or isolation.
+2. Run pnpm rust:layout; feature forwarding, module pin/monotonic and bus-only
+ checks; product-feature enabled/disabled builds; targeted Rust/frontend/E2E
+ suites, changed-line coverage ≥80%. Owner per-file requirement remains ≥90%.
+3. Write GitBook/docs accurately, including real privacy/SaaS/judge/fault behavior,
+ approved release count/builds, callback ownership and isolation limitations.
+ Run pnpm docs:generate and pnpm docs:check. Review entire branch, then independent
+ completion verification of all claimed checks. Keep PR #7331 ready for review.
+4. Trackers close only when all engines, released artifacts, migrated consumers,
+ every spawner row, deleted duplicates, cross-OS tests and docs are evidenced.
+ Any absent backend/test/release remains an explicit open task, never DONE.
diff --git a/gitbooks/developing/embed/api-index.json b/gitbooks/developing/embed/api-index.json
index 76919ff831f..2fa0d37e1ad 100644
--- a/gitbooks/developing/embed/api-index.json
+++ b/gitbooks/developing/embed/api-index.json
@@ -76,6 +76,7 @@
"ModelDefaults",
"OpenError",
"PendingApproval",
+ "PermissionFuture",
"PermissionLevel",
"PickListenPortError",
"ProfileError",
@@ -126,6 +127,7 @@
"TrustedAccess",
"TrustedAutomationSource",
"Turn",
+ "TurnCancellation",
"TurnContext",
"TurnOutcome",
"TurnRequest",
@@ -136,6 +138,8 @@
"absolute",
"agent_progress",
"artifacts",
+ "budget",
+ "cancellation",
"channels",
"chat_surface",
"complete",
@@ -143,20 +147,25 @@
"cron",
"embeddings",
"events",
+ "fanout",
"identity",
"install_backend_transport",
"installed_backend_transport",
"memory",
"modules",
+ "observe",
"process",
"profiles",
"providers",
+ "repository",
+ "routing",
"run_from_args",
"schema_for_rpc_method",
"seams",
"session_store",
"skill_registry",
- "stream"
+ "stream",
+ "structured"
],
"builder_setters": [
{
@@ -435,7 +444,7 @@
"skills": true,
"storage-file": false,
"storage-mongodb": false,
- "storage-sqlite": false,
+ "storage-sqlite": true,
"tinymemes": true,
"voice": false,
"web3": false,
diff --git a/gitbooks/developing/embed/api-index.md b/gitbooks/developing/embed/api-index.md
index 3e386b0c6d6..9df96592b9c 100644
--- a/gitbooks/developing/embed/api-index.md
+++ b/gitbooks/developing/embed/api-index.md
@@ -79,6 +79,7 @@ Generate the complete local Rust API reference with `cargo doc -p openhuman-embe
- [`ModelDefaults`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`OpenError`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`PendingApproval`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`PermissionFuture`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`PermissionLevel`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`PickListenPortError`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`ProfileError`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
@@ -129,6 +130,7 @@ Generate the complete local Rust API reference with `cargo doc -p openhuman-embe
- [`TrustedAccess`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`TrustedAutomationSource`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`Turn`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`TurnCancellation`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`TurnContext`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`TurnOutcome`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`TurnRequest`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
@@ -139,6 +141,8 @@ Generate the complete local Rust API reference with `cargo doc -p openhuman-embe
- [`absolute`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`agent_progress`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`artifacts`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`budget`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`cancellation`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`channels`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`chat_surface`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`complete`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
@@ -146,20 +150,25 @@ Generate the complete local Rust API reference with `cargo doc -p openhuman-embe
- [`cron`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`embeddings`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`events`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`fanout`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`identity`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`install_backend_transport`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`installed_backend_transport`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`memory`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`modules`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`observe`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`process`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`profiles`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`providers`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`repository`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`routing`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`run_from_args`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`schema_for_rpc_method`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`seams`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`session_store`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`skill_registry`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`stream`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`structured`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
The compiled capability report describes this build:
@@ -187,7 +196,7 @@ The compiled capability report describes this build:
"skills": true,
"storage-file": false,
"storage-mongodb": false,
- "storage-sqlite": false,
+ "storage-sqlite": true,
"tinymemes": true,
"voice": false,
"web3": false,
diff --git a/gitbooks/developing/embed/capability-matrix.md b/gitbooks/developing/embed/capability-matrix.md
index aba183fbf7e..c6219821881 100644
--- a/gitbooks/developing/embed/capability-matrix.md
+++ b/gitbooks/developing/embed/capability-matrix.md
@@ -25,7 +25,7 @@ This matrix comes from the default-feature compiled capability-report example. R
| `skills` | Yes |
| `storage-file` | No |
| `storage-mongodb` | No |
-| `storage-sqlite` | No |
+| `storage-sqlite` | Yes |
| `tinymemes` | Yes |
| `voice` | No |
| `web3` | No |
diff --git a/gitbooks/developing/embed/cookbook.md b/gitbooks/developing/embed/cookbook.md
index 6b7caefc55b..3462bef8af5 100644
--- a/gitbooks/developing/embed/cookbook.md
+++ b/gitbooks/developing/embed/cookbook.md
@@ -76,6 +76,14 @@ Lean runtime without background services.
Run: `cargo run -p openhuman-embed --example lean_headless`
+## Linux agent fleet memory and latency
+
+Measure retained runtime-owned agents using loopback inference and two worker threads.
+
+[Source](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/examples/linux_fleet.rs) · offline on Linux; use a fresh constrained cgroup for release measurements.
+
+Run: `cargo run -p openhuman-embed --example linux_fleet`
+
## Connect an actual MCP protocol stub over loopback
Connect an actual MCP protocol stub over loopback.
diff --git a/gitbooks/developing/loadable-modules.md b/gitbooks/developing/loadable-modules.md
index 0bbe97332cd..2efa748bee3 100644
--- a/gitbooks/developing/loadable-modules.md
+++ b/gitbooks/developing/loadable-modules.md
@@ -13,7 +13,10 @@ The trade is explicit. A module shares the core's address space and crash domain
## The registry
-`crates/openhuman-core/src/modules/registry/` compiles in fourteen records. Only this registry may select an artifact. Nothing reads a module name from config and goes looking for it.
+`crates/openhuman-core/src/modules/registry/` compiles in fourteen released
+records, plus the TinySecurity admission record when `security-module` is
+enabled. Only this registry may select an artifact. Nothing reads a module name
+from config and goes looking for it.
| Module | Provides |
| --- | --- |
@@ -34,6 +37,21 @@ Each record carries a version, the release it came from, and one `PlatformAsset`
Every asset carries a SHA-256 copied verbatim from the published release's own checksum file. Do not compute a replacement pin from a local build. The digest must describe the artifact the release workflow signed, not the one on your machine.
+### TinySecurity migration
+
+`vendor/tinysecurity` owns the native security engine. The `security-module`
+feature links its transport-free `tinysecurity-bus` contract and forwards through
+the host library chain. Asynchronous filesystem checks use immutable scopes
+containing host-authorized roots and internal-state reservations. The client
+requires artifact attestation and fails closed if loading or validation fails.
+
+The production registry pins TinySecurity v0.2.2 and its 11 supported host
+archives using digests copied from the published checksum manifest. Native CI
+loads these released archives through digest admission. Explicit local fixtures
+remain available for module development and never replace production pins.
+Shell policy, approvals, redaction, and crypto still run through their existing
+host implementations.
+
## Resolution order
Each step avoids the cost of the next:
diff --git a/llms-full.txt b/llms-full.txt
index 1658cc4d625..760b0e639c2 100644
--- a/llms-full.txt
+++ b/llms-full.txt
@@ -2441,6 +2441,85 @@ The [cookbook](https://github.com/tinyhumansai/openhuman/blob/main/gitbooks/deve
Hosts supply transport, credentials and application resources. Runtime settings establish shared defaults; agents narrow provider, access, prompt and tool behavior. Use ProfileRuntime when users require separate credentials and workspaces.
+## Cancelling one turn
+
+Acquire `Turn::cancellation_handle()` before sending a turn. The handle is
+cloneable and `cancel().await` waits for the turn to stop and for tracked
+commands to be reaped. The agent remains available for later turns:
+
+
+
+Cancellation is scoped to this turn, including while waiting for inference.
+Turns with a cancellation handle use owned interpreter subprocesses rather
+than the Node/Python pool, which has no acknowledged per-job abort API. This
+trades warm-worker reuse for awaited cleanup; ordinary turns retain pooling.
+The `meter` callback fires once on cancellation or a dropped send future,
+with `None` when dispatch has not supplied usage yet.
+
+Before send, cancellation prevents dispatch; after completion it is a no-op.
+On Unix, the built-in shell, Node, Python and npm commands kill their process
+group, including descendants. Other platforms stop the direct command. Host
+tools that spawn independent tasks or processes must provide their own cleanup;
+MCP server lifecycles remain owned by the agent. Keep polling `send()` while
+awaiting cancellation, for example in a spawned task.
+
+## Scoped worker hooks
+
+Hooks can be supplied at three levels: `RuntimeBuilder::tool_hook` /
+`post_turn_hook` for all agents, `AgentSpec::tool_hook` / `post_turn_hook`
+for one agent, and `Turn::tool_hook` / `post_turn_hook` for one dispatch.
+Tool callbacks run in that order. Named agent updates replace only that agent’s callback; per-turn callbacks are additional.
+`ToolHookContext` carries the agent/session identity when known, and `cwd`
+follows the execution workspace descriptor (including `Turn::cwd`), falling
+back to the embedding context's configured action root.
+The agent and turn hooks are never installed in the global registry, so
+concurrent workers and later turns do not pick up one another's callbacks.
+Post-turn callbacks run asynchronously with an owned session snapshot.
+Independently spawned tasks that build sessions must explicitly inherit
+`openhuman_core::agent::hooks::HookScope` to carry scoped hooks.
+
+Gateway attribution headers can be attached to `Route::header(name, value)`
+and used with `Turn::route`, or with `Provider::routed(route)` on an agent.
+They follow only that route's endpoint and are never saved to configuration,
+sent to background providers, or included as values in `Route`'s `Debug`.
+
+### Inline permission and usage policy
+
+`AgentSpec::can_use_tool` and `Turn::can_use_tool` await a host callback before
+executing each tool. The callback can wait for an approval UI and return
+`ToolHookDecision::Proceed`, `Deny`, or `ProceedWith`. It owns that wait;
+returning `Ask` denies execution. These callbacks add to existing tool policies,
+and a turn callback cannot override an agent denial.
+
+`AgentSpec::stop_hook` and `Turn::stop_hook` receive cumulative usage after each
+completed model call. Return `StopDecision::Continue` to observe usage, or
+`Stop` to prevent subsequent calls. Completed tool rounds may still execute;
+this is an after-call budget boundary, so hosts must refuse an already exhausted
+budget before sending a turn. Provider-reported charges remain authoritative,
+including known zero; missing charges remain unknown unless pricing is known.
+A policy stop uses a deterministic partial summary rather than spending on
+final-answer repair calls.
+
+### Per-turn tools and subprocess environment
+
+`Turn::tools` replaces this turn's host tool belt, including attached sources.
+An empty belt revokes host tools; the next turn returns to the agent's belt.
+Builtin tools still follow the agent definition. This supplies dynamic tools for
+in-process hosts; statically declared MCP servers retain their creation-time
+configuration.
+
+`Turn::tool_env` supplies the base environment of owned builtin subprocesses.
+Variables absent from it are not inherited from the daemon. The builtin command
+builders retain their own security/runtime additions, including Git restrictions,
+managed interpreter paths and scratch directories. Scoped turns bypass Node and
+Python pools, which cannot acknowledge per-job cancellation or swap a job's
+process environment. A host tool that spawns a separate Tokio task must explicitly
+carry the command environment and cleanup scopes into that task.
+
+Standalone exact source pins and generated Cargo patches: [consumer setup](CONSUMERS.md).
+Ordered fallbacks and required exploration: [routing](ROUTING.md).
+Host telemetry and the existing exporter: [observers](OBSERVERS.md).
+
# Embedding OpenHuman
@@ -2529,6 +2608,14 @@ Lean runtime without background services.
Run: `cargo run -p openhuman-embed --example lean_headless`
+## Linux agent fleet memory and latency
+
+Measure retained runtime-owned agents using loopback inference and two worker threads.
+
+[Source](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/examples/linux_fleet.rs) · offline on Linux; use a fresh constrained cgroup for release measurements.
+
+Run: `cargo run -p openhuman-embed --example linux_fleet`
+
## Connect an actual MCP protocol stub over loopback
Connect an actual MCP protocol stub over loopback.
@@ -2661,7 +2748,7 @@ This matrix comes from the default-feature compiled capability-report example. R
| `skills` | Yes |
| `storage-file` | No |
| `storage-mongodb` | No |
-| `storage-sqlite` | No |
+| `storage-sqlite` | Yes |
| `tinymemes` | Yes |
| `voice` | No |
| `web3` | No |
@@ -2799,6 +2886,7 @@ Generate the complete local Rust API reference with `cargo doc -p openhuman-embe
- [`ModelDefaults`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`OpenError`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`PendingApproval`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`PermissionFuture`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`PermissionLevel`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`PickListenPortError`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`ProfileError`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
@@ -2849,6 +2937,7 @@ Generate the complete local Rust API reference with `cargo doc -p openhuman-embe
- [`TrustedAccess`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`TrustedAutomationSource`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`Turn`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`TurnCancellation`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`TurnContext`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`TurnOutcome`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`TurnRequest`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
@@ -2859,6 +2948,8 @@ Generate the complete local Rust API reference with `cargo doc -p openhuman-embe
- [`absolute`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`agent_progress`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`artifacts`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`budget`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`cancellation`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`channels`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`chat_surface`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`complete`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
@@ -2866,20 +2957,25 @@ Generate the complete local Rust API reference with `cargo doc -p openhuman-embe
- [`cron`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`embeddings`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`events`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`fanout`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`identity`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`install_backend_transport`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`installed_backend_transport`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`memory`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`modules`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`observe`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`process`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`profiles`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`providers`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`repository`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`routing`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`run_from_args`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`schema_for_rpc_method`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`seams`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`session_store`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`skill_registry`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
- [`stream`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
+- [`structured`](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/src/lib.rs)
The compiled capability report describes this build:
@@ -2907,7 +3003,7 @@ The compiled capability report describes this build:
"skills": true,
"storage-file": false,
"storage-mongodb": false,
- "storage-sqlite": false,
+ "storage-sqlite": true,
"tinymemes": true,
"voice": false,
"web3": false,
@@ -3003,6 +3099,7 @@ The compiled capability report describes this build:
- [A per-agent post-turn hook observes completed turns](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/examples/hooks.rs): A per-agent post-turn hook observes completed turns. (offline; optional live via OPENHUMAN_EXAMPLE_LIVE=1 and BASE_URL/API_KEY/MODEL.)
- [Host tools and HostOnly keep the advertised tool catalog exact](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/examples/host_tools.rs): Host tools and HostOnly keep the advertised tool catalog exact. (offline with loopback stubs; no live path.)
- [Lean runtime without background services](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/examples/lean_headless.rs): Lean runtime without background services. (offline with loopback stubs; optional live via OPENHUMAN_EXAMPLE_LIVE.)
+- [Linux agent fleet memory and latency](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/examples/linux_fleet.rs): Measure retained runtime-owned agents using loopback inference and two worker threads. (offline on Linux; use a fresh constrained cgroup for release measurements.)
- [Connect an actual MCP protocol stub over loopback](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/examples/mcp.rs): Connect an actual MCP protocol stub over loopback. (offline with loopback stubs; no live path.; feature: mcp)
- [Tenant scoped memory facade with an in-memory engine](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/examples/memory.rs): Tenant scoped memory facade with an in-memory engine. (offline with loopback stubs; no live path.)
- [SaaS profiles isolate conversation history](https://github.com/tinyhumansai/openhuman/blob/main/crates/openhuman-embed/examples/profiles.rs): SaaS profiles isolate conversation history. (offline with loopback stubs; no live path.)
diff --git a/package.json b/package.json
index fc0d3a402fb..6d18463fcd3 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
"test:install-ps1": "pwsh -NoProfile -File scripts/tests/OpenHumanWindowsInstall.Tests.ps1",
"rust:check": "pnpm --filter openhuman-app rust:check",
"rust:clippy": "cargo clippy -p openhuman -p openhuman-cli -p openhuman-tinyhumans -- -D warnings && pnpm --filter openhuman-app rust:clippy",
- "rust:layout": "node scripts/ci/check-openhuman-rust-layout.mjs && node scripts/ci/check-crate-chain.mjs && node scripts/ci/check-storage-bypass.mjs",
+ "rust:layout": "node scripts/ci/check-security-module-dependencies.mjs && node scripts/ci/check-openhuman-rust-layout.mjs && node scripts/ci/check-crate-chain.mjs && node scripts/ci/check-storage-bypass.mjs",
"rust:ignored-tests": "node scripts/ci/check-ignored-tests.mjs",
"dep:audit": "bash scripts/dep-audit/run.sh",
"agent:runtime-boundary": "node scripts/ci/check-agent-runtime-boundary.mjs",
@@ -78,6 +78,7 @@
"devDependencies": {
"gpt-tokenizer": "^3.4.0",
"husky": "^9.1.7",
+ "smol-toml": "1.6.1",
"tsx": "^4.20.3",
"ws": "^8.20.0"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1749b185551..b812ccecb16 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -29,6 +29,9 @@ importers:
husky:
specifier: ^9.1.7
version: 9.1.7
+ smol-toml:
+ specifier: 1.6.1
+ version: 1.6.1
tsx:
specifier: ^4.20.3
version: 4.21.0
diff --git a/scripts/__tests__/check-security-module-dependencies.test.mjs b/scripts/__tests__/check-security-module-dependencies.test.mjs
new file mode 100644
index 00000000000..bf7d7b50bf9
--- /dev/null
+++ b/scripts/__tests__/check-security-module-dependencies.test.mjs
@@ -0,0 +1,63 @@
+import assert from 'node:assert/strict';
+import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+import { test } from 'node:test';
+import { checkManifest, checkRepository, HOST_MANIFESTS } from '../ci/check-security-module-dependencies.mjs';
+
+const forms = [
+ '[dependencies]\ntinysecurity-policy = "1"',
+ '[build-dependencies]\nalias = { package = "tinysecurity-policy", version = "1" }',
+ '[dev-dependencies.alias]\npackage = \'tinysecurity-crypto\'\nversion = "1"',
+ '[target.\'cfg(windows)\'.dependencies]\nalias = {\n package = "tinysecurity-policy",\n version = "1"\n}',
+ '[target.\'cfg(unix)\'.build-dependencies."alias"]\npackage = "tinysecurity-keyring"',
+ '[target.\'cfg(unix)\'.dev-dependencies]\n"tinysecurity-policy" = "1"',
+ '[workspace.dependencies]\nalias = { package = \'tinysecurity-policy\', version = "1" }',
+ '[patch.crates-io]\nalias = { package = "tinysecurity-policy", path = "../policy" }',
+ '[patch."https://github.com/example/repo".alias]\npackage = "tinysecurity-policy"\npath = "../policy"',
+];
+for (const [i, source] of forms.entries()) {
+ test(`rejects forbidden security crate TOML form ${i}`, () => {
+ assert.equal(checkManifest(source).length, 1);
+ });
+}
+test('allows bus, alias to bus and unrelated packages', () => {
+ assert.deepEqual(checkManifest('[dependencies]\ntinysecurity-bus = "1"\nfoo = { package = "tinysecurity-bus", version = "1" }\nother = "1"'), []);
+});
+test('package alias cannot hide forbidden crate behind bus name', () => {
+ assert.equal(checkManifest('[dependencies]\ntinysecurity-bus = { package = "tinysecurity-policy", version = "1" }').length, 1);
+});
+test('rejects the internal umbrella crate and its aliases', () => {
+ assert.equal(checkManifest('[dependencies]\ntinysecurity = "1"').length, 1);
+ assert.equal(checkManifest('[dependencies]\ninternal = { package = "tinysecurity", version = "1" }').length, 1);
+});
+test('invalid TOML refuses a pass', () => assert.throws(() => checkManifest('[dependencies\n'), /./));
+
+function fixture() {
+ const root = mkdtempSync(join(tmpdir(), 'security-dependencies-'));
+ for (const manifest of HOST_MANIFESTS) {
+ mkdirSync(join(root, manifest, '..'), { recursive: true });
+ writeFileSync(join(root, manifest), '[dependencies]\nserde = "1"\n');
+ }
+ return root;
+}
+test('repository examines root and host manifests while excluding vendor and worktrees', () => {
+ const root = fixture();
+ try {
+ for (const excluded of ['vendor/module', 'worktrees/other']) {
+ mkdirSync(join(root, excluded), { recursive: true });
+ writeFileSync(join(root, excluded, 'Cargo.toml'), forms[0]);
+ }
+ assert.deepEqual(checkRepository(root), []);
+ writeFileSync(join(root, 'crates/openhuman-cli/Cargo.toml'), forms[1]);
+ assert.equal(checkRepository(root).length, 1);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+test('missing required manifest and unreadable root refuse vacuous success', () => {
+ const root = fixture();
+ try {
+ rmSync(join(root, 'crates/openhuman-app/Cargo.toml'));
+ assert.throws(() => checkRepository(root), /Cargo.toml/);
+ assert.throws(() => checkRepository(join(root, 'absent')), /ENOENT/);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/scripts/__tests__/module-pins.test.mjs b/scripts/__tests__/module-pins.test.mjs
index dc1e7ce7599..828f15019ef 100644
--- a/scripts/__tests__/module-pins.test.mjs
+++ b/scripts/__tests__/module-pins.test.mjs
@@ -554,3 +554,13 @@ test("the submodule probe is not fooled by the superproject above it", () => {
rmSync(root, { recursive: true, force: true });
}
});
+
+
+test("conditional registry entries remain covered by the pin gate", () => {
+ const source = `pub const ALL: &[ModuleRecord] = &[
+ TINYSEARCH,
+ #[cfg(any(feature = "security-module", feature = "test-module"))]
+ TINYSECURITY,
+ ];`;
+ assert.deepEqual(parseAllList(source), ["TINYSEARCH", "TINYSECURITY"]);
+});
diff --git a/scripts/__tests__/self-hosted-lanes.test.mjs b/scripts/__tests__/self-hosted-lanes.test.mjs
index 2019314eafe..36525261f77 100644
--- a/scripts/__tests__/self-hosted-lanes.test.mjs
+++ b/scripts/__tests__/self-hosted-lanes.test.mjs
@@ -143,11 +143,12 @@ test("a core-only change still installs the node deps rust-core-coverage's mock
`${plan.profile}: rust-core-coverage needs a pnpm install`,
);
assert.equal(install.when, true, `${plan.profile}: that install runs`);
- // Exactly one install per profile: ex63 lanes share one checkout.
+ // ex63 lanes share a checkout; hosted scripts and Rust coverage run
+ // separately, and each needs the TOML parser/mock backend dependencies.
const installs = [...checks.values()].filter(
(c) => c.when && c.run === "pnpm install --frozen-lockfile",
);
- assert.equal(installs.length, 1, plan.profile);
+ assert.equal(installs.length, plan.profile === "ex63" ? 1 : 2, plan.profile);
}
const hosted = buildPlan({ profile: "hosted", areas: coreOnly });
const sub = selectLanes(hosted, ["rust-cov"]);
@@ -651,3 +652,18 @@ test("the rust-core path filter arms the lane for every crate the tui depends on
assert.ok(block.includes(`'crates/openhuman-${crate}/**'`), crate);
}
});
+
+
+test("an app-only manifest change runs the security guard with its parser prerequisite", () => {
+ const areas = { ...NONE, rustTauri: true };
+ for (const profile of ["hosted", "ex63"]) {
+ const plan = buildPlan({ profile, areas, env: EX63_ENV });
+ const frontend = plan.lanes.find((lane) => lane.name === "frontend");
+ const guard = frontend.checks.find((check) => check.name === "security-module-dependencies");
+ const install = frontend.checks.find((check) => check.name === "pnpm-install");
+ assert.equal(guard.when, true, profile);
+ assert.equal(install.when, true, profile);
+ assert.ok(guard.needs.includes("pnpm-install"));
+ assert.deepEqual(validatePlan(plan), []);
+ }
+});
diff --git a/scripts/__tests__/stage-modules.test.mjs b/scripts/__tests__/stage-modules.test.mjs
index 06b477574dc..cf2b00b4742 100644
--- a/scripts/__tests__/stage-modules.test.mjs
+++ b/scripts/__tests__/stage-modules.test.mjs
@@ -18,7 +18,10 @@ import { execFileSync } from "node:child_process";
import { gzipSync } from "node:zlib";
import { test } from "node:test";
-import { readRegistrySource } from "../ci/self-hosted/test-module-assets.mjs";
+import {
+ parseReleaseUrls,
+ readRegistrySource,
+} from "../ci/self-hosted/test-module-assets.mjs";
import { parseAllList } from "../lib/module-pins.mjs";
import {
bundledAssets,
@@ -41,6 +44,27 @@ const HOST_KEYS = [
"windows-11-arm64",
];
+test("an unpublished module cannot borrow the next record's release URL", () => {
+ const source = `const PENDING: ModuleRecord = ModuleRecord {
+ id: "pending",
+ version: "1.0.0",
+ release_url: "",
+ assets: &[],
+};
+const RELEASED: ModuleRecord = ModuleRecord {
+ id: "released",
+ version: "2.0.0",
+ release_url: "https://github.com/example/released/releases/tag/v2.0.0",
+ assets: &[],
+};`;
+ const urls = parseReleaseUrls(source);
+ assert.equal(urls.has("pending"), false);
+ assert.equal(
+ urls.get("released"),
+ "https://github.com/example/released/releases/tag/v2.0.0",
+ );
+});
+
for (const hostKey of HOST_KEYS) {
test(`every compiled module has one pinned ${hostKey} asset`, () => {
const source = readRegistrySource();
diff --git a/scripts/ci/check-module-pins.mjs b/scripts/ci/check-module-pins.mjs
index 669f42fe3b1..aa4d4ea2e0e 100755
--- a/scripts/ci/check-module-pins.mjs
+++ b/scripts/ci/check-module-pins.mjs
@@ -75,6 +75,7 @@ const ROOT = resolve(process.argv[2] ?? join(HERE, "..", ".."));
// scripts/ci/module-provider-pins.json (their release, and the commit of
// `builtAgainst` that release was built from, which must be the host's pin).
const PIN_MAP = {
+ tinysecurity: { submodule: "vendor/tinysecurity" },
tinysearch: { submodule: "vendor/tinysearch" },
tinycomputer: { submodule: "vendor/tinycomputer" },
tinybox: { submodule: "vendor/tinybox" },
diff --git a/scripts/ci/check-security-module-dependencies.mjs b/scripts/ci/check-security-module-dependencies.mjs
new file mode 100644
index 00000000000..b69d42d823c
--- /dev/null
+++ b/scripts/ci/check-security-module-dependencies.mjs
@@ -0,0 +1,75 @@
+// Enforce the security module's bus-only boundary before migration begins.
+// Parse TOML rather than scanning lines: Cargo supports package aliases,
+// per-dependency tables, quoted keys and target-specific dependencies.
+import { readFileSync, readdirSync } from 'node:fs';
+import { dirname, join, resolve } from 'node:path';
+import { fileURLToPath, pathToFileURL } from 'node:url';
+import { parse } from 'smol-toml';
+
+export const HOST_MANIFESTS = [
+ 'Cargo.toml',
+ ...['core', 'embed', 'tinyhumans', 'rpc', 'app', 'cli', 'tui'].map(
+ (name) => `crates/openhuman-${name}/Cargo.toml`
+ ),
+];
+const DEPENDENCY_TABLES = new Set(['dependencies', 'build-dependencies', 'dev-dependencies']);
+
+export function checkManifest(source, filename = 'Cargo.toml') {
+ const manifest = parse(source);
+ const violations = [];
+ function dependencies(table, location) {
+ for (const [alias, dependency] of Object.entries(table ?? {})) {
+ const pkg = typeof dependency === 'object' ? dependency.package ?? alias : alias;
+ if (typeof pkg !== 'string') throw new Error(`${filename}: invalid package in ${location}.${alias}`);
+ if ((pkg === 'tinysecurity' || pkg.startsWith('tinysecurity-')) && pkg !== 'tinysecurity-bus') {
+ violations.push(`${filename}: ${location}.${alias} links ${pkg}; only tinysecurity-bus is allowed`);
+ }
+ }
+ }
+ for (const name of DEPENDENCY_TABLES) {
+ dependencies(manifest[name], name);
+ dependencies(manifest.workspace?.[name], `workspace.${name}`);
+ for (const [target, table] of Object.entries(manifest.target ?? {})) {
+ dependencies(table[name], `target.${target}.${name}`);
+ }
+ }
+ for (const [registry, table] of Object.entries(manifest.patch ?? {})) {
+ dependencies(table, `patch.${registry}`);
+ }
+ // Cargo's legacy replacement table also contains package references.
+ for (const [key, dependency] of Object.entries(manifest.replace ?? {})) {
+ const alias = key.split(':')[0];
+ dependencies({ [alias]: dependency }, 'replace');
+ }
+ return violations;
+}
+
+export function checkRepository(root) {
+ const manifests = new Set(HOST_MANIFESTS);
+ function walk(dir) {
+ for (const entry of readdirSync(join(root, dir), { withFileTypes: true })) {
+ if (['target', 'vendor', 'worktrees', '.git'].includes(entry.name)) continue;
+ const path = join(dir, entry.name);
+ if (entry.isDirectory()) walk(path);
+ else if (entry.name === 'Cargo.toml') manifests.add(path);
+ }
+ }
+ // Only the host's crates tree is included; vendored repositories and other
+ // workflow checkouts independently enforce their own dependency boundaries.
+ walk('crates');
+ return [...manifests].flatMap((path) => checkManifest(readFileSync(join(root, path), 'utf8'), path));
+}
+
+if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
+ const root = resolve(process.argv[2] ?? join(dirname(fileURLToPath(import.meta.url)), '../..'));
+ try {
+ const violations = checkRepository(root);
+ if (violations.length) {
+ console.error(violations.join('\n'));
+ process.exitCode = 1;
+ } else console.log('Security module dependency boundary: PASS');
+ } catch (error) {
+ console.error(`Security module dependency check refused unreadable or invalid input: ${error.message}`);
+ process.exitCode = 2;
+ }
+}
diff --git a/scripts/ci/module-pin-exemptions.json b/scripts/ci/module-pin-exemptions.json
index 8af2cdaf35e..9720a45edb0 100644
--- a/scripts/ci/module-pin-exemptions.json
+++ b/scripts/ci/module-pin-exemptions.json
@@ -11,16 +11,5 @@
"Delete an entry the moment the pins are reconciled. An exemption that has",
"stopped being true fails the gate too \u2014 `expect` must still match."
],
- "exemptions": [
- {
- "id": "tinywallet",
- "expect": "v0.7.4-6-gaccb920a",
- "reason": "The vendored tree includes six unreleased commits; the shipped v0.7.4 artifact remains the last published runtime module. Remove this exception when the next artifact is published and both pins advance."
- },
- {
- "id": "tinychannels",
- "expect": "v0.1.12-3-g5e3b2044",
- "reason": "The vendored tree includes three unreleased commits; the shipped v0.1.12 artifact remains the last published runtime module. Remove this exception when the next artifact is published and both pins advance."
- }
- ]
+ "exemptions": []
}
diff --git a/scripts/ci/product-features.txt b/scripts/ci/product-features.txt
index a780310460c..1b49f3a4cf2 100644
--- a/scripts/ci/product-features.txt
+++ b/scripts/ci/product-features.txt
@@ -54,6 +54,7 @@ documents
# this build trusts, and the `modules` RPC namespace. Required by `documents` —
# without it the document tools have nothing to call.
modules
+security-module
# Saved automation graphs: create/run/schedule + the workflow_builder and
# flow_discovery agents.
diff --git a/scripts/ci/security-native-fixture.sh b/scripts/ci/security-native-fixture.sh
new file mode 100644
index 00000000000..15b5327645a
--- /dev/null
+++ b/scripts/ci/security-native-fixture.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# Exercise the released module through the compiled registry and archive digest.
+# For explicit local module development, set OPENHUMAN_TEST_SECURITY_MODULE.
+set -euo pipefail
+
+if [[ -z "${OPENHUMAN_TEST_SECURITY_MODULE:-}" ]]; then
+ case "$(uname -s)" in
+ Darwin)
+ case "$(uname -m)" in
+ arm64) security_host="macos-15-arm64" ;;
+ x86_64) security_host="macos-15-x86_64" ;;
+ *) echo "Unsupported macOS architecture" >&2; exit 1 ;;
+ esac ;;
+ Linux)
+ case "$(uname -m)" in
+ aarch64) security_host="ubuntu-22.04-arm64" ;;
+ x86_64) security_host="ubuntu-22.04-x86_64" ;;
+ *) echo "Unsupported Linux architecture" >&2; exit 1 ;;
+ esac ;;
+ MINGW* | MSYS* | CYGWIN*) security_host="windows-2025-x86_64" ;;
+ *) echo "Unsupported native security fixture host" >&2; exit 1 ;;
+ esac
+ export OPENHUMAN_TEST_SECURITY_RELEASE_HOST="${OPENHUMAN_TEST_SECURITY_RELEASE_HOST:-$security_host}"
+fi
+
+export OPENHUMAN_TEST_SECURITY_FIXTURE_DIR="$HOME/.cache/openhuman-security-fixtures"
+mkdir -p "$OPENHUMAN_TEST_SECURITY_FIXTURE_DIR"
+
+bash scripts/ci-cancel-aware.sh cargo test -p openhuman --lib \
+ --no-default-features --features inference,web3,modules,security-module \
+ modules::security -- --include-ignored --nocapture
diff --git a/scripts/ci/self-hosted/lanes-plan.mjs b/scripts/ci/self-hosted/lanes-plan.mjs
index d0558e6ffb3..80c90458dff 100644
--- a/scripts/ci/self-hosted/lanes-plan.mjs
+++ b/scripts/ci/self-hosted/lanes-plan.mjs
@@ -261,10 +261,10 @@ export function buildPlan({ profile, areas, env = {}, isPullRequest = true }) {
checks: [
{
name: "pnpm-install",
- // ex63: also for a core change, so rust-core-coverage's mock backend
- // (scripts/mock-api-server.mjs imports `ws`) can start when no
- // frontend file changed. One install per VM: lanes share a checkout.
- when: areas.frontend || areas.i18n || areas.scripts || (ex63 && core),
+ // All Rust changes run the bus-only dependency guard, whose TOML
+ // parser is a root package dependency. ex63 shares this install
+ // with rust-core-coverage's mock backend; hosted lanes are separate.
+ when: areas.frontend || areas.i18n || areas.scripts || rust,
run: "pnpm install --frozen-lockfile",
},
{
@@ -309,6 +309,12 @@ export function buildPlan({ profile, areas, env = {}, isPullRequest = true }) {
run: "pnpm docs:test",
},
{ name: "docs-drift", when: areas.docs, run: "pnpm docs:check" },
+ {
+ name: "security-module-dependencies",
+ when: areas.scripts || rust,
+ needs: ["pnpm-install"],
+ run: "node scripts/ci/check-security-module-dependencies.mjs",
+ },
{
name: "scripts-self-tests",
when: areas.scripts,
diff --git a/scripts/ci/self-hosted/test-module-assets.mjs b/scripts/ci/self-hosted/test-module-assets.mjs
index 7741faabda0..73ce05ff55d 100644
--- a/scripts/ci/self-hosted/test-module-assets.mjs
+++ b/scripts/ci/self-hosted/test-module-assets.mjs
@@ -33,9 +33,12 @@ export function readRegistrySource(dir = REGISTRY_DIR) {
/** `release_url` per record id, from the same source text. */
export function parseReleaseUrls(src) {
const urls = new Map();
- const re = /id: "([^"]+)",[\s\S]*?release_url: "([^"]+)"/g;
- for (const m of src.matchAll(re)) {
- if (!urls.has(m[1])) urls.set(m[1], m[2]);
+ // Keep fields inside their record. A global regex can skip an empty URL
+ // and accidentally associate the following module's release with this id.
+ for (const record of parseRecords(src).values()) {
+ if (record.id && record.releaseUrl && !urls.has(record.id)) {
+ urls.set(record.id, record.releaseUrl);
+ }
}
return urls;
}
diff --git a/scripts/lib/module-pins.mjs b/scripts/lib/module-pins.mjs
index 2be1c53f59a..70abd414bb2 100644
--- a/scripts/lib/module-pins.mjs
+++ b/scripts/lib/module-pins.mjs
@@ -49,6 +49,9 @@ export function parseAllList(src) {
if (!block) throw new Error("registry.rs: could not find `pub const ALL`");
return block[1]
.replace(/\/\/[^\n]*/g, "")
+ // Inspect every possible feature set. Attributes annotate the next record;
+ // commas inside cfg(any(...)) must not split the record list.
+ .replace(/#\[cfg\([^\]]*\)\]/g, "")
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0);
diff --git a/tests/fixtures/security-redaction-corpus.json b/tests/fixtures/security-redaction-corpus.json
new file mode 100644
index 00000000000..785222d1488
--- /dev/null
+++ b/tests/fixtures/security-redaction-corpus.json
@@ -0,0 +1,104 @@
+{
+ "text": [
+ {
+ "case": "anthropic_key",
+ "input": "sk-ant-api03-abcdefghijklmnopqrstuvwxyz0123456789",
+ "needle": "abcdefghijklmnopqrstuvwxyz0123456789",
+ "removed_by": [
+ "prefix",
+ "identity_hash",
+ "log",
+ "host_scrub",
+ "denials",
+ "credential_middleware"
+ ],
+ "composio_secret": "sk-ant-api03-abcdefghijklmnopqrstuvwxyz0123456789"
+ },
+ {
+ "case": "email",
+ "input": "alice@example.com",
+ "needle": "alice@example.com",
+ "removed_by": [
+ "prefix",
+ "identity_hash",
+ "pii"
+ ],
+ "composio_secret": "unrelated-configured-key"
+ },
+ {
+ "case": "linux_home",
+ "input": "/home/alice/report.txt",
+ "needle": "alice",
+ "removed_by": [
+ "prefix",
+ "identity_hash",
+ "approval"
+ ],
+ "composio_secret": "unrelated-configured-key"
+ },
+ {
+ "case": "macos_home",
+ "input": "/Users/alice/report.txt",
+ "needle": "alice",
+ "removed_by": [
+ "prefix",
+ "identity_hash",
+ "approval"
+ ],
+ "composio_secret": "unrelated-configured-key"
+ },
+ {
+ "case": "windows_home",
+ "input": "C:\\Users\\alice\\report.txt",
+ "needle": "alice",
+ "removed_by": [
+ "prefix",
+ "identity_hash",
+ "approval"
+ ],
+ "composio_secret": "unrelated-configured-key"
+ },
+ {
+ "case": "windows_home_doubled",
+ "input": "C:\\\\Users\\\\alice\\\\report.txt",
+ "needle": "alice",
+ "removed_by": [
+ "prefix",
+ "identity_hash"
+ ],
+ "composio_secret": "unrelated-configured-key"
+ },
+ {
+ "case": "ordinary_text",
+ "input": "hello world",
+ "needle": "hello world",
+ "removed_by": [
+ "prefix",
+ "identity_hash"
+ ],
+ "composio_secret": "unrelated-configured-key"
+ }
+ ],
+ "urls": [
+ {
+ "case": "basic_auth_and_query",
+ "input": "https://alice:hunter2@api.example.com/v1?key=secret#private",
+ "util": "https://redacted:redacted@api.example.com/v1?key=secret#private",
+ "storage": "https://***@api.example.com/v1?***",
+ "migration": "https://api.example.com/v1",
+ "endpoint": "https://api.example.com",
+ "inference": "api.example.com",
+ "wallet": "https://api.example.com"
+ },
+ {
+ "case": "plain_url",
+ "input": "https://api.example.com/v1",
+ "util": "https://api.example.com/v1",
+ "storage": "https://api.example.com/v1",
+ "migration": "https://api.example.com/v1",
+ "endpoint": "https://api.example.com",
+ "inference": "api.example.com",
+ "wallet": "https://api.example.com"
+ }
+ ]
+}
diff --git a/tests/security_characterization_e2e.rs b/tests/security_characterization_e2e.rs
new file mode 100644
index 00000000000..8b4a1a6d17b
--- /dev/null
+++ b/tests/security_characterization_e2e.rs
@@ -0,0 +1,369 @@
+//! Phase-zero security wire characterization through the authenticated production
+//! router. No server socket, inference, Docker process, or OS keychain is used.
+//! Existing sibling domain suites cover command/path policy, gate TTL/origins,
+//! restart durability and sandbox precedence; these tests pin their RPC boundary.
+//! Coverage retained beside the owning domains:
+//! - approval/gate_tests.rs (+ gate_core_flow/gate_origin_intercept/gate_ttl_and_triage/
+//! gate_subagent/gate_forced tests): allowlist, park/decide, origin and TTL rules.
+//! - approval/store_persistence_tests.rs and store_flow_trust_tests.rs: restart
+//! durability and flow trust; gate_triage_tests.rs pins blanket bypass/no audit.
+//! - policy/policy_disabled_tests.rs, policy_paths_and_risk_tests.rs,
+//! policy_trusted_roots_tests.rs: disabled floor, command/path and rate rules.
+//! - sandbox/ops_tests.rs: SaaS/env/agent precedence and Noop status.
+//! Private Composio and credential middleware tests consume the same corpus
+//! beside their owning modules, without widening production visibility.
+
+#[path = "support/env_guard.rs"]
+mod env_guard;
+
+use axum::{
+ body::{to_bytes, Body},
+ http::{Request, StatusCode},
+ Router,
+};
+use openhuman_core::{
+ config::Config,
+ security::approval::{gate::ApprovalGate, store, types::PendingApproval},
+};
+use serde_json::{json, Value};
+use tower::ServiceExt;
+
+const TOKEN: &str = "security-characterization-test-token";
+
+async fn rpc(router: &Router, method: &str, params: Value) -> Value {
+ let response = router
+ .clone()
+ .oneshot(
+ Request::builder()
+ .method("POST")
+ .uri("/rpc")
+ .header("content-type", "application/json")
+ .header("authorization", format!("Bearer {TOKEN}"))
+ .body(Body::from(
+ json!({"jsonrpc":"2.0","id":73,"method":method,"params":params}).to_string(),
+ ))
+ .unwrap(),
+ )
+ .await
+ .unwrap();
+ assert_eq!(response.status(), StatusCode::OK, "{method}");
+ let value: Value =
+ serde_json::from_slice(&to_bytes(response.into_body(), 1024 * 1024).await.unwrap())
+ .unwrap();
+ assert_eq!(value["jsonrpc"], "2.0");
+ assert_eq!(value["id"], 73);
+ value
+}
+
+fn result(response: &Value) -> &Value {
+ assert!(response.get("error").is_none(), "{response}");
+ let result = &response["result"];
+ // Controllers with logs preserve the existing inner result/logs envelope.
+ if result.get("logs").is_some() {
+ &result["result"]
+ } else {
+ result
+ }
+}
+
+#[tokio::test]
+async fn approval_policy_sandbox_and_secret_wire_contracts() {
+ let _lock = env_guard::env_lock_async().await;
+ let scratch = tempfile::tempdir().unwrap();
+ let _workspace = env_guard::EnvVarGuard::set_path("OPENHUMAN_WORKSPACE", scratch.path());
+ let _keyring = env_guard::EnvVarGuard::set("OPENHUMAN_KEYRING_BACKEND", "file");
+ let _sandbox = env_guard::EnvVarGuard::unset("OPENHUMAN_SANDBOX");
+ let _rate = env_guard::EnvVarGuard::unset("OPENHUMAN_MAX_ACTIONS_PER_HOUR");
+ let _storage = env_guard::EnvVarGuard::unset("OPENHUMAN_STORAGE_URL");
+ let _action =
+ env_guard::EnvVarGuard::set_path("OPENHUMAN_ACTION_DIR", &scratch.path().join("action"));
+ let mut config = Config::default();
+ config.workspace_dir = scratch.path().to_path_buf();
+ config.config_path = scratch.path().join("config.toml");
+ config.action_dir = scratch.path().join("action");
+ config.action_dir_override = Some(config.action_dir.clone());
+ config.save().await.unwrap();
+ openhuman_core::core::auth::init_rpc_token_with_value(TOKEN).unwrap();
+ openhuman_core::security::keyring::init_workspace(scratch.path());
+ let router = openhuman_rpc::server::build_core_http_router(false);
+
+ assert_eq!(
+ result(&rpc(&router, "openhuman.approval_list_pending", json!({})).await),
+ &json!([])
+ );
+ assert_eq!(
+ result(&rpc(&router, "openhuman.approval_get_gate_state", json!({})).await),
+ &json!({"installed":false,"disabledByEnv":false,"overrideIgnored":false,"host":"unknown"})
+ );
+ assert_eq!(
+ result(
+ &rpc(
+ &router,
+ "openhuman.approval_preauthorize_flow",
+ json!({"flow_id":"flow-before-boot","tool_names":["shell"]})
+ )
+ .await
+ ),
+ &json!({"flow_id":"flow-before-boot","granted":[],"already_trusted":[],"gate_installed":false})
+ );
+ let missing_gate = rpc(
+ &router,
+ "openhuman.approval_decide",
+ json!({"request_id":"missing","decision":"deny"}),
+ )
+ .await;
+ assert!(missing_gate["error"]["message"]
+ .as_str()
+ .unwrap()
+ .contains("gate is not installed"));
+
+ ApprovalGate::init_global(config.clone(), "session-security-characterization");
+ let pending = PendingApproval::new(
+ "orphan-from-previous-boot",
+ "shell",
+ "execute command",
+ json!({"command":"echo hello"}),
+ Some(chrono::Utc::now() + chrono::Duration::minutes(10)),
+ );
+ store::insert_pending(&config, &pending, "session-previous-boot").unwrap();
+ let listed = rpc(&router, "openhuman.approval_list_pending", json!({})).await;
+ assert_eq!(
+ result(&listed),
+ &serde_json::to_value(vec![pending.clone()]).unwrap()
+ );
+ assert!(result(&listed)[0].get("session_id").is_none());
+ let decided = rpc(
+ &router,
+ "openhuman.approval_decide",
+ json!({"request_id":pending.request_id,"decision":"deny"}),
+ )
+ .await;
+ assert_eq!(result(&decided), &serde_json::to_value(&pending).unwrap());
+ assert_eq!(
+ result(&rpc(&router, "openhuman.approval_list_pending", json!({})).await),
+ &json!([])
+ );
+ let repeated = rpc(
+ &router,
+ "openhuman.approval_decide",
+ json!({"request_id":pending.request_id,"decision":"deny"}),
+ )
+ .await;
+ assert!(repeated["error"]["message"]
+ .as_str()
+ .unwrap()
+ .contains("already decided or expired"));
+ let grants = rpc(
+ &router,
+ "openhuman.approval_preauthorize_flow",
+ json!({"flow_id":"flow-1","tool_names":["shell","shell"," "]}),
+ )
+ .await;
+ assert_eq!(
+ result(&grants),
+ &json!({"flow_id":"flow-1","granted":["shell"],"already_trusted":[],"gate_installed":true})
+ );
+ let again = rpc(
+ &router,
+ "openhuman.approval_preauthorize_flow",
+ json!({"flow_id":"flow-1","tool_names":["shell"]}),
+ )
+ .await;
+ assert_eq!(
+ result(&again),
+ &json!({"flow_id":"flow-1","granted":[],"already_trusted":["shell"],"gate_installed":true})
+ );
+
+ let policy = rpc(&router, "openhuman.security_policy_info", json!({})).await;
+ let expected = json!({
+ "autonomy": config.autonomy.level,
+ "workspace_only": config.autonomy.workspace_only,
+ "allowed_commands": config.autonomy.allowed_commands,
+ "max_actions_per_hour": config.autonomy.max_actions_per_hour,
+ "require_approval_for_medium_risk": config.autonomy.require_approval_for_medium_risk,
+ "block_high_risk_commands": config.autonomy.block_high_risk_commands,
+ });
+ assert_eq!(result(&policy), &expected);
+ assert_eq!(result(&policy).as_object().unwrap().len(), 6);
+
+ let resolved = rpc(
+ &router,
+ "openhuman.sandbox_resolve_policy",
+ json!({"sandbox_mode":"none"}),
+ )
+ .await;
+ assert_eq!(result(&resolved)["backend"], "none");
+ let validated = rpc(
+ &router,
+ "openhuman.sandbox_validate_policy",
+ json!({"policy":result(&resolved)}),
+ )
+ .await;
+ assert_eq!(result(&validated), &json!({"valid":true,"issues":[]}));
+ let status = rpc(
+ &router,
+ "openhuman.sandbox_status",
+ json!({"backend":"none"}),
+ )
+ .await;
+ assert_eq!(result(&status)["kind"], "none");
+ assert_eq!(result(&status)["status"], "ready");
+
+ // These are the actual existing namespace names; encryption.* is absent.
+ let encrypted = rpc(
+ &router,
+ "openhuman.encrypt_secret",
+ json!({"plaintext":"synthetic secret"}),
+ )
+ .await;
+ let ciphertext = result(&encrypted).as_str().unwrap();
+ assert!(ciphertext.starts_with("enc2:"));
+ assert!(!ciphertext.contains("synthetic secret"));
+ assert_eq!(
+ result(
+ &rpc(
+ &router,
+ "openhuman.decrypt_secret",
+ json!({"ciphertext":ciphertext})
+ )
+ .await
+ ),
+ "synthetic secret"
+ );
+ assert_eq!(
+ result(
+ &rpc(
+ &router,
+ "openhuman.decrypt_secret",
+ json!({"ciphertext":"legacy plaintext"})
+ )
+ .await
+ ),
+ "legacy plaintext"
+ );
+ assert!(rpc(
+ &router,
+ "openhuman.decrypt_secret",
+ json!({"ciphertext":"enc2:invalid"})
+ )
+ .await
+ .get("error")
+ .is_some());
+ assert!(
+ rpc(&router, "openhuman.encrypt_secret", json!({"plaintext":17}))
+ .await
+ .get("error")
+ .is_some()
+ );
+}
+
+#[tokio::test]
+async fn in_process_policy_latency_baseline() {
+ // Measure the existing tool decision hot path, with policy enforcement on.
+ // No time threshold: record the baseline before choosing a bus budget.
+ let scratch = tempfile::tempdir().unwrap();
+ let mut config = Config::default();
+ config.autonomy.enabled = true;
+ let action = scratch.path().join("action");
+ let state = scratch.path().join("state");
+ std::fs::create_dir_all(&action).unwrap();
+ std::fs::create_dir_all(&state).unwrap();
+ let policy =
+ openhuman_core::security::SecurityPolicy::from_config(&config.autonomy, &state, &action);
+ let path = action.join("baseline.txt").display().to_string();
+ let evaluate = || {
+ assert!(std::hint::black_box(policy.check_gated_command("ls")).is_ok());
+ assert!(std::hint::black_box(policy.is_path_string_allowed(&path)));
+ };
+ for _ in 0..100 {
+ evaluate();
+ }
+ let mut samples = Vec::with_capacity(10_000);
+ for _ in 0..10_000 {
+ let start = std::time::Instant::now();
+ evaluate();
+ samples.push(start.elapsed().as_nanos());
+ }
+ samples.sort_unstable();
+ println!(
+ "security in-process baseline: samples={} checks_per_sample=2 p50_ns={} p99_ns={}",
+ samples.len(),
+ samples[4999],
+ samples[9899]
+ );
+}
+
+#[test]
+fn shared_redaction_corpus_records_existing_catches_and_gaps() {
+ let corpus: Value =
+ serde_json::from_str(include_str!("fixtures/security-redaction-corpus.json")).unwrap();
+ for case in corpus["text"].as_array().unwrap() {
+ let input = case["input"].as_str().unwrap();
+ let needle = case["needle"].as_str().unwrap();
+ // Unknown-key approval args exercise text/path scrubbing rather than
+ // the separate sensitive-field blanket replacement.
+ let approval =
+ openhuman_core::security::approval::redact::redact_args(&json!({"payload":input}));
+ openhuman_core::tools::registry::denials::record(
+ "characterization",
+ "test",
+ "blocked",
+ input,
+ );
+ let denial = openhuman_core::tools::registry::denials::list(1)
+ .pop()
+ .unwrap()
+ .reason;
+ let outputs = [
+ ("denials", denial),
+ ("prefix", openhuman_core::security::redact(input)),
+ ("identity_hash", openhuman_core::util::redact::redact(input)),
+ (
+ "log",
+ openhuman_core::core::log_redaction::scrub_secrets(input),
+ ),
+ (
+ "host_scrub",
+ openhuman_core::security::scrub::sanitize_text(input).value,
+ ),
+ (
+ "approval",
+ approval["payload"].as_str().unwrap().to_string(),
+ ),
+ (
+ "pii",
+ openhuman_core::security::pii::redact_identifiers(input),
+ ),
+ ];
+ for (name, output) in outputs {
+ let removed = case["removed_by"]
+ .as_array()
+ .unwrap()
+ .iter()
+ .any(|v| v == name);
+ assert_eq!(
+ !output.contains(needle),
+ removed,
+ "case={} redactor={name} output={output:?}",
+ case["case"]
+ );
+ }
+ }
+}
+
+#[test]
+fn shared_url_redaction_corpus_pins_query_preservation_gap() {
+ let corpus: Value =
+ serde_json::from_str(include_str!("fixtures/security-redaction-corpus.json")).unwrap();
+ for case in corpus["urls"].as_array().unwrap() {
+ let input = case["input"].as_str().unwrap();
+ assert_eq!(
+ openhuman_core::util::redact::redact_url_for_log(input),
+ case["util"]
+ );
+ assert_eq!(
+ openhuman_core::config::schema::storage::redact_url(input),
+ case["storage"]
+ );
+ }
+}
diff --git a/vendor/tinybox b/vendor/tinybox
index b61c6bcebf3..6e413449dc7 160000
--- a/vendor/tinybox
+++ b/vendor/tinybox
@@ -1 +1 @@
-Subproject commit b61c6bcebf3c0dbaf6ce3e36fae8131ebd6d78ae
+Subproject commit 6e413449dc7afedbef9f2ab46d61ebe46061abe1
diff --git a/vendor/tinysecurity b/vendor/tinysecurity
new file mode 160000
index 00000000000..b196016a383
--- /dev/null
+++ b/vendor/tinysecurity
@@ -0,0 +1 @@
+Subproject commit b196016a383276acc7c0ead0e4c285faf62eb35c