Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/ci-lite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,33 @@ jobs:
- name: Verify the desktop shell forwards every default-ON core gate
run: node scripts/ci/check-feature-forwarding.mjs

vendored-crates-gate:
name: Vendored Crates Gate (no silently inlined crates)
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
# Deliberately NOT `submodules: recursive`. The check reads the git
# INDEX (mode 160000), so an uninitialised submodule is indistinguishable
# from an initialised one — and an inlined crate is distinguishable from
# both. Cloning ~10 submodules to answer that would be pure cost.

# Fail when a crate the build describes as vendored has been inlined into
# the tree instead. `3ee5a3cad` did exactly that to tinywallet, leaving the
# manifest comments intact; the resulting silent fork hid a SLIP-10
# key-derivation bug from every other host until #5533 (#5559).
#
# Deliberately NOT filtered on `changes`: the drift can be introduced by
# editing a manifest, .gitmodules, the module registry, or by deleting a
# gitlink, and a skipped job counts as a pass in the gate below. The check
# is a second of pure Node with no dependencies and no Rust toolchain.
- name: Verify every crate documented as vendored really is
run: node scripts/ci/check-vendored-crates.mjs

pester-install:
name: PowerShell Install Test (Pester)
needs: [changes]
Expand Down Expand Up @@ -1043,6 +1070,7 @@ jobs:
- tinycortex-tests
- orch-ip-gate
- feature-forwarding-gate
- vendored-crates-gate
if: always()
runs-on: ubuntu-latest
timeout-minutes: 15
Expand All @@ -1063,6 +1091,7 @@ jobs:
["TinyCortex Memory Tests"]="${{ needs['tinycortex-tests'].result }}"
["Orchestration IP Gate"]="${{ needs['orch-ip-gate'].result }}"
["Feature Forwarding Gate"]="${{ needs['feature-forwarding-gate'].result }}"
["Vendored Crates Gate"]="${{ needs['vendored-crates-gate'].result }}"
)

failed=0
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@
[submodule "vendor/tinywallet"]
path = vendor/tinywallet
url = https://github.com/tinyhumansai/tinywallet
[submodule "vendor/tinydocs"]
path = vendor/tinydocs
url = https://github.com/tinyhumansai/tinydocs
53 changes: 52 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ crates are therefore synchronous, I/O-free, and runtime-free.

| Crate | Owns | OpenHuman keeps |
| --- | --- | --- |
| `tinydocs` | the `.docx` spec types, their size limits, validation, and OOXML synthesis (`docx-rs` sits behind it) | the artifact pipeline, the `spawn_blocking` hop, and the generation deadline — `src/openhuman/tools/impl/document/` |
| `tinydocs` | the `.docx` / `.pptx` spec types, their size limits, and validation. OOXML synthesis lives behind its `docx`/`pptx`/`pdf` gates and this host does **not** enable them — it takes the crate with `default-features = false`, for the wire contract only, and synthesis runs in the TinyBus module | the artifact pipeline, the `spawn_blocking` hop, and the generation deadline — `src/openhuman/tools/impl/document/` |
| `tinywallet` | the BTC / EVM / Solana / Tron address formats: parsing, validation, encoding conversions | RPC endpoint resolution, transaction assembly and broadcast, key custody — `src/openhuman/web3/` |

Consequences worth knowing before touching either seam:
Expand Down Expand Up @@ -232,6 +232,57 @@ Consequences worth knowing before touching either seam:
`default-features = false` — the wire contract, not the writers, which run in
the TinyBus module instead (see the module host section).

#### The vendoring guard — `scripts/ci/check-vendored-crates.mjs`

**An inlined copy of a vendored crate is a silent fork, and this has already
happened once.** On 2026-08-12 `3ee5a3cad` removed the `vendor/tinywallet`
submodule and inlined ~3,700 lines of crate source into
`src/openhuman/web3/wallet/`, rewriting every `crate::` path and collapsing the
crate's granular chain gates onto OpenHuman's single `web3` gate. It was
collateral damage from a larger change, not a decision — the manifest comments
and this file kept describing the crate-based design, so code and docs
contradicted each other and nothing failed. Four fixes then accrued in
OpenHuman's copy that no other host saw, one of them a SLIP-10 bug where a path
segment already carrying the hardening bit was OR-ed with it again, so
`m/44'/501'/2147483648'` and `m/44'/501'/0'` derived **the same key**
(#5533 / tinywallet#16 / tinywallet#17). `tinydocs` was in exactly that state
until #5559 — four comment blocks describing a submodule that did not exist,
including a `git submodule update --init vendor/tinydocs` that could not work.

The guard asserts, for every crate a manifest comment describes as vendored,
every `path = "…/vendor/<name>"` dependency, and every id in
`modules::registry::ALL`: a `.gitmodules` entry, a **gitlink in the git index**
(mode 160000 — this is the assertion an inlining trips), and a declared path
dependency. It reads the index rather than the filesystem, so the lane needs no
`git submodule update --init` and no Rust toolchain.

Exemptions live in `INTENTIONALLY_NOT_VENDORED` / `VENDORED_IN_TREE` in
`scripts/lib/vendored-crates.mjs` and **require a reason string** — the same
rule as `INTENTIONALLY_NOT_FORWARDED`, and for the same reason: an explicit
exclusion is the only thing that keeps "deliberate" distinguishable from
"forgotten". Both lists are staleness-checked, so a waiver that stopped being
true fails rather than quietly covering the next regression. Today they hold
`tinyjuice` and `tinyvoice` (module-only — the host links neither crate) and
`motosan-ai-oauth` (in-tree source, no upstream repo).

#### TinyWallet's host-side residue is deliberate — do not re-litigate it

Signing and transaction building moved into the `tinywallet` module, and what
stayed behind is a deliberate remainder rather than unfinished migration. The
host takes the crate with `default-features = false` and **`tx-codec` rather
than `tx`**: `tx` is the only gate that pulls the `bitcoin` crate and its
native secp256k1 C build, while `tx-codec` gives the verification half
(`recompute_txid`, `verify_contract`, `digest`, `attach_signature`) with only
`sha2`. `key`, `asset`, `client`, `tx` and `x402` are dropped. The `web3` gate
therefore now sheds 5 crates, not the 25 it once did; the residue
(`bech32`, `keccak`, `ripemd`, `sha3`, `tinywallet`) is the wire contract,
address validation and transaction *verification*, plus RPC endpoint
resolution, assembly/broadcast and key custody on the OpenHuman side. The three
`tinywallet::key::derive` call sites are all `#[cfg(test)]` — production derives
inside the module. Prefer `verify_contract` over `verify_transfer` at any new
call site: the latter is a substring scan over the hex that a decoy field or a
substituted amount defeats.

### Backend API access — `src/api/` over `tinyhumans-sdk`

Calls to the TinyHumans cloud backend go through the vendored
Expand Down
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,11 @@ unicode-width = { version = "0.2", optional = true }
# After cloning: `git submodule update --init vendor/tinydocs`.
#
# Optional: exclusive to the default-ON `documents` feature.
#
# The submodule is pinned at the tag whose module artifact `modules::registry`
# pins (v0.1.13). Host and module then validate against one revision of the
# spec rather than two that merely agree today.
tinydocs = { path = "vendor/tinydocs", default-features = false, optional = true }

# TinyWallet — host-agnostic multi-chain wallet primitives. Owns the address
# formats themselves: parsing, validation, and the conversions between their
Expand Down Expand Up @@ -717,7 +722,7 @@ inference = ["dep:cpal"]
# reference instead of extracted text
# (`agent::multimodal::extract_pdf_text`). Slim / headless builds opt out via
# `--no-default-features --features "<explicit list without documents>"`.
documents = ["modules"]
documents = ["modules", "dep:tinydocs"]
# The dynamic module host (`openhuman::modules`): the loader that admits a
# compiled `cdylib` through tinybus's ABI descriptor, manifest, dependency and
# SHA-256 gates, plus the `modules` RPC namespace and the registry of modules
Expand Down
9 changes: 9 additions & 0 deletions app/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading