lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 945a7d4255a75acf48c5a6ed90510615c1c78e9f
parent 255da079561540603b924321cebc47443aaa9a6e
Author: triesap <tyson@radroots.org>
Date:   Wed, 12 Aug 2026 00:04:37 +0000

merge: reconcile core library authorities

- preserve machine contract relocation and service decision reservations
- retain the complete mobile settings media operation and retrieval surfaces
- keep read-only relay delivery capability fail-closed
- pass workspace checks focused suites and 362 xtask tests

Diffstat:
MAGENTS.md | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
MAGENT_INSTRUCTIONS.md | 51+++++++++++++++++++++++++++++++++++++++++++++++++--
MCONTRIBUTING.md | 22++++++++++++----------
MCargo.lock | 1+
MCargo.toml | 1+
Mbuild/nix/apps.nix | 1+
Mbuild/nix/checks.nix | 1+
Mbuild/nix/common.nix | 5+----
Rdocs/api/radroots.txt -> contracts/api_baselines/radroots.txt | 0
Rdocs/api/radroots_blossom.txt -> contracts/api_baselines/radroots_blossom.txt | 0
Rdocs/api/radroots-core.txt -> contracts/api_baselines/radroots_core.txt | 0
Rdocs/api/radroots_event.txt -> contracts/api_baselines/radroots_event.txt | 0
Rdocs/api/radroots_event_codec.txt -> contracts/api_baselines/radroots_event_codec.txt | 0
Rdocs/api/radroots_geonames.txt -> contracts/api_baselines/radroots_geonames.txt | 0
Rdocs/api/radroots_identity.txt -> contracts/api_baselines/radroots_identity.txt | 0
Rdocs/api/radroots_nostr.txt -> contracts/api_baselines/radroots_nostr.txt | 0
Rdocs/api/radroots_nostr_connect.txt -> contracts/api_baselines/radroots_nostr_connect.txt | 0
Rdocs/api/radroots_protocol.txt -> contracts/api_baselines/radroots_protocol.txt | 0
Rdocs/api/radroots_sdk.txt -> contracts/api_baselines/radroots_sdk.txt | 0
Rdocs/api/radroots_secrets.txt -> contracts/api_baselines/radroots_secrets.txt | 0
Rdocs/api/radroots_signing.txt -> contracts/api_baselines/radroots_signing.txt | 0
Rdocs/api/radroots_storage.txt -> contracts/api_baselines/radroots_storage.txt | 0
Rdocs/api/radroots_trade.txt -> contracts/api_baselines/radroots_trade.txt | 0
Rdocs/api/radroots_transport.txt -> contracts/api_baselines/radroots_transport.txt | 0
Rdocs/api/radroots_transport_nostr.txt -> contracts/api_baselines/radroots_transport_nostr.txt | 0
Acontracts/architecture/decisions/public_api_leakage_migration_baseline.v1.toml | 39+++++++++++++++++++++++++++++++++++++++
Acontracts/architecture/decisions/services_hardening_events.v1.json | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontracts/architecture/decisions/services_hardening_host.v1.json | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontracts/architecture/deviations.toml | 327+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontracts/architecture/retired_compatibility.v1.toml | 43+++++++++++++++++++++++++++++++++++++++++++
Acontracts/conformance/vectors/rhi/evidence_attestation_decision.v1.json | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontracts/conformance/vectors/trade/mutation_index_tags_decision.v1.json | 147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dcontracts/crates/catalog.v1.toml | 1624-------------------------------------------------------------------------------
Acontracts/crates/catalog.v2.toml | 1686+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcontracts/crates/generated/package_groups.v1.toml | 2+-
Mcontracts/crates/generated/platform_inventory.v1.toml | 2+-
Mcontracts/crates/generated/release_inventory.v2.toml | 2+-
Mcontracts/crates/release.v2.toml | 14+++++++-------
Rdocs/specs/radroots_crates_release_v1.dot -> contracts/crates/release_v1/radroots_crates_release_v1.dot | 0
Rdocs/specs/radroots_crates_release_v1.sha256 -> contracts/crates/release_v1/radroots_crates_release_v1.sha256 | 0
Rdocs/specs/radroots_crates_release_v1.toml -> contracts/crates/release_v1/radroots_crates_release_v1.toml | 0
Rdocs/specs/radroots_crates_release_v1_inventory.csv -> contracts/crates/release_v1/radroots_crates_release_v1_inventory.csv | 0
Acontracts/hygiene/prototype-contracts.v1.toml | 226+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcontracts/releases/api_boundaries.toml | 57---------------------------------------------------------
Mcrates/blossom/README.md | 6+++---
Mcrates/core/README.md | 6+++---
Mcrates/event/tests/package_boundary.rs | 2+-
Mcrates/event/tests/source_boundary.rs | 2+-
Mcrates/event_codec/README.md | 8++++----
Mcrates/event_codec/tests/package_boundary.rs | 2+-
Acrates/event_codec/tests/services_hardening_event_decisions.rs | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/geonames/README.md | 4++--
Mcrates/geonames/tests/package_boundary.rs | 11+++--------
Mcrates/identity/README.md | 10+++++-----
Mcrates/mobile_core/Cargo.toml | 37+++++++++++++++++++++++++------------
Mcrates/mobile_ffi/Cargo.toml | 10+++++++++-
Mcrates/mobile_ffi/src/dto.rs | 39+++++++++++++++++++++++++++++++--------
Mcrates/nostr/README.md | 2+-
Mcrates/nostr/tests/package_boundary.rs | 2+-
Mcrates/nostr_connect/README.md | 8++++----
Mcrates/nostr_connect/tests/package_boundary.rs | 3++-
Mcrates/protocol/README.md | 4++--
Mcrates/radroots/README.md | 4++--
Mcrates/sdk/Cargo.toml | 6+++++-
Mcrates/sdk/README.md | 4++--
Mcrates/secrets/README.md | 8++++----
Mcrates/secrets/tests/security_contract.rs | 2+-
Mcrates/signing/README.md | 2+-
Mcrates/signing/tests/package_boundary.rs | 4++--
Mcrates/storage/README.md | 2+-
Mcrates/studio_application/Cargo.toml | 7++++++-
Mcrates/studio_ffi/Cargo.toml | 7++++++-
Mcrates/studio_nostr/Cargo.toml | 7++++++-
Mcrates/studio_runtime/Cargo.toml | 7++++++-
Mcrates/studio_storage/Cargo.toml | 4+++-
Mcrates/trade/README.md | 8++++----
Mcrates/trade/tests/package_boundary.rs | 2+-
Mcrates/transport/README.md | 2+-
Mcrates/transport/tests/package_boundary.rs | 6+++---
Mcrates/transport_nostr/Cargo.toml | 7++++++-
Mcrates/transport_nostr/README.md | 4++--
Mcrates/transport_nostr/tests/legacy_quarantine.rs | 7++++---
Mcrates/transport_nostr/tests/package_boundary.rs | 11++++-------
Mcrates/transport_nostr/tests/workspace_consumers.rs | 2+-
Ddocs/api/README.md | 49-------------------------------------------------
Ddocs/decisions/0001-public-api-leakage-migration-baseline.md | 36------------------------------------
Ddocs/engineering/ci.md | 24------------------------
Ddocs/implementation/COMPATIBILITY_SHIMS.md | 23-----------------------
Ddocs/implementation/DEPENDENCY_RESOLUTION.md | 14--------------
Ddocs/implementation/DEVIATIONS.md | 56--------------------------------------------------------
Ddocs/implementation/HISTORY_PRESERVATION.md | 25-------------------------
Ddocs/implementation/PUBLICATION_FREEZE.md | 22----------------------
Ddocs/implementation/STEP_REPORT_TEMPLATE.md | 58----------------------------------------------------------
Ddocs/implementation/TRACEABILITY.md | 21---------------------
Ddocs/implementation/deviations.toml | 330-------------------------------------------------------------------------------
Ddocs/migration/identity.md | 25-------------------------
Ddocs/migration/release-v1.md | 25-------------------------
Ddocs/migration/trade-ids.md | 24------------------------
Ddocs/nip46-current-conformance.md | 92-------------------------------------------------------------------------------
Ddocs/specs/README.md | 40----------------------------------------
Ddocs/specs/radroots_crates_release_v1.md | 1589-------------------------------------------------------------------------------
Mimports/studio_mpl_legacy_core/Cargo.toml | 5+----
Msupply-chain/config.toml | 440++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mtools/xtask/README | 3+++
Mtools/xtask/src/architecture.rs | 88++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mtools/xtask/src/architecture/api_leakage.rs | 253++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mtools/xtask/src/architecture/core_contract.rs | 7++++---
Mtools/xtask/src/architecture/dependency_boundary.rs | 2+-
Atools/xtask/src/architecture/retired_compatibility.rs | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtools/xtask/src/build_control.rs | 21+++++++++++++++++----
Mtools/xtask/src/catalog.rs | 518++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mtools/xtask/src/contract.rs | 87+++++++++++++++++++++++++++++++++++--------------------------------------------
Mtools/xtask/src/hygiene.rs | 1663+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtools/xtask/src/main.rs | 5+++++
Mtools/xtask/src/release_qualification.rs | 2+-
Mtools/xtask/src/target_qualification.rs | 2+-
Dtools/xtask/tests/fixtures/api-leakage/adr-exception.rs | 3---
Atools/xtask/tests/services_hardening_host_decision.rs | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
118 files changed, 6517 insertions(+), 4679 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md @@ -14,24 +14,46 @@ This file exists for compatibility with tools that look for AGENTS.md. ## 2. Source of intent -- Read `docs/specs/README.md` and - `docs/specs/radroots_crates_release_v1.md` before changing a public package, - package identity, dependency, feature, or release control. -- The Markdown specification is normative. Its TOML catalog is the executable - package and dependency representation; the CSV and DOT files are review - aids. +- Read `contracts/crates/release.v2.toml`, + `contracts/crates/release_v1/radroots_crates_release_v1.toml`, and + `contracts/crates/catalog.v2.toml` before changing a public package, package + identity, dependency, feature, or release control. +- Machine contracts under `contracts/**` are the standalone authority. Human + specifications, decisions, runbooks, and qualification evidence belong + under the parent monorepo's `docs/oss/lib/**` authority and must never become + a standalone build, test, package, or release input. +- The pre-implementation service-event reservation is + `contracts/architecture/decisions/services_hardening_events.v1.json`. + Service-event source, registry, generated, and consumer work must implement + that exact kind, tag, cardinality, query, and supersession contract; it may + not reinterpret the reservation from current prototype wire behavior. +- The pre-implementation local-admin, process-exit, doctor, readiness, + peer-credential, systemd, and bare-Rust host decisions are reserved by + `contracts/architecture/decisions/services_hardening_host.v1.json`. + Service-host and service-owned operator contracts must implement or narrow + that boundary without adding a second transport, exit map, or readiness + authority. +- Source-lock consumer identities include `sdk`, `mobile`, `studio`, `myc`, + and `rhi`. Only the first three are generated-artifact product identities; + accepting a service consumer marker must not expose an artifact route. - Current source and tests are implementation evidence. They do not silently override `radroots.crates.release.v1`. - Record any evidence-based plan deviation in - `docs/implementation/deviations.toml`, following - `docs/implementation/DEVIATIONS.md`, before proceeding. Validate it with - `cargo xtask architecture`. + `contracts/architecture/deviations.toml` before proceeding. Validate it with + `cargo xtask architecture`; a normative architecture exception also requires + the applicable machine decision under `contracts/architecture/decisions/**`. + Deviation anchors must resolve the Release V1 TOML through a validated + selector: `repositories.<name>`, `repository_policy`, `release_policy`, + `quality_policy.coverage`, or `package.<name>`. ## 3. Repository operating model - This is a public open-source library workspace; optimize for durable library design, portability, determinism, and explicit contracts. - Keep release and validation automation forge-agnostic; repo-owned xtask commands, Nix apps, tags, and contract metadata are canonical, while committed provider-specific workflow automation is not. -- `.github/**` and capsule-local CI workflows are forbidden. Any required monorepo orchestration belongs exclusively to the parent repository's root `.act/**` authority and must not be copied into this standalone capsule. +- Do not add or retain tracked `docs/**`, `.github/**`, or `.act/**` content. + Keep validation forge-agnostic. Any required monorepo orchestration belongs + exclusively to the parent repository's root `.act/**` authority and must not + be copied into this standalone capsule. - Prefer clean target-state changes over compatibility scaffolding unless compatibility is explicitly required. - Stay within the requested scope and the smallest coherent file set. - Do not fold unrelated cleanup, speculative refactors, or roadmap work into the same change. @@ -59,10 +81,16 @@ Before editing code: - `nix run .#release-preflight` - `cargo xtask architecture` for controlled deviation records and local spec anchors +- Public API baselines live in `contracts/api_baselines/**`. Regenerate one + with `cargo-public-api` `0.52.0` and rustdoc JSON from + `nightly-2026-07-16`, writing the reviewed output back to that directory. - targeted `cargo check -p <crate>` and `cargo test -p <crate>` only inside the Nix shell - `cargo xtask dto-roots --write` after changing configured DTO exports and `cargo xtask dto-roots --check` for exact generated-root freshness - targeted `cargo xtask contract ...`, `cargo xtask coverage ...`, `cargo xtask release ...`, or `cargo xtask hygiene ...` only when narrowing a repo-owned workflow +- `cargo xtask hygiene prototype-contracts` for the governed report-only + service-prototype census; use `--strict` only when the cleanup sequence has + made every non-allowlisted finding release-blocking - if Beads is active, read `.beads/PRIME.md` ## 6. Rust engineering rules @@ -83,6 +111,18 @@ Before editing code: ## 7. Architecture, contract, and release discipline - `contracts/` and `tools/xtask` are authoritative for core-library contracts, conformance, coverage, hygiene, and release-candidate governance. +- `contracts/crates/catalog.v2.toml` is the package-catalog authority. Preserve + imported packages as `provenance_kind = "imported"` with their immutable + repository, revision, path, and tree digest. New repository-native packages + must be active, unpublished `provenance_kind = "native"` entries and must + store only `introduction_tree_sha256`; never embed a self-referential + introducing commit OID. +- Before validating a new native catalog entry, stage the complete package path + and run `cargo xtask catalog check` or `cargo xtask catalog write`. The + pre-commit digest is derived from stage-zero index records, not the mutable + worktree. After the introducing commit, the same command derives the first + adding commit from repository history and verifies its immutable tree. Do + not rewrite that digest for later source changes. - Behavior changes that affect public surfaces must update the relevant contract metadata, conformance vectors, export rules, or validation flows in the same change. - Keep pure flake checks and repo-aware command apps aligned with the documented Nix command map. - This repository owns packages 1-17 in `radroots.crates.release.v1`, from @@ -99,7 +139,8 @@ Before editing code: feature closures. - During the migration, every package remains non-publishable until its package-realistic release gates pass and publication is explicitly - authorized. Follow `docs/implementation/PUBLICATION_FREEZE.md`. + authorized. `contracts/releases/publish_policy.toml` is the machine + authority; validation metadata does not authorize upload. ## 8. Service hardening boundaries @@ -141,9 +182,9 @@ trusted-publisher configuration without explicit authorization. - Split unrelated changes into separate commits. - If repository evidence proves a planned step obsolete or unsafe, record the evidence, affected specification anchor, disposition, and validation in - `docs/implementation/deviations.toml`, following - `docs/implementation/DEVIATIONS.md`. A normative architecture change also - requires an approved decision record. Never silently skip or reorder work. + `contracts/architecture/deviations.toml`. A normative architecture change + also requires an approved machine decision under + `contracts/architecture/decisions/**`. Never silently skip or reorder work. ## 11. Definition of done diff --git a/AGENT_INSTRUCTIONS.md b/AGENT_INSTRUCTIONS.md @@ -66,10 +66,14 @@ Use this mental model: - keep domain logic inside the correct crate rather than spreading it across the workspace - `contracts/` - core-library contract metadata, release-candidate policy, coverage governance, and public conformance assets +- `contracts/api_baselines/` + - reviewed generated public Rust API surfaces +- `contracts/architecture/` + - machine-readable deviations, decisions, and compatibility-retirement authority +- `contracts/crates/release_v1/` + - historical machine catalog, inventory, graph, and checksums retained by release V2 - `contracts/conformance/` - cross-language and cross-surface vector expectations -- `docs/` - - durable workflow and environment documentation - `build/nix/`, `flake.nix`, `treefmt.nix` - canonical environment and CI contract - `tools/xtask/` @@ -77,6 +81,17 @@ Use this mental model: Do not duplicate contract knowledge between crates when `contracts/`, `contracts/conformance/`, or `tools/xtask` already owns it. +Do not add or retain tracked `docs/**`, `.github/**`, or `.act/**`. Root +`README.md`, `AGENTS.md`, `AGENT_INSTRUCTIONS.md`, conventional public project +files, package READMEs, and Rustdoc carry concise standalone guidance. Extended +human authority is parent-owned and is never a standalone command input. + +Deviation `spec_anchors` target the Release V1 TOML and must use one of the +machine selectors enforced by `cargo xtask architecture`: +`repositories.<name>`, `repository_policy`, `release_policy`, +`quality_policy.coverage`, or `package.<name>`. Markdown heading fragments and +unresolved free-form fragments are invalid. + ## 5. Rust engineering standards ### Core design @@ -137,6 +152,18 @@ Do not duplicate contract knowledge between crates when `contracts/`, `contracts `contracts/`, `contracts/conformance/`, and `tools/xtask` are first-class parts of the product surface, not secondary metadata. +The package authority is `contracts/crates/catalog.v2.toml`. Imported entries +retain their exact immutable source repository, full revision, source path, and +source-tree digest. A newly created repository-native package instead uses +`provenance_kind = "native"` and records only its +`introduction_tree_sha256`; native entries are active and unpublished. Stage +the complete new package path before running `cargo xtask catalog check` or +`cargo xtask catalog write`. Before the first commit, xtask verifies the digest +against canonical stage-zero index tree records. After that commit, xtask +derives the earliest adding commit from history and verifies the same digest +against that commit's package tree. Later source changes do not change the +introduction digest, and the catalog never stores the introducing commit OID. + When a change affects exported models, transforms, identifiers, or public runtime expectations: - update the relevant contract metadata @@ -146,6 +173,23 @@ When a change affects exported models, transforms, identifiers, or public runtim Do not change public behavior in Rust and leave contract or conformance assets stale. +Public API baselines are generated with `cargo-public-api` `0.52.0` and +rustdoc JSON from `nightly-2026-07-16`; the workspace's pinned stable toolchain +still governs package verification. From the canonical development shell, +regenerate one package with: + +```sh +RUSTC="$(rustup which --toolchain nightly-2026-07-16 rustc)" \ +RUSTDOC="$(rustup which --toolchain nightly-2026-07-16 rustdoc)" \ +cargo public-api --manifest-path crates/<crate>/Cargo.toml \ + --all-features -sss \ + > contracts/api_baselines/<package>.txt +``` + +Review each baseline change with the package's machine charter and intended +SemVer impact. Generated listings are evidence of the Rust surface, not +authority to expand it. + ## 7. Canonical validation strategy Use the smallest authoritative lane that proves the change green. @@ -165,6 +209,9 @@ Targeted iteration inside the Nix shell: - `cargo xtask dto-roots --write` after changing configured DTO exports - `cargo xtask release preflight` - `cargo xtask hygiene forbidden-identifiers` +- `cargo xtask hygiene prototype-contracts` for the deterministic report-only + service-prototype census; strict mode is enabled only after the owning + cleanup sequence clears its findings Validation rules: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md @@ -4,9 +4,10 @@ Radroots core-library changes are contract-driven and independently reviewable. Before editing, read these files in order: 1. `AGENTS.md` -2. `docs/specs/README.md` -3. `docs/specs/radroots_crates_release_v1.md` for crate-surface work -4. `AGENT_INSTRUCTIONS.md` +2. `AGENT_INSTRUCTIONS.md` +3. `contracts/crates/release.v2.toml` +4. `contracts/crates/release_v1/radroots_crates_release_v1.toml` for + crate-surface work 5. the affected manifests, implementation, contracts, and tests The release-v1 architecture identifier is `radroots.crates.release.v1`. This @@ -42,10 +43,11 @@ Keep commits focused and keep public commit language independent of any private checkout. Do not publish, tag, merge, or change registry ownership without explicit authorization. -When current evidence proves a planned step obsolete or unsafe, follow -`docs/implementation/DEVIATIONS.md` and validate the machine-readable ledger -with `cargo xtask architecture`. Complete -`docs/implementation/STEP_REPORT_TEMPLATE.md`, and keep -`docs/implementation/TRACEABILITY.md` aligned with durable requirements. -Record the evidence and affected spec anchor before changing the plan; do not -silently redefine the architecture. +When current evidence proves a planned step obsolete or unsafe, update +`contracts/architecture/deviations.toml` and validate it with +`cargo xtask architecture`. Record the evidence and affected machine-contract +anchor before changing the plan. Anchors must use a validated Release V1 TOML +selector (`repositories.<name>`, `repository_policy`, `release_policy`, +`quality_policy.coverage`, or `package.<name>`); Markdown heading fragments are +not machine anchors. A normative change also requires the applicable machine +decision. Do not silently redefine the architecture. diff --git a/Cargo.lock b/Cargo.lock @@ -3609,6 +3609,7 @@ version = "0.1.0-alpha" dependencies = [ "async-trait", "hex", + "libc", "nostr 0.44.1", "nostr-relay-builder", "nostr-sdk 0.44.0", diff --git a/Cargo.toml b/Cargo.toml @@ -214,6 +214,7 @@ keyring = { version = "3.6.3", default-features = false, features = [ "linux-native-sync-persistent", "vendored", ] } +libc = { version = "0.2" } nostr = { version = "0.44.7", default-features = false } nostr-relay-pool = { version = "0.44.0" } nostr-sdk = { version = "0.44.1" } diff --git a/build/nix/apps.nix b/build/nix/apps.nix @@ -90,6 +90,7 @@ in runtimeInputs = common.runtimeInputs.stable; command = '' cargo run -q -p xtask -- hygiene forbidden-identifiers + cargo run -q -p xtask -- hygiene prototype-contracts ''; }; diff --git a/build/nix/checks.nix b/build/nix/checks.nix @@ -216,6 +216,7 @@ in initGit = true; command = '' xtask hygiene forbidden-identifiers + xtask hygiene prototype-contracts ''; }; } diff --git a/build/nix/common.nix b/build/nix/common.nix @@ -42,10 +42,6 @@ let ../../rust-toolchain.toml ../../contracts ../../crates - ../../docs/api - ../../docs/decisions - ../../docs/implementation - ../../docs/specs ../../fuzz ../../tools ] @@ -195,6 +191,7 @@ let ''; contractCommand = '' cargo run -q -p xtask -- hygiene forbidden-identifiers + cargo run -q -p xtask -- hygiene prototype-contracts cargo check -q ${coreContractCargoArgs} cargo test -q ${coreContractCargoArgs} cargo run -q -p xtask -- contract validate diff --git a/docs/api/radroots.txt b/contracts/api_baselines/radroots.txt diff --git a/docs/api/radroots_blossom.txt b/contracts/api_baselines/radroots_blossom.txt diff --git a/docs/api/radroots-core.txt b/contracts/api_baselines/radroots_core.txt diff --git a/docs/api/radroots_event.txt b/contracts/api_baselines/radroots_event.txt diff --git a/docs/api/radroots_event_codec.txt b/contracts/api_baselines/radroots_event_codec.txt diff --git a/docs/api/radroots_geonames.txt b/contracts/api_baselines/radroots_geonames.txt diff --git a/docs/api/radroots_identity.txt b/contracts/api_baselines/radroots_identity.txt diff --git a/docs/api/radroots_nostr.txt b/contracts/api_baselines/radroots_nostr.txt diff --git a/docs/api/radroots_nostr_connect.txt b/contracts/api_baselines/radroots_nostr_connect.txt diff --git a/docs/api/radroots_protocol.txt b/contracts/api_baselines/radroots_protocol.txt diff --git a/docs/api/radroots_sdk.txt b/contracts/api_baselines/radroots_sdk.txt diff --git a/docs/api/radroots_secrets.txt b/contracts/api_baselines/radroots_secrets.txt diff --git a/docs/api/radroots_signing.txt b/contracts/api_baselines/radroots_signing.txt diff --git a/docs/api/radroots_storage.txt b/contracts/api_baselines/radroots_storage.txt diff --git a/docs/api/radroots_trade.txt b/contracts/api_baselines/radroots_trade.txt diff --git a/docs/api/radroots_transport.txt b/contracts/api_baselines/radroots_transport.txt diff --git a/docs/api/radroots_transport_nostr.txt b/contracts/api_baselines/radroots_transport_nostr.txt diff --git a/contracts/architecture/decisions/public_api_leakage_migration_baseline.v1.toml b/contracts/architecture/decisions/public_api_leakage_migration_baseline.v1.toml @@ -0,0 +1,39 @@ +schema_version = 1 +decision_id = "radroots.public_api_leakage_migration_baseline.v1" +status = "accepted_historical" +accepted_date = "2026-07-27" +completed_step = 313 +publication_authorized = false +exception_ids = [ + "RCRV1-API-001", + "RCRV1-API-002", + "RCRV1-API-003", + "RCRV1-API-004", + "RCRV1-API-005", + "RCRV1-API-006", + "RCRV1-API-007", + "RCRV1-API-008", +] +active_exception_ids = [] +forbidden_expansion = [ + "broader_aliases", + "keyring", + "new_items", + "new_upstream_paths", + "platform_specific_types", + "reqwest", + "sqlx", + "tokio", +] + +[[removal_milestone]] +package = "radroots_identity" +step = 42 + +[[removal_milestone]] +package = "radroots_nostr" +step = 124 + +[[removal_milestone]] +package = "radroots_nostr_connect" +step = 140 diff --git a/contracts/architecture/decisions/services_hardening_events.v1.json b/contracts/architecture/decisions/services_hardening_events.v1.json @@ -0,0 +1,118 @@ +{ + "schema": "radroots.services-hardening.event-decisions.v1", + "contract_version": 1, + "decision_state": "reserved_preimplementation", + "trade_mutation": { + "contract_family": "radroots.trade.mutation-index.v1", + "event_class": "regular_immutable", + "event_kinds": [ + { "mutation_type": "proposal", "kind": 3470, "contract_id": "radroots.trade.proposal.v1" }, + { "mutation_type": "decision", "kind": 3471, "contract_id": "radroots.trade.decision.v1" }, + { "mutation_type": "revision_proposal", "kind": 3472, "contract_id": "radroots.trade.revision_proposal.v1" }, + { "mutation_type": "revision_decision", "kind": 3473, "contract_id": "radroots.trade.revision_decision.v1" }, + { "mutation_type": "cancellation", "kind": 3474, "contract_id": "radroots.trade.cancellation.v1" } + ], + "canonical_tag_order": [ + "contract", + "d:trade", + "x:mutation", + "x:root", + "x:parent_sorted", + "p:buyer", + "p:seller" + ], + "tags": [ + { "name": "contract", "marker": "", "cardinality": "exactly_one", "value": "content_contract_id", "relay_filter": "none" }, + { "name": "d", "marker": "", "cardinality": "exactly_one", "value": "trade_id_lower_hex_32", "relay_filter": "#d" }, + { "name": "x", "marker": "mutation", "cardinality": "exactly_one", "value": "mutation_id_lower_hex_64", "relay_filter": "#x" }, + { "name": "x", "marker": "root", "cardinality": "proposal_zero_other_mutations_exactly_one", "value": "root_mutation_id_lower_hex_64", "relay_filter": "#x" }, + { "name": "x", "marker": "parent", "cardinality": "proposal_zero_other_mutations_one_to_four_sorted_unique", "value": "parent_mutation_id_lower_hex_64", "relay_filter": "#x" }, + { "name": "p", "marker": "", "cardinality": "first_of_exactly_two", "value": "buyer_pubkey_lower_hex_64", "relay_filter": "#p" }, + { "name": "p", "marker": "", "cardinality": "second_of_exactly_two", "value": "seller_pubkey_lower_hex_64", "relay_filter": "#p" } + ], + "filter_contract": { + "trade_id": "kind plus #d", + "mutation_id": "kind plus #x followed by marker verification", + "root_mutation_id": "kind plus #x followed by marker verification", + "party_pubkey": "kind plus #p followed by exact ordered buyer-first and seller-second comparison against canonical content" + }, + "validation": { + "structural_tags_are_derived": true, + "unknown_structural_marker_rejected": true, + "duplicate_or_conflicting_structural_tag_rejected": true, + "caller_structural_tags_forbidden": true, + "legacy_contract_d_p_e_shape_accepted": false + } + }, + "rhi_attestation": { + "contract_id": "radroots.rhi.evidence_attestation.v1", + "kind_constant": "KIND_RHI_EVIDENCE_ATTESTATION", + "kind": 3441, + "event_class": "regular_immutable", + "replaceability": "none", + "content_encoding": "RFC8785_JCS_JSON_UTF8", + "statement_digest": { + "algorithm": "sha256", + "domain_separator_utf8_nul": "radroots:rhi-evidence-attestation-statement:v1", + "payload_fields": [ + "contract_id", + "contract_version", + "issuer_pubkey", + "trade_id", + "claim_mutation_id", + "outcome", + "reason_codes", + "reducer_contract_id", + "reducer_contract_version", + "projection_digest", + "evidence_manifest_digest", + "evidence_policy_digest", + "observed_at_unix_s", + "attestation_method", + "supersedes_report_id", + "supersedes_event_id", + "trade_generation" + ], + "excluded_fields": ["report_id", "statement_digest", "event_id", "signature"], + "formula": "SHA256(domain_separator_utf8 || 0x00 || RFC8785(payload)_utf8)" + }, + "fixed_values": { + "contract_version": 1, + "attestation_method": "signed_evidence_snapshot", + "outcomes": ["valid", "invalid", "indeterminate"] + }, + "canonical_tag_order": [ + "contract", + "d:trade", + "x:claim", + "x:statement", + "t:outcome", + "x:supersedes_report", + "e:supersedes_event" + ], + "tags": [ + { "name": "contract", "marker": "", "cardinality": "exactly_one", "value": "radroots.rhi.evidence_attestation.v1", "relay_filter": "none" }, + { "name": "d", "marker": "", "cardinality": "exactly_one", "value": "trade_id_lower_hex_32", "relay_filter": "#d" }, + { "name": "x", "marker": "claim", "cardinality": "exactly_one", "value": "claim_mutation_id_lower_hex_64", "relay_filter": "#x" }, + { "name": "x", "marker": "statement", "cardinality": "exactly_one", "value": "statement_digest_lower_hex_64", "relay_filter": "#x" }, + { "name": "t", "marker": "", "cardinality": "exactly_one", "value": "radroots:rhi-outcome:<valid|invalid|indeterminate>", "relay_filter": "#t" }, + { "name": "x", "marker": "supersedes_report", "cardinality": "zero_or_one", "value": "superseded_statement_digest_lower_hex_64", "relay_filter": "#x" }, + { "name": "e", "marker": "", "cardinality": "zero_or_one", "value": "superseded_nostr_event_id_lower_hex_64", "relay_filter": "#e" } + ], + "supersession": { + "requires_both_references_or_neither": true, + "report_id_equals_statement_digest": true, + "current_selection": "highest_committed_trade_generation_then_observed_at_then_statement_digest", + "relay_arrival_order_authoritative": false, + "deletion_request_changes_retained_history": false + }, + "validation": { + "author_equals_issuer": true, + "content_is_exact_canonical_report": true, + "structural_tags_are_derived": true, + "duplicate_or_conflicting_structural_tag_rejected": true, + "caller_structural_tags_forbidden": true, + "reason_codes_sorted_unique": true + } + } +} diff --git a/contracts/architecture/decisions/services_hardening_host.v1.json b/contracts/architecture/decisions/services_hardening_host.v1.json @@ -0,0 +1,139 @@ +{ + "schema": "radroots.services-hardening.host-decisions.v1", + "contract_version": 1, + "decision_state": "reserved_preimplementation", + "local_admin": { + "transport": "http_1_1_over_unix_domain_socket", + "base_path": "/v1", + "tcp_admin": false, + "cors": false, + "browser_authentication": false, + "mutation_request_envelope": { + "required_fields": ["contract_version", "operation_id", "request"], + "optional_fields": ["correlation_id"], + "contract_version": 1, + "operation_id_semantics": "caller_stable_idempotency_identity", + "correlation_id_semantics": "caller_safe_trace_identity_or_daemon_generated_when_absent", + "identical_operation_id_reuse": "return_original_committed_result", + "different_request_operation_id_reuse": { + "admin_error_code": "operation_id_conflict", + "cli_exit": 5 + } + }, + "success_response_envelope": { + "required_fields": ["contract_version", "ok", "correlation_id", "result"], + "contract_version": 1, + "ok": true + }, + "failure_response_envelope": { + "required_fields": ["contract_version", "ok", "correlation_id", "error"], + "error_required_fields": ["code", "message"], + "contract_version": 1, + "ok": false + }, + "output_safety": { + "request_body_max_utf8_bytes": 65536, + "response_body_max_utf8_bytes": 1048576, + "operation_id_max_utf8_bytes": 128, + "correlation_id_max_utf8_bytes": 128, + "error_code_max_utf8_bytes": 64, + "error_message_max_utf8_bytes": 256, + "redaction_required": true, + "forbidden_material": ["secret_or_credential_material", "private_identity_material", "decrypted_payload", "raw_absolute_or_resolved_path", "raw_sql_or_database_error", "raw_provider_error", "raw_relay_or_network_error", "source_error_chain"] + }, + "common_route_suffixes": [ + { "method": "GET", "path": "/status", "operation_suffix": "status.get", "request_model": "empty", "response_model": "service_status_v1" }, + { "method": "GET", "path": "/config/effective", "operation_suffix": "config.effective.get", "request_model": "empty", "response_model": "effective_config_v1" }, + { "method": "GET", "path": "/identity/status", "operation_suffix": "identity.status.get", "request_model": "identity_status_query_v1", "response_model": "identity_status_v1" }, + { "method": "POST", "path": "/identity/rekey", "operation_suffix": "identity.rekey", "request_model": "identity_rekey_request_v1", "response_model": "identity_mutation_receipt_v1" }, + { "method": "POST", "path": "/identity/replace", "operation_suffix": "identity.replace", "request_model": "identity_replace_request_v1", "response_model": "identity_mutation_receipt_v1" }, + { "method": "GET", "path": "/state/status", "operation_suffix": "state.status.get", "request_model": "empty", "response_model": "state_status_v1" }, + { "method": "POST", "path": "/state/backup", "operation_suffix": "state.backup.create", "request_model": "state_backup_request_v1", "response_model": "state_backup_receipt_v1" }, + { "method": "GET", "path": "/metrics/snapshot", "operation_suffix": "metrics.snapshot.get", "request_model": "empty", "response_model": "metrics_snapshot_v1" } + ], + "unknown_major_version": "unsupported_contract_version", + "unknown_route": "route_not_found", + "duplicate_json_fields_rejected": true, + "unknown_json_fields_rejected": true + }, + "peer_authorization": { + "linux_service_host": { + "credential_api": "SO_PEERCRED", + "required": true, + "allow": ["peer_uid_equals_daemon_euid", "peer_primary_gid_equals_configured_admin_gid"], + "credential_unavailable": "deny", + "parent_mode_without_admin_gid": "0700", + "socket_mode_without_admin_gid": "0600", + "parent_mode_with_admin_gid": "0750", + "socket_mode_with_admin_gid": "0660" + }, + "macos_interactive": { + "credential_api": "none_v1", + "required": false, + "authority": "filesystem_owner_permissions_only", + "parent_mode": "0700", + "socket_mode": "0600", + "peer_credential_equivalence_claim": false + }, + "other_platforms": { "admin_support": "unsupported_v1" } + }, + "tcp_operations": { + "routes": [ + { "method": "GET", "path": "/livez", "source": "cached_supervisor_state" }, + { "method": "GET", "path": "/readyz", "source": "cached_readiness_state" }, + { "method": "GET", "path": "/metrics", "source": "cached_bounded_metrics_snapshot" } + ], + "active_probe_per_request": false, + "additional_routes": false + }, + "doctor": { + "schema": "radroots.service.doctor.v1", + "contract_version": 1, + "required_fields": ["contract_version", "service", "instance", "status", "checks"], + "check_required_fields": ["id", "status", "required", "deadline_ms", "summary", "remediation_code"], + "statuses": ["pass", "fail", "timeout", "skipped"], + "required_skipped": "forbidden", + "aggregate_statuses": ["pass", "degraded", "fail"], + "aggregation": { + "required_fail_or_timeout": "fail", + "optional_fail_timeout_or_skipped": "degraded", + "otherwise": "pass" + }, + "optional_nonpass_exit": 0, + "summary_max_utf8_bytes": 256, + "raw_error_or_path_allowed": false, + "required_fail_or_timeout_exit": 6 + }, + "exit_codes": [ + { "code": 0, "name": "success", "meaning": "successful command or completed graceful first-signal shutdown" }, + { "code": 1, "name": "unexpected_internal", "meaning": "unexpected invariant, critical task, or internal failure" }, + { "code": 2, "name": "input_or_configuration", "meaning": "CLI, config, validation, or unsupported contract input" }, + { "code": 3, "name": "service_or_dependency_unavailable", "meaning": "daemon, required provider, relay, source, or local dependency unavailable" }, + { "code": 4, "name": "state_or_identity_unavailable", "meaning": "state, schema, lock, credential, or identity unavailable" }, + { "code": 5, "name": "operation_rejected_or_conflict", "meaning": "authorization rejection, idempotency conflict, stale generation, or domain conflict" }, + { "code": 6, "name": "doctor_required_check_failed", "meaning": "one or more required doctor checks failed or timed out" } + ], + "forced_signal_exit": "operating_system_128_plus_signal_not_remapped", + "systemd": { + "sd_notify_v1": false, + "service_type": "simple", + "readiness_authority": "cached_http_readyz", + "process_running_does_not_imply_ready": true + }, + "bare_rust_linux": { + "qualification_base": "debian_bookworm_slim_digest_pinned_per_receipt", + "architectures": ["x86_64", "aarch64"], + "rust_install": "rustup_profile_minimal_exact_repository_toolchain", + "apt_packages": ["build-essential", "ca-certificates", "git"], + "not_required_by_final_graph": ["clang", "libclang-dev", "libsodium-dev", "libsqlite3-dev", "libssl-dev", "pkg-config"], + "sqlite": "bundled", + "tls": "rustls", + "proof": [ + "fresh_digest_pinned_base_for_each_architecture", + "install_only_declared_apt_packages_and_exact_rust_toolchain", + "locked_format_check_test_clippy_rustdoc_release_build", + "repeat_with_network_disabled_from_governed_vendor_bundle", + "fail_if_undeclared_native_package_is_installed_or_linked" + ] + } +} diff --git a/contracts/architecture/deviations.toml b/contracts/architecture/deviations.toml @@ -0,0 +1,327 @@ +schema_version = 1 +architecture_id = "radroots.crates.release.v1" + +[[deviation]] +id = "RCRV1-DEV-001" +date = "2026-07-27" +status = "active" +approval = "Explicit user correction dated 2026-07-27." +affected_steps = [ + "015", + "016", + "017", + "018", + "019", + "020", + "021", + "022", + "023", + "026", +] +spec_anchors = [ + "contracts/crates/release_v1/radroots_crates_release_v1.toml#repositories.lib", + "contracts/crates/release_v1/radroots_crates_release_v1.toml#repository_policy", +] +source_evidence = [ + "The final v1 specification allocates 17 public packages to radrootslabs/lib and 2 to radrootslabs/sdk.", + "Both existing repositories have independent histories, workspaces, lockfiles, remotes, and standalone release boundaries.", +] +replacement_action = "Retain the two existing standalone repositories; replace import and monorepo-unification work with independent workspace, lockfile, metadata, dependency, and release qualification." +verification = [ + "Both repository-local architecture validators resolve every spec anchor.", + "The synchronized architecture catalog enforces the exact 17/2 ownership partition.", + "Each standalone repository owns a required architecture CI adapter over its repository-local command surface.", +] +unresolved_risk = "Parent gitlinks cannot advance until the new standalone commits are public-remote reachable under separate authorization." +normative_architecture_change = false +adr_required = false + +[[deviation]] +id = "RCRV1-DEV-010" +date = "2026-08-03" +status = "closed" +approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user." +affected_steps = ["201", "215", "269", "294", "301", "313"] +spec_anchors = [ + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_transport_nostr", + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_transport", +] +source_evidence = [ + "The Step 294 downstream matrix proved the former app_rt radroots_net_core consumer had migrated and no current first-party manifest retained an edge to radroots_net.", + "Step 301 full workspace qualification proved that the assigned radroots_nostr_runtime Step 215 deletion and radroots_net Step 313 deletion had not been applied even though both removal gates were satisfied.", + "Both obsolete packages failed against the final transport boundary when the complete all-feature workspace closure was checked.", +] +replacement_action = "Delete radroots_nostr_runtime, its NostrDB runtime adapter, and radroots_net during Step 301 qualification; forbid their package, dependency, alias, feature, and source identities from being reintroduced." +verification = [ + "Transport-owned tests reject reintroduction of radroots_nostr_runtime, radroots_net, or the NostrDB runtime-adapter feature.", + "Workspace-wide source tests reject every removed transport-client identifier without a compatibility exception.", + "The full all-target and all-feature workspace qualification compiles without either predecessor package.", +] +unresolved_risk = "None for the two removed packages; historical names remain only in governed specifications, migration evidence, and fail-closed regression assertions." +normative_architecture_change = false +adr_required = false +closure_evidence = [ + "crates/transport_nostr/tests/workspace_consumers.rs proves both package directories and workspace dependency identities are absent.", + "crates/transport_nostr/tests/legacy_quarantine.rs proves release policy and the compatibility ledger no longer classify either package as a retained shim.", +] + +[[deviation]] +id = "RCRV1-DEV-008" +date = "2026-08-01" +status = "closed" +approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user." +affected_steps = ["153", "155", "171", "179", "226", "288", "293", "313"] +spec_anchors = [ + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_secrets", + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_storage_sqlite", +] +source_evidence = [ + "The final radroots_storage_sqlite scaffold can consume radroots_secrets immediately and has no predecessor secret dependency.", + "Step 179, not Step 153, owns transfer of the current SDK private database and its encrypted records into canonical SQLite storage.", + "Mixed publish-frozen runtime, Nostr-account, SimpleX preview, SDK private-store, Myc, and other external hosts still require predecessor vault/store behavior until their ordered migration steps.", +] +replacement_action = "Activate the final radroots_storage_sqlite and SDK dependency edges in Step 153; confine predecessor vault/store imports to exact publish-frozen quarantine packages and the SDK private-store module; Step 179 transfers canonical private storage, Steps 226/288/293 migrate the remaining SDK and downstream consumers, and Step 313 removes every remaining compatibility package and legacy name." +verification = [ + "Consumer-migration tests enumerate every lib package manifest that still names radroots_secret_vault or radroots_protected_store and reject any unapproved or publishable consumer.", + "Storage SQLite package-boundary tests require radroots_secrets and reject all predecessor secret package names.", + "SDK source-boundary tests confine predecessor imports to private_store.rs and require the final optional radroots_secrets dependency edge.", + "Step 155 release-policy validation keeps every quarantine package non-publishable until its exact removal gate.", +] +unresolved_risk = "None; the predecessor packages and all active manifest consumers are absent." +normative_architecture_change = false +adr_required = false +closure_evidence = [ + "crates/secrets/tests/consumer_migration.rs rejects every predecessor package directory, workspace dependency, and active manifest reference.", + "Step 313 source census confirmed the SDK, Myc, CLI, and library runtime paths use only radroots_secrets and final storage owners.", +] + +[[deviation]] +id = "RCRV1-DEV-007" +date = "2026-07-30" +status = "closed" +approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user." +affected_steps = ["122", "170", "215", "235", "305"] +spec_anchors = [ + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_transport", +] +source_evidence = [ + "Step 120 migrated canonical adapters and registries to independent EventSource and EventSink contracts.", + "Mixed runtime delivery workers still consume predecessor request and receipt models until their ordered RCLD 40 migration.", + "The runtime delivery worker also accepts opaque byte payloads, while the final EventSink contract accepts only verified SignedEvent values; deleting the bridge at Step 170 would silently remove a supported private runtime path before sync orchestration owns that decision.", + "The standalone publish-frozen SDK still maps user-facing target and satisfaction models into predecessor outbox orchestration until Step 235.", + "oss/cli/src/runtime/{config,sync,transport}.rs and oss/radrootsd/src/core/transport_publish.rs still import the predecessor identity aliases and Reticulum target helpers; those standalone repositories are outside the approved crate-surface mutation scope.", +] +replacement_action = "Remove the monolithic trait from radroots_transport in Step 122; retain one explicitly named runtime-owned unpublished shim until Step 215 at the sync-orchestration retirement gate, the SDK-local unpublished target/satisfaction mapping until Step 235, and documentation-hidden external-consumer aliases/helpers until the fail-closed package-realistic Step 305 gate." +verification = [ + "Transport source-boundary tests reject every removed public predecessor name and require the singular runtime-owned shim.", + "Release policy keeps runtime and SDK publication disabled while either downstream shim exists.", + "Steps 215 and 235 are the exact fail-closed final-removal gates for the remaining runtime and SDK mappings.", + "Step 305 rejects publication until oss/cli and oss/radrootsd no longer require the documentation-hidden external-consumer aliases and Reticulum helpers.", +] +unresolved_risk = "None; final consumers use TransportId, Target, TargetScope, TargetLabel, and TargetFingerprint directly." +normative_architecture_change = false +adr_required = false +closure_evidence = [ + "crates/transport/tests/package_boundary.rs rejects the hidden Reticulum constructors, legacy constant spellings, and prefixed target aliases.", + "Step 313 source census confirmed every external-consumer alias and helper is absent after the daemon cutover to final transport identities.", +] + +[[deviation]] +id = "RCRV1-DEV-009" +date = "2026-08-02" +status = "closed" +approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user." +affected_steps = [ + "170", + "179", + "189", + "196", + "201", + "213", + "226", + "235", + "263", + "269", + "288", + "292", + "313", +] +spec_anchors = [ + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_storage", + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_storage_sqlite", + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_sdk", +] +source_evidence = [ + "The Step 170 first-party census found radroots_event_index consumers in the standalone CLI, SDK, SDK bindings, and indexer repositories.", + "The census found radroots_event_store and radroots_outbox consumers in the standalone CLI and SDK, plus active library transport_nostr orchestration scheduled to move in Step 196.", + "The census found radroots_runtime_store consumed by the standalone CLI, whose crate cutover is scheduled after the canonical library storage and sync implementations are complete.", + "Deleting these packages before their ordered migrations would make the independently buildable first-party repositories unresolvable and would remove the source data needed by the approved one-shot importer.", +] +replacement_action = "Keep radroots_event_index, radroots_event_store, radroots_outbox, and radroots_runtime_store as documentation-hidden publish-frozen compatibility packages with no new consumers; port durable behavior into radroots_storage_sqlite through Step 189, remove local transport coupling in Step 196, migrate library/SDK/binding/CLI/indexer consumers in their ordered steps, and delete every remaining package at Step 313." +verification = [ + "Package manifests carry machine-readable publish-frozen metadata naming the final replacement, deviation, Step 313 removal gate, and prohibition on new consumers.", + "A storage-owned quarantine test requires the four packages to remain private and absent from the approved publication inventory.", + "Workspace checks and tests prove current migration consumers remain buildable while publication stays frozen.", + "Step 313 performs the all-first-party forbidden-name search and final package deletion.", +] +unresolved_risk = "None; all four predecessor packages and their active dependency edges are absent." +normative_architecture_change = false +adr_required = false +closure_evidence = [ + "crates/storage_sqlite/tests/package_boundary.rs and crates/storage/tests/workspace_consumers.rs reject the four predecessor package identities.", + "Step 313 removed the final library packages, SDK runtime/event-index surfaces, and CLI source dependency branch after all standalone consumers migrated.", +] + +[[deviation]] +id = "RCRV1-DEV-005" +date = "2026-07-28" +status = "active" +approval = "Explicit user Rust version-policy update dated 2026-07-28 17:47 UTC." +affected_steps = [ + "013", + "019", + "020", + "021", + "022", + "023", + "024", + "025", + "026", + "305", +] +spec_anchors = [ + "contracts/crates/release_v1/radroots_crates_release_v1.toml#repositories.lib", +] +source_evidence = [ + "The prior 1.0.0 and mixed 0.1.0 prerelease crate cohorts were explicitly declared incorrect.", + "The user requires every Rust crate in radrootslabs/lib to remain exactly 0.1.0-alpha until further explicit notice.", +] +replacement_action = "Pin every workspace package, lockfile entry, and internal Radroots dependency requirement in radrootslabs/lib to 0.1.0-alpha; preserve independent protocol and sibling-repository versions; reject library cohort drift until new explicit authority is recorded." +verification = [ + "Repository architecture validation rejects any workspace package version other than 0.1.0-alpha.", + "Internal Radroots dependency requirements resolve exactly to =0.1.0-alpha.", + "Synchronized release specifications record the library cohort independently from the SDK repository version.", +] +unresolved_risk = "The prerelease cohort intentionally prevents independent library package version advancement until a future explicit policy change." +normative_architecture_change = false +adr_required = false + +[[deviation]] +id = "RCRV1-DEV-002" +date = "2026-07-27" +status = "active" +approval = "Explicit user correction dated 2026-07-27." +affected_steps = ["249"] +spec_anchors = [ + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots", + "contracts/crates/release_v1/radroots_crates_release_v1.toml#repositories.sdk", +] +source_evidence = [ + "The final v1 specification assigns the radroots facade to the existing sdk repository.", + "The approved sequence requires radroots to be the first crate-surface mutation after architecture controls are green.", +] +replacement_action = "Scaffold radroots in the sdk repository immediately after Step 014, then execute Steps 250-260 in their original order without repeating the scaffold portion of Step 249." +verification = [ + "The sdk release policy reserves radroots as an approved local package while publication remains frozen.", + "The facade scaffold checkpoint must add radroots only to the sdk workspace and architecture policy.", +] +unresolved_risk = "The facade remains non-publishable until the package-realistic Step 305 enablement gate." +normative_architecture_change = false +adr_required = false + +[[deviation]] +id = "RCRV1-DEV-004" +date = "2026-07-28" +status = "active" +approval = "Explicit user coverage-policy update dated 2026-07-28 17:15 UTC." +affected_steps = [ + "098", + "155", + "225", + "260", + "268", + "294", + "298", + "299", + "301", + "302", + "303", + "304", + "314", +] +spec_anchors = [ + "contracts/crates/release_v1/radroots_crates_release_v1.toml#quality_policy.coverage", +] +source_evidence = [ + "The oss/lib codebase is under heavy development during the multi-RCLD refactor.", + "The user explicitly replaced the active 100% coverage requirement with a uniform 90% module requirement.", +] +replacement_action = "Enforce 90% executable-line, function, region, and branch coverage for every required oss/lib crate; retain only no-branch-record exceptions; defer restoration of 100% until an explicit future contract update after refactor stabilization." +verification = [ + "Contract validation rejects any base coverage dimension other than 90% or disabled required branches.", + "Coverage policy-gate tests prove values below 90% fail and values at or above 90% pass.", + "The required-crate inventory remains complete and crate-specific numeric thresholds below 90% remain forbidden.", +] +unresolved_risk = "A 90% development gate admits untested paths that a later 100% gate would reject; the final restoration remains intentionally unscheduled pending explicit authority." +normative_architecture_change = false +adr_required = false + +[[deviation]] +id = "RCRV1-DEV-011" +date = "2026-08-03" +status = "closed" +approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user." +affected_steps = ["225", "226", "248"] +spec_anchors = [ + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_geonames", + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_sdk", +] +source_evidence = [ + "The standalone SDK manifest and GeoNames module still resolve radroots_geocoder and its test-fixture feature while the final provider package is implemented in this independently versioned repository.", + "The legacy package has no in-repository consumer, is publish disabled, and is already excluded from the exact release-v1 public package inventory.", + "Deleting the package at Step 225 would make the independently buildable SDK repository unresolvable before its ordered manifest and API cutover begins at Step 226.", +] +replacement_action = "Keep radroots_geocoder as a documentation-marked, machine-classified publish-frozen bridge with no new consumers, features, contracts, or behavior; migrate the standalone SDK to radroots_geonames beginning at Step 226 and delete the bridge at the SDK retirement gate in Step 248." +verification = [ + "The predecessor manifest names radroots_geonames as its replacement, RCRV1-DEV-011 as authority, and Step 248 as the exact removal gate.", + "GeoNames package quarantine tests require the predecessor to remain private and absent from the approved publication inventory while the SDK source census remains non-empty.", + "Step 248 must reject every remaining package, dependency, feature, import, and error-adapter reference to radroots_geocoder before deletion.", +] +unresolved_risk = "None; the SDK consumes radroots_geonames and the predecessor package is absent." +normative_architecture_change = false +adr_required = false +closure_evidence = [ + "crates/geonames/tests/package_boundary.rs rejects the superseded package directory and release-policy identity.", + "The SDK Step 248 checkpoint removed every radroots_geocoder dependency, feature, import, and error-adapter reference.", +] + +[[deviation]] +id = "RCRV1-DEV-012" +date = "2026-08-03" +status = "closed" +approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user." +affected_steps = ["279", "282", "283"] +spec_anchors = [ + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_transport", + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_transport_nostr", + "contracts/crates/release_v1/radroots_crates_release_v1.toml#package.radroots_sdk", +] +source_evidence = [ + "The first-party mobile compile gate proved that its real relay-backed identity, profile, and post operations had no equivalent after the initial shared-engine bridge cutover.", + "The generic source request could bound pages and targets but could not express event kind, author, or event-time constraints required for correct profile and feed queries.", + "Restoring the retired parallel runtime or accepting client-side filtering after page truncation would violate the final SDK and transport ownership model.", +] +replacement_action = "Before qualifying the mobile artifact, add bounded transport-neutral fetch selectors, translate them in the concrete Nostr adapter, complete SDK-owned explicit local-signing and Nostr composition, and map the mobile presentation contract over those SDK operations without restoring direct lower-package dependencies." +verification = [ + "Transport selector construction rejects oversized, duplicate, and reversed-range inputs and binds selectors into page validation.", + "The Nostr adapter applies kind, author, and time constraints remotely and defensively filters returned events before page bounds.", + "The shared-engine SDK and mobile integration gates must pass before Steps 282 and 283 are marked complete.", +] +unresolved_risk = "None; the shared-engine and mobile qualification checkpoints completed with the final selector and SDK ownership model." +normative_architecture_change = false +adr_required = false +closure_evidence = [ + "crates/transport/tests/source_contract.rs verifies bounded canonical selector construction and request binding.", + "crates/transport_nostr/src/source.rs tests remote selector translation and defensive result filtering before page bounds.", + "Steps 282 and 283 qualified the SDK-owned signing, Nostr composition, and mobile presentation bridge without lower-package ownership leakage.", +] diff --git a/contracts/architecture/retired_compatibility.v1.toml b/contracts/architecture/retired_compatibility.v1.toml @@ -0,0 +1,43 @@ +schema_version = 1 +contract_id = "radroots.retired_compatibility.v1" +status = "enforced" + +[[retired_bridge]] +id = "radroots_authority" +final_owners = ["radroots_signing"] +removal_step = 313 + +[[retired_bridge]] +id = "radroots_geocoder" +final_owners = ["radroots_geonames"] +removal_step = 313 + +[[retired_bridge]] +id = "radroots_net" +final_owners = ["radroots_transport"] +removal_step = 301 + +[[retired_bridge]] +id = "radroots_nostr_connect_hidden_prelude" +final_owners = ["radroots_nostr_connect"] +removal_step = 313 + +[[retired_bridge]] +id = "radroots_nostr_connect_prefixed_client_bridge" +final_owners = ["radroots_nostr_connect"] +removal_step = 313 + +[[retired_bridge]] +id = "radroots_nostr_signer" +final_owners = ["radroots_nostr_connect", "radroots_signing"] +removal_step = 313 + +[[retired_bridge]] +id = "radroots_nostr_runtime" +final_owners = ["radroots_transport_nostr"] +removal_step = 301 + +[[retired_bridge]] +id = "nostrdb_runtime_adapter" +final_owners = ["radroots_storage_sqlite"] +removal_step = 301 diff --git a/contracts/conformance/vectors/rhi/evidence_attestation_decision.v1.json b/contracts/conformance/vectors/rhi/evidence_attestation_decision.v1.json @@ -0,0 +1,153 @@ +{ + "suite": "rhi_evidence_attestation_decision", + "contract_version": "1.0.0", + "vectors": [ + { + "id": "rhi_evidence_attestation_current_001", + "kind": "rhi.evidence_attestation.valid", + "input": { + "statement_payload": { + "attestation_method": "signed_evidence_snapshot", + "claim_mutation_id": "2222222222222222222222222222222222222222222222222222222222222222", + "contract_id": "radroots.rhi.evidence_attestation.v1", + "contract_version": 1, + "evidence_manifest_digest": "4444444444444444444444444444444444444444444444444444444444444444", + "evidence_policy_digest": "5555555555555555555555555555555555555555555555555555555555555555", + "issuer_pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "observed_at_unix_s": 1800000000, + "outcome": "indeterminate", + "projection_digest": "6666666666666666666666666666666666666666666666666666666666666666", + "reason_codes": ["required_source_incomplete"], + "reducer_contract_id": "radroots.trade.reducer.v1", + "reducer_contract_version": 1, + "supersedes_event_id": null, + "supersedes_report_id": null, + "trade_generation": 7, + "trade_id": "11111111111111111111111111111111" + } + }, + "expected": { + "kind": 3441, + "event_class": "regular_immutable", + "canonical_statement_payload_utf8": "{\"attestation_method\":\"signed_evidence_snapshot\",\"claim_mutation_id\":\"2222222222222222222222222222222222222222222222222222222222222222\",\"contract_id\":\"radroots.rhi.evidence_attestation.v1\",\"contract_version\":1,\"evidence_manifest_digest\":\"4444444444444444444444444444444444444444444444444444444444444444\",\"evidence_policy_digest\":\"5555555555555555555555555555555555555555555555555555555555555555\",\"issuer_pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"observed_at_unix_s\":1800000000,\"outcome\":\"indeterminate\",\"projection_digest\":\"6666666666666666666666666666666666666666666666666666666666666666\",\"reason_codes\":[\"required_source_incomplete\"],\"reducer_contract_id\":\"radroots.trade.reducer.v1\",\"reducer_contract_version\":1,\"supersedes_event_id\":null,\"supersedes_report_id\":null,\"trade_generation\":7,\"trade_id\":\"11111111111111111111111111111111\"}", + "statement_digest": "461acfea579481f6aadba47c31abc07eaa700f66abc074b8926ba41266082b44", + "report_id": "461acfea579481f6aadba47c31abc07eaa700f66abc074b8926ba41266082b44", + "canonical_event_content_utf8": "{\"attestation_method\":\"signed_evidence_snapshot\",\"claim_mutation_id\":\"2222222222222222222222222222222222222222222222222222222222222222\",\"contract_id\":\"radroots.rhi.evidence_attestation.v1\",\"contract_version\":1,\"evidence_manifest_digest\":\"4444444444444444444444444444444444444444444444444444444444444444\",\"evidence_policy_digest\":\"5555555555555555555555555555555555555555555555555555555555555555\",\"issuer_pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"observed_at_unix_s\":1800000000,\"outcome\":\"indeterminate\",\"projection_digest\":\"6666666666666666666666666666666666666666666666666666666666666666\",\"reason_codes\":[\"required_source_incomplete\"],\"reducer_contract_id\":\"radroots.trade.reducer.v1\",\"reducer_contract_version\":1,\"report_id\":\"461acfea579481f6aadba47c31abc07eaa700f66abc074b8926ba41266082b44\",\"statement_digest\":\"461acfea579481f6aadba47c31abc07eaa700f66abc074b8926ba41266082b44\",\"supersedes_event_id\":null,\"supersedes_report_id\":null,\"trade_generation\":7,\"trade_id\":\"11111111111111111111111111111111\"}", + "tags": [ + ["contract", "radroots.rhi.evidence_attestation.v1"], + ["d", "11111111111111111111111111111111"], + ["x", "2222222222222222222222222222222222222222222222222222222222222222", "claim"], + ["x", "461acfea579481f6aadba47c31abc07eaa700f66abc074b8926ba41266082b44", "statement"], + ["t", "radroots:rhi-outcome:indeterminate"] + ] + } + }, + { + "id": "rhi_evidence_attestation_superseding_002", + "kind": "rhi.evidence_attestation.valid", + "input": { + "statement_payload": { + "attestation_method": "signed_evidence_snapshot", + "claim_mutation_id": "2222222222222222222222222222222222222222222222222222222222222222", + "contract_id": "radroots.rhi.evidence_attestation.v1", + "contract_version": 1, + "evidence_manifest_digest": "4444444444444444444444444444444444444444444444444444444444444444", + "evidence_policy_digest": "5555555555555555555555555555555555555555555555555555555555555555", + "issuer_pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "observed_at_unix_s": 1800000100, + "outcome": "valid", + "projection_digest": "6666666666666666666666666666666666666666666666666666666666666666", + "reason_codes": ["scope_satisfied"], + "reducer_contract_id": "radroots.trade.reducer.v1", + "reducer_contract_version": 1, + "supersedes_event_id": "8888888888888888888888888888888888888888888888888888888888888888", + "supersedes_report_id": "7777777777777777777777777777777777777777777777777777777777777777", + "trade_generation": 8, + "trade_id": "11111111111111111111111111111111" + } + }, + "expected": { + "canonical_statement_payload_utf8": "{\"attestation_method\":\"signed_evidence_snapshot\",\"claim_mutation_id\":\"2222222222222222222222222222222222222222222222222222222222222222\",\"contract_id\":\"radroots.rhi.evidence_attestation.v1\",\"contract_version\":1,\"evidence_manifest_digest\":\"4444444444444444444444444444444444444444444444444444444444444444\",\"evidence_policy_digest\":\"5555555555555555555555555555555555555555555555555555555555555555\",\"issuer_pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"observed_at_unix_s\":1800000100,\"outcome\":\"valid\",\"projection_digest\":\"6666666666666666666666666666666666666666666666666666666666666666\",\"reason_codes\":[\"scope_satisfied\"],\"reducer_contract_id\":\"radroots.trade.reducer.v1\",\"reducer_contract_version\":1,\"supersedes_event_id\":\"8888888888888888888888888888888888888888888888888888888888888888\",\"supersedes_report_id\":\"7777777777777777777777777777777777777777777777777777777777777777\",\"trade_generation\":8,\"trade_id\":\"11111111111111111111111111111111\"}", + "statement_digest": "61af3caa6a3a14ff7bb026e9f294826d1d1957a5aff3d814156acee9cf0d5807", + "report_id": "61af3caa6a3a14ff7bb026e9f294826d1d1957a5aff3d814156acee9cf0d5807", + "canonical_event_content_utf8": "{\"attestation_method\":\"signed_evidence_snapshot\",\"claim_mutation_id\":\"2222222222222222222222222222222222222222222222222222222222222222\",\"contract_id\":\"radroots.rhi.evidence_attestation.v1\",\"contract_version\":1,\"evidence_manifest_digest\":\"4444444444444444444444444444444444444444444444444444444444444444\",\"evidence_policy_digest\":\"5555555555555555555555555555555555555555555555555555555555555555\",\"issuer_pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"observed_at_unix_s\":1800000100,\"outcome\":\"valid\",\"projection_digest\":\"6666666666666666666666666666666666666666666666666666666666666666\",\"reason_codes\":[\"scope_satisfied\"],\"reducer_contract_id\":\"radroots.trade.reducer.v1\",\"reducer_contract_version\":1,\"report_id\":\"61af3caa6a3a14ff7bb026e9f294826d1d1957a5aff3d814156acee9cf0d5807\",\"statement_digest\":\"61af3caa6a3a14ff7bb026e9f294826d1d1957a5aff3d814156acee9cf0d5807\",\"supersedes_event_id\":\"8888888888888888888888888888888888888888888888888888888888888888\",\"supersedes_report_id\":\"7777777777777777777777777777777777777777777777777777777777777777\",\"trade_generation\":8,\"trade_id\":\"11111111111111111111111111111111\"}", + "tags": [ + ["contract", "radroots.rhi.evidence_attestation.v1"], + ["d", "11111111111111111111111111111111"], + ["x", "2222222222222222222222222222222222222222222222222222222222222222", "claim"], + ["x", "61af3caa6a3a14ff7bb026e9f294826d1d1957a5aff3d814156acee9cf0d5807", "statement"], + ["t", "radroots:rhi-outcome:valid"], + ["x", "7777777777777777777777777777777777777777777777777777777777777777", "supersedes_report"], + ["e", "8888888888888888888888888888888888888888888888888888888888888888"] + ], + "mutates_prior_report": false + } + }, + { + "id": "rhi_evidence_attestation_wrong_kind_003", + "kind": "rhi.evidence_attestation.invalid", + "input": { "base": "rhi_evidence_attestation_current_001", "kind": 3440 }, + "expected": { "layer": "wire", "error_code": "invalid_attestation_kind" } + }, + { + "id": "rhi_evidence_attestation_wrong_author_004", + "kind": "rhi.evidence_attestation.invalid", + "input": { "base": "rhi_evidence_attestation_current_001", "event_author": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, + "expected": { "layer": "wire", "error_code": "issuer_author_mismatch" } + }, + { + "id": "rhi_evidence_attestation_noncanonical_content_005", + "kind": "rhi.evidence_attestation.invalid", + "input": { "base": "rhi_evidence_attestation_current_001", "content_transform": "append_ascii_space" }, + "expected": { "layer": "wire", "error_code": "noncanonical_report_content" } + }, + { + "id": "rhi_evidence_attestation_digest_mismatch_006", + "kind": "rhi.evidence_attestation.invalid", + "input": { "base": "rhi_evidence_attestation_current_001", "statement_digest": "0000000000000000000000000000000000000000000000000000000000000000" }, + "expected": { "layer": "wire", "error_code": "statement_digest_mismatch" } + }, + { + "id": "rhi_evidence_attestation_unknown_outcome_007", + "kind": "rhi.evidence_attestation.invalid", + "input": { "base": "rhi_evidence_attestation_current_001", "outcome": "complete" }, + "expected": { "layer": "wire", "error_code": "invalid_outcome" } + }, + { + "id": "rhi_evidence_attestation_missing_claim_tag_008", + "kind": "rhi.evidence_attestation.invalid", + "input": { "base": "rhi_evidence_attestation_current_001", "remove_tag": ["x", "claim"] }, + "expected": { "layer": "wire", "error_code": "missing_claim_tag" } + }, + { + "id": "rhi_evidence_attestation_duplicate_trade_tag_009", + "kind": "rhi.evidence_attestation.invalid", + "input": { "base": "rhi_evidence_attestation_current_001", "append_tag": ["d", "99999999999999999999999999999999"] }, + "expected": { "layer": "wire", "error_code": "duplicate_trade_tag" } + }, + { + "id": "rhi_evidence_attestation_duplicate_statement_tag_010", + "kind": "rhi.evidence_attestation.invalid", + "input": { "base": "rhi_evidence_attestation_current_001", "append_tag": ["x", "9999999999999999999999999999999999999999999999999999999999999999", "statement"] }, + "expected": { "layer": "wire", "error_code": "duplicate_statement_tag" } + }, + { + "id": "rhi_evidence_attestation_incomplete_supersession_011", + "kind": "rhi.evidence_attestation.invalid", + "input": { "base": "rhi_evidence_attestation_current_001", "supersedes_report_id": "7777777777777777777777777777777777777777777777777777777777777777", "supersedes_event_id": null }, + "expected": { "layer": "wire", "error_code": "incomplete_supersession_reference" } + }, + { + "id": "rhi_evidence_attestation_stale_supersession_012", + "kind": "rhi.evidence_attestation.invalid", + "input": { "base": "rhi_evidence_attestation_superseding_002", "current_trade_generation": 8, "candidate_trade_generation": 7 }, + "expected": { "layer": "admission", "error_code": "stale_trade_generation" } + }, + { + "id": "rhi_evidence_attestation_caller_structural_tag_013", + "kind": "rhi.evidence_attestation.invalid", + "input": { "builder_extra_tags": [["d", "11111111111111111111111111111111"]] }, + "expected": { "layer": "builder", "error_code": "caller_structural_tag_forbidden" } + } + ] +} diff --git a/contracts/conformance/vectors/trade/mutation_index_tags_decision.v1.json b/contracts/conformance/vectors/trade/mutation_index_tags_decision.v1.json @@ -0,0 +1,147 @@ +{ + "suite": "trade_mutation_index_decision", + "contract_version": "1.0.0", + "vectors": [ + { + "id": "trade_mutation_index_all_fields_001", + "kind": "trade.mutation_index_tags.valid", + "input": { + "contract_id": "radroots.trade.revision_proposal.v1", + "trade_id": "11111111111111111111111111111111", + "mutation_id": "2222222222222222222222222222222222222222222222222222222222222222", + "root_mutation_id": "3333333333333333333333333333333333333333333333333333333333333333", + "parent_mutation_ids": [ + "4444444444444444444444444444444444444444444444444444444444444444", + "5555555555555555555555555555555555555555555555555555555555555555" + ], + "buyer_pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "seller_pubkey": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + "expected": { + "kind": 3472, + "tags": [ + ["contract", "radroots.trade.revision_proposal.v1"], + ["d", "11111111111111111111111111111111"], + ["x", "2222222222222222222222222222222222222222222222222222222222222222", "mutation"], + ["x", "3333333333333333333333333333333333333333333333333333333333333333", "root"], + ["x", "4444444444444444444444444444444444444444444444444444444444444444", "parent"], + ["x", "5555555555555555555555555555555555555555555555555555555555555555", "parent"], + ["p", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], + ["p", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"] + ] + } + }, + { + "id": "trade_mutation_index_proposal_002", + "kind": "trade.mutation_index_tags.valid", + "input": { + "contract_id": "radroots.trade.proposal.v1", + "root_mutation_id": null, + "parent_mutation_ids": [] + }, + "expected": { + "kind": 3470, + "tag_names_and_semantics": ["contract", "d:trade", "x:mutation", "p:buyer-first", "p:seller-second"] + } + }, + { + "id": "trade_mutation_index_decision_003", + "kind": "trade.mutation_index_tags.valid", + "input": { + "contract_id": "radroots.trade.decision.v1", + "root_mutation_id": "3333333333333333333333333333333333333333333333333333333333333333", + "parent_mutation_ids": ["4444444444444444444444444444444444444444444444444444444444444444"] + }, + "expected": { + "kind": 3471, + "tag_names_and_semantics": ["contract", "d:trade", "x:mutation", "x:root", "x:parent", "p:buyer-first", "p:seller-second"] + } + }, + { + "id": "trade_mutation_index_revision_decision_004", + "kind": "trade.mutation_index_tags.valid", + "input": { + "contract_id": "radroots.trade.revision_decision.v1", + "root_mutation_id": "3333333333333333333333333333333333333333333333333333333333333333", + "parent_mutation_ids": ["4444444444444444444444444444444444444444444444444444444444444444"] + }, + "expected": { + "kind": 3473, + "tag_names_and_semantics": ["contract", "d:trade", "x:mutation", "x:root", "x:parent", "p:buyer-first", "p:seller-second"] + } + }, + { + "id": "trade_mutation_index_cancellation_005", + "kind": "trade.mutation_index_tags.valid", + "input": { + "contract_id": "radroots.trade.cancellation.v1", + "root_mutation_id": "3333333333333333333333333333333333333333333333333333333333333333", + "parent_mutation_ids": ["4444444444444444444444444444444444444444444444444444444444444444"] + }, + "expected": { + "kind": 3474, + "tag_names_and_semantics": ["contract", "d:trade", "x:mutation", "x:root", "x:parent", "p:buyer-first", "p:seller-second"] + } + }, + { + "id": "trade_mutation_index_missing_mutation_006", + "kind": "trade.mutation_index_tags.invalid", + "input": { "base": "trade_mutation_index_all_fields_001", "remove_tag": ["x", "mutation"] }, + "expected": { "layer": "wire", "error_code": "missing_mutation_tag" } + }, + { + "id": "trade_mutation_index_duplicate_trade_007", + "kind": "trade.mutation_index_tags.invalid", + "input": { "base": "trade_mutation_index_all_fields_001", "append_tag": ["d", "99999999999999999999999999999999"] }, + "expected": { "layer": "wire", "error_code": "duplicate_trade_tag" } + }, + { + "id": "trade_mutation_index_proposal_with_root_008", + "kind": "trade.mutation_index_tags.invalid", + "input": { "base": "trade_mutation_index_proposal_002", "append_tag": ["x", "3333333333333333333333333333333333333333333333333333333333333333", "root"] }, + "expected": { "layer": "wire", "error_code": "unexpected_root_tag" } + }, + { + "id": "trade_mutation_index_nonproposal_without_root_009", + "kind": "trade.mutation_index_tags.invalid", + "input": { "base": "trade_mutation_index_all_fields_001", "remove_tag": ["x", "root"] }, + "expected": { "layer": "wire", "error_code": "missing_root_tag" } + }, + { + "id": "trade_mutation_index_unsorted_parents_010", + "kind": "trade.mutation_index_tags.invalid", + "input": { "base": "trade_mutation_index_all_fields_001", "swap_tag_indexes": [4, 5] }, + "expected": { "layer": "wire", "error_code": "noncanonical_parent_order" } + }, + { + "id": "trade_mutation_index_wrong_party_order_011", + "kind": "trade.mutation_index_tags.invalid", + "input": { "base": "trade_mutation_index_all_fields_001", "swap_tag_indexes": [6, 7] }, + "expected": { "layer": "wire", "error_code": "party_tag_order_mismatch" } + }, + { + "id": "trade_mutation_index_legacy_parent_event_tag_012", + "kind": "trade.mutation_index_tags.invalid", + "input": { "base": "trade_mutation_index_all_fields_001", "replace_tag": { "index": 4, "tag": ["e", "4444444444444444444444444444444444444444444444444444444444444444"] } }, + "expected": { "layer": "wire", "error_code": "legacy_parent_event_tag" } + }, + { + "id": "trade_mutation_index_caller_structural_tag_013", + "kind": "trade.mutation_index_tags.invalid", + "input": { "builder_extra_tags": [["d", "11111111111111111111111111111111"]] }, + "expected": { "layer": "builder", "error_code": "caller_structural_tag_forbidden" } + }, + { + "id": "trade_mutation_index_proposal_with_parent_014", + "kind": "trade.mutation_index_tags.invalid", + "input": { "base": "trade_mutation_index_proposal_002", "append_tag": ["x", "4444444444444444444444444444444444444444444444444444444444444444", "parent"] }, + "expected": { "layer": "wire", "error_code": "unexpected_parent_tag" } + }, + { + "id": "trade_mutation_index_nonproposal_without_parent_015", + "kind": "trade.mutation_index_tags.invalid", + "input": { "base": "trade_mutation_index_all_fields_001", "remove_tags": [["x", "parent"]] }, + "expected": { "layer": "wire", "error_code": "missing_parent_tag" } + } + ] +} diff --git a/contracts/crates/catalog.v1.toml b/contracts/crates/catalog.v1.toml @@ -1,1624 +0,0 @@ -schema_version = 1 -schema = "radroots.workspace.catalog.v1" -architecture = "radroots.crates.release.v2" -consolidation = "radroots.rust.consolidation.v1" -version = "0.1.0-alpha" -rust_version = "1.97.1" -edition = "2024" -resolver = "3" -public_package_count = 19 -package_count = 61 -digest_algorithm = "sha256-raw-bytes-v1" -source_tree_digest_algorithm = "sha256-git-ls-tree-r-v1" -source_provenance_policy = "immutable_after_source_retirement" -provenance_correction_contract = "approved_correction_record_required" -retired_packages = [ - "radroots_app_core", - "radroots_app_ffi", - "radroots_app_wasm", - "radroots_app_bindgen", - "radroots-studio-domain", - "radroots-studio-application", - "radroots-studio-nostr", - "radroots-studio-storage", - "radroots-studio-ffi", - "radroots-studio-uniffi-bindgen", - "radroots_sdk_xtask", -] - -[[package]] -name = "radroots_core" -path = "crates/core" -state = "active" -tier = "foundation" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["core"] -permitted_dependency_tiers = ["foundation"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/core" -source_tree_sha256 = "2b1c006eb548b1e3671d4bae1cf0202900909c40affe224e4f5dd098de3fe7c7" -compatibility = ["rust_api", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_identity" -path = "crates/identity" -state = "active" -tier = "foundation" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["identity"] -permitted_dependency_tiers = ["foundation"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/identity" -source_tree_sha256 = "6da935d01da21254f13477cfa45fc44e3bc48dc3e3a8786f7dd8d528fc51b5b9" -compatibility = ["rust_api", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_blossom" -path = "crates/blossom" -state = "active" -tier = "foundation" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["protocol"] -permitted_dependency_tiers = ["foundation"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/blossom" -source_tree_sha256 = "09cd49bdca7432ba0bab915950a6c7fe882ee75f6e70c8916c1f04030f27745d" -compatibility = ["rust_api", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_protocol" -path = "crates/protocol" -state = "active" -tier = "foundation" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["protocol"] -permitted_dependency_tiers = ["foundation"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/protocol" -source_tree_sha256 = "05f7c372daec2da2c2e249bc10a645fbe11bb36ca4f45b7ebb52094fb39dc593" -compatibility = ["rust_api", "wire", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_event" -path = "crates/event" -state = "active" -tier = "domain" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["event"] -permitted_dependency_tiers = ["foundation", "domain"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/event" -source_tree_sha256 = "bfe3e6ece4d728ba9e5b6476376c172d621a310788c420e33f81a4cdfc3067ef" -compatibility = ["rust_api", "wire", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_event_codec" -path = "crates/event_codec" -state = "active" -tier = "domain" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["event"] -permitted_dependency_tiers = ["foundation", "domain"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/event_codec" -source_tree_sha256 = "6b51309e57b163d7577ec4b7f0fd8e28b2c98aa44cb7fe51a38346300e6078f7" -compatibility = ["rust_api", "wire", "generated", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_trade" -path = "crates/trade" -state = "active" -tier = "domain" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["trade"] -permitted_dependency_tiers = ["foundation", "domain"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/trade" -source_tree_sha256 = "10c873d05c12255ebbdba57772688bb033a183f569959184f1fe9dac248b9bda" -compatibility = ["rust_api", "behavior", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_signing" -path = "crates/signing" -state = "active" -tier = "spi" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["signing"] -permitted_dependency_tiers = ["foundation", "domain", "spi"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/signing" -source_tree_sha256 = "55d4e9f719358a3bb5287688947a34773646aabc7b902a6da790b5c4c0d72026" -compatibility = ["rust_api", "security", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_transport" -path = "crates/transport" -state = "active" -tier = "spi" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["transport"] -permitted_dependency_tiers = ["foundation", "domain", "spi"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/transport" -source_tree_sha256 = "4f39941e5a3c7ec82e179198900541dfcd66ef6bfcffcc7de7e89b604461598a" -compatibility = ["rust_api", "wire", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_secrets" -path = "crates/secrets" -state = "active" -tier = "spi" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["security"] -permitted_dependency_tiers = ["foundation", "domain", "spi"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/secrets" -source_tree_sha256 = "f20923b87e73a65db157f75342fa6814a0b90707e2210f387f9aaddbd2705cf2" -compatibility = ["rust_api", "security", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_storage" -path = "crates/storage" -state = "active" -tier = "spi" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["storage"] -permitted_dependency_tiers = ["foundation", "domain", "spi"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/storage" -source_tree_sha256 = "383e648daaded9c02d8bf64164697fbd23f0c9a82887b0210a487c1803ccdf91" -compatibility = ["rust_api", "data", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_nostr" -path = "crates/nostr" -state = "active" -tier = "adapter" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["nostr"] -permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/nostr" -source_tree_sha256 = "5d526e5aa8c98b71218a341346a70250d278866b6818204b8a78b9772508bb0e" -compatibility = ["rust_api", "wire", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_nostr_connect" -path = "crates/nostr_connect" -state = "active" -tier = "adapter" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["nostr"] -permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/nostr_connect" -source_tree_sha256 = "10cb6c86a37d11fdf09aa8f47486e504b7ad94d2e0ba92f241442ec10a4578b5" -compatibility = ["rust_api", "wire", "security", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_storage_sqlite" -path = "crates/storage_sqlite" -state = "active" -tier = "adapter" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["storage"] -permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/storage_sqlite" -source_tree_sha256 = "1ba1c8adc6741e931220eae5fdcef7a05894e3d8276a869448ab6583b13b93e9" -compatibility = ["rust_api", "data", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_transport_nostr" -path = "crates/transport_nostr" -state = "active" -tier = "adapter" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["transport"] -permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/transport_nostr" -source_tree_sha256 = "1f8b471ee468200dc61e8452184d213906d3ca4b13e64b311aca7ad40fdacac6" -compatibility = ["rust_api", "network", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_geonames" -path = "crates/geonames" -state = "active" -tier = "adapter" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["geonames"] -permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/geonames" -source_tree_sha256 = "1ae21934d6b6dadf4baebad3f4658c6cfe04d4abb4b36f869644c87cebacdc3a" -compatibility = ["rust_api", "data", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_sync" -path = "crates/sync" -state = "active" -tier = "orchestration" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "portable", "public_native"] -owners = ["sync"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/sync" -source_tree_sha256 = "23a16aaddc8366fd7ad6461e97103de03593d23a16687a7a2b6f86fc3a683c53" -compatibility = ["rust_api", "behavior", "features", "package"] -replaces = [] - -[[package]] -name = "radroots_nostrdb" -path = "crates/nostrdb" -state = "active" -tier = "preview" -visibility = "private_adapter" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/nostrdb" -source_tree_sha256 = "f1824eb3fe9f02046aabe40319d2c346de499c74a90aca11fe648e76bc8e744b" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_transport_reticulum" -path = "crates/transport_reticulum" -state = "active" -tier = "preview" -visibility = "private_adapter" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/transport_reticulum" -source_tree_sha256 = "590a69dabb65e891900884e5b67636d0a2e83f40a8d92fc4fdf3fb68bf9ac493" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_simplex_app_store" -path = "crates/simplex_app_store" -state = "active" -tier = "preview" -visibility = "private_adapter" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/simplex_app_store" -source_tree_sha256 = "43d51e5601d16666faa32607da732e8c48d9c75dee8c308c782b90da5fa8fdf5" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_simplex_agent_proto" -path = "crates/simplex_agent_proto" -state = "active" -tier = "preview" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/simplex_agent_proto" -source_tree_sha256 = "9ba0704f166f7cd3aa08f4cb8c3548f0da4d641266f5c6ac3691db0ebb2576f2" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_simplex_chat_proto" -path = "crates/simplex_chat_proto" -state = "active" -tier = "preview" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/simplex_chat_proto" -source_tree_sha256 = "1ba255a084e8962c472c5d0fb89592b8e775cc1017827ff13d18315e6bebf0d9" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_simplex_smp_crypto" -path = "crates/simplex_smp_crypto" -state = "active" -tier = "preview" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/simplex_smp_crypto" -source_tree_sha256 = "618926bd97e2b8e68732aff55b56817ee942ae66fbd4670a8a87ce9c28f37a93" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_simplex_smp_proto" -path = "crates/simplex_smp_proto" -state = "active" -tier = "preview" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/simplex_smp_proto" -source_tree_sha256 = "4d3580ab580ec4008af0f1b6c45cfaf418b8f8f112d80e2c6338c83aba44c730" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_simplex_smp_transport" -path = "crates/simplex_smp_transport" -state = "active" -tier = "preview" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/simplex_smp_transport" -source_tree_sha256 = "50d902c20901de5647e8a3dbe359b9d79eb336d8952c363b914f51ee39240f24" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_replica_schema" -path = "crates/replica_schema" -state = "active" -tier = "preview" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/replica_schema" -source_tree_sha256 = "65c91da5221a98f33d134ff19d760dc7185c33985574c127e7e3ffe9b6636538" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_replica_store" -path = "crates/replica_store" -state = "active" -tier = "preview" -visibility = "private_adapter" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/replica_store" -source_tree_sha256 = "6b4761d89269e5468b924c30d2b1f9da4ae42adcb5c6ae85bd65c2c47a565e08" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_replica_sync" -path = "crates/replica_sync" -state = "active" -tier = "preview" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", - "fixture", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/replica_sync" -source_tree_sha256 = "8c957b0bc99c690c40d74cf747ece37b9f5b2d6a4849171fc2d0cce675850c90" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_mesh" -path = "crates/mesh" -state = "active" -tier = "preview" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/mesh" -source_tree_sha256 = "2aa1e901b21ecaa135e7af10ef96270aeae0a19eff5e5006ffdcd1aca62e7979" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_mesh_agent_client" -path = "crates/mesh_agent_client" -state = "active" -tier = "preview" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/mesh_agent_client" -source_tree_sha256 = "37a7a74761d6922264787d9dfc22c01453c423e0dfcece854a37ee9df725232a" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_mesh_agent_proto" -path = "crates/mesh_agent_proto" -state = "active" -tier = "preview" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "preview"] -owners = ["preview"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/mesh_agent_proto" -source_tree_sha256 = "85767b6a613bd1e167484bb60221107c43648472e4cebfdcbeda7e4bc38f9c57" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_runtime_paths" -path = "crates/runtime_paths" -state = "active" -tier = "runtime" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "preview"] -owners = ["runtime"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", - "runtime", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/runtime_paths" -source_tree_sha256 = "59ed1e101555715827c33e7781ff4993149b2177011ab1401f5cd832a1666120" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_runtime_distribution" -path = "crates/runtime_distribution" -state = "active" -tier = "runtime" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "preview"] -owners = ["runtime"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", - "runtime", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/runtime_distribution" -source_tree_sha256 = "412a69a157253216fc08daa967e3de4f980b35cb6ef098baa4a77d74175ba193" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_runtime_manager" -path = "crates/runtime_manager" -state = "active" -tier = "runtime" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "preview"] -owners = ["runtime"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", - "runtime", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/runtime_manager" -source_tree_sha256 = "d6df1cfee50affc1e8bb3fcfbf283873c0442031d90db08da31bf3d668547809" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_sql_core" -path = "crates/sql_core" -state = "active" -tier = "adapter" -visibility = "private_adapter" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native", "wasm32"] -groups = ["coverage_required", "preview", "wasm"] -owners = ["storage"] -permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/sql_core" -source_tree_sha256 = "18e107bdeeee8761f731a1c389e475e5caebbd2a958c0646d0c6aa5f8c95b8dc" -compatibility = ["package_private"] -replaces = [] - -[[package]] -name = "radroots_test_fixtures" -path = "crates/test_fixtures" -state = "active" -tier = "fixture" -visibility = "private_fixture" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "preview"] -owners = ["testing"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", - "runtime", - "fixture", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "crates/test_fixtures" -source_tree_sha256 = "763e4af22fbf5e7f11709c1b00f55569dbe3ebf8b679bdb937d462d576f02524" -compatibility = ["fixtures"] -replaces = [] - -[[package]] -name = "xtask" -path = "tools/xtask" -state = "active" -tier = "tool" -visibility = "private_tool" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "tools"] -owners = ["architecture"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", - "runtime", - "fixture", - "codegen", - "tool", -] -source_repository = "https://github.com/radrootslabs/lib" -source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" -source_path = "tools/xtask" -source_tree_sha256 = "2846b559151c835b1a561153166074625a8430130b2f1adcaf1a358eb0da20ff" -compatibility = ["command_surface"] -replaces = [] - -[[package]] -name = "radroots_sdk" -path = "crates/sdk" -state = "active" -tier = "sdk" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native", "sdk"] -owners = ["sdk"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", -] -source_repository = "https://github.com/radrootslabs/sdk" -source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" -source_path = "crates/sdk" -source_tree_sha256 = "6cc78f544bd0a0d74783cbba118ea4815ce4b4bd094433193844621352411efb" -compatibility = ["rust_api", "features", "package", "product"] -replaces = [] - -[[package]] -name = "radroots" -path = "crates/radroots" -state = "active" -tier = "facade" -visibility = "public_release" -publish = true -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["any"] -groups = ["coverage_required", "portable", "public_native", "sdk"] -owners = ["sdk"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", -] -source_repository = "https://github.com/radrootslabs/sdk" -source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" -source_path = "crates/radroots" -source_tree_sha256 = "7c9b4392eb7e8d8e799f4a62c9886f349161bad440659beb0d1a739911ab1425" -compatibility = ["rust_api", "features", "package", "product"] -replaces = [] - -[[package]] -name = "radroots_core_bindings" -path = "crates/core_bindings" -state = "active" -tier = "codegen" -visibility = "private_codegen" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "sdk", "boundaries"] -owners = ["sdk"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "codegen", -] -source_repository = "https://github.com/radrootslabs/sdk" -source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" -source_path = "crates/core_bindings" -source_tree_sha256 = "f1a72bde7364179687bbdf6c4dacddacd504c52a884af941ce3557bae27ee981" -compatibility = ["generated", "package_private"] -replaces = [] - -[[package]] -name = "radroots_event_bindings" -path = "crates/event_bindings" -state = "active" -tier = "codegen" -visibility = "private_codegen" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "sdk", "boundaries"] -owners = ["sdk"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "codegen", -] -source_repository = "https://github.com/radrootslabs/sdk" -source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" -source_path = "crates/event_bindings" -source_tree_sha256 = "32f23a76a5c135de841cae623a28de3424dace3b6ba46af8c2cccc35b7c62f09" -compatibility = ["generated", "package_private"] -replaces = [] - -[[package]] -name = "radroots_identity_bindings" -path = "crates/identity_bindings" -state = "active" -tier = "codegen" -visibility = "private_codegen" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "sdk", "boundaries"] -owners = ["sdk"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "codegen", -] -source_repository = "https://github.com/radrootslabs/sdk" -source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" -source_path = "crates/identity_bindings" -source_tree_sha256 = "a0cf1539805704195cf82f9ffffa06e443a9c1cabd90a47bfcf6110f20549acd" -compatibility = ["generated", "package_private"] -replaces = [] - -[[package]] -name = "radroots_trade_bindings" -path = "crates/trade_bindings" -state = "active" -tier = "codegen" -visibility = "private_codegen" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "sdk", "boundaries"] -owners = ["sdk"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "codegen", -] -source_repository = "https://github.com/radrootslabs/sdk" -source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" -source_path = "crates/trade_bindings" -source_tree_sha256 = "28a60e08d65a891961189d5e614bbc008fe36574b3823333375339c589236342" -compatibility = ["generated", "package_private"] -replaces = [] - -[[package]] -name = "radroots_replica_schema_bindings" -path = "crates/replica_schema_bindings" -state = "active" -tier = "codegen" -visibility = "private_codegen" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "sdk", "boundaries"] -owners = ["sdk"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", - "sdk", - "facade", - "codegen", -] -source_repository = "https://github.com/radrootslabs/sdk" -source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" -source_path = "crates/replica_schema_bindings" -source_tree_sha256 = "41aeb6520962da64f13e09449d03b6df901b788965732eff439b282fe8f8abb1" -compatibility = ["generated", "package_private"] -replaces = [] - -[[package]] -name = "radroots_event_codec_wasm" -path = "crates/event_codec_wasm" -state = "active" -tier = "boundary" -visibility = "private_boundary" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["wasm32"] -groups = ["coverage_required", "sdk", "wasm", "boundaries"] -owners = ["sdk"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "boundary", -] -source_repository = "https://github.com/radrootslabs/sdk" -source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" -source_path = "crates/event_codec_wasm" -source_tree_sha256 = "698abf3b77aa87b6460432342908b475c55bb55d285dc9aef24e27239e4cfa3c" -compatibility = ["wasm", "generated", "package_private"] -replaces = [] - -[[package]] -name = "radroots_replica_store_wasm" -path = "crates/replica_store_wasm" -state = "active" -tier = "boundary" -visibility = "private_boundary" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["wasm32"] -groups = ["coverage_required", "sdk", "wasm", "boundaries"] -owners = ["sdk"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", - "sdk", - "facade", - "boundary", -] -source_repository = "https://github.com/radrootslabs/sdk" -source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" -source_path = "crates/replica_store_wasm" -source_tree_sha256 = "59cb050d0493047b57aebc4e1bb3adfd5af2fcd464de1f76ffabc1dfabe2baad" -compatibility = ["wasm", "generated", "package_private"] -replaces = [] - -[[package]] -name = "radroots_replica_sync_wasm" -path = "crates/replica_sync_wasm" -state = "active" -tier = "boundary" -visibility = "private_boundary" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["wasm32"] -groups = ["coverage_required", "sdk", "wasm", "boundaries"] -owners = ["sdk"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "preview", - "sdk", - "facade", - "boundary", -] -source_repository = "https://github.com/radrootslabs/sdk" -source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" -source_path = "crates/replica_sync_wasm" -source_tree_sha256 = "95f2a291066bab18c744f50a910feecc913dd50a4a3b6c9a464247695d8faffc" -compatibility = ["wasm", "generated", "package_private"] -replaces = [] - -[[package]] -name = "radroots_sdk_ffi" -path = "crates/sdk_ffi" -state = "active" -tier = "boundary" -visibility = "private_boundary" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["native"] -groups = ["coverage_required", "sdk", "boundaries"] -owners = ["sdk"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "boundary", -] -source_repository = "https://github.com/radrootslabs/sdk" -source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" -source_path = "crates/ffi" -source_tree_sha256 = "f07e2087f0506b146848476048b32b01584905e2928fe56a36d4f90663590e33" -compatibility = ["ffi", "generated", "package_private"] -replaces = [] - -[[package]] -name = "radroots_sdk_sql_wasm_runtime" -path = "crates/sdk_sql_wasm_runtime" -state = "active" -tier = "adapter" -visibility = "private_adapter" -publish = false -version = "0.1.0-alpha" -license = "MIT OR Apache-2.0" -platforms = ["wasm32"] -groups = ["coverage_required", "sdk", "wasm"] -owners = ["sdk"] -permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] -source_repository = "https://github.com/radrootslabs/sdk" -source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" -source_path = "crates/sql_wasm_runtime" -source_tree_sha256 = "6ca1dca6f248c50b6bcd4c684b4ad31acd17b8bfc36a8b0072872f6db927c303" -compatibility = ["wasm", "data", "package_private"] -replaces = [] - -[[package]] -name = "radroots_mobile_core" -path = "crates/mobile_core" -state = "active" -tier = "runtime" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "GPL-3.0-or-later" -platforms = ["native", "wasm32"] -groups = ["coverage_required", "mobile"] -owners = ["mobile"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "runtime", -] -source_repository = "https://github.com/radrootslabs/app_rt" -source_revision = "7ab1a8624d50890d6d18545ffb47d8083afa8c67" -source_path = "crates/core" -source_tree_sha256 = "8ae546ee60a97bfc145c12ea94dcd6d528bf5f49e974159442738c29eb3d6ee1" -compatibility = ["product", "lifecycle", "package_private"] -replaces = ["radroots_app_core"] - -[[package]] -name = "radroots_mobile_ffi" -path = "crates/mobile_ffi" -state = "active" -tier = "boundary" -visibility = "private_boundary" -publish = false -version = "0.1.0-alpha" -license = "GPL-3.0-or-later" -platforms = ["apple", "android"] -groups = ["coverage_required", "mobile", "boundaries"] -owners = ["mobile"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "runtime", - "boundary", -] -source_repository = "https://github.com/radrootslabs/app_rt" -source_revision = "7ab1a8624d50890d6d18545ffb47d8083afa8c67" -source_path = "crates/ffi" -source_tree_sha256 = "9a38699ad6d39b9f8764fed6a67bd55908ff54cb2e3cae9a3890ce1a46789b2b" -compatibility = ["ffi", "swift", "kotlin", "lifecycle", "package_private"] -replaces = ["radroots_app_ffi"] - -[[package]] -name = "radroots_mobile_wasm" -path = "crates/mobile_wasm" -state = "active" -tier = "boundary" -visibility = "private_boundary" -publish = false -version = "0.1.0-alpha" -license = "GPL-3.0-or-later" -platforms = ["wasm32"] -groups = ["coverage_required", "mobile", "wasm", "boundaries"] -owners = ["mobile"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "runtime", - "boundary", -] -source_repository = "https://github.com/radrootslabs/app_rt" -source_revision = "7ab1a8624d50890d6d18545ffb47d8083afa8c67" -source_path = "crates/wasm" -source_tree_sha256 = "8c65c867eafc0643ca9fd86150766af4f4ab2f0f8321d66306418261a8f43124" -compatibility = ["wasm", "lifecycle", "package_private"] -replaces = ["radroots_app_wasm"] - -[[package]] -name = "radroots_mobile_bindgen" -path = "crates/mobile_bindgen" -state = "active" -tier = "codegen" -visibility = "private_codegen" -publish = false -version = "0.1.0-alpha" -license = "GPL-3.0-or-later" -platforms = ["native"] -groups = ["coverage_required", "mobile", "boundaries"] -owners = ["mobile"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "runtime", - "boundary", - "codegen", -] -source_repository = "https://github.com/radrootslabs/app_rt" -source_revision = "7ab1a8624d50890d6d18545ffb47d8083afa8c67" -source_path = "crates/bindgen" -source_tree_sha256 = "f0a9d7ec9794257bc56063117208e6e83a34efe1b852e8af6d6a56a1693d27fa" -compatibility = ["generated", "swift", "kotlin", "package_private"] -replaces = ["radroots_app_bindgen"] - -[[package]] -name = "radroots_studio_domain" -path = "crates/studio_domain" -state = "active" -tier = "application_domain" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "GPL-3.0-only" -platforms = ["native"] -groups = ["coverage_required", "studio"] -owners = ["studio"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "application_domain", -] -source_repository = "https://github.com/radrootslabs/studio_app" -source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" -source_path = "core/crates/domain" -source_tree_sha256 = "25f8abe2f8f4e9dfeb6450116be67eb3faad53f2d3879a4c142796238f74b0a9" -compatibility = ["product", "data", "package_private"] -replaces = ["radroots-studio-domain"] - -[[package]] -name = "radroots_studio_preferences" -path = "crates/studio_preferences" -state = "active" -tier = "application_domain" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "MPL-2.0" -platforms = ["native"] -groups = ["coverage_required", "studio"] -owners = ["studio"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "application_domain", -] -source_repository = "https://github.com/radrootslabs/_radroots" -source_revision = "6074a4745be361f21bb47d4778c74a14b2d57954" -source_path = "studio_app/studio_app_core/crates/core" -source_tree_sha256 = "0237265710a676ce1db0fb3091e1e5d9a5831339e00076ea2a33b96d6343834d" -compatibility = ["product", "preferences", "package_private"] -replaces = ["studio_app_core"] - -[[package]] -name = "radroots_studio_application" -path = "crates/studio_application" -state = "active" -tier = "application" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "GPL-3.0-only" -platforms = ["native"] -groups = ["coverage_required", "studio"] -owners = ["studio"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "application_domain", - "application", -] -source_repository = "https://github.com/radrootslabs/studio_app" -source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" -source_path = "core/crates/application" -source_tree_sha256 = "8b0b5d4d74dde0af3c5fb3dac979b0cf57cccf073d597f7bd35b1e73a711fe0b" -compatibility = ["product", "behavior", "package_private"] -replaces = ["radroots-studio-application"] - -[[package]] -name = "radroots_studio_nostr" -path = "crates/studio_nostr" -state = "active" -tier = "application_adapter" -visibility = "private_adapter" -publish = false -version = "0.1.0-alpha" -license = "GPL-3.0-only" -platforms = ["native"] -groups = ["coverage_required", "studio"] -owners = ["studio"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "application_domain", - "application", - "application_adapter", -] -source_repository = "https://github.com/radrootslabs/studio_app" -source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" -source_path = "core/crates/nostr" -source_tree_sha256 = "2ce5ce0227ab190102a94ffdf80d95b37ed35f5ef62286c4e3e19cd42a777115" -compatibility = ["network", "security", "package_private"] -replaces = ["radroots-studio-nostr"] - -[[package]] -name = "radroots_studio_storage" -path = "crates/studio_storage" -state = "active" -tier = "application_adapter" -visibility = "private_adapter" -publish = false -version = "0.1.0-alpha" -license = "GPL-3.0-only" -platforms = ["native"] -groups = ["coverage_required", "studio"] -owners = ["studio"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "application_domain", - "application", - "application_adapter", -] -source_repository = "https://github.com/radrootslabs/studio_app" -source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" -source_path = "core/crates/storage" -source_tree_sha256 = "f3addecbd7f6dbccda4a438597b4e433443a34b48a6c4ef9efa90e49e8f05c4b" -compatibility = ["data", "keyring", "package_private"] -replaces = ["radroots-studio-storage"] - -[[package]] -name = "radroots_studio_runtime" -path = "crates/studio_runtime" -state = "active" -tier = "runtime_composition" -visibility = "private_runtime" -publish = false -version = "0.1.0-alpha" -license = "GPL-3.0-only" -platforms = ["native"] -groups = ["coverage_required", "studio"] -owners = ["studio"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "application_domain", - "application", - "application_adapter", - "runtime_composition", -] -source_repository = "https://github.com/radrootslabs/studio_app" -source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" -source_path = "core/crates/storage" -source_tree_sha256 = "f3addecbd7f6dbccda4a438597b4e433443a34b48a6c4ef9efa90e49e8f05c4b" -compatibility = ["product", "lifecycle", "package_private"] -replaces = [] - -[[package]] -name = "radroots_studio_ffi" -path = "crates/studio_ffi" -state = "active" -tier = "boundary" -visibility = "private_boundary" -publish = false -version = "0.1.0-alpha" -license = "GPL-3.0-only" -platforms = ["linux", "macos", "windows"] -groups = ["coverage_required", "studio", "boundaries"] -owners = ["studio"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "application_domain", - "application", - "application_adapter", - "runtime_composition", - "boundary", -] -source_repository = "https://github.com/radrootslabs/studio_app" -source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" -source_path = "core/crates/ffi" -source_tree_sha256 = "0bb1d02eb963a606a288d9c35fb418de7bfd914dc5e2c4a38112af64c643151c" -compatibility = ["ffi", "kotlin", "product", "lifecycle", "package_private"] -replaces = ["radroots-studio-ffi"] - -[[package]] -name = "radroots_studio_uniffi_bindgen" -path = "crates/studio_uniffi_bindgen" -state = "active" -tier = "codegen" -visibility = "private_codegen" -publish = false -version = "0.1.0-alpha" -license = "GPL-3.0-only" -platforms = ["native"] -groups = ["coverage_required", "studio", "boundaries"] -owners = ["studio"] -permitted_dependency_tiers = [ - "foundation", - "domain", - "spi", - "adapter", - "orchestration", - "sdk", - "facade", - "application_domain", - "application", - "application_adapter", - "runtime_composition", - "boundary", - "codegen", -] -source_repository = "https://github.com/radrootslabs/studio_app" -source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" -source_path = "core/tools/uniffi-bindgen" -source_tree_sha256 = "0eedd47c17fc7b2b84d953f2c4613aecf96575df466eb106450c7d7eee25ca9b" -compatibility = ["generated", "kotlin", "package_private"] -replaces = ["radroots-studio-uniffi-bindgen"] diff --git a/contracts/crates/catalog.v2.toml b/contracts/crates/catalog.v2.toml @@ -0,0 +1,1686 @@ +schema_version = 2 +schema = "radroots.workspace.catalog.v2" +architecture = "radroots.crates.release.v2" +consolidation = "radroots.rust.consolidation.v1" +version = "0.1.0-alpha" +rust_version = "1.97.1" +edition = "2024" +resolver = "3" +public_package_count = 19 +package_count = 61 +digest_algorithm = "sha256-raw-bytes-v1" +source_tree_digest_algorithm = "sha256-git-ls-tree-r-v1" +native_introduction_tree_digest_algorithm = "sha256-git-tree-records-z-v1" +source_provenance_policy = "imported_revision_tree_or_native_introduction_tree" +provenance_correction_contract = "approved_correction_record_required" +retired_packages = [ + "radroots_app_core", + "radroots_app_ffi", + "radroots_app_wasm", + "radroots_app_bindgen", + "radroots-studio-domain", + "radroots-studio-application", + "radroots-studio-nostr", + "radroots-studio-storage", + "radroots-studio-ffi", + "radroots-studio-uniffi-bindgen", + "radroots_sdk_xtask", +] + +[[package]] +name = "radroots_core" +path = "crates/core" +state = "active" +tier = "foundation" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["core"] +permitted_dependency_tiers = ["foundation"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/core" +source_tree_sha256 = "2b1c006eb548b1e3671d4bae1cf0202900909c40affe224e4f5dd098de3fe7c7" +compatibility = ["rust_api", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_identity" +path = "crates/identity" +state = "active" +tier = "foundation" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["identity"] +permitted_dependency_tiers = ["foundation"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/identity" +source_tree_sha256 = "6da935d01da21254f13477cfa45fc44e3bc48dc3e3a8786f7dd8d528fc51b5b9" +compatibility = ["rust_api", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_blossom" +path = "crates/blossom" +state = "active" +tier = "foundation" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["protocol"] +permitted_dependency_tiers = ["foundation"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/blossom" +source_tree_sha256 = "09cd49bdca7432ba0bab915950a6c7fe882ee75f6e70c8916c1f04030f27745d" +compatibility = ["rust_api", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_protocol" +path = "crates/protocol" +state = "active" +tier = "foundation" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["protocol"] +permitted_dependency_tiers = ["foundation"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/protocol" +source_tree_sha256 = "05f7c372daec2da2c2e249bc10a645fbe11bb36ca4f45b7ebb52094fb39dc593" +compatibility = ["rust_api", "wire", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_event" +path = "crates/event" +state = "active" +tier = "domain" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["event"] +permitted_dependency_tiers = ["foundation", "domain"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/event" +source_tree_sha256 = "bfe3e6ece4d728ba9e5b6476376c172d621a310788c420e33f81a4cdfc3067ef" +compatibility = ["rust_api", "wire", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_event_codec" +path = "crates/event_codec" +state = "active" +tier = "domain" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["event"] +permitted_dependency_tiers = ["foundation", "domain"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/event_codec" +source_tree_sha256 = "6b51309e57b163d7577ec4b7f0fd8e28b2c98aa44cb7fe51a38346300e6078f7" +compatibility = ["rust_api", "wire", "generated", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_trade" +path = "crates/trade" +state = "active" +tier = "domain" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["trade"] +permitted_dependency_tiers = ["foundation", "domain"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/trade" +source_tree_sha256 = "10c873d05c12255ebbdba57772688bb033a183f569959184f1fe9dac248b9bda" +compatibility = ["rust_api", "behavior", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_signing" +path = "crates/signing" +state = "active" +tier = "spi" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["signing"] +permitted_dependency_tiers = ["foundation", "domain", "spi"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/signing" +source_tree_sha256 = "55d4e9f719358a3bb5287688947a34773646aabc7b902a6da790b5c4c0d72026" +compatibility = ["rust_api", "security", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_transport" +path = "crates/transport" +state = "active" +tier = "spi" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["transport"] +permitted_dependency_tiers = ["foundation", "domain", "spi"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/transport" +source_tree_sha256 = "4f39941e5a3c7ec82e179198900541dfcd66ef6bfcffcc7de7e89b604461598a" +compatibility = ["rust_api", "wire", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_secrets" +path = "crates/secrets" +state = "active" +tier = "spi" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["security"] +permitted_dependency_tiers = ["foundation", "domain", "spi"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/secrets" +source_tree_sha256 = "f20923b87e73a65db157f75342fa6814a0b90707e2210f387f9aaddbd2705cf2" +compatibility = ["rust_api", "security", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_storage" +path = "crates/storage" +state = "active" +tier = "spi" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["storage"] +permitted_dependency_tiers = ["foundation", "domain", "spi"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/storage" +source_tree_sha256 = "383e648daaded9c02d8bf64164697fbd23f0c9a82887b0210a487c1803ccdf91" +compatibility = ["rust_api", "data", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_nostr" +path = "crates/nostr" +state = "active" +tier = "adapter" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["nostr"] +permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/nostr" +source_tree_sha256 = "5d526e5aa8c98b71218a341346a70250d278866b6818204b8a78b9772508bb0e" +compatibility = ["rust_api", "wire", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_nostr_connect" +path = "crates/nostr_connect" +state = "active" +tier = "adapter" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["nostr"] +permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/nostr_connect" +source_tree_sha256 = "10cb6c86a37d11fdf09aa8f47486e504b7ad94d2e0ba92f241442ec10a4578b5" +compatibility = ["rust_api", "wire", "security", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_storage_sqlite" +path = "crates/storage_sqlite" +state = "active" +tier = "adapter" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["storage"] +permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/storage_sqlite" +source_tree_sha256 = "1ba1c8adc6741e931220eae5fdcef7a05894e3d8276a869448ab6583b13b93e9" +compatibility = ["rust_api", "data", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_transport_nostr" +path = "crates/transport_nostr" +state = "active" +tier = "adapter" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["transport"] +permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/transport_nostr" +source_tree_sha256 = "1f8b471ee468200dc61e8452184d213906d3ca4b13e64b311aca7ad40fdacac6" +compatibility = ["rust_api", "network", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_geonames" +path = "crates/geonames" +state = "active" +tier = "adapter" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["geonames"] +permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/geonames" +source_tree_sha256 = "1ae21934d6b6dadf4baebad3f4658c6cfe04d4abb4b36f869644c87cebacdc3a" +compatibility = ["rust_api", "data", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_sync" +path = "crates/sync" +state = "active" +tier = "orchestration" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "portable", "public_native"] +owners = ["sync"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/sync" +source_tree_sha256 = "23a16aaddc8366fd7ad6461e97103de03593d23a16687a7a2b6f86fc3a683c53" +compatibility = ["rust_api", "behavior", "features", "package"] +replaces = [] + +[[package]] +name = "radroots_nostrdb" +path = "crates/nostrdb" +state = "active" +tier = "preview" +visibility = "private_adapter" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/nostrdb" +source_tree_sha256 = "f1824eb3fe9f02046aabe40319d2c346de499c74a90aca11fe648e76bc8e744b" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_transport_reticulum" +path = "crates/transport_reticulum" +state = "active" +tier = "preview" +visibility = "private_adapter" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/transport_reticulum" +source_tree_sha256 = "590a69dabb65e891900884e5b67636d0a2e83f40a8d92fc4fdf3fb68bf9ac493" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_simplex_app_store" +path = "crates/simplex_app_store" +state = "active" +tier = "preview" +visibility = "private_adapter" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/simplex_app_store" +source_tree_sha256 = "43d51e5601d16666faa32607da732e8c48d9c75dee8c308c782b90da5fa8fdf5" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_simplex_agent_proto" +path = "crates/simplex_agent_proto" +state = "active" +tier = "preview" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/simplex_agent_proto" +source_tree_sha256 = "9ba0704f166f7cd3aa08f4cb8c3548f0da4d641266f5c6ac3691db0ebb2576f2" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_simplex_chat_proto" +path = "crates/simplex_chat_proto" +state = "active" +tier = "preview" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/simplex_chat_proto" +source_tree_sha256 = "1ba255a084e8962c472c5d0fb89592b8e775cc1017827ff13d18315e6bebf0d9" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_simplex_smp_crypto" +path = "crates/simplex_smp_crypto" +state = "active" +tier = "preview" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/simplex_smp_crypto" +source_tree_sha256 = "618926bd97e2b8e68732aff55b56817ee942ae66fbd4670a8a87ce9c28f37a93" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_simplex_smp_proto" +path = "crates/simplex_smp_proto" +state = "active" +tier = "preview" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/simplex_smp_proto" +source_tree_sha256 = "4d3580ab580ec4008af0f1b6c45cfaf418b8f8f112d80e2c6338c83aba44c730" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_simplex_smp_transport" +path = "crates/simplex_smp_transport" +state = "active" +tier = "preview" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/simplex_smp_transport" +source_tree_sha256 = "50d902c20901de5647e8a3dbe359b9d79eb336d8952c363b914f51ee39240f24" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_replica_schema" +path = "crates/replica_schema" +state = "active" +tier = "preview" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/replica_schema" +source_tree_sha256 = "65c91da5221a98f33d134ff19d760dc7185c33985574c127e7e3ffe9b6636538" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_replica_store" +path = "crates/replica_store" +state = "active" +tier = "preview" +visibility = "private_adapter" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/replica_store" +source_tree_sha256 = "6b4761d89269e5468b924c30d2b1f9da4ae42adcb5c6ae85bd65c2c47a565e08" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_replica_sync" +path = "crates/replica_sync" +state = "active" +tier = "preview" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", + "fixture", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/replica_sync" +source_tree_sha256 = "8c957b0bc99c690c40d74cf747ece37b9f5b2d6a4849171fc2d0cce675850c90" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_mesh" +path = "crates/mesh" +state = "active" +tier = "preview" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/mesh" +source_tree_sha256 = "2aa1e901b21ecaa135e7af10ef96270aeae0a19eff5e5006ffdcd1aca62e7979" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_mesh_agent_client" +path = "crates/mesh_agent_client" +state = "active" +tier = "preview" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/mesh_agent_client" +source_tree_sha256 = "37a7a74761d6922264787d9dfc22c01453c423e0dfcece854a37ee9df725232a" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_mesh_agent_proto" +path = "crates/mesh_agent_proto" +state = "active" +tier = "preview" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "preview"] +owners = ["preview"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/mesh_agent_proto" +source_tree_sha256 = "85767b6a613bd1e167484bb60221107c43648472e4cebfdcbeda7e4bc38f9c57" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_runtime_paths" +path = "crates/runtime_paths" +state = "active" +tier = "runtime" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "preview"] +owners = ["runtime"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", + "runtime", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/runtime_paths" +source_tree_sha256 = "59ed1e101555715827c33e7781ff4993149b2177011ab1401f5cd832a1666120" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_runtime_distribution" +path = "crates/runtime_distribution" +state = "active" +tier = "runtime" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "preview"] +owners = ["runtime"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", + "runtime", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/runtime_distribution" +source_tree_sha256 = "412a69a157253216fc08daa967e3de4f980b35cb6ef098baa4a77d74175ba193" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_runtime_manager" +path = "crates/runtime_manager" +state = "active" +tier = "runtime" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "preview"] +owners = ["runtime"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", + "runtime", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/runtime_manager" +source_tree_sha256 = "d6df1cfee50affc1e8bb3fcfbf283873c0442031d90db08da31bf3d668547809" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_sql_core" +path = "crates/sql_core" +state = "active" +tier = "adapter" +visibility = "private_adapter" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native", "wasm32"] +groups = ["coverage_required", "preview", "wasm"] +owners = ["storage"] +permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/sql_core" +source_tree_sha256 = "18e107bdeeee8761f731a1c389e475e5caebbd2a958c0646d0c6aa5f8c95b8dc" +compatibility = ["package_private"] +replaces = [] + +[[package]] +name = "radroots_test_fixtures" +path = "crates/test_fixtures" +state = "active" +tier = "fixture" +visibility = "private_fixture" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "preview"] +owners = ["testing"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", + "runtime", + "fixture", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "crates/test_fixtures" +source_tree_sha256 = "763e4af22fbf5e7f11709c1b00f55569dbe3ebf8b679bdb937d462d576f02524" +compatibility = ["fixtures"] +replaces = [] + +[[package]] +name = "xtask" +path = "tools/xtask" +state = "active" +tier = "tool" +visibility = "private_tool" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "tools"] +owners = ["architecture"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", + "runtime", + "fixture", + "codegen", + "tool", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/lib" +source_revision = "3f35c869c827b35b27e7a7d789d409e6c3def6a8" +source_path = "tools/xtask" +source_tree_sha256 = "2846b559151c835b1a561153166074625a8430130b2f1adcaf1a358eb0da20ff" +compatibility = ["command_surface"] +replaces = [] + +[[package]] +name = "radroots_sdk" +path = "crates/sdk" +state = "active" +tier = "sdk" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native", "sdk"] +owners = ["sdk"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/sdk" +source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" +source_path = "crates/sdk" +source_tree_sha256 = "6cc78f544bd0a0d74783cbba118ea4815ce4b4bd094433193844621352411efb" +compatibility = ["rust_api", "features", "package", "product"] +replaces = [] + +[[package]] +name = "radroots" +path = "crates/radroots" +state = "active" +tier = "facade" +visibility = "public_release" +publish = true +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["any"] +groups = ["coverage_required", "portable", "public_native", "sdk"] +owners = ["sdk"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/sdk" +source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" +source_path = "crates/radroots" +source_tree_sha256 = "7c9b4392eb7e8d8e799f4a62c9886f349161bad440659beb0d1a739911ab1425" +compatibility = ["rust_api", "features", "package", "product"] +replaces = [] + +[[package]] +name = "radroots_core_bindings" +path = "crates/core_bindings" +state = "active" +tier = "codegen" +visibility = "private_codegen" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "sdk", "boundaries"] +owners = ["sdk"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "codegen", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/sdk" +source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" +source_path = "crates/core_bindings" +source_tree_sha256 = "f1a72bde7364179687bbdf6c4dacddacd504c52a884af941ce3557bae27ee981" +compatibility = ["generated", "package_private"] +replaces = [] + +[[package]] +name = "radroots_event_bindings" +path = "crates/event_bindings" +state = "active" +tier = "codegen" +visibility = "private_codegen" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "sdk", "boundaries"] +owners = ["sdk"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "codegen", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/sdk" +source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" +source_path = "crates/event_bindings" +source_tree_sha256 = "32f23a76a5c135de841cae623a28de3424dace3b6ba46af8c2cccc35b7c62f09" +compatibility = ["generated", "package_private"] +replaces = [] + +[[package]] +name = "radroots_identity_bindings" +path = "crates/identity_bindings" +state = "active" +tier = "codegen" +visibility = "private_codegen" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "sdk", "boundaries"] +owners = ["sdk"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "codegen", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/sdk" +source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" +source_path = "crates/identity_bindings" +source_tree_sha256 = "a0cf1539805704195cf82f9ffffa06e443a9c1cabd90a47bfcf6110f20549acd" +compatibility = ["generated", "package_private"] +replaces = [] + +[[package]] +name = "radroots_trade_bindings" +path = "crates/trade_bindings" +state = "active" +tier = "codegen" +visibility = "private_codegen" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "sdk", "boundaries"] +owners = ["sdk"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "codegen", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/sdk" +source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" +source_path = "crates/trade_bindings" +source_tree_sha256 = "28a60e08d65a891961189d5e614bbc008fe36574b3823333375339c589236342" +compatibility = ["generated", "package_private"] +replaces = [] + +[[package]] +name = "radroots_replica_schema_bindings" +path = "crates/replica_schema_bindings" +state = "active" +tier = "codegen" +visibility = "private_codegen" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "sdk", "boundaries"] +owners = ["sdk"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", + "sdk", + "facade", + "codegen", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/sdk" +source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" +source_path = "crates/replica_schema_bindings" +source_tree_sha256 = "41aeb6520962da64f13e09449d03b6df901b788965732eff439b282fe8f8abb1" +compatibility = ["generated", "package_private"] +replaces = [] + +[[package]] +name = "radroots_event_codec_wasm" +path = "crates/event_codec_wasm" +state = "active" +tier = "boundary" +visibility = "private_boundary" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["wasm32"] +groups = ["coverage_required", "sdk", "wasm", "boundaries"] +owners = ["sdk"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "boundary", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/sdk" +source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" +source_path = "crates/event_codec_wasm" +source_tree_sha256 = "698abf3b77aa87b6460432342908b475c55bb55d285dc9aef24e27239e4cfa3c" +compatibility = ["wasm", "generated", "package_private"] +replaces = [] + +[[package]] +name = "radroots_replica_store_wasm" +path = "crates/replica_store_wasm" +state = "active" +tier = "boundary" +visibility = "private_boundary" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["wasm32"] +groups = ["coverage_required", "sdk", "wasm", "boundaries"] +owners = ["sdk"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", + "sdk", + "facade", + "boundary", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/sdk" +source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" +source_path = "crates/replica_store_wasm" +source_tree_sha256 = "59cb050d0493047b57aebc4e1bb3adfd5af2fcd464de1f76ffabc1dfabe2baad" +compatibility = ["wasm", "generated", "package_private"] +replaces = [] + +[[package]] +name = "radroots_replica_sync_wasm" +path = "crates/replica_sync_wasm" +state = "active" +tier = "boundary" +visibility = "private_boundary" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["wasm32"] +groups = ["coverage_required", "sdk", "wasm", "boundaries"] +owners = ["sdk"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "preview", + "sdk", + "facade", + "boundary", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/sdk" +source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" +source_path = "crates/replica_sync_wasm" +source_tree_sha256 = "95f2a291066bab18c744f50a910feecc913dd50a4a3b6c9a464247695d8faffc" +compatibility = ["wasm", "generated", "package_private"] +replaces = [] + +[[package]] +name = "radroots_sdk_ffi" +path = "crates/sdk_ffi" +state = "active" +tier = "boundary" +visibility = "private_boundary" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["native"] +groups = ["coverage_required", "sdk", "boundaries"] +owners = ["sdk"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "boundary", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/sdk" +source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" +source_path = "crates/ffi" +source_tree_sha256 = "f07e2087f0506b146848476048b32b01584905e2928fe56a36d4f90663590e33" +compatibility = ["ffi", "generated", "package_private"] +replaces = [] + +[[package]] +name = "radroots_sdk_sql_wasm_runtime" +path = "crates/sdk_sql_wasm_runtime" +state = "active" +tier = "adapter" +visibility = "private_adapter" +publish = false +version = "0.1.0-alpha" +license = "MIT OR Apache-2.0" +platforms = ["wasm32"] +groups = ["coverage_required", "sdk", "wasm"] +owners = ["sdk"] +permitted_dependency_tiers = ["foundation", "domain", "spi", "adapter"] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/sdk" +source_revision = "170ecf2b620107fadca85fcb11fbf3798b4ca1ef" +source_path = "crates/sql_wasm_runtime" +source_tree_sha256 = "6ca1dca6f248c50b6bcd4c684b4ad31acd17b8bfc36a8b0072872f6db927c303" +compatibility = ["wasm", "data", "package_private"] +replaces = [] + +[[package]] +name = "radroots_mobile_core" +path = "crates/mobile_core" +state = "active" +tier = "runtime" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "GPL-3.0-or-later" +platforms = ["native", "wasm32"] +groups = ["coverage_required", "mobile"] +owners = ["mobile"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "runtime", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/app_rt" +source_revision = "7ab1a8624d50890d6d18545ffb47d8083afa8c67" +source_path = "crates/core" +source_tree_sha256 = "8ae546ee60a97bfc145c12ea94dcd6d528bf5f49e974159442738c29eb3d6ee1" +compatibility = ["product", "lifecycle", "package_private"] +replaces = ["radroots_app_core"] + +[[package]] +name = "radroots_mobile_ffi" +path = "crates/mobile_ffi" +state = "active" +tier = "boundary" +visibility = "private_boundary" +publish = false +version = "0.1.0-alpha" +license = "GPL-3.0-or-later" +platforms = ["apple", "android"] +groups = ["coverage_required", "mobile", "boundaries"] +owners = ["mobile"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "runtime", + "boundary", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/app_rt" +source_revision = "7ab1a8624d50890d6d18545ffb47d8083afa8c67" +source_path = "crates/ffi" +source_tree_sha256 = "9a38699ad6d39b9f8764fed6a67bd55908ff54cb2e3cae9a3890ce1a46789b2b" +compatibility = ["ffi", "swift", "kotlin", "lifecycle", "package_private"] +replaces = ["radroots_app_ffi"] + +[[package]] +name = "radroots_mobile_wasm" +path = "crates/mobile_wasm" +state = "active" +tier = "boundary" +visibility = "private_boundary" +publish = false +version = "0.1.0-alpha" +license = "GPL-3.0-or-later" +platforms = ["wasm32"] +groups = ["coverage_required", "mobile", "wasm", "boundaries"] +owners = ["mobile"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "runtime", + "boundary", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/app_rt" +source_revision = "7ab1a8624d50890d6d18545ffb47d8083afa8c67" +source_path = "crates/wasm" +source_tree_sha256 = "8c65c867eafc0643ca9fd86150766af4f4ab2f0f8321d66306418261a8f43124" +compatibility = ["wasm", "lifecycle", "package_private"] +replaces = ["radroots_app_wasm"] + +[[package]] +name = "radroots_mobile_bindgen" +path = "crates/mobile_bindgen" +state = "active" +tier = "codegen" +visibility = "private_codegen" +publish = false +version = "0.1.0-alpha" +license = "GPL-3.0-or-later" +platforms = ["native"] +groups = ["coverage_required", "mobile", "boundaries"] +owners = ["mobile"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "runtime", + "boundary", + "codegen", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/app_rt" +source_revision = "7ab1a8624d50890d6d18545ffb47d8083afa8c67" +source_path = "crates/bindgen" +source_tree_sha256 = "f0a9d7ec9794257bc56063117208e6e83a34efe1b852e8af6d6a56a1693d27fa" +compatibility = ["generated", "swift", "kotlin", "package_private"] +replaces = ["radroots_app_bindgen"] + +[[package]] +name = "radroots_studio_domain" +path = "crates/studio_domain" +state = "active" +tier = "application_domain" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "GPL-3.0-only" +platforms = ["native"] +groups = ["coverage_required", "studio"] +owners = ["studio"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "application_domain", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/studio_app" +source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" +source_path = "core/crates/domain" +source_tree_sha256 = "25f8abe2f8f4e9dfeb6450116be67eb3faad53f2d3879a4c142796238f74b0a9" +compatibility = ["product", "data", "package_private"] +replaces = ["radroots-studio-domain"] + +[[package]] +name = "radroots_studio_preferences" +path = "crates/studio_preferences" +state = "active" +tier = "application_domain" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "MPL-2.0" +platforms = ["native"] +groups = ["coverage_required", "studio"] +owners = ["studio"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "application_domain", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/_radroots" +source_revision = "6074a4745be361f21bb47d4778c74a14b2d57954" +source_path = "studio_app/studio_app_core/crates/core" +source_tree_sha256 = "0237265710a676ce1db0fb3091e1e5d9a5831339e00076ea2a33b96d6343834d" +compatibility = ["product", "preferences", "package_private"] +replaces = ["studio_app_core"] + +[[package]] +name = "radroots_studio_application" +path = "crates/studio_application" +state = "active" +tier = "application" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "GPL-3.0-only" +platforms = ["native"] +groups = ["coverage_required", "studio"] +owners = ["studio"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "application_domain", + "application", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/studio_app" +source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" +source_path = "core/crates/application" +source_tree_sha256 = "8b0b5d4d74dde0af3c5fb3dac979b0cf57cccf073d597f7bd35b1e73a711fe0b" +compatibility = ["product", "behavior", "package_private"] +replaces = ["radroots-studio-application"] + +[[package]] +name = "radroots_studio_nostr" +path = "crates/studio_nostr" +state = "active" +tier = "application_adapter" +visibility = "private_adapter" +publish = false +version = "0.1.0-alpha" +license = "GPL-3.0-only" +platforms = ["native"] +groups = ["coverage_required", "studio"] +owners = ["studio"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "application_domain", + "application", + "application_adapter", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/studio_app" +source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" +source_path = "core/crates/nostr" +source_tree_sha256 = "2ce5ce0227ab190102a94ffdf80d95b37ed35f5ef62286c4e3e19cd42a777115" +compatibility = ["network", "security", "package_private"] +replaces = ["radroots-studio-nostr"] + +[[package]] +name = "radroots_studio_storage" +path = "crates/studio_storage" +state = "active" +tier = "application_adapter" +visibility = "private_adapter" +publish = false +version = "0.1.0-alpha" +license = "GPL-3.0-only" +platforms = ["native"] +groups = ["coverage_required", "studio"] +owners = ["studio"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "application_domain", + "application", + "application_adapter", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/studio_app" +source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" +source_path = "core/crates/storage" +source_tree_sha256 = "f3addecbd7f6dbccda4a438597b4e433443a34b48a6c4ef9efa90e49e8f05c4b" +compatibility = ["data", "keyring", "package_private"] +replaces = ["radroots-studio-storage"] + +[[package]] +name = "radroots_studio_runtime" +path = "crates/studio_runtime" +state = "active" +tier = "runtime_composition" +visibility = "private_runtime" +publish = false +version = "0.1.0-alpha" +license = "GPL-3.0-only" +platforms = ["native"] +groups = ["coverage_required", "studio"] +owners = ["studio"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "application_domain", + "application", + "application_adapter", + "runtime_composition", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/studio_app" +source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" +source_path = "core/crates/storage" +source_tree_sha256 = "f3addecbd7f6dbccda4a438597b4e433443a34b48a6c4ef9efa90e49e8f05c4b" +compatibility = ["product", "lifecycle", "package_private"] +replaces = [] + +[[package]] +name = "radroots_studio_ffi" +path = "crates/studio_ffi" +state = "active" +tier = "boundary" +visibility = "private_boundary" +publish = false +version = "0.1.0-alpha" +license = "GPL-3.0-only" +platforms = ["linux", "macos", "windows"] +groups = ["coverage_required", "studio", "boundaries"] +owners = ["studio"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "application_domain", + "application", + "application_adapter", + "runtime_composition", + "boundary", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/studio_app" +source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" +source_path = "core/crates/ffi" +source_tree_sha256 = "0bb1d02eb963a606a288d9c35fb418de7bfd914dc5e2c4a38112af64c643151c" +compatibility = ["ffi", "kotlin", "product", "lifecycle", "package_private"] +replaces = ["radroots-studio-ffi"] + +[[package]] +name = "radroots_studio_uniffi_bindgen" +path = "crates/studio_uniffi_bindgen" +state = "active" +tier = "codegen" +visibility = "private_codegen" +publish = false +version = "0.1.0-alpha" +license = "GPL-3.0-only" +platforms = ["native"] +groups = ["coverage_required", "studio", "boundaries"] +owners = ["studio"] +permitted_dependency_tiers = [ + "foundation", + "domain", + "spi", + "adapter", + "orchestration", + "sdk", + "facade", + "application_domain", + "application", + "application_adapter", + "runtime_composition", + "boundary", + "codegen", +] +provenance_kind = "imported" +source_repository = "https://github.com/radrootslabs/studio_app" +source_revision = "2b5fe5d8321fd0e248a9d304651b1ba54ee6a180" +source_path = "core/tools/uniffi-bindgen" +source_tree_sha256 = "0eedd47c17fc7b2b84d953f2c4613aecf96575df466eb106450c7d7eee25ca9b" +compatibility = ["generated", "kotlin", "package_private"] +replaces = ["radroots-studio-uniffi-bindgen"] diff --git a/contracts/crates/generated/package_groups.v1.toml b/contracts/crates/generated/package_groups.v1.toml @@ -1,5 +1,5 @@ schema = "radroots.workspace.package-groups.v1" -catalog_sha256 = "8c27cebf6825f9ed74e122513c661f6dd31837dddb39d0988014121d8ab05e75" +catalog_sha256 = "d42a2ec2f86f4df22b0deee5afe0fb7e971dae2f3657758961ff9d19c7e2d27e" [[group]] id = "boundaries" diff --git a/contracts/crates/generated/platform_inventory.v1.toml b/contracts/crates/generated/platform_inventory.v1.toml @@ -1,5 +1,5 @@ schema = "radroots.workspace.platform-inventory.v1" -catalog_sha256 = "8c27cebf6825f9ed74e122513c661f6dd31837dddb39d0988014121d8ab05e75" +catalog_sha256 = "d42a2ec2f86f4df22b0deee5afe0fb7e971dae2f3657758961ff9d19c7e2d27e" [[platform]] id = "android" diff --git a/contracts/crates/generated/release_inventory.v2.toml b/contracts/crates/generated/release_inventory.v2.toml @@ -1,5 +1,5 @@ schema = "radroots.workspace.release-inventory.v2" -catalog_sha256 = "8c27cebf6825f9ed74e122513c661f6dd31837dddb39d0988014121d8ab05e75" +catalog_sha256 = "d42a2ec2f86f4df22b0deee5afe0fb7e971dae2f3657758961ff9d19c7e2d27e" architecture = "radroots.crates.release.v2" version = "0.1.0-alpha" public_packages = ["radroots", "radroots_blossom", "radroots_core", "radroots_event", "radroots_event_codec", "radroots_geonames", "radroots_identity", "radroots_nostr", "radroots_nostr_connect", "radroots_protocol", "radroots_sdk", "radroots_secrets", "radroots_signing", "radroots_storage", "radroots_storage_sqlite", "radroots_sync", "radroots_trade", "radroots_transport", "radroots_transport_nostr"] diff --git a/contracts/crates/release.v2.toml b/contracts/crates/release.v2.toml @@ -1,4 +1,4 @@ -schema_version = 1 +schema_version = 2 spec_id = "radroots.crates.release.v2" status = "approved_not_published" supersedes_without_mutation = "radroots.crates.release.v1" @@ -28,22 +28,22 @@ public_packages = [ "radroots", ] -[[v1_artifact]] -path = "docs/specs/radroots_crates_release_v1.md" +[[v1_retired_human_artifact]] +former_path = "docs/specs/radroots_crates_release_v1.md" sha256 = "ea2c1f0f5c53fae56a247ae7519b065c9a0d62dafb998b75a48075f4a875b5eb" [[v1_artifact]] -path = "docs/specs/radroots_crates_release_v1.toml" +path = "contracts/crates/release_v1/radroots_crates_release_v1.toml" sha256 = "1dc18437200dcd65b52090493306f452dade89b5116401d71be4ba4127239b19" [[v1_artifact]] -path = "docs/specs/radroots_crates_release_v1_inventory.csv" +path = "contracts/crates/release_v1/radroots_crates_release_v1_inventory.csv" sha256 = "5020875c2cda4b2c9568c8b3f0fad5cd96756c3c779a72481a9652e558f77891" [[v1_artifact]] -path = "docs/specs/radroots_crates_release_v1.dot" +path = "contracts/crates/release_v1/radroots_crates_release_v1.dot" sha256 = "d47de10be596a4d33fee102a4f0617f66700b49515a75a1f42d62c9710043059" [[v1_artifact]] -path = "docs/specs/radroots_crates_release_v1.sha256" +path = "contracts/crates/release_v1/radroots_crates_release_v1.sha256" sha256 = "5759ceaae30a9435346320c2791cfda9eb1559b779ea79fd2e94810e14efa281" diff --git a/docs/specs/radroots_crates_release_v1.dot b/contracts/crates/release_v1/radroots_crates_release_v1.dot diff --git a/docs/specs/radroots_crates_release_v1.sha256 b/contracts/crates/release_v1/radroots_crates_release_v1.sha256 diff --git a/docs/specs/radroots_crates_release_v1.toml b/contracts/crates/release_v1/radroots_crates_release_v1.toml diff --git a/docs/specs/radroots_crates_release_v1_inventory.csv b/contracts/crates/release_v1/radroots_crates_release_v1_inventory.csv diff --git a/contracts/hygiene/prototype-contracts.v1.toml b/contracts/hygiene/prototype-contracts.v1.toml @@ -0,0 +1,226 @@ +schema = "radroots.prototype-contract-source-guard.v1" +mode = "report_only" + +[scan] +roots = [ + ".cargo", + ".envrc", + ".gitignore", + "AGENTS.md", + "AGENT_INSTRUCTIONS.md", + "BUILD.md", + "CHANGELOG.md", + "CONTRIBUTING.md", + "Cargo.lock", + "Cargo.toml", + "README.md", + "build", + "crates", + "deny.toml", + "dto_bindgen.toml", + "flake.lock", + "flake.nix", + "fuzz", + "rust-toolchain-coverage.toml", + "rust-toolchain.toml", + "tools", + "treefmt.nix", +] +path_roots = ["."] +path_excludes = [".direnv", ".git", ".treefmt-cache", "result", "target"] +extensions = [ + "capnp", + "csv", + "dot", + "json", + "lock", + "md", + "nix", + "rs", + "sha256", + "sh", + "sql", + "toml", + "ts", + "txt", +] +extensionless_names = [ + ".envrc", + ".gitignore", + "LICENSE-APACHE", + "LICENSE-MIT", + "README", +] + +[limits] +max_scan_entries = 20000 +max_inventory_bytes = 33554432 +max_file_bytes = 8388608 +max_matches = 4096 +max_reported_findings = 200 +max_reported_allowlisted = 200 + +[[pattern]] +id = "config-env" +needle = "config.env" +match_kind = "substring" +description = "prototype environment-file configuration selector" + +[[pattern]] +id = "env-example" +needle = ".env.example" +match_kind = "substring" +description = "prototype service environment example" +match_path = true + +[[pattern]] +id = "env-file-flag" +needle = "--env-file" +match_kind = "substring" +description = "prototype environment-file CLI flag" + +[[pattern]] +id = "myc-paths-environment" +needle = "MYC_PATHS_" +match_kind = "substring" +description = "prototype Myc path environment contract" + +[[pattern]] +id = "rhi-paths-environment" +needle = "RHI_PATHS_" +match_kind = "substring" +description = "prototype RHI path environment contract" + +[[pattern]] +id = "trade-validation-receipt" +needle = "trade_validation_receipt" +match_kind = "substring" +description = "prototype trade validation receipt surface" + +[[pattern]] +id = "json-file-state" +needle = "JsonFile" +match_kind = "substring" +description = "prototype JSON mutable-state backend" + +[[pattern]] +id = "jsonl-file-state" +needle = "JsonlFile" +match_kind = "substring" +description = "prototype JSONL mutable-state backend" + +[[pattern]] +id = "identity-auto-generation" +needle = "allow_generate_identity" +match_kind = "substring" +description = "prototype ordinary-run identity generation" + +[[pattern]] +id = "identity-json-example" +needle = "identity.example.json" +match_kind = "substring" +description = "prototype plaintext identity example" +match_path = true + +[[pattern]] +id = "rhi-worker-path" +needle = "workers/rhi" +match_kind = "substring" +description = "prototype RHI worker path" +match_path = true + +[[pattern]] +id = "external-command-provider" +needle = "external_command" +match_kind = "substring" +description = "prototype executable signer-provider selector" + +[[pattern]] +id = "logging-output-directory" +needle = "logging.output_dir" +match_kind = "substring" +description = "prototype daemon-owned log-directory selector" + +[[pattern]] +id = "never-rolling-appender" +needle = "rolling::never" +match_kind = "substring" +description = "prototype service-owned non-rolling file logger" + +[[pattern]] +id = "daily-rolling-appender" +needle = "rolling::daily" +match_kind = "substring" +description = "prototype service-owned daily file logger" + +[[pattern]] +id = "import-json-flag" +needle = "import-json" +match_kind = "substring" +description = "prototype mutable-state import CLI surface" + +[[pattern]] +id = "import-json-identifier" +needle = "import_json" +match_kind = "substring" +description = "prototype mutable-state import implementation surface" + +[[pattern]] +id = "legacy-concept" +needle = "legacy" +match_kind = "word_prefix" +description = "legacy product or compatibility concept requiring review" +path_prefixes = [ + "crates/runtime_paths", + "crates/secrets", + "crates/service_host", + "crates/service_sqlite", +] + +[[pattern]] +id = "compatibility-concept" +needle = "compat" +match_kind = "word_prefix" +description = "compatibility product concept requiring review" +path_prefixes = [ + "crates/runtime_paths", + "crates/secrets", + "crates/service_host", + "crates/service_sqlite", +] + +[[pattern]] +id = "deprecated-concept" +needle = "deprecated" +match_kind = "word_prefix" +description = "deprecated product concept requiring review" +path_prefixes = [ + "crates/runtime_paths", + "crates/secrets", + "crates/service_host", + "crates/service_sqlite", +] + +[[allow]] +pattern_id = "import-json-identifier" +path = "crates/replica_store_wasm/src/wasm_impl.rs" +line_contains = "#[wasm_bindgen(js_name = replica_store_import_json)]" +reason = "The replica-store interchange API is not a Myc or RHI mutable service-state importer." + +[[allow]] +pattern_id = "import-json-identifier" +path = "crates/replica_store_wasm/src/wasm_impl.rs" +line_contains = "pub fn replica_store_import_json" +reason = "The replica-store interchange API is not a Myc or RHI mutable service-state importer." + +[[allow]] +pattern_id = "import-json-identifier" +path = "tools/xtask/src/sdk_generation/wasm_declarations.rs" +line_contains = "replica_store_import_json" +reason = "The generated replica-store interchange declaration is not a Myc or RHI mutable service-state importer." + +[[allow]] +pattern_id = "compatibility-concept" +path = "crates/secrets/src/wrapping.rs" +line_contains = "dyn-compatible data-key wrapping" +reason = "This describes Rust trait object safety rather than a Radroots-owned compatibility path." diff --git a/contracts/releases/api_boundaries.toml b/contracts/releases/api_boundaries.toml @@ -97,60 +97,3 @@ allowed_public_paths = [] [[package]] name = "radroots" allowed_public_paths = [] -[[exception]] -id = "RCRV1-API-006" -package = "radroots_nostr_connect" -source = "src/client.rs" -forbidden_path = "nostr" -items = [ - "client::RadrootsNostrConnectClientTarget", - "client::RadrootsNostrConnectClientTarget::new", - "client::RadrootsNostrConnectClientTransport", - "client::build_request_event", - "client::execute_request_with_transport", - "client::parse_response_event", -] -observed_paths = [ - "nostr::Event", - "nostr::Keys", - "nostr::PublicKey", - "nostr::RelayUrl", -] -adr = "docs/decisions/0001-public-api-leakage-migration-baseline.md" -removal_step = 313 -rationale = "Hidden, publish-frozen client signatures remain only for CLI, Myc, and residual first-party cutovers in Steps 271, 288, and 293." - -[[exception]] -id = "RCRV1-API-007" -package = "radroots_nostr_connect" -source = "src/message.rs" -forbidden_path = "nostr" -items = [ - "message::RadrootsNostrConnectPendingConnectionPollOutcome", - "message::RadrootsNostrConnectRemoteSessionCapability", - "message::RadrootsNostrConnectRequest", - "message::RadrootsNostrConnectResponse", -] -observed_paths = [ - "nostr::Event", - "nostr::PublicKey", - "nostr::RelayUrl", - "nostr::UnsignedEvent", -] -adr = "docs/decisions/0001-public-api-leakage-migration-baseline.md" -removal_step = 313 -rationale = "Hidden, publish-frozen aliases remain only for CLI, Myc, and residual first-party cutovers in Steps 271, 288, and 293." - -[[exception]] -id = "RCRV1-API-008" -package = "radroots_nostr_connect" -source = "src/uri.rs" -forbidden_path = "nostr" -items = [ - "uri::RadrootsNostrConnectBunkerUri", - "uri::RadrootsNostrConnectClientUri", -] -observed_paths = ["nostr::PublicKey", "nostr::RelayUrl"] -adr = "docs/decisions/0001-public-api-leakage-migration-baseline.md" -removal_step = 313 -rationale = "Hidden, publish-frozen URI aliases remain only for CLI, Myc, and residual first-party cutovers in Steps 271, 288, and 293." diff --git a/crates/blossom/README.md b/crates/blossom/README.md @@ -59,9 +59,9 @@ Supporting states and constructors remain in these modules: - `url` — structural blob URLs and the stricter approved-reference state. The normative responsibility and dependency boundary are defined by the -[`radroots_blossom` package charter](../../docs/specs/radroots_crates_release_v1.md). -The reviewed pre-release surface is recorded in -[`docs/api`](../../docs/api/README.md). +[`radroots_blossom` package charter](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). +The reviewed pre-release surface is recorded in the +[`radroots_blossom` API baseline](../../contracts/api_baselines/radroots_blossom.txt). ## Features diff --git a/crates/core/README.md b/crates/core/README.md @@ -46,9 +46,9 @@ Focused errors and operations remain in these modules: - `unit` — unit parsing, dimensions, and deterministic conversions. The normative responsibility and dependency boundary are defined by the -[`radroots_core` package charter](../../docs/specs/radroots_crates_release_v1.md). -The reviewed pre-release surface is recorded in -[`docs/api`](../../docs/api/README.md). +[`radroots_core` package charter](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). +The reviewed pre-release surface is recorded in the +[`radroots_core` API baseline](../../contracts/api_baselines/radroots_core.txt). ## Features diff --git a/crates/event/tests/package_boundary.rs b/crates/event/tests/package_boundary.rs @@ -22,7 +22,7 @@ const RELAY_HINT: &str = include_str!("../src/relay_hint.rs"); const TRADE: &str = include_str!("../src/trade.rs"); const ADMISSION: &str = include_str!("../src/admission.rs"); const VERIFICATION: &str = include_str!("../src/verification.rs"); -const PUBLIC_API: &str = include_str!("../../../docs/api/radroots_event.txt"); +const PUBLIC_API: &str = include_str!("../../../contracts/api_baselines/radroots_event.txt"); const CODEC_MANIFEST: &str = include_str!("../../event_codec/Cargo.toml"); #[test] diff --git a/crates/event/tests/source_boundary.rs b/crates/event/tests/source_boundary.rs @@ -116,7 +116,7 @@ fn public_api_has_no_redundant_radroots_type_prefixes() { .parent() .and_then(Path::parent) .expect("lib repo root"); - let baseline = fs::read_to_string(repo_root.join("docs/api/radroots_event.txt")) + let baseline = fs::read_to_string(repo_root.join("contracts/api_baselines/radroots_event.txt")) .expect("read radroots_event public API baseline"); let mut prefixed = baseline .split(|character: char| !is_identifier_character(character)) diff --git a/crates/event_codec/README.md b/crates/event_codec/README.md @@ -29,7 +29,7 @@ The Release V1 canonical root consists of `Codec`, `DecodeError`, canonical modules so each import states whether it encodes, decodes, verifies, or admits data. Legacy top-level domain routes are not exposed. The canonical surface is recorded in the -[public API baseline](../../docs/api/radroots_event_codec.txt). +[public API baseline](../../contracts/api_baselines/radroots_event_codec.txt). ## Verification pipeline @@ -146,9 +146,9 @@ responsibilities belong to adapter and runtime crates. The authoritative Release V1 responsibility, dependency, feature, module, and forbidden-scope contract is the -[Radroots crates Release V1 specification](../../docs/specs/radroots_crates_release_v1.md). -The baseline generation procedure and toolchain are documented in -[`docs/api/README.md`](../../docs/api/README.md). +[Radroots crates Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). +The reviewed surface is the +[`radroots_event_codec` API baseline](../../contracts/api_baselines/radroots_event_codec.txt). ## Copyright diff --git a/crates/event_codec/tests/package_boundary.rs b/crates/event_codec/tests/package_boundary.rs @@ -10,7 +10,7 @@ const VERIFICATION: &str = include_str!("../src/verification/v1.rs"); const EXAMPLE: &str = include_str!("../examples/verify_profile.rs"); const FUZZ_LOCK: &str = include_str!("../../../fuzz/event_codec/Cargo.lock"); const FUZZ_MANIFEST: &str = include_str!("../../../fuzz/event_codec/Cargo.toml"); -const PUBLIC_API: &str = include_str!("../../../docs/api/radroots_event_codec.txt"); +const PUBLIC_API: &str = include_str!("../../../contracts/api_baselines/radroots_event_codec.txt"); #[test] fn manifest_has_final_identity_and_required_radroots_dependencies() { diff --git a/crates/event_codec/tests/services_hardening_event_decisions.rs b/crates/event_codec/tests/services_hardening_event_decisions.rs @@ -0,0 +1,256 @@ +#![forbid(unsafe_code)] + +use serde_json::Value; +use sha2::{Digest, Sha256}; +use std::collections::BTreeSet; + +const DECISION: &str = + include_str!("../../../contracts/architecture/decisions/services_hardening_events.v1.json"); +const REGISTRY: &str = + include_str!("../../../contracts/event_store/event_contract_registry_v7.inventory.json"); +const TRADE_VECTORS: &str = include_str!( + "../../../contracts/conformance/vectors/trade/mutation_index_tags_decision.v1.json" +); +const RHI_VECTORS: &str = include_str!( + "../../../contracts/conformance/vectors/rhi/evidence_attestation_decision.v1.json" +); + +fn json(source: &str) -> Value { + serde_json::from_str(source).expect("services-hardening machine contract must be valid JSON") +} + +fn strings(value: &Value, field: &str) -> Vec<String> { + value[field] + .as_array() + .expect("field must be an array") + .iter() + .map(|entry| entry.as_str().expect("entry must be a string").to_owned()) + .collect() +} + +fn error_codes(vectors: &Value) -> BTreeSet<String> { + vectors["vectors"] + .as_array() + .expect("vector list") + .iter() + .filter_map(|vector| vector["expected"]["error_code"].as_str()) + .map(str::to_owned) + .collect() +} + +fn verify_attestation_digest_vector(vector: &Value) -> String { + let canonical_payload = vector["expected"]["canonical_statement_payload_utf8"] + .as_str() + .expect("canonical statement payload"); + assert_eq!( + serde_json::to_string(&vector["input"]["statement_payload"]).unwrap(), + canonical_payload + ); + let mut hasher = Sha256::new(); + hasher.update(b"radroots:rhi-evidence-attestation-statement:v1\0"); + hasher.update(canonical_payload.as_bytes()); + let digest = hex::encode(hasher.finalize()); + assert_eq!(vector["expected"]["statement_digest"], digest); + assert_eq!(vector["expected"]["report_id"], digest); + let canonical_event = vector["expected"]["canonical_event_content_utf8"] + .as_str() + .expect("canonical event content"); + let mut event_content = json(canonical_event); + assert_eq!(event_content["report_id"], digest); + assert_eq!(event_content["statement_digest"], digest); + let event_object = event_content.as_object_mut().expect("report object"); + event_object.remove("report_id"); + event_object.remove("statement_digest"); + assert_eq!( + serde_json::to_string(&event_content).unwrap(), + canonical_payload + ); + digest +} + +#[test] +fn services_hardening_event_decision_reserves_unique_exact_kinds() { + let decision = json(DECISION); + assert_eq!( + decision["schema"], + "radroots.services-hardening.event-decisions.v1" + ); + assert_eq!(decision["decision_state"], "reserved_preimplementation"); + + let expected_trade = BTreeSet::from([3470_u64, 3471, 3472, 3473, 3474]); + let trade = decision["trade_mutation"]["event_kinds"] + .as_array() + .expect("trade event kinds"); + let actual_trade = trade + .iter() + .map(|entry| entry["kind"].as_u64().expect("numeric kind")) + .collect::<BTreeSet<_>>(); + assert_eq!(actual_trade, expected_trade); + + let registry = json(REGISTRY); + let registered = registry["kind_contracts"] + .as_array() + .expect("registry kind contracts") + .iter() + .map(|entry| entry["kind"].as_u64().expect("registered numeric kind")) + .collect::<Vec<_>>(); + for kind in expected_trade { + assert!(registered.contains(&kind), "trade kind {kind} must exist"); + } + let attestation_kind = decision["rhi_attestation"]["kind"] + .as_u64() + .expect("attestation kind"); + assert_eq!(attestation_kind, 3441); + assert!( + !registered.contains(&attestation_kind), + "reserved attestation kind must not collide with a registered kind" + ); +} + +#[test] +fn services_hardening_trade_tag_cardinality_and_query_contract_is_exact() { + let decision = json(DECISION); + let trade = &decision["trade_mutation"]; + assert_eq!(trade["event_class"], "regular_immutable"); + assert_eq!( + strings(trade, "canonical_tag_order"), + [ + "contract", + "d:trade", + "x:mutation", + "x:root", + "x:parent_sorted", + "p:buyer", + "p:seller", + ] + ); + let tags = trade["tags"].as_array().expect("trade tags"); + assert_eq!(tags.len(), 7); + assert_eq!(tags[1]["cardinality"], "exactly_one"); + assert_eq!(tags[2]["cardinality"], "exactly_one"); + assert_eq!( + tags[3]["cardinality"], + "proposal_zero_other_mutations_exactly_one" + ); + assert_eq!( + tags[4]["cardinality"], + "proposal_zero_other_mutations_one_to_four_sorted_unique" + ); + assert_eq!(tags[5]["cardinality"], "first_of_exactly_two"); + assert_eq!(tags[6]["cardinality"], "second_of_exactly_two"); + assert_eq!( + trade["validation"]["legacy_contract_d_p_e_shape_accepted"], + false + ); + + let vectors = json(TRADE_VECTORS); + let first = &vectors["vectors"][0]; + assert_eq!(first["expected"]["kind"], 3472); + assert_eq!( + first["expected"]["tags"] + .as_array() + .expect("exact tags") + .len(), + 8 + ); + assert_eq!(first["expected"]["tags"][1].as_array().unwrap().len(), 2); + assert_eq!(first["expected"]["tags"][6].as_array().unwrap().len(), 2); + assert_eq!(first["expected"]["tags"][7].as_array().unwrap().len(), 2); + let kinds = vectors["vectors"] + .as_array() + .expect("trade decision vectors") + .iter() + .filter_map(|vector| vector["expected"]["kind"].as_u64()) + .collect::<BTreeSet<_>>(); + assert_eq!(kinds, BTreeSet::from([3470, 3471, 3472, 3473, 3474])); + assert_eq!( + error_codes(&vectors), + BTreeSet::from([ + "caller_structural_tag_forbidden".to_owned(), + "duplicate_trade_tag".to_owned(), + "legacy_parent_event_tag".to_owned(), + "missing_parent_tag".to_owned(), + "missing_mutation_tag".to_owned(), + "missing_root_tag".to_owned(), + "noncanonical_parent_order".to_owned(), + "party_tag_order_mismatch".to_owned(), + "unexpected_parent_tag".to_owned(), + "unexpected_root_tag".to_owned(), + ]) + ); +} + +#[test] +fn services_hardening_attestation_is_immutable_and_fully_bound() { + let decision = json(DECISION); + let attestation = &decision["rhi_attestation"]; + assert_eq!(attestation["kind"], 3441); + assert_eq!(attestation["event_class"], "regular_immutable"); + assert_eq!(attestation["replaceability"], "none"); + assert_eq!(attestation["content_encoding"], "RFC8785_JCS_JSON_UTF8"); + assert_eq!( + attestation["fixed_values"]["attestation_method"], + "signed_evidence_snapshot" + ); + assert_eq!( + strings(attestation, "canonical_tag_order"), + [ + "contract", + "d:trade", + "x:claim", + "x:statement", + "t:outcome", + "x:supersedes_report", + "e:supersedes_event", + ] + ); + assert_eq!( + attestation["supersession"]["requires_both_references_or_neither"], + true + ); + assert_eq!( + attestation["supersession"]["report_id_equals_statement_digest"], + true + ); + assert_eq!( + attestation["supersession"]["relay_arrival_order_authoritative"], + false + ); + + let vectors = json(RHI_VECTORS); + let positive = &vectors["vectors"][0]; + assert_eq!(positive["expected"]["kind"], 3441); + verify_attestation_digest_vector(positive); + assert_eq!(positive["expected"]["tags"][1].as_array().unwrap().len(), 2); + assert_eq!(positive["expected"]["tags"][4].as_array().unwrap().len(), 2); + assert_eq!( + vectors["vectors"][1]["expected"]["mutates_prior_report"], + false + ); + verify_attestation_digest_vector(&vectors["vectors"][1]); + assert_eq!( + vectors["vectors"][1]["expected"]["tags"][6] + .as_array() + .unwrap() + .len(), + 2 + ); + assert_eq!( + error_codes(&vectors), + BTreeSet::from([ + "caller_structural_tag_forbidden".to_owned(), + "duplicate_statement_tag".to_owned(), + "duplicate_trade_tag".to_owned(), + "incomplete_supersession_reference".to_owned(), + "invalid_attestation_kind".to_owned(), + "invalid_outcome".to_owned(), + "issuer_author_mismatch".to_owned(), + "missing_claim_tag".to_owned(), + "noncanonical_report_content".to_owned(), + "stale_trade_generation".to_owned(), + "statement_digest_mismatch".to_owned(), + ]) + ); + let stale = &vectors["vectors"][11]; + assert_eq!(stale["expected"]["layer"], "admission"); +} diff --git a/crates/geonames/README.md b/crates/geonames/README.md @@ -11,9 +11,9 @@ client types, or define a generic geocoder SPI. Publication remains disabled during the `0.1.0-alpha` refactor. The authoritative package charter is the -[`radroots_geonames` section of the Release V1 specification](https://github.com/radrootslabs/lib/blob/master/docs/specs/radroots_crates_release_v1.md#17-radroots_geonames). +[`radroots_geonames` section of the Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). The reviewed Rust surface is recorded in the -[public API baseline](../../docs/api/radroots_geonames.txt). +[public API baseline](../../contracts/api_baselines/radroots_geonames.txt). ## Prepare a query without I/O diff --git a/crates/geonames/tests/package_boundary.rs b/crates/geonames/tests/package_boundary.rs @@ -5,8 +5,7 @@ use std::path::Path; const MANIFEST: &str = include_str!("../Cargo.toml"); const README: &str = include_str!("../README.md"); const EXAMPLE: &str = include_str!("../examples/prepare_query.rs"); -const PUBLIC_API: &str = include_str!("../../../docs/api/radroots_geonames.txt"); -const API_INDEX: &str = include_str!("../../../docs/api/README.md"); +const PUBLIC_API: &str = include_str!("../../../contracts/api_baselines/radroots_geonames.txt"); const ROOT: &str = include_str!("../src/lib.rs"); const PUBLISH_POLICY: &str = include_str!("../../../contracts/releases/publish_policy.toml"); @@ -49,9 +48,9 @@ fn documentation_example_and_reviewed_api_baseline_are_complete() { "## Database and query behavior", "## Errors, serialization, and side effects", "## Intended consumers", - "radroots_crates_release_v1.md#17-radroots_geonames", + "radroots_crates_release_v1.toml", "examples/prepare_query.rs", - "docs/api/radroots_geonames.txt", + "contracts/api_baselines/radroots_geonames.txt", ] { assert!(README.contains(required), "README is missing `{required}`"); } @@ -97,10 +96,6 @@ fn documentation_example_and_reviewed_api_baseline_are_complete() { "public API exposes `{forbidden}`" ); } - assert!( - API_INDEX - .contains("| `radroots_geonames` | [`radroots_geonames.txt`](radroots_geonames.txt) |") - ); } #[test] diff --git a/crates/identity/README.md b/crates/identity/README.md @@ -51,11 +51,11 @@ Focused values remain in these modules: - `username` — canonical username parsing, bounds, and normalization. The normative responsibility and dependency boundary are defined by the -[`radroots_identity` package charter](../../docs/specs/radroots_crates_release_v1.md). -The reviewed pre-release surface is recorded in -[`docs/api`](../../docs/api/README.md). Signing, Nostr-key, secret-provider, -and storage ownership is documented in the -[`identity` migration boundary](../../docs/migration/identity.md). +[`radroots_identity` package charter](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). +The reviewed pre-release surface is recorded in the +[`radroots_identity` API baseline](../../contracts/api_baselines/radroots_identity.txt). +Signing, Nostr-key, secret-provider, and storage ownership remains outside +this public-only identity package as described by the package boundary above. ## Features diff --git a/crates/mobile_core/Cargo.toml b/crates/mobile_core/Cargo.toml @@ -21,26 +21,39 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } [features] default = [] mobile-social = [ - "radroots_sdk/blossom", - "radroots_sdk/nostr", - "radroots_sdk/sync", - "dep:tokio", + "radroots_sdk/blossom", + "radroots_sdk/nostr", + "radroots_sdk/sync", + "dep:tokio", ] [dependencies] radroots_blossom = { workspace = true, default-features = false, features = [ - "serde", - "std", + "serde", + "std", ] } radroots_sdk = { workspace = true, features = ["sqlite"] } -radroots_event = { workspace = true, default-features = false, features = ["std"] } -radroots_event_codec = { workspace = true, default-features = false, features = ["json", "std"] } -radroots_identity = { workspace = true, default-features = false, features = ["std"] } -radroots_protocol = { workspace = true, default-features = false, features = ["std"] } -radroots_signing = { workspace = true, default-features = false, features = ["std"] } +radroots_event = { workspace = true, default-features = false, features = [ + "std", +] } +radroots_event_codec = { workspace = true, default-features = false, features = [ + "json", + "std", +] } +radroots_identity = { workspace = true, default-features = false, features = [ + "std", +] } +radroots_protocol = { workspace = true, default-features = false, features = [ + "std", +] } +radroots_signing = { workspace = true, default-features = false, features = [ + "std", +] } radroots_storage = { workspace = true, default-features = false } radroots_sync = { workspace = true, default-features = false } -radroots_transport = { workspace = true, default-features = false, features = ["std"] } +radroots_transport = { workspace = true, default-features = false, features = [ + "std", +] } radroots_transport_nostr = { workspace = true } chrono = { workspace = true } hex = { workspace = true } diff --git a/crates/mobile_ffi/Cargo.toml b/crates/mobile_ffi/Cargo.toml @@ -35,6 +35,7 @@ thiserror = { workspace = true } uniffi = { workspace = true, features = ["tokio"] } [target.'cfg(unix)'.dependencies] +libc = { workspace = true } rustix = { workspace = true } [dev-dependencies] @@ -43,4 +44,11 @@ nostr-relay-builder = { git = "https://github.com/rust-nostr/nostr.git", rev = " nostr-sdk = { git = "https://github.com/rust-nostr/nostr.git", rev = "5bba5163eb77107f82c4a8262cf29d7f33a73219", package = "nostr-sdk" } secp256k1 = { workspace = true } tempfile = { workspace = true } -tokio = { workspace = true, features = ["io-util", "macros", "net", "rt-multi-thread", "sync", "time"] } +tokio = { workspace = true, features = [ + "io-util", + "macros", + "net", + "rt-multi-thread", + "sync", + "time", +] } diff --git a/crates/mobile_ffi/src/dto.rs b/crates/mobile_ffi/src/dto.rs @@ -1,9 +1,9 @@ //! Focused, versioned value types owned by the native boundary. #[cfg(unix)] -use std::os::unix::fs::FileExt; +use std::os::fd::{FromRawFd, OwnedFd, RawFd}; #[cfg(unix)] -use std::os::{fd::BorrowedFd, unix::io::RawFd}; +use std::os::unix::fs::FileExt; use radroots_blossom::{BlobDescriptor, MediaType, Sha256}; use radroots_event::{ @@ -1143,12 +1143,18 @@ fn read_media_file_descriptor( ) -> Result<Vec<u8>, RadrootsAppError> { let raw_file_descriptor = RawFd::try_from(file_descriptor) .map_err(|_| RadrootsAppError::invalid_argument("media_handle_unavailable"))?; - // SAFETY: the host owns this descriptor for the duration of the synchronous - // FFI call. Duplicating it immediately gives Rust independent ownership - // without closing or otherwise consuming the host descriptor. - let borrowed = unsafe { BorrowedFd::borrow_raw(raw_file_descriptor) }; - let owned = rustix::io::dup(borrowed) - .map_err(|_| RadrootsAppError::invalid_argument("media_handle_unavailable"))?; + // SAFETY: `fcntl(F_DUPFD_CLOEXEC)` accepts any in-range integer descriptor + // and reports EBADF for an unavailable one. No borrowed or owned Rust + // descriptor is constructed until the kernel has duplicated it. + let duplicated = unsafe { libc::fcntl(raw_file_descriptor, libc::F_DUPFD_CLOEXEC, 0) }; + if duplicated < 0 { + return Err(RadrootsAppError::invalid_argument( + "media_handle_unavailable", + )); + } + // SAFETY: a nonnegative F_DUPFD_CLOEXEC result is a new descriptor owned by + // this call. The host's original descriptor remains independently owned. + let owned = unsafe { OwnedFd::from_raw_fd(duplicated) }; let file = std::fs::File::from(owned); let metadata = file .metadata() @@ -2271,6 +2277,23 @@ mod tests { ); } + #[cfg(unix)] + #[test] + fn prepared_media_rejects_unavailable_in_range_file_descriptors() { + let bytes = png(2, 2); + let blossom = blossom_slot(); + let input = photo_input(i32::MAX as u64, &bytes, Sha256::digest(&bytes).to_hex()); + + assert_eq!( + input + .command_and_media(1_800_000_000, Some(&blossom)) + .expect_err("unavailable in-range descriptor") + .report() + .code, + "media_handle_unavailable" + ); + } + #[test] fn prepared_media_rejects_digest_tamper_and_path_like_references() { let bytes = png(2, 2); diff --git a/crates/nostr/README.md b/crates/nostr/README.md @@ -11,7 +11,7 @@ state. Live Nostr transport belongs in `radroots_transport_nostr`; application composition belongs in `radroots_sdk` or an advanced host. The authoritative package charter is the -[`radroots_nostr` section of the Release V1 specification](https://github.com/radrootslabs/lib/blob/master/docs/specs/radroots_crates_release_v1.md#10-radroots_nostr). +[`radroots_nostr` section of the Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). ## Quick start diff --git a/crates/nostr/tests/package_boundary.rs b/crates/nostr/tests/package_boundary.rs @@ -22,7 +22,7 @@ const README: &str = include_str!("../README.md"); const SIGNING_MODULE: &str = include_str!("../src/signing.rs"); const TAG_MODULE: &str = include_str!("../src/tag.rs"); const TYPES_MODULE: &str = include_str!("../src/types.rs"); -const PUBLIC_API: &str = include_str!("../../../docs/api/radroots_nostr.txt"); +const PUBLIC_API: &str = include_str!("../../../contracts/api_baselines/radroots_nostr.txt"); const IDENTITY_MANIFEST: &str = include_str!("../../identity/Cargo.toml"); const IDENTITY_KEY_MODULE: &str = include_str!("../../identity/src/key.rs"); const TRANSPORT_MANIFEST: &str = include_str!("../../transport_nostr/Cargo.toml"); diff --git a/crates/nostr_connect/README.md b/crates/nostr_connect/README.md @@ -26,7 +26,7 @@ The curated root exports are `Client`, `Server`, `Method`, `Permission`, `Request`, `Response`, `BunkerUri`, `ClientUri`, and `Error`. Supporting types remain in their owning modules so callers make protocol boundaries explicit. The reviewed Rust surface is recorded in the -[public API baseline](../../docs/api/radroots_nostr_connect.txt). +[public API baseline](../../contracts/api_baselines/radroots_nostr_connect.txt). ## Preparing a client request @@ -130,9 +130,9 @@ applications, and host runtimes. The authoritative responsibility, dependency, feature, module, root-export, and forbidden-scope contract is the -[Radroots crates Release V1 specification](../../docs/specs/radroots_crates_release_v1.md). -The baseline generation procedure and pinned toolchain are documented in -[`docs/api/README.md`](../../docs/api/README.md). +[Radroots crates Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). +The reviewed surface is the +[`radroots_nostr_connect` API baseline](../../contracts/api_baselines/radroots_nostr_connect.txt). ## Copyright diff --git a/crates/nostr_connect/tests/package_boundary.rs b/crates/nostr_connect/tests/package_boundary.rs @@ -8,7 +8,8 @@ use radroots_nostr_connect::{ const MANIFEST: &str = include_str!("../Cargo.toml"); const README: &str = include_str!("../README.md"); const EXAMPLE: &str = include_str!("../examples/prepare_request.rs"); -const PUBLIC_API: &str = include_str!("../../../docs/api/radroots_nostr_connect.txt"); +const PUBLIC_API: &str = + include_str!("../../../contracts/api_baselines/radroots_nostr_connect.txt"); const CLIENT: &str = include_str!("../src/client.rs"); const METHOD: &str = include_str!("../src/method.rs"); const PERMISSION: &str = include_str!("../src/permission.rs"); diff --git a/crates/protocol/README.md b/crates/protocol/README.md @@ -82,6 +82,6 @@ tools. Applications should normally enter through `radroots` or a versioned boundary. The package charter is the -[Release V1 specification](../../docs/specs/radroots_crates_release_v1.md). +[Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). The reviewed Rust surface is recorded in the -[public API baseline](../../docs/api/radroots_protocol.txt). +[public API baseline](../../contracts/api_baselines/radroots_protocol.txt). diff --git a/crates/radroots/README.md b/crates/radroots/README.md @@ -90,6 +90,6 @@ use radroots::sdk; ``` The normative package charter is the [`radroots` release-v1 -specification](../../docs/specs/radroots_crates_release_v1.md#19-radroots). +specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). The reviewed pre-release public API is recorded in the -[`radroots` baseline](../../docs/api/radroots.txt). +[`radroots` baseline](../../contracts/api_baselines/radroots.txt). diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml @@ -118,7 +118,11 @@ nostr = { workspace = true, features = ["std"] } radroots_blossom = { workspace = true } serde_json = { workspace = true, features = ["std"] } tempfile = { workspace = true } -tokio = { workspace = true, features = ["io-util", "macros", "rt-multi-thread"] } +tokio = { workspace = true, features = [ + "io-util", + "macros", + "rt-multi-thread", +] } [[test]] name = "package_boundary" diff --git a/crates/sdk/README.md b/crates/sdk/README.md @@ -11,7 +11,7 @@ and `Result`. Advanced operations live in the `farm`, `listing`, `trade`, modules. The package charter is the normative [`radroots_sdk` crate -specification](../../docs/specs/radroots_crates_release_v1.md#18-radroots_sdk). +specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). This advanced front door is intended for CLI, Studio, FFI/mobile, and native applications that need to compose storage, signing, transport, or sync capabilities directly. Ordinary Rust applications should use the curated @@ -171,4 +171,4 @@ publication remains blocked pending the approval packet and a separately authorized operator step. The crate is licensed under `MIT OR Apache-2.0`. The reviewed all-features public API baseline is recorded at -[`docs/api/radroots_sdk.txt`](../../docs/api/radroots_sdk.txt). +[`contracts/api_baselines/radroots_sdk.txt`](../../contracts/api_baselines/radroots_sdk.txt). diff --git a/crates/secrets/README.md b/crates/secrets/README.md @@ -26,7 +26,7 @@ The curated root exports only `EncryptedEnvelope`, `Error`, `SecretId`, `SecretRef`, `SecretProvider`, and `KeyWrapping`. Supporting request, policy, adapter, and value types remain in their owning modules so security boundaries stay explicit. The reviewed Rust surface is recorded in the -[public API baseline](../../docs/api/radroots_secrets.txt). +[public API baseline](../../contracts/api_baselines/radroots_secrets.txt). ## Explicit provider and envelope flow @@ -172,9 +172,9 @@ remain with their dedicated packages and hosts. The authoritative responsibility, dependency, feature, module, root-export, and forbidden-scope contract is the -[Radroots crates Release V1 specification](../../docs/specs/radroots_crates_release_v1.md). -The baseline generation procedure and pinned toolchain are documented in -[`docs/api/README.md`](../../docs/api/README.md). +[Radroots crates Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). +The reviewed surface is the +[`radroots_secrets` API baseline](../../contracts/api_baselines/radroots_secrets.txt). ## Copyright diff --git a/crates/secrets/tests/security_contract.rs b/crates/secrets/tests/security_contract.rs @@ -9,7 +9,7 @@ use radroots_secrets::{Error, SecretId, SecretRef}; use std::fs; use std::path::{Path, PathBuf}; -const PUBLIC_API: &str = include_str!("../../../docs/api/radroots_secrets.txt"); +const PUBLIC_API: &str = include_str!("../../../contracts/api_baselines/radroots_secrets.txt"); #[test] fn reviewed_api_forbids_secret_bearing_clone_serialize_and_byte_access() { diff --git a/crates/signing/README.md b/crates/signing/README.md @@ -12,7 +12,7 @@ belongs in `radroots_nostr_connect`; applications compose those adapters in `radroots_sdk` or their own host layer. The authoritative package charter is the -[`radroots_signing` section of the Release V1 specification](https://github.com/radrootslabs/lib/blob/master/docs/specs/radroots_crates_release_v1.md#8-radroots_signing). +[`radroots_signing` section of the Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). ## Typical flow diff --git a/crates/signing/tests/package_boundary.rs b/crates/signing/tests/package_boundary.rs @@ -9,7 +9,7 @@ use radroots_signing::{ const MANIFEST: &str = include_str!("../Cargo.toml"); const EXAMPLE: &str = include_str!("../examples/host_signer.rs"); -const PUBLIC_API: &str = include_str!("../../../docs/api/radroots_signing.txt"); +const PUBLIC_API: &str = include_str!("../../../contracts/api_baselines/radroots_signing.txt"); const README: &str = include_str!("../README.md"); const ROOT: &str = include_str!("../src/lib.rs"); @@ -150,7 +150,7 @@ fn package_documentation_and_reviewed_api_baseline_are_complete() { "## Security and side effects", "## Features", "## Intended consumers", - "radroots_crates_release_v1.md#8-radroots_signing", + "radroots_crates_release_v1.toml", "examples/host_signer.rs", ] { assert!(README.contains(required), "README is missing {required}"); diff --git a/crates/storage/README.md b/crates/storage/README.md @@ -12,7 +12,7 @@ is the native durable backend and the opt-in [`memory`] module is the bounded deterministic reference implementation. The authoritative package charter is the -[`radroots_storage` section of the Release V1 specification](https://github.com/radrootslabs/lib/blob/master/docs/specs/radroots_crates_release_v1.md#13-radroots_storage). +[`radroots_storage` section of the Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). ## Typical flow diff --git a/crates/studio_application/Cargo.toml b/crates/studio_application/Cargo.toml @@ -14,7 +14,12 @@ include = ["src/**", "tests/**", "Cargo.toml"] [dependencies] radroots_studio_domain.workspace = true secrecy = "=0.10.3" -tokio = { version = "=1.47.1", features = ["macros", "rt-multi-thread", "sync", "time"] } +tokio = { version = "=1.47.1", features = [ + "macros", + "rt-multi-thread", + "sync", + "time", +] } [lints] workspace = true diff --git a/crates/studio_ffi/Cargo.toml b/crates/studio_ffi/Cargo.toml @@ -22,7 +22,12 @@ radroots_studio_domain.workspace = true radroots_studio_nostr.workspace = true radroots_studio_runtime.workspace = true radroots_studio_storage.workspace = true -tokio = { version = "=1.47.1", features = ["macros", "rt-multi-thread", "sync", "time"] } +tokio = { version = "=1.47.1", features = [ + "macros", + "rt-multi-thread", + "sync", + "time", +] } uniffi = "=0.32.0" [build-dependencies] diff --git a/crates/studio_nostr/Cargo.toml b/crates/studio_nostr/Cargo.toml @@ -23,7 +23,12 @@ tokio = { version = "=1.47.1", features = ["sync", "time"] } [dev-dependencies] nostr-relay-builder = { git = "https://github.com/rust-nostr/nostr.git", rev = "5bba5163eb77107f82c4a8262cf29d7f33a73219", package = "nostr-relay-builder" } -tokio = { version = "=1.47.1", features = ["macros", "rt-multi-thread", "sync", "time"] } +tokio = { version = "=1.47.1", features = [ + "macros", + "rt-multi-thread", + "sync", + "time", +] } [lints] workspace = true diff --git a/crates/studio_runtime/Cargo.toml b/crates/studio_runtime/Cargo.toml @@ -16,7 +16,12 @@ radroots_studio_application.workspace = true radroots_studio_domain.workspace = true radroots_studio_nostr.workspace = true radroots_studio_storage.workspace = true -tokio = { version = "=1.47.1", features = ["macros", "rt-multi-thread", "sync", "time"] } +tokio = { version = "=1.47.1", features = [ + "macros", + "rt-multi-thread", + "sync", + "time", +] } uuid.workspace = true [dev-dependencies] diff --git a/crates/studio_storage/Cargo.toml b/crates/studio_storage/Cargo.toml @@ -16,7 +16,9 @@ fs2 = "=0.4.3" keyring = "=4.1.6" radroots_studio_application.workspace = true radroots_studio_domain.workspace = true -refinery = { version = "=0.9.2", default-features = false, features = ["rusqlite"] } +refinery = { version = "=0.9.2", default-features = false, features = [ + "rusqlite", +] } getrandom.workspace = true hmac.workspace = true rusqlite = { version = "=0.39.0", features = ["backup", "bundled"] } diff --git a/crates/trade/README.md b/crates/trade/README.md @@ -29,7 +29,7 @@ human or business-workflow identifier and has no conversion to or from Operational-listing host planning and binding-generation machinery are outside this algorithm package. The reviewed Rust surface is recorded in the -[public API baseline](../../docs/api/radroots_trade.txt). +[public API baseline](../../contracts/api_baselines/radroots_trade.txt). ## Deterministic reduction @@ -140,9 +140,9 @@ storage, orchestration, and front-door packages. The authoritative Release V1 responsibility, dependency, feature, module, root-export, and forbidden-scope contract is the -[Radroots crates Release V1 specification](../../docs/specs/radroots_crates_release_v1.md). -The baseline generation procedure and toolchain are documented in -[`docs/api/README.md`](../../docs/api/README.md). +[Radroots crates Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). +The reviewed surface is the +[`radroots_trade` API baseline](../../contracts/api_baselines/radroots_trade.txt). ## Copyright diff --git a/crates/trade/tests/package_boundary.rs b/crates/trade/tests/package_boundary.rs @@ -18,7 +18,7 @@ const WORKFLOW: &str = include_str!("../src/workflow.rs"); const PACKAGE_TIERS: &str = include_str!("../../../contracts/releases/package_tiers.toml"); const README: &str = include_str!("../README.md"); const EXAMPLE: &str = include_str!("../examples/reduce_trade.rs"); -const PUBLIC_API: &str = include_str!("../../../docs/api/radroots_trade.txt"); +const PUBLIC_API: &str = include_str!("../../../contracts/api_baselines/radroots_trade.txt"); #[test] fn manifest_has_final_identity_and_required_radroots_dependencies() { diff --git a/crates/transport/README.md b/crates/transport/README.md @@ -11,7 +11,7 @@ adapters such as `radroots_transport_nostr` implement the SPI; applications compose those adapters in `radroots_sdk` or their own host layer. The authoritative package charter is the -[`radroots_transport` section of the Release V1 specification](https://github.com/radrootslabs/lib/blob/master/docs/specs/radroots_crates_release_v1.md#9-radroots_transport). +[`radroots_transport` section of the Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). ## Typical flow diff --git a/crates/transport/tests/package_boundary.rs b/crates/transport/tests/package_boundary.rs @@ -10,14 +10,14 @@ use radroots_transport::{ const MANIFEST: &str = include_str!("../Cargo.toml"); const EXAMPLE: &str = include_str!("../examples/host_transport.rs"); -const PUBLIC_API: &str = include_str!("../../../docs/api/radroots_transport.txt"); +const PUBLIC_API: &str = include_str!("../../../contracts/api_baselines/radroots_transport.txt"); const README: &str = include_str!("../README.md"); const ROOT: &str = include_str!("../src/lib.rs"); const SOURCE: &str = include_str!("../src/source.rs"); const SINK: &str = include_str!("../src/sink.rs"); const ID: &str = include_str!("../src/id.rs"); const TARGET: &str = include_str!("../src/target.rs"); -const DEVIATIONS: &str = include_str!("../../../docs/implementation/deviations.toml"); +const DEVIATIONS: &str = include_str!("../../../contracts/architecture/deviations.toml"); #[test] fn manifest_has_final_identity_features_and_required_radroots_dependencies() { @@ -81,7 +81,7 @@ fn package_documentation_and_reviewed_api_baseline_are_complete() { "## Security and side effects", "## Features", "## Intended consumers", - "radroots_crates_release_v1.md#9-radroots_transport", + "radroots_crates_release_v1.toml", "examples/host_transport.rs", ] { assert!(README.contains(required), "README is missing {required}"); diff --git a/crates/transport_nostr/Cargo.toml b/crates/transport_nostr/Cargo.toml @@ -53,7 +53,12 @@ tokio-tungstenite = { workspace = true } url = { workspace = true } [dev-dependencies] -tokio = { workspace = true, features = ["macros", "net", "rt-multi-thread", "time"] } +tokio = { workspace = true, features = [ + "macros", + "net", + "rt-multi-thread", + "time", +] } [lints] workspace = true diff --git a/crates/transport_nostr/README.md b/crates/transport_nostr/README.md @@ -13,9 +13,9 @@ policies belong to `radroots_sync` and host applications. It does own bounded relay profiles, per-relay reconnect suppression, and evidence-based status. The authoritative package charter is the -[`radroots_transport_nostr` section of the Release V1 specification](https://github.com/radrootslabs/lib/blob/master/docs/specs/radroots_crates_release_v1.md#15-radroots_transport_nostr). +[`radroots_transport_nostr` section of the Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml). The reviewed Rust surface is recorded in the -[public API baseline](../../docs/api/radroots_transport_nostr.txt). +[public API baseline](../../contracts/api_baselines/radroots_transport_nostr.txt). ## Configure without connecting diff --git a/crates/transport_nostr/tests/legacy_quarantine.rs b/crates/transport_nostr/tests/legacy_quarantine.rs @@ -1,8 +1,9 @@ use std::fs; use std::path::Path; -const DEVIATIONS: &str = include_str!("../../../docs/implementation/deviations.toml"); -const SHIMS: &str = include_str!("../../../docs/implementation/COMPATIBILITY_SHIMS.md"); +const DEVIATIONS: &str = include_str!("../../../contracts/architecture/deviations.toml"); +const RETIRED_COMPATIBILITY: &str = + include_str!("../../../contracts/architecture/retired_compatibility.v1.toml"); const PUBLISH_POLICY: &str = include_str!("../../../contracts/releases/publish_policy.toml"); #[test] @@ -29,7 +30,7 @@ fn superseded_transport_packages_are_removed_from_release_authority() { assert!(!workspace_manifest.contains(package)); assert!(!approved.contains(package)); assert!(!private.contains(&format!("\"{package}\""))); - assert!(!SHIMS.contains(&format!("| `{package}` |"))); + assert!(RETIRED_COMPATIBILITY.contains(&format!("id = \"{package}\""))); } } diff --git a/crates/transport_nostr/tests/package_boundary.rs b/crates/transport_nostr/tests/package_boundary.rs @@ -5,8 +5,8 @@ use std::path::Path; const MANIFEST: &str = include_str!("../Cargo.toml"); const README: &str = include_str!("../README.md"); const EXAMPLE: &str = include_str!("../examples/configure_transport.rs"); -const PUBLIC_API: &str = include_str!("../../../docs/api/radroots_transport_nostr.txt"); -const API_INDEX: &str = include_str!("../../../docs/api/README.md"); +const PUBLIC_API: &str = + include_str!("../../../contracts/api_baselines/radroots_transport_nostr.txt"); const ROOT: &str = include_str!("../src/lib.rs"); #[test] @@ -60,9 +60,9 @@ fn documentation_example_and_reviewed_api_baseline_are_complete() { "## Serialization and diagnostics", "## Features and runtime requirements", "## Intended consumers", - "radroots_crates_release_v1.md#15-radroots_transport_nostr", + "radroots_crates_release_v1.toml", "examples/configure_transport.rs", - "docs/api/radroots_transport_nostr.txt", + "contracts/api_baselines/radroots_transport_nostr.txt", ] { assert!(README.contains(required), "README is missing `{required}`"); } @@ -113,9 +113,6 @@ fn documentation_example_and_reviewed_api_baseline_are_complete() { "reviewed public API baseline exposes `{forbidden}`" ); } - assert!(API_INDEX.contains( - "| `radroots_transport_nostr` | [`radroots_transport_nostr.txt`](radroots_transport_nostr.txt) |" - )); } fn radroots_dependency_keys(manifest: &str) -> BTreeSet<&str> { diff --git a/crates/transport_nostr/tests/workspace_consumers.rs b/crates/transport_nostr/tests/workspace_consumers.rs @@ -33,7 +33,7 @@ fn superseded_transport_packages_and_nostrdb_adapter_are_removed() { fs::read_to_string(workspace.join("Cargo.toml")).expect("workspace manifest"); let nostrdb_manifest = fs::read_to_string(workspace.join("crates/nostrdb/Cargo.toml")).expect("nostrdb manifest"); - let deviations = fs::read_to_string(workspace.join("docs/implementation/deviations.toml")) + let deviations = fs::read_to_string(workspace.join("contracts/architecture/deviations.toml")) .expect("deviation authority"); assert!(!workspace_manifest.contains("radroots_nostr_runtime")); diff --git a/docs/api/README.md b/docs/api/README.md @@ -1,49 +0,0 @@ -# Public API baselines - -This directory records reviewed pre-release public API surfaces for publishable -Radroots packages. Baselines are generated with `cargo-public-api` `0.52.0`, -`nightly-2026-07-16` for rustdoc JSON, and the package's complete public feature -set. Package verification remains governed by the workspace's pinned stable -toolchain. - -Regenerate a package baseline from the workspace root inside the canonical -development shell with: - -```sh -RUSTC="$(rustup which --toolchain nightly-2026-07-16 rustc)" \ -RUSTDOC="$(rustup which --toolchain nightly-2026-07-16 rustdoc)" \ -cargo public-api --manifest-path crates/<crate>/Cargo.toml \ - --all-features -sss \ - > docs/api/<package>.txt -``` - -Install the exact tool version, when it is not already available, with: - -```sh -cargo install cargo-public-api --version 0.52.0 --locked -rustup toolchain install nightly-2026-07-16 --profile minimal -``` - -Review baseline changes together with the package charter and intended SemVer -impact. A generated listing is evidence of the Rust surface, not authority to -expand a package beyond its charter. - -| Package | Baseline | Charter | -| --- | --- | --- | -| `radroots_core` | [`radroots_core.txt`](radroots_core.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_identity` | [`radroots_identity.txt`](radroots_identity.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_blossom` | [`radroots_blossom.txt`](radroots_blossom.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_protocol` | [`radroots_protocol.txt`](radroots_protocol.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_event` | [`radroots_event.txt`](radroots_event.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_event_codec` | [`radroots_event_codec.txt`](radroots_event_codec.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_trade` | [`radroots_trade.txt`](radroots_trade.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_signing` | [`radroots_signing.txt`](radroots_signing.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_transport` | [`radroots_transport.txt`](radroots_transport.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_nostr` | [`radroots_nostr.txt`](radroots_nostr.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_nostr_connect` | [`radroots_nostr_connect.txt`](radroots_nostr_connect.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_secrets` | [`radroots_secrets.txt`](radroots_secrets.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_storage` | [`radroots_storage.txt`](radroots_storage.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_transport_nostr` | [`radroots_transport_nostr.txt`](radroots_transport_nostr.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_geonames` | [`radroots_geonames.txt`](radroots_geonames.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots_sdk` | [`radroots_sdk.txt`](radroots_sdk.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | -| `radroots` | [`radroots.txt`](radroots.txt) | [release V1 specification](../specs/radroots_crates_release_v1.md) | diff --git a/docs/decisions/0001-public-api-leakage-migration-baseline.md b/docs/decisions/0001-public-api-leakage-migration-baseline.md @@ -1,36 +0,0 @@ -# ADR 0001: Public API leakage migration baseline - -Status: accepted for the crates release V1 migration -Date: 2026-07-27 - -## Context - -The Release V1 architecture forbids generic public packages from exposing -SQLx, Tokio, Reqwest, Nostr SDK, keyring, or platform-specific implementation -types. The existing identity, Nostr, and Nostr Connect packages predate that -boundary and still expose a finite set of upstream Nostr types while -publication remains frozen. - -## Decision - -The synchronized API-boundary contract records only the reviewed findings -under exception IDs RCRV1-API-001, RCRV1-API-002, RCRV1-API-003, -RCRV1-API-004, RCRV1-API-005, RCRV1-API-006, RCRV1-API-007, and -RCRV1-API-008. - -Every exception is package-, source-, item-, forbidden-root-, and -observed-path-specific. New items, new upstream paths, SQLx, Tokio, Reqwest, -keyring, platform-specific types, or broader aliases remain forbidden. The -exceptions authorize no publication. - -The identity exceptions must be removed by the Step 042 conformance gate, the -Nostr SDK exceptions by Step 124, and the Nostr Connect exceptions by Step -140. The owning package refactors may remove them earlier. - -## Consequences - -The architecture command fails closed when an ADR is missing, an exception is -expired or broadened, or a new public implementation type appears. Concrete -implementation crates may use third-party types internally, but those types do -not become public API unless the synchronized contract explicitly permits the -package and path. diff --git a/docs/engineering/ci.md b/docs/engineering/ci.md @@ -1,24 +0,0 @@ -# Architecture continuous integration - -The pull-request architecture lane is a thin GitHub adapter over the -forge-agnostic repository command: - -```sh -nix run .#architecture -``` - -The command validates the synchronized release specification, workspace and -package metadata, production dependency paths, the Cargo-resolved package-tier -graph, public API implementation leakage, contract artifacts, DTO roots, and -generated-manifest freshness. The same command is exposed as the -`architecture` flake check, so `nix flake check` includes the lane. - -The workflow grants only read access to repository contents. Action -dependencies are pinned to full commit identifiers. Its Nix store cache is -content-addressed from the current source and lock inputs; generated files are -read from the checkout on every run and are never restored from a workflow -cache. - -Repository administrators may require the `Architecture / architecture` -status after this commit is publicly reachable. Changing branch protection or -other repository administration remains a separate authorized operation. diff --git a/docs/implementation/COMPATIBILITY_SHIMS.md b/docs/implementation/COMPATIBILITY_SHIMS.md @@ -1,23 +0,0 @@ -# Compatibility shim retirement - -Step 313 completed the coordinated compatibility retirement after every -first-party consumer cut over to the final Release V1 owners. No compatibility -package, hidden prelude, alternate crate identity, or second contract authority -remains in the release source graph. - -| Retired bridge | Final owner | Cutover evidence | Final removal | -| --- | --- | --- | --- | -| `radroots_authority` | `radroots_signing` | downstream Steps 269-294 | Step 313 | -| `radroots_nostr_signer` | `radroots_signing`, `radroots_nostr_connect`, Myc-private state | crate Steps 109-143; SDK Step 248; downstream Steps 269-294 | Step 313 | -| hidden `radroots_nostr_connect::prelude` and prefixed client bridge | final `radroots_nostr_connect` client state machine | CLI Step 271; Myc Step 288; residual consumers Steps 293-294 | Step 313 | -| `radroots_geocoder` | `radroots_geonames` | SDK Steps 226 and 248 | Step 313 source census | - -Release policy contains only the exact 19 publishable packages plus explicitly -deferred private/preview packages. A source census and architecture validation -must fail if any retired package name or compatibility route returns. - -`radroots_nostr_runtime`, the private NostrDB runtime adapter, and -`radroots_net` were deleted during Step 301 qualification. The full workspace -matrix proved their assigned removal edits had been missed even though their -consumer gates were satisfied, and neither obsolete all-feature closure could -compile against the final transport boundary. diff --git a/docs/implementation/DEPENDENCY_RESOLUTION.md b/docs/implementation/DEPENDENCY_RESOLUTION.md @@ -1,14 +0,0 @@ -# Dependency resolution - -This standalone repository owns its `Cargo.lock`. Under `RCRV1-DEV-001`, the -release-v1 refactor does not combine it with the SDK lockfile or make either -repository depend on the other's workspace state. - -Step 017 verified the current lock checksum as -`4462008577c9b46a97a01acce7efd34c95e98e46bc54468cb278f066f7943726`. -Repeated `cargo metadata --locked --no-deps --format-version 1` and the full -repository contract lane leave it unchanged. - -Dependency changes must use repository-owned extbuild commands, preserve -`--locked` zero-diff validation, and update this evidence when the resolved -graph intentionally changes. diff --git a/docs/implementation/DEVIATIONS.md b/docs/implementation/DEVIATIONS.md @@ -1,56 +0,0 @@ -# Implementation deviations - -The machine-readable authority is [`deviations.toml`](deviations.toml). -Repository checks validate it on every architecture and full check lane. This -ledger records evidence-based changes to implementation planning; it does not -silently change `radroots.crates.release.v1`. - -## Active records - -| ID | Affected steps | Approved disposition | -| --- | --- | --- | -| `RCRV1-DEV-001` | 015-023 | Preserve the existing standalone `lib` and `sdk` repositories; replace repository import/unification with independent qualification. | -| `RCRV1-DEV-002` | 249 | Pull only the facade scaffold forward to immediately after Step 014 in `sdk`; do not repeat it later. | -| `RCRV1-DEV-004` | 098, 155, 225, 260, 268, 294, 298-299, 301-304, 314 | Enforce a temporary 90% four-dimension coverage baseline during heavy development; restore 100% only through a future explicit contract update. | -| `RCRV1-DEV-005` | 013, 019-026, 027-315 | Pin every Rust crate and internal Radroots dependency in `radrootslabs/lib` to exactly `0.1.0-alpha` until further explicit authority. | - -## Closed records - -| ID | Closure | -| --- | --- | -| `RCRV1-DEV-007` | Final transport identities replaced every runtime, SDK, CLI, and daemon compatibility mapping; Step 313 removed the last hidden aliases/helpers. | -| `RCRV1-DEV-008` | Final secrets and storage owners replaced every vault/store predecessor; Step 313 removed the packages and remaining consumers. | -| `RCRV1-DEV-009` | Final storage and sync owners replaced the four predecessor packages; Step 313 removed the final SDK/CLI/index surfaces. | -| `RCRV1-DEV-010` | Step 301 removed the overdue Nostr-runtime and broad-network bridges after their consumer gates were green. | -| `RCRV1-DEV-011` | Step 248 completed the SDK GeoNames cutover and removed the geocoder bridge. | -| `RCRV1-DEV-012` | Steps 282-283 qualified the bounded selector, SDK signing/Nostr composition, and mobile bridge. | - -## Record template - -Add one `[[deviation]]` table to `deviations.toml`: - -```toml -[[deviation]] -id = "RCRV1-DEV-NNN" -date = "YYYY-MM-DD" -status = "active" # active | closed | superseded -approval = "Explicit approving decision." -affected_steps = ["NNN"] -spec_anchors = ["docs/specs/<durable-spec>#<anchor>"] -source_evidence = ["Committed source evidence."] -replacement_action = "Smallest safe disposition." -verification = ["Command or review evidence."] -unresolved_risk = "none, or a concrete bounded risk" -normative_architecture_change = false -adr_required = false -closure_evidence = [] # omit while active; required when closed or superseded -``` - -Every field is mandatory except `closure_evidence` on active records. Spec -anchors must resolve inside `docs/specs/`; affected steps must be three-digit -IDs in 001-315. A normative architecture change needs explicit approval and -the appropriate ADR decision before the record can be accepted. - -Do not silently skip, merge, reorder, or broaden implementation steps. Keep a -red checkpoint uncommitted and mark the next step blocked until its evidence or -approval is complete. diff --git a/docs/implementation/HISTORY_PRESERVATION.md b/docs/implementation/HISTORY_PRESERVATION.md @@ -1,25 +0,0 @@ -# Standalone history preservation - -Step 015 is satisfied under `RCRV1-DEV-001` without importing or combining -repository history. This repository remains the independent source authority -for the 17 lower release-v1 packages. - -## Verified checkpoint - -- repository: `git@github.com:radrootslabs/lib.git` -- reviewed baseline: `466f3cc36739179bc17edb9db796530729ba5219` -- verified candidate parent: `fab75a9d3950b92ed70e7e3d8cec0d55d1caf34b` -- baseline relationship: the reviewed baseline is an ancestor of the verified - candidate parent -- submodules: none -- import, subtree, filter-repo, history merge, repository rename, or archive: - not required and not performed - -`git log --follow` retains representative history for -`crates/core/Cargo.toml` and `tools/xtask/src/main.rs`. `git fsck --full` -completed successfully with no corrupt or missing reachable objects. It -reported only unreachable dangling objects retained by Git; those are not -part of the release candidate and were not pruned or modified. - -The next workspace steps must preserve this repository, its lockfile, and its -release boundary independently from `radrootslabs/sdk`. diff --git a/docs/implementation/PUBLICATION_FREEZE.md b/docs/implementation/PUBLICATION_FREEZE.md @@ -1,22 +0,0 @@ -# Crates.io publication freeze - -Crates.io upload remains frozen for the complete release-v1 crate refactor. -Step 305 enabled validation metadata for exactly the 17 approved public -packages: - -```toml -publish = ["crates-io"] -``` - -`contracts/releases/publish_policy.toml` is the machine authority. Repository -contract and release-preflight validation reject an unexpected registry, -package, order, version, or enablement checkpoint; every private, preview, -build, and test-support package remains non-publishable. - -This validation-only state permits packaging, crates.io dry-runs, and local -ephemeral-registry qualification. It does not authorize upload or any crates.io -mutation. - -Changing the freeze requires an independently reviewed release-control commit. -Actual publication, tag creation, registry ownership changes, and -trusted-publisher changes always require separate explicit authorization. diff --git a/docs/implementation/STEP_REPORT_TEMPLATE.md b/docs/implementation/STEP_REPORT_TEMPLATE.md @@ -1,58 +0,0 @@ -# Commit-step report template - -Complete this record in the owning rolling-commit document after verification -and before the next handoff step begins. - -```text -Step: -Title: -Repository: -Branch: -Commit SHA: - -Spec anchors: -- ... - -Files changed: -- ... - -Behavior implemented: -- ... - -Tests and verification: -- command: - result: -- command: - result: - -Self-review: -- public API review: -- architecture-boundary review: -- error/secret review: -- feature/target review: -- documentation review: -- generated/lockfile diff review: - -Deviations: -- none -or -- RCRV1-DEV-NNN and evidence - -Unresolved issues: -- none -or -- ... - -Known pre-existing failures: -- none -or -- command, exact failure, evidence, and why it is outside this step - -Next-step safety: -- SAFE / BLOCKED -- reason: -``` - -A step is not complete without its commit SHA, exact command outcomes, -self-review, deviation disposition, and next-step safety decision. A blocked -step does not authorize later work. diff --git a/docs/implementation/TRACEABILITY.md b/docs/implementation/TRACEABILITY.md @@ -1,21 +0,0 @@ -# Release-v1 requirement traceability - -This matrix maps durable architecture requirements to implementation ownership -and verification. It adds no product requirements; the synchronized -`docs/specs/` bundle remains normative. - -| Durable requirement | Owning package or control | Handoff steps | Required evidence | -| --- | --- | --- | --- | -| Exactly 19 public packages with a 17/2 repository split | release policy and architecture catalog | 013, 015-026, 304-305 | Cargo-resolved graph report and exact allowlist validation | -| Every `radrootslabs/lib` Rust crate remains in the frozen `0.1.0-alpha` cohort | library repository version authority and architecture validators | 013, 019-315 | exact workspace package, dependency requirement, lockfile, and synchronized-spec validation | -| Public-only identity and separated signing/secrets | `radroots_identity`, `radroots_signing`, `radroots_secrets` | 052-054, 099-111, 147-155 | public API, feature, dependency, and redaction tests | -| One canonical `TradeId` | `radroots_event`, `radroots_trade` | 073-098 | compile/API inventory and trade conformance | -| Version-neutral protocol ownership | `radroots_protocol` and private generators | 055-064, 261-268 | contract vectors and generated freshness | -| Independent transport source/sink with extensible identity | `radroots_transport` and adapters | 112-134, 190-207 | transport conformance and forward-compatibility fixtures | -| Storage SPI with SQLite backend | `radroots_storage`, `radroots_storage_sqlite` | 156-189 | backend conformance, migration, recovery, and leakage gates | -| Shared sync engine and explicit lifecycle | `radroots_sync` | 208-225 | pull/push, idempotency, cancellation, and close tests | -| Safe SDK defaults and curated facade | `radroots_sdk`, `radroots` | 226-260 | clean-project package smoke tests and compile-time surface guards | -| Preview and implementation packages remain private | release policy and graph validator | 013, 023-026, 304-305 | private-closure and forbidden-edge fixtures | -| Package-realistic reproducible release | release tooling in both repositories | 295-315 | locked zero-diff package, extracted, local-registry, target, and coverage gates | -| Every first-party consumer migrates | downstream cutover matrix | 269-294 | discovered consumer inventory and canary results | -| Deviations remain explicit and reviewable | `docs/implementation/deviations.toml` | 014 and every affected step | `cargo xtask architecture` plus step report evidence | diff --git a/docs/implementation/deviations.toml b/docs/implementation/deviations.toml @@ -1,330 +0,0 @@ -schema_version = 1 -architecture_id = "radroots.crates.release.v1" - -[[deviation]] -id = "RCRV1-DEV-001" -date = "2026-07-27" -status = "active" -approval = "Explicit user correction dated 2026-07-27." -affected_steps = [ - "015", - "016", - "017", - "018", - "019", - "020", - "021", - "022", - "023", - "026", -] -spec_anchors = [ - "docs/specs/radroots_crates_release_v1.md#repository-ownership", - "docs/specs/radroots_crates_release_v1.toml#repository_policy", -] -source_evidence = [ - "The final v1 specification allocates 17 public packages to radrootslabs/lib and 2 to radrootslabs/sdk.", - "Both existing repositories have independent histories, workspaces, lockfiles, remotes, and standalone release boundaries.", -] -replacement_action = "Retain the two existing standalone repositories; replace import and monorepo-unification work with independent workspace, lockfile, metadata, dependency, and release qualification." -verification = [ - "Both repository-local architecture validators resolve every spec anchor.", - "The synchronized architecture catalog enforces the exact 17/2 ownership partition.", - "Each standalone repository owns a required architecture CI adapter over its repository-local command surface.", -] -unresolved_risk = "Parent gitlinks cannot advance until the new standalone commits are public-remote reachable under separate authorization." -normative_architecture_change = false -adr_required = false - -[[deviation]] -id = "RCRV1-DEV-010" -date = "2026-08-03" -status = "closed" -approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user." -affected_steps = ["201", "215", "269", "294", "301", "313"] -spec_anchors = [ - "docs/specs/radroots_crates_release_v1.md#15-radroots_transport_nostr", - "docs/specs/radroots_crates_release_v1.md#20-current-to-target-migration-map", -] -source_evidence = [ - "The Step 294 downstream matrix proved the former app_rt radroots_net_core consumer had migrated and no current first-party manifest retained an edge to radroots_net.", - "Step 301 full workspace qualification proved that the assigned radroots_nostr_runtime Step 215 deletion and radroots_net Step 313 deletion had not been applied even though both removal gates were satisfied.", - "Both obsolete packages failed against the final transport boundary when the complete all-feature workspace closure was checked.", -] -replacement_action = "Delete radroots_nostr_runtime, its NostrDB runtime adapter, and radroots_net during Step 301 qualification; forbid their package, dependency, alias, feature, and source identities from being reintroduced." -verification = [ - "Transport-owned tests reject reintroduction of radroots_nostr_runtime, radroots_net, or the NostrDB runtime-adapter feature.", - "Workspace-wide source tests reject every removed transport-client identifier without a compatibility exception.", - "The full all-target and all-feature workspace qualification compiles without either predecessor package.", -] -unresolved_risk = "None for the two removed packages; historical names remain only in governed specifications, migration evidence, and fail-closed regression assertions." -normative_architecture_change = false -adr_required = false -closure_evidence = [ - "crates/transport_nostr/tests/workspace_consumers.rs proves both package directories and workspace dependency identities are absent.", - "crates/transport_nostr/tests/legacy_quarantine.rs proves release policy and the compatibility ledger no longer classify either package as a retained shim.", -] - -[[deviation]] -id = "RCRV1-DEV-008" -date = "2026-08-01" -status = "closed" -approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user." -affected_steps = ["153", "155", "171", "179", "226", "288", "293", "313"] -spec_anchors = [ - "docs/specs/radroots_crates_release_v1.md#12-radroots_secrets", - "docs/specs/radroots_crates_release_v1.md#20-current-to-target-migration-map", -] -source_evidence = [ - "The final radroots_storage_sqlite scaffold can consume radroots_secrets immediately and has no predecessor secret dependency.", - "Step 179, not Step 153, owns transfer of the current SDK private database and its encrypted records into canonical SQLite storage.", - "Mixed publish-frozen runtime, Nostr-account, SimpleX preview, SDK private-store, Myc, and other external hosts still require predecessor vault/store behavior until their ordered migration steps.", -] -replacement_action = "Activate the final radroots_storage_sqlite and SDK dependency edges in Step 153; confine predecessor vault/store imports to exact publish-frozen quarantine packages and the SDK private-store module; Step 179 transfers canonical private storage, Steps 226/288/293 migrate the remaining SDK and downstream consumers, and Step 313 removes every remaining compatibility package and legacy name." -verification = [ - "Consumer-migration tests enumerate every lib package manifest that still names radroots_secret_vault or radroots_protected_store and reject any unapproved or publishable consumer.", - "Storage SQLite package-boundary tests require radroots_secrets and reject all predecessor secret package names.", - "SDK source-boundary tests confine predecessor imports to private_store.rs and require the final optional radroots_secrets dependency edge.", - "Step 155 release-policy validation keeps every quarantine package non-publishable until its exact removal gate.", -] -unresolved_risk = "None; the predecessor packages and all active manifest consumers are absent." -normative_architecture_change = false -adr_required = false -closure_evidence = [ - "crates/secrets/tests/consumer_migration.rs rejects every predecessor package directory, workspace dependency, and active manifest reference.", - "Step 313 source census confirmed the SDK, Myc, CLI, and library runtime paths use only radroots_secrets and final storage owners.", -] - -[[deviation]] -id = "RCRV1-DEV-007" -date = "2026-07-30" -status = "closed" -approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user." -affected_steps = ["122", "170", "215", "235", "305"] -spec_anchors = [ - "docs/specs/radroots_crates_release_v1.md#9-radroots_transport", - "docs/specs/radroots_crates_release_v1.md#111-separate-source-and-sink-contracts", -] -source_evidence = [ - "Step 120 migrated canonical adapters and registries to independent EventSource and EventSink contracts.", - "Mixed runtime delivery workers still consume predecessor request and receipt models until their ordered RCLD 40 migration.", - "The runtime delivery worker also accepts opaque byte payloads, while the final EventSink contract accepts only verified SignedEvent values; deleting the bridge at Step 170 would silently remove a supported private runtime path before sync orchestration owns that decision.", - "The standalone publish-frozen SDK still maps user-facing target and satisfaction models into predecessor outbox orchestration until Step 235.", - "oss/cli/src/runtime/{config,sync,transport}.rs and oss/radrootsd/src/core/transport_publish.rs still import the predecessor identity aliases and Reticulum target helpers; those standalone repositories are outside the approved crate-surface mutation scope.", -] -replacement_action = "Remove the monolithic trait from radroots_transport in Step 122; retain one explicitly named runtime-owned unpublished shim until Step 215 at the sync-orchestration retirement gate, the SDK-local unpublished target/satisfaction mapping until Step 235, and documentation-hidden external-consumer aliases/helpers until the fail-closed package-realistic Step 305 gate." -verification = [ - "Transport source-boundary tests reject every removed public predecessor name and require the singular runtime-owned shim.", - "Release policy keeps runtime and SDK publication disabled while either downstream shim exists.", - "Steps 215 and 235 are the exact fail-closed final-removal gates for the remaining runtime and SDK mappings.", - "Step 305 rejects publication until oss/cli and oss/radrootsd no longer require the documentation-hidden external-consumer aliases and Reticulum helpers.", -] -unresolved_risk = "None; final consumers use TransportId, Target, TargetScope, TargetLabel, and TargetFingerprint directly." -normative_architecture_change = false -adr_required = false -closure_evidence = [ - "crates/transport/tests/package_boundary.rs rejects the hidden Reticulum constructors, legacy constant spellings, and prefixed target aliases.", - "Step 313 source census confirmed every external-consumer alias and helper is absent after the daemon cutover to final transport identities.", -] - -[[deviation]] -id = "RCRV1-DEV-009" -date = "2026-08-02" -status = "closed" -approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user." -affected_steps = [ - "170", - "179", - "189", - "196", - "201", - "213", - "226", - "235", - "263", - "269", - "288", - "292", - "313", -] -spec_anchors = [ - "docs/specs/radroots_crates_release_v1.md#13-radroots_storage", - "docs/specs/radroots_crates_release_v1.md#14-radroots_storage_sqlite", - "docs/specs/radroots_crates_release_v1.md#20-current-to-target-migration-map", -] -source_evidence = [ - "The Step 170 first-party census found radroots_event_index consumers in the standalone CLI, SDK, SDK bindings, and indexer repositories.", - "The census found radroots_event_store and radroots_outbox consumers in the standalone CLI and SDK, plus active library transport_nostr orchestration scheduled to move in Step 196.", - "The census found radroots_runtime_store consumed by the standalone CLI, whose crate cutover is scheduled after the canonical library storage and sync implementations are complete.", - "Deleting these packages before their ordered migrations would make the independently buildable first-party repositories unresolvable and would remove the source data needed by the approved one-shot importer.", -] -replacement_action = "Keep radroots_event_index, radroots_event_store, radroots_outbox, and radroots_runtime_store as documentation-hidden publish-frozen compatibility packages with no new consumers; port durable behavior into radroots_storage_sqlite through Step 189, remove local transport coupling in Step 196, migrate library/SDK/binding/CLI/indexer consumers in their ordered steps, and delete every remaining package at Step 313." -verification = [ - "Package manifests carry machine-readable publish-frozen metadata naming the final replacement, deviation, Step 313 removal gate, and prohibition on new consumers.", - "A storage-owned quarantine test requires the four packages to remain private and absent from the approved publication inventory.", - "Workspace checks and tests prove current migration consumers remain buildable while publication stays frozen.", - "Step 313 performs the all-first-party forbidden-name search and final package deletion.", -] -unresolved_risk = "None; all four predecessor packages and their active dependency edges are absent." -normative_architecture_change = false -adr_required = false -closure_evidence = [ - "crates/storage_sqlite/tests/package_boundary.rs and crates/storage/tests/workspace_consumers.rs reject the four predecessor package identities.", - "Step 313 removed the final library packages, SDK runtime/event-index surfaces, and CLI source dependency branch after all standalone consumers migrated.", -] - -[[deviation]] -id = "RCRV1-DEV-005" -date = "2026-07-28" -status = "active" -approval = "Explicit user Rust version-policy update dated 2026-07-28 17:47 UTC." -affected_steps = [ - "013", - "019", - "020", - "021", - "022", - "023", - "024", - "025", - "026", - "305", -] -spec_anchors = [ - "docs/specs/radroots_crates_release_v1.md#221-initial-versions", - "docs/specs/radroots_crates_release_v1.toml#repositories.lib", -] -source_evidence = [ - "The prior 1.0.0 and mixed 0.1.0 prerelease crate cohorts were explicitly declared incorrect.", - "The user requires every Rust crate in radrootslabs/lib to remain exactly 0.1.0-alpha until further explicit notice.", -] -replacement_action = "Pin every workspace package, lockfile entry, and internal Radroots dependency requirement in radrootslabs/lib to 0.1.0-alpha; preserve independent protocol and sibling-repository versions; reject library cohort drift until new explicit authority is recorded." -verification = [ - "Repository architecture validation rejects any workspace package version other than 0.1.0-alpha.", - "Internal Radroots dependency requirements resolve exactly to =0.1.0-alpha.", - "Synchronized release specifications record the library cohort independently from the SDK repository version.", -] -unresolved_risk = "The prerelease cohort intentionally prevents independent library package version advancement until a future explicit policy change." -normative_architecture_change = false -adr_required = false - -[[deviation]] -id = "RCRV1-DEV-002" -date = "2026-07-27" -status = "active" -approval = "Explicit user correction dated 2026-07-27." -affected_steps = ["249"] -spec_anchors = [ - "docs/specs/radroots_crates_release_v1.md#radroots", - "docs/specs/radroots_crates_release_v1.toml#repositories.sdk", -] -source_evidence = [ - "The final v1 specification assigns the radroots facade to the existing sdk repository.", - "The approved sequence requires radroots to be the first crate-surface mutation after architecture controls are green.", -] -replacement_action = "Scaffold radroots in the sdk repository immediately after Step 014, then execute Steps 250-260 in their original order without repeating the scaffold portion of Step 249." -verification = [ - "The sdk release policy reserves radroots as an approved local package while publication remains frozen.", - "The facade scaffold checkpoint must add radroots only to the sdk workspace and architecture policy.", -] -unresolved_risk = "The facade remains non-publishable until the package-realistic Step 305 enablement gate." -normative_architecture_change = false -adr_required = false - -[[deviation]] -id = "RCRV1-DEV-004" -date = "2026-07-28" -status = "active" -approval = "Explicit user coverage-policy update dated 2026-07-28 17:15 UTC." -affected_steps = [ - "098", - "155", - "225", - "260", - "268", - "294", - "298", - "299", - "301", - "302", - "303", - "304", - "314", -] -spec_anchors = [ - "docs/specs/radroots_crates_release_v1.md#245-coverage-policy", - "docs/specs/radroots_crates_release_v1.toml#quality_policy.coverage", -] -source_evidence = [ - "The oss/lib codebase is under heavy development during the multi-RCLD refactor.", - "The user explicitly replaced the active 100% coverage requirement with a uniform 90% module requirement.", -] -replacement_action = "Enforce 90% executable-line, function, region, and branch coverage for every required oss/lib crate; retain only no-branch-record exceptions; defer restoration of 100% until an explicit future contract update after refactor stabilization." -verification = [ - "Contract validation rejects any base coverage dimension other than 90% or disabled required branches.", - "Coverage policy-gate tests prove values below 90% fail and values at or above 90% pass.", - "The required-crate inventory remains complete and crate-specific numeric thresholds below 90% remain forbidden.", -] -unresolved_risk = "A 90% development gate admits untested paths that a later 100% gate would reject; the final restoration remains intentionally unscheduled pending explicit authority." -normative_architecture_change = false -adr_required = false - -[[deviation]] -id = "RCRV1-DEV-011" -date = "2026-08-03" -status = "closed" -approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user." -affected_steps = ["225", "226", "248"] -spec_anchors = [ - "docs/specs/radroots_crates_release_v1.md#17-radroots_geonames", - "docs/specs/radroots_crates_release_v1.md#20-current-to-target-migration-map", -] -source_evidence = [ - "The standalone SDK manifest and GeoNames module still resolve radroots_geocoder and its test-fixture feature while the final provider package is implemented in this independently versioned repository.", - "The legacy package has no in-repository consumer, is publish disabled, and is already excluded from the exact release-v1 public package inventory.", - "Deleting the package at Step 225 would make the independently buildable SDK repository unresolvable before its ordered manifest and API cutover begins at Step 226.", -] -replacement_action = "Keep radroots_geocoder as a documentation-marked, machine-classified publish-frozen bridge with no new consumers, features, contracts, or behavior; migrate the standalone SDK to radroots_geonames beginning at Step 226 and delete the bridge at the SDK retirement gate in Step 248." -verification = [ - "The predecessor manifest names radroots_geonames as its replacement, RCRV1-DEV-011 as authority, and Step 248 as the exact removal gate.", - "GeoNames package quarantine tests require the predecessor to remain private and absent from the approved publication inventory while the SDK source census remains non-empty.", - "Step 248 must reject every remaining package, dependency, feature, import, and error-adapter reference to radroots_geocoder before deletion.", -] -unresolved_risk = "None; the SDK consumes radroots_geonames and the predecessor package is absent." -normative_architecture_change = false -adr_required = false -closure_evidence = [ - "crates/geonames/tests/package_boundary.rs rejects the superseded package directory and release-policy identity.", - "The SDK Step 248 checkpoint removed every radroots_geocoder dependency, feature, import, and error-adapter reference.", -] - -[[deviation]] -id = "RCRV1-DEV-012" -date = "2026-08-03" -status = "closed" -approval = "All code-review recommendations and the full multi-RCLD implementation sequence were explicitly approved by the user." -affected_steps = ["279", "282", "283"] -spec_anchors = [ - "docs/specs/radroots_crates_release_v1.md#9-radroots_transport", - "docs/specs/radroots_crates_release_v1.md#15-radroots_transport_nostr", - "docs/specs/radroots_crates_release_v1.md#18-radroots_sdk", -] -source_evidence = [ - "The first-party mobile compile gate proved that its real relay-backed identity, profile, and post operations had no equivalent after the initial shared-engine bridge cutover.", - "The generic source request could bound pages and targets but could not express event kind, author, or event-time constraints required for correct profile and feed queries.", - "Restoring the retired parallel runtime or accepting client-side filtering after page truncation would violate the final SDK and transport ownership model.", -] -replacement_action = "Before qualifying the mobile artifact, add bounded transport-neutral fetch selectors, translate them in the concrete Nostr adapter, complete SDK-owned explicit local-signing and Nostr composition, and map the mobile presentation contract over those SDK operations without restoring direct lower-package dependencies." -verification = [ - "Transport selector construction rejects oversized, duplicate, and reversed-range inputs and binds selectors into page validation.", - "The Nostr adapter applies kind, author, and time constraints remotely and defensively filters returned events before page bounds.", - "The shared-engine SDK and mobile integration gates must pass before Steps 282 and 283 are marked complete.", -] -unresolved_risk = "None; the shared-engine and mobile qualification checkpoints completed with the final selector and SDK ownership model." -normative_architecture_change = false -adr_required = false -closure_evidence = [ - "crates/transport/tests/source_contract.rs verifies bounded canonical selector construction and request binding.", - "crates/transport_nostr/src/source.rs tests remote selector translation and defensive result filtering before page bounds.", - "Steps 282 and 283 qualified the SDK-owned signing, Nostr composition, and mobile presentation bridge without lower-package ownership leakage.", -] diff --git a/docs/migration/identity.md b/docs/migration/identity.md @@ -1,25 +0,0 @@ -# Identity, signing, and secret ownership migration - -`radroots_identity` now owns public values only: `PublicKey`, `IdentityId`, -`AccountId`, `PublicIdentity`, `Profile`, and `Username`. The removed -`RadrootsIdentity` API, raw secret bytes, key generation, nsec encoding, NIP-49 -encryption/decryption, and encrypted identity files have no compatibility -aliases in this package. - -The approved destination boundaries are: - -- `radroots_nostr::key` for explicit Nostr key parsing, nsec/NIP-49 conversion, - and host-requested local Nostr key creation; -- `radroots_signing` for the signer SPI, requests, receipts, authorization, and - actor provenance, without owning raw secret bytes; -- `radroots_nostr::signing` for concrete local Nostr signing adapters; -- `radroots_secrets::{reference, provider, envelope, wrapping}` for secret - references, providers, wrapping, and versioned encrypted envelopes; -- host storage adapters composed from `radroots_secrets` and - `radroots_storage_sqlite` for durable secret persistence. - -Those destination APIs are introduced by their ordered release checkpoints. -Until then, callers must not recreate secret ownership or persistence in -`radroots_identity` or add a compatibility shim. The former identity/profile -file helpers, default paths, runtime-path resolution, and encrypted storage -APIs have been removed from this package. diff --git a/docs/migration/release-v1.md b/docs/migration/release-v1.md @@ -1,25 +0,0 @@ -# Release V1 breaking migration - -Release V1 is an intentional breaking cut to the 17-package public library -surface. All packages remain in `oss/lib`, use version `0.1.0-alpha`, and are -qualified independently from the two front-door packages in `oss/sdk`. - -| Retired surface | Release V1 owner | -| --- | --- | -| authority and Nostr signer packages | `radroots_signing` and `radroots_nostr_connect` | -| vault and protected-store packages | `radroots_secrets` plus `radroots_storage` / `radroots_storage_sqlite` | -| event store, event index, outbox, and runtime store packages | `radroots_storage`, `radroots_storage_sqlite`, and `radroots_sync` | -| runtime and broad network packages | explicit host composition over `radroots_transport`, `radroots_transport_nostr`, storage, and sync | -| geocoder package | `radroots_geonames` | -| event-codec domain roots | `radroots_event_codec::{decode,encode,verify,admission}` | -| trade operational-listing and validation-receipt modules | `radroots_sdk::listing` for product operations; `radroots_event` retains the canonical listing model | -| prefixed transport target aliases and Reticulum helpers | `TransportId`, `Target`, `TargetSet`, and `target::{TargetScope,TargetLabel,TargetFingerprint}` | -| Nostr Connect prelude and prefixed client bridge | explicit `radroots_nostr_connect` root types and modules | - -No compatibility package, hidden prelude, type alias, dual schema, or sibling -source path remains. Consumers must migrate atomically to the final owner; the -release does not provide a deprecated intermediate API. - -The 17 packages are enabled only for package-realistic validation. Actual -crates.io publication remains blocked until the approval packet is complete -and a separate operator action is explicitly authorized. diff --git a/docs/migration/trade-ids.md b/docs/migration/trade-ids.md @@ -1,24 +0,0 @@ -# Trade protocol identity migration - -`radroots_event::trade` owns the canonical protocol identifiers used by -authenticated trade events: - -- `TradeId` identifies one protocol trade and stores 16 canonical bytes; -- `CandidateId` identifies canonical candidate terms and stores 32 bytes; -- `MutationId` identifies a canonical trade mutation and stores 32 bytes. - -Import these types from `radroots_event::trade`. The deliberate -`radroots_event::id` facade reexports the same types for code that groups -canonical event-bound identifiers. Both paths resolve to the same definitions; -neither path introduces a wrapper or conversion. - -`radroots_trade::model::OrderId` is the separate human or business-workflow -identifier. It is not an alias for `TradeId`, and no conversion exists between -them. Protocol code must not infer or construct a `TradeId` from an `OrderId`. -Persisted and wire boundaries encode protocol IDs as lowercase hexadecimal -only through their explicit `to_hex` and parsing APIs. - -The algorithm package's former `TradeId(OrderId)` wrapper is removed at its -ordered package-refactor checkpoint. New event-domain code must use the -canonical `radroots_event::trade::TradeId` surface now and must not add another -trade identifier definition. diff --git a/docs/nip46-current-conformance.md b/docs/nip46-current-conformance.md @@ -1,92 +0,0 @@ -# Current NIP-46 conformance - -This document defines the public compatibility contract for the Nostr Connect -protocol, signer-session, and relay-client changes that bring the Radroots -libraries into conformance with the current NIP-46 lifecycle. - -## Qualified baseline - -The contribution is based on commit `e4e8ae87f6c230c4f26c317bd3107a6710e936a0` -from `origin/master`. The baseline tests for `radroots_nostr_connect`, -`radroots_nostr_signer`, and `radroots_nostr` pass together before the behavior -changes described here. - -The repository-wide contract lane has an unrelated baseline hygiene failure in -the trade workflow surface. NIP-46 changes must not add to that failure, and -the affected crate and contract lanes remain required for every checkpoint. - -## Wire compatibility - -`connect` accepts the current positional parameter sequence: - -1. remote-signer public key; -2. optional connection secret; -3. optional requested permissions; -4. optional JSON-stringified client metadata. - -One-, two-, and three-parameter messages remain valid. Empty positional values -are emitted when a later optional value is present. Client metadata contains -only `name`, `url`, and `image`; requested permissions remain the third -`connect` parameter and the `perms` query parameter of a `nostrconnect://` -token. - -`logout` is a typed zero-parameter request. Its successful response is the -string `ack`. Unsupported custom methods keep their existing error and custom -response behavior. - -## Client metadata - -Client metadata is untrusted, unauthenticated display input. It never selects -an identity, grants a permission, changes an approval requirement, or affects -signing authorization. - -The protocol model normalizes and validates metadata at URI and request -boundaries: - -- names are trimmed, non-empty, free of control characters, and at most 128 - UTF-8 bytes; -- URL and image values are at most 2,048 UTF-8 bytes, use `http` or `https`, - contain no credentials, and are normalized through the URL parser; -- the JSON metadata parameter is bounded before parsing; -- absent and legacy metadata decode as `None` or the default empty value. - -Diagnostic output may identify which field is invalid, but must not log the -connection secret or a complete untrusted metadata payload. - -## URI behavior - -`nostrconnect://` requires at least one relay and a non-empty secret. Its -requested permissions and display metadata use separate typed fields. -`bunker://` requires at least one relay and keeps its secret optional. Repeated -relay parameters retain input order in both URI forms. - -## Signer persistence and revocation - -Connection drafts and records may carry optional validated client metadata. -JSON state created before the field existed decodes with metadata absent. The -native SQLite store persists the same value through a forward migration while -retaining existing connection rows. - -Revocation remains an explicit, idempotent transition to `Revoked`. A service -handling `logout` must publish the acknowledgement before revoking the session; -transport publication ordering is intentionally owned by the service rather -than hidden inside the storage transition. - -## Relay-client boundary - -`radroots_nostr` owns the public client operations needed by portable NIP-46 -adapters. An adapter can add relays, connect, subscribe, publish events, and -explicitly unsubscribe without importing `nostr-sdk` directly. The wrapper -does not create detached listener tasks or hide subscription ownership. - -The `client` and `events` feature combination must compile for native and -`wasm32-unknown-unknown`. Default and non-client feature behavior remains -unchanged. - -## Deterministic conformance - -Checked-in vectors cover legacy and four-parameter `connect`, metadata bounds, -repeated relay order, optional bunker secrets, mandatory client secrets, -secret-echo responses, relay switching, auth continuation, typed logout, and -malformed request or response envelopes. Fixtures use public deterministic -keys and non-routable example domains; they contain no reusable credentials. diff --git a/docs/specs/README.md b/docs/specs/README.md @@ -1,40 +0,0 @@ -# Release specification index - -This directory carries the coordinated `radroots.crates.release.v1` contract -for the two existing standalone Rust repositories. - -The `radrootslabs/lib` repository owns packages 1-17, from `radroots_core` -through `radroots_geonames`. The `radrootslabs/sdk` repository owns packages -18-19, `radroots_sdk` and `radroots`. No third Rust repository is part of this -release architecture. - -## Files - -- `radroots_crates_release_v1.md` is the normative architecture and - publication specification. -- `radroots_crates_release_v1.toml` is the machine-readable package, - dependency, feature, and repository-allocation catalog. -- `radroots_crates_release_v1_inventory.csv` is the reviewable package - inventory. -- `radroots_crates_release_v1.dot` is the reviewable dependency and repository - ownership graph. -- `radroots_crates_release_v1.sha256` pins the synchronized contract artifact - contents. - -## Precedence and change control - -Repository instruction files govern how work is performed. Within this -release contract, the Markdown specification is normative, the TOML catalog -is its executable representation, and the CSV and DOT files are review aids. -Current code and tests are implementation evidence, not authority to silently -change the package architecture. - -The four contract artifacts and their hashes MUST match the copies in -`radrootslabs/sdk`. A change to package identity, ownership, dependency -direction, or release policy MUST update both repositories together and MUST -fail validation if the copies diverge. - -During migration, package manifests remain non-publishable until their -package-realistic release gates pass. Cross-repository dependencies use -registry versions in release candidates; a sibling checkout is never a -production dependency. diff --git a/docs/specs/radroots_crates_release_v1.md b/docs/specs/radroots_crates_release_v1.md @@ -1,1589 +0,0 @@ -# Radroots Crates Release V1 - -**Normative identifier:** `radroots.crates.release.v1` -**Document status:** Final pressure-tested architecture and publication specification -**Date:** 2026-07-26 -**Coverage amendment:** 2026-07-28 temporary heavy-development baseline -**Source snapshots reviewed:** -- `radrootslabs/lib@466f3cc36739179bc17edb9db796530729ba5219` -- `radrootslabs/sdk@fd8384aee348034e0c8ea17a868fe7f094770050` - -**Repository allocation:** the existing `radrootslabs/lib` and -`radrootslabs/sdk` repositories remain independent. The first 17 packages are -owned by `lib`; `radroots_sdk` and `radroots` are owned by `sdk`. No third Rust -repository is created for release V1. - -**Registry context supplied by the project:** no Radroots crate is currently published on crates.io. Deleted experimental publications create no compatibility, pluralization, deprecation, or version-continuity requirement. - -## 1. Normative language and status - -The words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and **MAY** are normative. - -This specification freezes the **package identities, ownership boundaries, naming model, dependency direction, and release gates** for the first durable Radroots crates.io release. - -It does not claim that the current source tree is already publishable. Publication remains blocked until the refactor is implemented and every acceptance gate in this document passes against packaged artifacts. - -## 2. Final executive decision - -Radroots SHALL publish exactly **19 durable package identities** for release V1: - -1. `radroots_core` -2. `radroots_identity` -3. `radroots_blossom` -4. `radroots_protocol` -5. `radroots_event` -6. `radroots_event_codec` -7. `radroots_trade` -8. `radroots_signing` -9. `radroots_transport` -10. `radroots_nostr` -11. `radroots_nostr_connect` -12. `radroots_secrets` -13. `radroots_storage` -14. `radroots_storage_sqlite` -15. `radroots_transport_nostr` -16. `radroots_sync` -17. `radroots_geonames` -18. `radroots_sdk` -19. `radroots` - -This is neither the current workspace publication list nor a collapse into `radroots_sdk`. - -The package family is implemented across the two existing standalone -repositories. `radrootslabs/lib` owns packages 1-17, and `radrootslabs/sdk` -owns packages 18-19. Cross-repository dependencies resolve through registry -versions; neither repository depends on a sibling checkout. - -The architecture is a layered network/protocol stack: - -```text -portable values / identity / protocol contracts - ↓ -event model and deterministic domain algorithms - ↓ -signing, transport, secrets, and storage SPIs - ↓ -concrete native backends and network adapters - ↓ -local-first synchronization engine - ↓ -advanced SDK - ↓ -ordinary-user façade -``` - -## 3. Final pressure-test changes from the directionally approved draft - -The final review makes the following deliberate changes: - -1. **`radroots_contracts` becomes `radroots_protocol`.** The package is a durable, versioned wire/operation protocol boundary rather than a general-purpose “contracts” bucket. -2. **`radroots_store` and `radroots_store_sqlite` become `radroots_storage` and `radroots_storage_sqlite`.** “Storage” is unambiguous in an agricultural marketplace and describes the package family more accurately than “store.” -3. **`radroots_nostr_connect` remains independent.** It is a bidirectional security protocol with URIs, permissions, client/server state, and independent SDK/Myc consumers. It is not merely a convenience NIP module. -4. **Actor ownership is refined.** Public keys/accounts live in identity; event author roles live in the event contract model; actor provenance, authorization, and signer behavior live in signing. -5. **Trade identity is made singular.** The conflicting `TradeId`/`OrderId` definitions MUST be replaced by one canonical protocol `TradeId` and a separately named business `OrderId`. -6. **Public codegen features are removed.** `dto-bindgen`, binding generation, WASM wrappers, and fixture switches remain private build/test concerns. -7. **The workspace moves to Cargo resolver 3.** A virtual Rust 2024 workspace MUST explicitly set `resolver = "3"`. -8. **Release V1 uses MSRV 1.97.1.** The patch release is selected rather than 1.97.0 because it contains a compiler miscompilation fix. -9. **Lower crates do not remain permanently lockstep.** The current - `radrootslabs/lib` development cohort is frozen at `0.1.0-alpha`, while - `radroots` and `radroots_sdk` remain an exact `0.1.0` lockstep pair. Lower - packages may follow independent SemVer only after explicit future authority - ends the temporary library version freeze. - -## 4. Non-negotiable architecture invariants - -1. Every public package MUST have a durable name and at least one named direct consumer besides `radroots`. -2. Every dependency edge MUST point downward in the architecture. -3. Domain and protocol packages MUST NOT depend on storage, networking, process lifecycle, or host UI. -4. Generic SPIs MUST NOT expose concrete SQLite, Tokio, Reqwest, Nostr SDK, keyring, or OS-specific types. -5. Adapters and backends MUST implement SPIs; SPIs MUST NOT depend on adapters or backends. -6. Synchronization MUST orchestrate sources, sinks, signing, and storage without owning an executor or scheduler. -7. `radroots_sdk` MUST compose lower packages and own client-level commit semantics; it MUST NOT become a dumping ground for code that has an independent durable boundary. -8. `radroots` MUST provide meaningful curation and documentation; it MUST NOT be `pub use radroots_sdk::*`. -9. Version generations MUST live in modules and schema IDs, never in package names. -10. Preview implementation code MAY remain in the monorepo but MUST NOT appear in a published dependency or feature until registry-ready. -11. No public package may have a normal, optional, build, or target-specific dependency on a private Radroots package. -12. No first-party consumer may rely on production sibling paths after cutover. - -## 5. Final public package inventory - -### 5.1 Repository ownership - -- `https://github.com/radrootslabs/lib` owns `radroots_core` through - `radroots_geonames` (packages 1-17). -- `https://github.com/radrootslabs/sdk` owns `radroots_sdk` and `radroots` - (packages 18-19). -- Both repositories retain their existing histories and remain independently - buildable, testable, packageable, and releasable. -- The two repositories carry synchronized copies of this release-family - contract. A coordinated release MUST reject any content-hash or package - allocation mismatch between those copies. - -| Order | Package | Rust crate path | Tier | Permanent responsibility | -|---:|---|---|---|---| -| 1 | `radroots_core` | `radroots_core` | foundation | Foundational value objects and deterministic invariants: decimal, currency, money, percentage, quantity, units, and pricing. | -| 2 | `radroots_identity` | `radroots_identity` | foundation | Public identity and account value types: canonical public keys, identity IDs, account IDs, public profiles, and usernames. | -| 3 | `radroots_blossom` | `radroots_blossom` | protocol primitive | Portable Blossom protocol primitives: canonical blob URLs, hashes, media descriptors, byte-verification typestates, and authorization claims. | -| 4 | `radroots_protocol` | `radroots_protocol` | versioned wire contract | Versioned cross-process and cross-language schemas, capability catalogs, operation descriptors, stable error reports, schema IDs, and structural validation. | -| 5 | `radroots_event` | `radroots_event` | domain protocol model | Canonical Radroots event-domain models, validated event identifiers, tags, event contracts, authoring drafts, signed/verified typestates, and NIP-01 wire-neutral representations. | -| 6 | `radroots_event_codec` | `radroots_event_codec` | deterministic algorithm | Deterministic canonical encoding, decoding, ID/signature verification, contract validation, admission, and manifest generation for Radroots events. | -| 7 | `radroots_trade` | `radroots_trade` | domain algorithm | Trade validation, evidence models, deterministic reduction, conflict analysis, and side-effect-free workflow plans over the canonical event trade model. | -| 8 | `radroots_signing` | `radroots_signing` | host SPI | Object-safe author/signing SPI, actor provenance, authorization checks, requests, receipts, progress, capabilities, and normalized signing errors. | -| 9 | `radroots_transport` | `radroots_transport` | network SPI | Transport-neutral target identities, capability/status models, source and sink SPIs, delivery/fetch policies, bounded requests, provenance, and normalized outcomes. | -| 10 | `radroots_nostr` | `radroots_nostr` | protocol adapter | Portable conversion between Radroots native event/identity types and Nostr protocol types, typed NIP helpers, and concrete local signing adapters; no live relay client. | -| 11 | `radroots_nostr_connect` | `radroots_nostr_connect` | security protocol | Nostr Connect/NIP-46 URIs, methods, permissions, requests, responses, client/server state machines, timeout-independent protocol validation, and normalized errors. | -| 12 | `radroots_secrets` | `radroots_secrets` | security SPI | Secret references, provider and key-wrapping SPIs, versioned encrypted envelopes, zeroization-safe secret handling, and explicit memory/file/keyring adapters. | -| 13 | `radroots_storage` | `radroots_storage` | storage SPI | Backend-neutral canonical event, operation journal, outbox, transport evidence, projection, private-artifact metadata, backup, status, and atomic commit interfaces, plus an in-memory reference backend. | -| 14 | `radroots_storage_sqlite` | `radroots_storage_sqlite` | native storage backend | SQLite implementation of the storage SPIs with schema migration, WAL, locking, integrity, backup/restore, crash recovery, and encrypted private storage. | -| 15 | `radroots_transport_nostr` | `radroots_transport_nostr` | native network adapter | Concrete Nostr EventSource/EventSink implementation: relay URL policy, connection, NIP-42 authentication, bounded fetch pages, delivery, status, and relay-outcome normalization. | -| 16 | `radroots_sync` | `radroots_sync` | local-first orchestration | Shared pull, verification, canonical admission, duplicate handling, projection refresh, outbox signing/delivery, status, and retry-decision orchestration without owning scheduling. | -| 17 | `radroots_geonames` | `radroots_geonames` | concrete data provider | GeoNames asset specification, authenticated/integrity-checked acquisition, database lifecycle, and forward/reverse locality lookup using provider-owned types. | -| 18 | `radroots_sdk` | `radroots_sdk` | advanced front door | Host-neutral asynchronous client engine, product operations, capability reporting, explicit storage/signing/transport composition, diagnostics, backup/restore, and safe commit semantics. | -| 19 | `radroots` | `radroots` | ordinary-user front door | Canonical Rust onboarding package with curated modules, safe defaults, stable convenience builders, domain aggregation, examples, and primary documentation. | - -## 6. Detailed package specifications - - ### 1. `radroots_core` - - **Rust crate path:** `radroots_core` - **Tier:** foundation - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** no_std + alloc; std feature - **Direct intended consumers:** radroots_event, radroots_trade, radroots_sdk, radroots - - **Normative responsibility.** Foundational value objects and deterministic invariants: decimal, currency, money, percentage, quantity, units, and pricing. - - **Required Radroots dependencies:** None - **Optional Radroots dependencies:** None - **Default features:** `std`, `serde` - **Complete public feature vocabulary:** `std`, `serde` - - **Public modules** - - - `currency` -- `decimal` -- `money` -- `percent` -- `pricing` -- `quantity` -- `unit` - - **Permitted root exports:** `Currency`, `Decimal`, `Money`, `Percent`, `Quantity`, `QuantityPrice`, `Unit`, `Error` - - **Explicitly forbidden.** Identifiers, identities, event kinds, networking, persistence, clocks, filesystem paths, process behavior, or application configuration. - - - ### 2. `radroots_identity` - - **Rust crate path:** `radroots_identity` - **Tier:** foundation - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** no_std + alloc; std feature - **Direct intended consumers:** radroots_event, radroots_signing, radroots_transport, radroots_nostr, radroots_nostr_connect, radroots_sdk, services - - **Normative responsibility.** Public identity and account value types: canonical public keys, identity IDs, account IDs, public profiles, and usernames. - - **Required Radroots dependencies:** None - **Optional Radroots dependencies:** None - **Default features:** `std`, `serde` - **Complete public feature vocabulary:** `std`, `serde` - - **Public modules** - - - `account` -- `key` -- `profile` -- `username` - - **Permitted root exports:** `AccountId`, `IdentityId`, `PublicIdentity`, `PublicKey`, `Profile`, `Username`, `Error` - - **Explicitly forbidden.** Secret keys, key generation, NIP-49 encryption, keyrings, files, SQLite, runtime paths, upstream nostr::Event values, signer sessions, or host account selection. - - - ### 3. `radroots_blossom` - - **Rust crate path:** `radroots_blossom` - **Tier:** protocol primitive - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** no_std + alloc; std feature - **Direct intended consumers:** radroots_event, radroots_event_codec, radroots_nostr, media clients - - **Normative responsibility.** Portable Blossom protocol primitives: canonical blob URLs, hashes, media descriptors, byte-verification typestates, and authorization claims. - - **Required Radroots dependencies:** None - **Optional Radroots dependencies:** None - **Default features:** `std`, `serde` - **Complete public feature vocabulary:** `std`, `serde` - - **Public modules** - - - `authorization` -- `descriptor` -- `hash` -- `media_type` -- `url` - - **Permitted root exports:** `BlobUrl`, `Sha256`, `MediaType`, `BlobDescriptor`, `ByteVerifiedDescriptor`, `AuthorizationClaim`, `Error` - - **Explicitly forbidden.** HTTP clients, upload scheduling, cache management, filesystem traversal, application media policy, or global authentication state. - - - ### 4. `radroots_protocol` - - **Rust crate path:** `radroots_protocol` - **Tier:** versioned wire contract - **API maturity at first publish:** durable identity; independently versioned contract modules - **Platform contract:** no_std + alloc; std feature - **Direct intended consumers:** radroots_event, radroots_signing, radroots_transport, radroots_storage, radroots_sync, radroots_sdk, radrootsd, bindings - - **Normative responsibility.** Versioned cross-process and cross-language schemas, capability catalogs, operation descriptors, stable error reports, schema IDs, and structural validation. - - **Required Radroots dependencies:** None - **Optional Radroots dependencies:** None - **Default features:** `std`, `serde` - **Complete public feature vocabulary:** `std`, `serde` - - **Public modules** - - - `capability::v1` -- `error::v1` -- `event::v1` -- `runtime::v1` -- `radrootsd::transport_publish::v5` -- `schema` - - **Permitted root exports:** No broad root exports. - - **Explicitly forbidden.** Native clients, storage, network I/O, executor/runtime ownership, domain reducers, upstream dependency types, unversioned serialized DTOs, or package names containing protocol generations. - - - ### 5. `radroots_event` - - **Rust crate path:** `radroots_event` - **Tier:** domain protocol model - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** no_std + alloc; std feature - **Direct intended consumers:** radroots_event_codec, radroots_trade, radroots_signing, radroots_transport, radroots_storage, radroots_sync, radroots_nostr, radroots_sdk, indexers - - **Normative responsibility.** Canonical Radroots event-domain models, validated event identifiers, tags, event contracts, authoring drafts, signed/verified typestates, and NIP-01 wire-neutral representations. - - **Required Radroots dependencies:** `radroots_core`, `radroots_identity`, `radroots_blossom`, `radroots_protocol` - **Optional Radroots dependencies:** None - **Default features:** `std`, `serde` - **Complete public feature vocabulary:** `std`, `serde`, `knowledge` - - **Public modules** - - - `admission` -- `calendar` -- `contract` -- `draft` -- `envelope` -- `farm` -- `food` -- `id` -- `knowledge` -- `listing` -- `media` -- `post` -- `profile` -- `social` -- `tag` -- `trade` -- `wire` - - **Permitted root exports:** `Event`, `GenericEventDraft`, `SignedEvent`, `VerifiedEvent`, `EventId`, `EventKind`, `EventTag`, `Error` - - **Explicitly forbidden.** Live Nostr clients, relay pools, signing backends, SQLite, outbox claims, retry scheduling, application state, or duplicate trade/order identifier concepts. - - - ### 6. `radroots_event_codec` - - **Rust crate path:** `radroots_event_codec` - **Tier:** deterministic algorithm - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** no_std + alloc where selected features permit; std feature - **Direct intended consumers:** radroots_nostr, radroots_storage_sqlite, radroots_transport_nostr, radroots_sync, radroots_sdk, bindings - - **Normative responsibility.** Deterministic canonical encoding, decoding, ID/signature verification, contract validation, admission, and manifest generation for Radroots events. - - **Required Radroots dependencies:** `radroots_event`, `radroots_blossom`, `radroots_protocol` - **Optional Radroots dependencies:** None - **Default features:** `std`, `json` - **Complete public feature vocabulary:** `std`, `serde`, `json`, `knowledge`, `manifests` - - **Public modules** - - - `admission` -- `canonical` -- `decode` -- `encode` -- `manifest` -- `verify` - - **Permitted root exports:** `Codec`, `DecodeError`, `EncodeError`, `VerificationError` - - **Explicitly forbidden.** nostr-sdk clients, relay networking, persistence, background work, upstream client errors, or host configuration. - - - ### 7. `radroots_trade` - - **Rust crate path:** `radroots_trade` - **Tier:** domain algorithm - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** no_std + alloc for model/reducer; std feature - **Direct intended consumers:** radroots_storage, radroots_sync, radroots_sdk, RHI, applications - - **Normative responsibility.** Trade validation, evidence models, deterministic reduction, conflict analysis, and side-effect-free workflow plans over the canonical event trade model. - - **Required Radroots dependencies:** `radroots_core`, `radroots_identity`, `radroots_event` - **Optional Radroots dependencies:** None - **Default features:** `std`, `serde`, `json` - **Complete public feature vocabulary:** `std`, `serde`, `json` - - **Public modules** - - - `evidence` -- `model` -- `reducer` -- `validation` -- `workflow` - - **Permitted root exports:** `Projection`, `ReductionInput`, `ReducerIssue`, `WorkflowPlan`, `ValidationError`, `Error` - - **Explicitly forbidden.** A second TradeId definition, actor authorization, signers, event-store access, SQLx, filesystem state, transport delivery, outbox mutation, or process scheduling. - - - ### 8. `radroots_signing` - - **Rust crate path:** `radroots_signing` - **Tier:** host SPI - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** no_std + alloc core; std feature - **Direct intended consumers:** radroots_nostr, radroots_sync, radroots_sdk, CLI, Studio, FFI hosts - - **Normative responsibility.** Object-safe author/signing SPI, actor provenance, authorization checks, requests, receipts, progress, capabilities, and normalized signing errors. - - **Required Radroots dependencies:** `radroots_identity`, `radroots_event`, `radroots_protocol` - **Optional Radroots dependencies:** None - **Default features:** `std`, `serde` - **Complete public feature vocabulary:** `std`, `serde` - - **Public modules** - - - `actor` -- `capability` -- `error` -- `request` -- `receipt` -- `signer` -- `status` - - **Permitted root exports:** `Actor`, `Signer`, `SignRequest`, `SignReceipt`, `SignerStatus`, `Error` - - **Explicitly forbidden.** Raw secret-key ownership, keyrings, relay networking, NIP-46 session persistence, SQL, UI prompts, or executor creation. - - - ### 9. `radroots_transport` - - **Rust crate path:** `radroots_transport` - **Tier:** network SPI - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** no_std + alloc data model; std feature - **Direct intended consumers:** radroots_storage, radroots_transport_nostr, radroots_sync, radroots_sdk, services, future adapters - - **Normative responsibility.** Transport-neutral target identities, capability/status models, source and sink SPIs, delivery/fetch policies, bounded requests, provenance, and normalized outcomes. - - **Required Radroots dependencies:** `radroots_identity`, `radroots_event`, `radroots_protocol` - **Optional Radroots dependencies:** None - **Default features:** `std`, `serde` - **Complete public feature vocabulary:** `std`, `serde` - - **Public modules** - - - `capability` -- `endpoint` -- `error` -- `outcome` -- `policy` -- `sink` -- `source` -- `target` - - **Permitted root exports:** `TransportId`, `Target`, `TargetSet`, `EventSource`, `EventSink`, `DeliveryRequest`, `DeliveryReceipt`, `FetchRequest`, `FetchPage`, `Error` - - **Explicitly forbidden.** Closed enums that prevent new transports, Reticulum-specific constants, Nostr URLs at the generic root, storage/outbox access, retries, scheduler ownership, or silent fallback. - - - ### 10. `radroots_nostr` - - **Rust crate path:** `radroots_nostr` - **Tier:** protocol adapter - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** no_std + alloc for conversion surface; std feature - **Direct intended consumers:** radroots_nostr_connect, radroots_transport_nostr, radroots_sdk, Myc, radrootsd - - **Normative responsibility.** Portable conversion between Radroots native event/identity types and Nostr protocol types, typed NIP helpers, and concrete local signing adapters; no live relay client. - - **Required Radroots dependencies:** `radroots_identity`, `radroots_event`, `radroots_event_codec` - **Optional Radroots dependencies:** `radroots_signing`, `radroots_blossom` - **Default features:** `std`, `events` - **Complete public feature vocabulary:** `std`, `events`, `signing`, `nip17`, `blossom` - - **Public modules** - - - `blossom` -- `event` -- `filter` -- `key` -- `nip17` -- `signing` -- `tag` - - **Permitted root exports:** `Error` - - **Explicitly forbidden.** nostr-sdk relay pools, reqwest clients, runtime ownership, broad aliases of upstream nostr types at the root, account persistence, or outbox orchestration. - - - ### 11. `radroots_nostr_connect` - - **Rust crate path:** `radroots_nostr_connect` - **Tier:** security protocol - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** std for v1; protocol data kept portable - **Direct intended consumers:** radroots_sdk, Myc, remote signer tooling - - **Normative responsibility.** Nostr Connect/NIP-46 URIs, methods, permissions, requests, responses, client/server state machines, timeout-independent protocol validation, and normalized errors. - - **Required Radroots dependencies:** `radroots_identity`, `radroots_event`, `radroots_nostr`, `radroots_protocol` - **Optional Radroots dependencies:** None - **Default features:** `serde` - **Complete public feature vocabulary:** `serde` - - **Public modules** - - - `client` -- `error` -- `message` -- `method` -- `permission` -- `server` -- `uri` - - **Permitted root exports:** `Client`, `Server`, `Method`, `Permission`, `Request`, `Response`, `BunkerUri`, `ClientUri`, `Error` - - **Explicitly forbidden.** Relay-pool implementation, secret persistence, approval UI, global sessions, Tokio runtime ownership, or Myc-specific service storage. - - - ### 12. `radroots_secrets` - - **Rust crate path:** `radroots_secrets` - **Tier:** security SPI - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** no_std + alloc core; std adapters - **Direct intended consumers:** radroots_storage_sqlite, radroots_sdk, signing hosts, services - - **Normative responsibility.** Secret references, provider and key-wrapping SPIs, versioned encrypted envelopes, zeroization-safe secret handling, and explicit memory/file/keyring adapters. - - **Required Radroots dependencies:** None - **Optional Radroots dependencies:** None - **Default features:** `std`, `serde` - **Complete public feature vocabulary:** `std`, `serde`, `memory`, `file`, `keyring` - - **Public modules** - - - `envelope` -- `error` -- `id` -- `provider` -- `wrapping` -- `memory` -- `file` -- `keyring` - - **Permitted root exports:** `SecretId`, `SecretRef`, `SecretProvider`, `KeyWrapping`, `EncryptedEnvelope`, `Error` - - **Explicitly forbidden.** Public secret bytes, Clone/Debug/Serialize for secret-bearing values, identity profiles, domain tables, arbitrary key/value storage, hidden key generation, or process-global vaults. - - - ### 13. `radroots_storage` - - **Rust crate path:** `radroots_storage` - **Tier:** storage SPI - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** std for v1 - **Direct intended consumers:** radroots_storage_sqlite, radroots_sync, radroots_sdk, indexers, tests, future backends - - **Normative responsibility.** Backend-neutral canonical event, operation journal, outbox, transport evidence, projection, private-artifact metadata, backup, status, and atomic commit interfaces, plus an in-memory reference backend. - - **Required Radroots dependencies:** `radroots_event`, `radroots_trade`, `radroots_transport`, `radroots_protocol` - **Optional Radroots dependencies:** None - **Default features:** `memory`, `serde` - **Complete public feature vocabulary:** `memory`, `serde` - - **Public modules** - - - `atomic` -- `backup` -- `event` -- `journal` -- `memory` -- `outbox` -- `private_artifact` -- `projection` -- `status` - - **Permitted root exports:** `Storage`, `EventStore`, `Journal`, `Outbox`, `ProjectionStore`, `BackupSource`, `StorageStatus`, `Error` - - **Explicitly forbidden.** SQL text, SQLx pools or transactions, filesystem paths, application UI state, concrete retry loops, Nostr clients, or unconstrained raw key/value escape hatches. - - - ### 14. `radroots_storage_sqlite` - - **Rust crate path:** `radroots_storage_sqlite` - **Tier:** native storage backend - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** std-only native backend - **Direct intended consumers:** radroots_sdk, CLI, Studio, mobile/FFI - - **Normative responsibility.** SQLite implementation of the storage SPIs with schema migration, WAL, locking, integrity, backup/restore, crash recovery, and encrypted private storage. - - **Required Radroots dependencies:** `radroots_storage`, `radroots_event_codec`, `radroots_secrets` - **Optional Radroots dependencies:** None - **Default features:** None - **Complete public feature vocabulary:** None - - **Public modules** - - - `backup` -- `config` -- `integrity` -- `lock` -- `migration` -- `open` -- `status` - - **Permitted root exports:** `SqliteStorage`, `OpenOptions`, `OpenMode`, `Paths`, `Error` - - **Explicitly forbidden.** Public SqlitePool/Connection/Transaction handles, caller-supplied arbitrary SQL, Studio state, global connection pools, runtime installation, or silent schema downgrade. - - - ### 15. `radroots_transport_nostr` - - **Rust crate path:** `radroots_transport_nostr` - **Tier:** native network adapter - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** std-only; Tokio implementation detail in v1 - **Direct intended consumers:** radroots_sdk, CLI, radrootsd, advanced hosts - - **Normative responsibility.** Concrete Nostr EventSource/EventSink implementation: relay URL policy, connection, NIP-42 authentication, bounded fetch pages, delivery, status, and relay-outcome normalization. - - **Required Radroots dependencies:** `radroots_transport`, `radroots_nostr`, `radroots_event_codec`, `radroots_protocol` - **Optional Radroots dependencies:** None - **Default features:** None - **Complete public feature vocabulary:** None - - **Public modules** - - - `auth` -- `client` -- `relay` -- `sink` -- `source` -- `status` - - **Permitted root exports:** `NostrTransport`, `Config`, `RelayUrl`, `RelayUrlPolicy`, `Error` - - **Explicitly forbidden.** Event-store ingestion, outbox claiming, retry scheduling, projection refresh, global relay clients, direct SQL, or transport fallback. - - - ### 16. `radroots_sync` - - **Rust crate path:** `radroots_sync` - **Tier:** local-first orchestration - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** std-only in v1; executor-neutral public API - **Direct intended consumers:** radroots_sdk, CLI, Studio, mobile/FFI, advanced hosts - - **Normative responsibility.** Shared pull, verification, canonical admission, duplicate handling, projection refresh, outbox signing/delivery, status, and retry-decision orchestration without owning scheduling. - - **Required Radroots dependencies:** `radroots_event`, `radroots_event_codec`, `radroots_signing`, `radroots_transport`, `radroots_storage`, `radroots_trade`, `radroots_protocol` - **Optional Radroots dependencies:** None - **Default features:** `serde` - **Complete public feature vocabulary:** `serde` - - **Public modules** - - - `ingest` -- `policy` -- `projection` -- `pull` -- `push` -- `status` - - **Permitted root exports:** `Engine`, `PullRequest`, `PullReceipt`, `PushRequest`, `PushPreparation`, `PushStatus`, `SigningRunReceipt`, `AdmissionRunReceipt`, `DeliveryExecutionReceipt`, `SyncStatus`, `Error` - - **Explicitly forbidden.** Creating an executor, spawning hidden workers, installing timers globally, owning process lifecycle, storing UI state, or transport-specific branches outside adapters. - - - ### 17. `radroots_geonames` - - **Rust crate path:** `radroots_geonames` - **Tier:** concrete data provider - **API maturity at first publish:** durable identity; pre-1.0 API - **Platform contract:** std-only - **Direct intended consumers:** radroots_sdk, CLI, geocoding applications - - **Normative responsibility.** GeoNames asset specification, authenticated/integrity-checked acquisition, database lifecycle, and forward/reverse locality lookup using provider-owned types. - - **Required Radroots dependencies:** None - **Optional Radroots dependencies:** None - **Default features:** None - **Complete public feature vocabulary:** None - - **Public modules** - - - `asset` -- `database` -- `download` -- `model` -- `query` - - **Permitted root exports:** `Geocoder`, `AssetSpec`, `AssetStatus`, `Query`, `Candidate`, `Point`, `Error` - - **Explicitly forbidden.** Generic multi-provider abstraction before a second provider exists, runtime-path policy, hidden downloads, SDK configuration types, SQLx/reqwest types in the public API, or test-fixture features. - - - ### 18. `radroots_sdk` - - **Rust crate path:** `radroots_sdk` - **Tier:** advanced front door - **API maturity at first publish:** durable identity; lockstep pre-1.0 API with radroots - **Platform contract:** std-only native engine - **Direct intended consumers:** CLI, Studio, FFI/mobile, advanced native applications - - **Normative responsibility.** Host-neutral asynchronous client engine, product operations, capability reporting, explicit storage/signing/transport composition, diagnostics, backup/restore, and safe commit semantics. - - **Required Radroots dependencies:** `radroots_core`, `radroots_identity`, `radroots_protocol`, `radroots_event`, `radroots_event_codec`, `radroots_trade`, `radroots_signing`, `radroots_transport`, `radroots_storage` - **Optional Radroots dependencies:** `radroots_secrets`, `radroots_storage_sqlite`, `radroots_nostr`, `radroots_nostr_connect`, `radroots_transport_nostr`, `radroots_sync`, `radroots_geonames` - **Default features:** `memory` - **Complete public feature vocabulary:** `memory`, `sqlite`, `sync`, `nostr`, `nip46`, `local-signing`, `radrootsd`, `geonames`, `knowledge`, `native`, `full` - - **Public modules** - - - `capability` -- `client` -- `diagnostics` -- `error` -- `farm` -- `listing` -- `signing` -- `storage` -- `sync` -- `trade` -- `transport` - - **Permitted root exports:** `Client`, `ClientBuilder`, `Error`, `Result` - - **Explicitly forbidden.** Global runtimes or subscribers, hidden workers, process signals, CLI parsing, UI state, Studio databases, raw SQLx/upstream client types, broad wildcard reexports, or a nominal no_std claim. - - - ### 19. `radroots` - - **Rust crate path:** `radroots` - **Tier:** ordinary-user front door - **API maturity at first publish:** durable identity; lockstep pre-1.0 API with radroots_sdk - **Platform contract:** std-only - **Direct intended consumers:** ordinary Rust applications, examples, documentation - - **Normative responsibility.** Canonical Rust onboarding package with curated modules, safe defaults, stable convenience builders, domain aggregation, examples, and primary documentation. - - **Required Radroots dependencies:** `radroots_sdk`, `radroots_core`, `radroots_identity`, `radroots_event`, `radroots_trade`, `radroots_transport` - **Optional Radroots dependencies:** None - **Default features:** `client` - **Complete public feature vocabulary:** `client`, `native`, `nostr`, `nip46`, `radrootsd`, `geonames`, `knowledge`, `full` - - **Public modules** - - - `client` -- `event` -- `farm` -- `identity` -- `knowledge` -- `listing` -- `signing` -- `storage` -- `sync` -- `trade` -- `transport` - - **Permitted root exports:** `Client`, `ClientBuilder`, `Error`, `Result` - - **Explicitly forbidden.** A public radroots::sdk namespace, wildcard reexport of radroots_sdk, duplicate engine implementation, CLI binary, hidden network/filesystem/keychain side effects, or exposure of every lower-crate symbol. - - -## 7. Normative dependency graph - -### 7.1 Direct Radroots edges - -| Dependency | Dependent | Edge | -|---|---|---| -| `radroots_blossom` | `radroots_event` | required | -| `radroots_core` | `radroots_event` | required | -| `radroots_identity` | `radroots_event` | required | -| `radroots_protocol` | `radroots_event` | required | -| `radroots_blossom` | `radroots_event_codec` | required | -| `radroots_event` | `radroots_event_codec` | required | -| `radroots_protocol` | `radroots_event_codec` | required | -| `radroots_core` | `radroots_trade` | required | -| `radroots_event` | `radroots_trade` | required | -| `radroots_identity` | `radroots_trade` | required | -| `radroots_event` | `radroots_signing` | required | -| `radroots_identity` | `radroots_signing` | required | -| `radroots_protocol` | `radroots_signing` | required | -| `radroots_event` | `radroots_transport` | required | -| `radroots_identity` | `radroots_transport` | required | -| `radroots_protocol` | `radroots_transport` | required | -| `radroots_blossom` | `radroots_nostr` | optional | -| `radroots_signing` | `radroots_nostr` | optional | -| `radroots_event` | `radroots_nostr` | required | -| `radroots_event_codec` | `radroots_nostr` | required | -| `radroots_identity` | `radroots_nostr` | required | -| `radroots_event` | `radroots_nostr_connect` | required | -| `radroots_identity` | `radroots_nostr_connect` | required | -| `radroots_nostr` | `radroots_nostr_connect` | required | -| `radroots_protocol` | `radroots_nostr_connect` | required | -| `radroots_event` | `radroots_storage` | required | -| `radroots_protocol` | `radroots_storage` | required | -| `radroots_trade` | `radroots_storage` | required | -| `radroots_transport` | `radroots_storage` | required | -| `radroots_event_codec` | `radroots_storage_sqlite` | required | -| `radroots_secrets` | `radroots_storage_sqlite` | required | -| `radroots_storage` | `radroots_storage_sqlite` | required | -| `radroots_event_codec` | `radroots_transport_nostr` | required | -| `radroots_nostr` | `radroots_transport_nostr` | required | -| `radroots_protocol` | `radroots_transport_nostr` | required | -| `radroots_transport` | `radroots_transport_nostr` | required | -| `radroots_event` | `radroots_sync` | required | -| `radroots_event_codec` | `radroots_sync` | required | -| `radroots_protocol` | `radroots_sync` | required | -| `radroots_signing` | `radroots_sync` | required | -| `radroots_storage` | `radroots_sync` | required | -| `radroots_trade` | `radroots_sync` | required | -| `radroots_transport` | `radroots_sync` | required | -| `radroots_geonames` | `radroots_sdk` | optional | -| `radroots_nostr` | `radroots_sdk` | optional | -| `radroots_nostr_connect` | `radroots_sdk` | optional | -| `radroots_secrets` | `radroots_sdk` | optional | -| `radroots_storage_sqlite` | `radroots_sdk` | optional | -| `radroots_sync` | `radroots_sdk` | optional | -| `radroots_transport_nostr` | `radroots_sdk` | optional | -| `radroots_core` | `radroots_sdk` | required | -| `radroots_event` | `radroots_sdk` | required | -| `radroots_event_codec` | `radroots_sdk` | required | -| `radroots_identity` | `radroots_sdk` | required | -| `radroots_protocol` | `radroots_sdk` | required | -| `radroots_signing` | `radroots_sdk` | required | -| `radroots_storage` | `radroots_sdk` | required | -| `radroots_trade` | `radroots_sdk` | required | -| `radroots_transport` | `radroots_sdk` | required | -| `radroots_core` | `radroots` | required | -| `radroots_event` | `radroots` | required | -| `radroots_identity` | `radroots` | required | -| `radroots_sdk` | `radroots` | required | -| `radroots_trade` | `radroots` | required | -| `radroots_transport` | `radroots` | required | - -### 7.2 Architectural graph - -```text -radroots_core radroots_identity radroots_blossom - \ | / - \ | / - +---------- radroots_protocol --------+ - | - radroots_event - / | \ - / | \ - radroots_event_codec | radroots_trade - | - +----------+-----------+ - | | | - radroots_signing radroots_transport radroots_storage - | | | - | radroots_nostr +--> radroots_storage_sqlite - | | - | radroots_nostr_connect - | | - +---- radroots_transport_nostr - | - radroots_sync - -radroots_geonames -------------------------+ - | -all selected lower packages ----------> radroots_sdk ---> radroots -``` - -This diagram is explanatory. The release tool MUST use Cargo-resolved metadata as authority. - -## 8. Type ownership and canonical paths - -| Concept | Canonical owning crate | Rule | -|---|---|---| -| Decimal, money, quantity, unit, pricing | `radroots_core` | No duplicate wrapper in SDK or trade. | -| Public key, identity ID, account ID, username | `radroots_identity` | Secret material is forbidden here. | -| Event ID, event signature, coordinate, D-tag, event kind | `radroots_event` | Store bytes/newtypes, not unvalidated Strings. | -| Event contract author role | `radroots_event::contract` | This is an event-authoring rule, not an account property. | -| Actor provenance and author context | `radroots_signing` | Combines identity with event author roles at the signing boundary. | -| Canonical protocol TradeId/CandidateId/MutationId | `radroots_event::trade` | Exactly one definition. | -| Human/business OrderId | `radroots_trade` | MUST NOT be aliased or wrapped as TradeId. | -| Runtime/wire DTO generations | `radroots_protocol` | Native packages convert at boundaries. | -| Secret references and encrypted envelopes | `radroots_secrets` | No domain-specific tables. | -| Transport ID, target, capability, outcome | `radroots_transport` | TransportId is extensible, not a closed enum. | -| Native event/outbox/journal/projection storage | `radroots_storage` | Backend-neutral interfaces only. | -| SQLite schema and connection behavior | `radroots_storage_sqlite` | SQLx remains private. | -| Relay URL and Nostr network status | `radroots_transport_nostr` | Protocol conversions remain in `radroots_nostr`. | -| Pull/push/ingest/projection orchestration | `radroots_sync` | No host scheduling. | -| Client-level requests, plans, receipts, diagnostics | `radroots_sdk` | Use lower canonical types rather than duplicate wrappers. | - -## 9. Rust API and naming law - -### 9.1 Packages, crates, modules, and types - -- Cargo package names MUST use lowercase snake case: `radroots_event_codec`. -- Rust crate paths MUST use the same lowercase snake case: `radroots_event_codec`. -- Modules MUST use singular snake-case nouns unless the concept is inherently plural. -- Types and traits MUST use `UpperCamelCase`; functions and methods MUST use `snake_case`. -- Items MUST NOT repeat their crate or module name: - - `radroots_core::Money`, not `RadrootsCoreMoney`. - - `radroots_sdk::Error`, not `RadrootsSdkError`. - - `radroots_transport::Target`, not `RadrootsTransportTarget`. -- Protocol schema IDs retain the `radroots.*` namespace. -- Generated Swift/Kotlin types MAY retain a `Radroots` prefix where the target language lacks module-level namespacing. - -### 9.2 Public surface discipline - -- Crate roots MUST be small. -- Wildcard reexports and `pub use models::*` are forbidden. -- Lower crates MUST NOT publish a broad `prelude` in V1. -- Every native public struct MUST have private fields unless it is an intentionally passive versioned DTO in `radroots_protocol`. -- Evolvable enums and reports SHOULD be `#[non_exhaustive]`. -- Semantic IDs MUST NOT implement `Deref<Target = str>`. -- IDs SHOULD store canonical bytes or validated compact representations; string encoding belongs at boundaries. -- Use `FromStr`, `TryFrom`, `AsRef`, `Display`, and explicit `into_string`/`to_hex` methods. -- No root-level aliases to upstream `nostr`, `nostr_sdk`, `sqlx`, `reqwest`, `tokio`, or keyring types. -- Public functions MUST NOT panic for untrusted input. -- Builders/plans/receipts SHOULD be `#[must_use]`. -- Constructors with more than three independent options SHOULD use builders or option structs. -- Empty request structs are forbidden when an idiomatic no-argument method conveys the same operation. - -### 9.3 Trait classification - -Every public trait MUST be marked in documentation as one of: - -1. **Host SPI** — downstream implementation is supported. -2. **Sealed extension** — downstream calls are supported; implementation is not. -3. **Internal** — not public. - -Host SPIs MUST: - -- be dyn-compatible where runtime injection is needed; -- be `Send + Sync` for the native SDK; -- return boxed futures or equivalent dyn-compatible futures; -- define cancellation and deadline behavior; -- define error normalization; -- avoid associated types that leak backend implementations; -- not expose private or third-party implementation types. - -The Rust SPI is native. Browser and generated-language transports use `radroots_protocol` DTOs and language-native interfaces rather than weakening native `Send + Sync` guarantees. - -## 10. Feature law - -1. Features MUST be additive and safe under unification. -2. Features MUST describe user-visible capabilities, not implementation assembly. -3. Optional dependencies MUST be referenced with `dep:` so implementation names do not become accidental features. -4. Default features MUST remain safe for the life of a compatible release line. -5. Enabling a feature MUST NOT itself: - - access a network; - - create files; - - read a keyring; - - generate keys; - - contact a daemon; - - install logging; - - start workers. -6. Mutually exclusive backend features are forbidden; incompatible backends belong in separate packages. -7. Public crates MUST NOT expose `dto-bindgen`, fixtures, coverage, codegen, migration-forge, or internal runtime features. -8. `std` is the only valid feature name for standard-library support. -9. Public feature removal is a breaking change. -10. `--all-features` MUST build on every declared target for which the package claims support. - -### 10.1 `radroots_sdk` - -```toml -[features] -default = ["memory"] - -# Safe, in-process storage; no files or network. -memory = ["radroots_storage/memory"] - -# Explicit native capabilities. -sqlite = ["dep:radroots_storage_sqlite"] -sync = ["dep:radroots_sync"] -nostr = [ - "sync", - "dep:radroots_nostr", - "dep:radroots_transport_nostr", -] -nip46 = [ - "nostr", - "dep:radroots_nostr_connect", -] -local-signing = [ - "dep:radroots_secrets", - "radroots_nostr/signing", -] -radrootsd = [ - "sync", - "dep:reqwest", -] -geonames = ["dep:radroots_geonames"] -knowledge = [ - "radroots_event/knowledge", - "radroots_event_codec/knowledge", -] - -native = ["sqlite", "sync", "local-signing"] -full = [ - "native", - "nostr", - "nip46", - "radrootsd", - "geonames", - "knowledge", -] -``` - -### 10.2 `radroots` - -```toml -[features] -default = ["client"] - -client = ["radroots_sdk/default"] -native = ["client", "radroots_sdk/native"] -nostr = ["client", "radroots_sdk/nostr"] -nip46 = ["nostr", "radroots_sdk/nip46"] -radrootsd = ["client", "radroots_sdk/radrootsd"] -geonames = ["client", "radroots_sdk/geonames"] -knowledge = ["client", "radroots_sdk/knowledge"] -full = ["radroots_sdk/full"] -``` - -Reticulum, mesh, Simplex, NostrDB, replica, and SP1 feature names MUST NOT appear in published V1 manifests. - -## 11. Network and transport SPI - -### 11.1 Separate source and sink contracts - -One monolithic transport trait is rejected. The final SPI provides independent contracts: - -```rust -pub trait EventSource: Send + Sync { - fn status(&self) -> BoxFuture<'_, Result<SourceStatus, Error>>; - fn fetch(&self, request: FetchRequest) - -> BoxFuture<'_, Result<FetchPage, Error>>; -} - -pub trait EventSink: Send + Sync { - fn status(&self) -> BoxFuture<'_, Result<SinkStatus, Error>>; - fn deliver(&self, request: DeliveryRequest) - -> BoxFuture<'_, Result<DeliveryReceipt, Error>>; -} -``` - -A transport MAY implement either or both. - -### 11.2 Extensible transport identity - -`TransportId` MUST be a validated newtype with built-in constants such as `NOSTR`, `RETICULUM`, `LOCAL`, and `RADROOTSD`. It MUST NOT be a closed enum that forces a breaking release for every new transport. - -### 11.3 Bounded and explicit operation semantics - -- Fetch is paginated or streaming and always bounded. -- Every request carries an operation/request ID. -- Deadlines are explicit; no adapter owns a global timeout. -- Delivery satisfaction policy is explicit. -- Partial success is represented per target. -- Retryability is data, not an implicit loop. -- Authentication challenges are explicit outcomes. -- No transport silently falls back to another. -- Provenance records source, endpoint fingerprint, observed time, and adapter. -- Adapter errors are normalized while preserving a non-secret source chain. -- Target URIs and relay URLs are validated before connection. -- SSRF-sensitive schemes and private-network policies are explicit. -- TLS verification is enabled by default and cannot be silently disabled. -- Payload, target, tag, response, and page limits are constants covered by tests. - -## 12. Storage architecture - -### 12.1 Logical ownership - -`radroots_storage` owns interfaces for: - -- canonical event admission and queries; -- operation journal; -- outbox and delivery evidence; -- projection checkpoints and invalidation; -- private-artifact metadata; -- backup/restore contracts; -- storage status and integrity; -- atomic workflow commits. - -`radroots_storage_sqlite` implements them. - -### 12.2 Native layout - -The SQLite V1 layout SHALL use: - -```text -runtime.sqlite - canonical event source - event admission/visibility - operation journal - outbox and delivery evidence - projection metadata and checkpoints - -private.sqlite - encrypted signing references - private farm coordinates - private trade artifacts - NIP-46 private session material where host policy permits - -host-owned databases - UI preferences - Studio state - application presentation caches -``` - -`studio.sqlite` is removed from the SDK. - -### 12.3 Correctness requirements - -- One runtime database permits atomic event/journal/outbox transitions. -- Cross-database workflows use explicit staged commits and recovery markers. -- Public API exposes high-level atomic operations, not raw SQL transactions. -- No public `pool()` escape hatch. -- Open modes: read-only, read-write-existing, create. -- Explicit asynchronous `close()` and shutdown status. -- Advisory/process locking is mandatory for writable file stores. -- Concurrent readers are supported; writer policy is explicit. -- Migrations are transactional and forward-only by default. -- Downgrade requires an explicit offline export/import path. -- WAL and busy timeout are configured and reported. -- Backup uses a versioned manifest, per-member hashes, path/symlink validation, and atomic finalization. -- Restore uses staging, verification, and atomic replacement. -- Crash/failure injection covers every durable commit point. -- Secret material inclusion in backup is explicit and policy-controlled. - -## 13. Identity, signing, and secret boundaries - -### 13.1 Identity - -`radroots_identity` contains no private key and no upstream Nostr event object. `PublicKey` is a validated canonical Radroots author key. NIP-19/npub conversion lives in `radroots_nostr`. - -### 13.2 Signing - -`radroots_signing::Signer` signs an immutable canonical `AuthoredEventPlan`. The signing layer: - -- authorizes actor role and expected public key before invoking a signer; -- verifies the signer result matches the exact draft; -- supports local and remote implementations; -- exposes capability and progress data; -- defines cancellation before and after remote request publication; -- never logs or serializes private material. - -Concrete local Nostr signing lives in `radroots_nostr`; NIP-46 protocol state lives in `radroots_nostr_connect`; host composition lives in `radroots_sdk`. - -### 13.3 Secrets - -Secret-bearing values: - -- MUST NOT implement ordinary `Debug`; -- MUST NOT implement `Serialize`; -- MUST NOT implement `Clone` unless the clone is a reference/handle; -- MUST zeroize owned plaintext where technically possible; -- MUST expose only redacted diagnostics; -- MUST use typed `SecretRef` handles across storage boundaries. - -## 14. Event and trade model corrections - -1. `radroots_event` remains the canonical owner of event-bound identifiers and trade wire identities. -2. `radroots_trade` MUST delete its conflicting `TradeId(OrderId)` definition. -3. `TradeId` and `OrderId` MUST remain semantically distinct. -4. `radroots_trade` consumes canonical event trade models and owns reducers, evidence, validation, and workflow plans. -5. Trade MUST NOT depend on authority, storage, SQLx, Nostr clients, or transports. -6. Event codec MUST own wire conversion; trade reducers operate on validated native inputs. -7. Typed authoring policy MUST reject reserved event kinds before any signer is consulted. -8. Native event typestates distinguish raw, ID-verified, signature-verified, contract-validated, admitted, and visible events. - -## 15. Error model - -Each crate owns a native `Error` with preserved sources. `radroots_protocol::error::v1::ErrorReport` is the serialized boundary. - -One generated authority MUST define: - -- stable code; -- class; -- retryability; -- recovery actions; -- capability ID; -- operation ID; -- safe structured details; -- redaction behavior. - -Hand-maintained duplicate match tables and a separate unsynchronized catalog are forbidden. - -Third-party errors MUST NOT appear as public variants. Sensitive source messages MUST be redacted before entering a protocol report or tracing field. - -## 16. SDK and façade rules - -### 16.1 `radroots_sdk` - -- `Client` is `Clone + Send + Sync`. -- The host owns the executor and scheduling. -- The SDK starts no unbounded or hidden worker. -- Background processing requires an explicit returned worker/driver handle. -- Dropping a future before/after commit has documented effects. -- `Client::close()` is explicit and asynchronous where storage is active. -- Product writes retain prepare → authorize/sign → durable enqueue → optional deliver semantics. -- Lower canonical types are reused rather than copied into `Sdk*` wrappers. -- No `RadrootsSdk*` prefixes inside the crate. -- Root exports are only `Client`, `ClientBuilder`, `Error`, and `Result`. - -### 16.2 `radroots` - -- Primary documentation and examples use `radroots`. -- The façade adds curated modules, convenience constructors, safe defaults, and domain aggregation. -- Advanced hosts use `radroots_sdk` directly. -- There is no `radroots::sdk` public namespace. -- The façade does not expose implementation crates accidentally. - -## 17. Public code generation and cross-language policy - -- Rust binding, WASM, UniFFI, Swift, Kotlin, TypeScript, and codegen crates remain `publish = false`. -- Public runtime crates have no codegen feature or codegen dependency. -- Versioned language DTOs derive from `radroots_protocol`. -- Deterministic event algorithms derive from `radroots_event_codec`. -- Generated artifacts are checked in or generated reproducibly and carry source hashes. -- Rust native module structure is not mechanically mirrored into other languages. -- Language runtimes own networking, scheduling, keychain prompts, and UI lifecycle where appropriate. - -## 18. Private and deferred packages - -The following remain private in release V1: - -```text -replica schema/store/sync family -Reticulum adapter -mesh protocol/agent/client family -SimpleX protocol/crypto/store/runtime family -NostrDB adapter -SP1 guest/host -runtime paths/manager/distribution helpers -radrootsd SDK adapter implementation -FFI, binding, WASM, and generated-package build crates -fixtures, conformance runners, fuzz targets, and xtask -``` - -Private preview code remains tested. It may become public only after passing the new-package admission rule. - -## 19. New-package admission rule - -After release V1, a new `radroots_*` package requires an ADR proving: - -1. a durable domain/protocol/SPI/backend boundary; -2. at least two meaningful direct consumers, or one unavoidable platform/backend isolation boundary; -3. an independently supportable SemVer surface; -4. a publishable resolved dependency closure; -5. a name expected to survive five years; -6. why a module or feature is insufficient; -7. documentation, conformance tests, ownership, security review, and release automation. - -Names containing `common`, `utils`, `types`, `models`, `preview`, `unstable`, `v1`, `v2`, `manager`, or a second `core` are presumptively rejected. - -## 20. Current-to-target migration map - -| Current package/family | Final owner | Required action | -|---|---|---| -| `radroots_core` | `radroots_core` | Retain the snake-case package name and remove RadrootsCore type prefixes. | -| `radroots_identity` | `radroots_identity + radroots_signing + radroots_secrets + radroots_storage` | Keep only public identity/account concepts in identity; move secrets, signers, and persistence. | -| `radroots_blossom` | `radroots_blossom` | Retain portable protocol primitives. | -| `radroots_protocol_contract_v1` | `radroots_protocol::event::v1 / capability::v1` | Retired, non-publishable compatibility package after merge; final removal at Step 270 after the CLI cutover. | -| `radroots_runtime_contract_v1` | `radroots_protocol::runtime::v1` | Retired, non-publishable SDK compatibility package after merge; final removal at Step 270 after the CLI cutover. | -| `radroots_transport_publish_protocol` | `radroots_protocol::radrootsd::transport_publish::v5` | Retired, non-publishable compatibility package after merge; final removal at Step 286 after the radrootsd cutover. | -| `radroots_event` | `radroots_event` | Retain singular package; narrow to canonical event-domain model. | -| `radroots_event_codec` | `radroots_event_codec` | Retain; remove live Nostr/upstream client responsibilities. | -| `radroots_event_index` | `radroots_storage::projection/index` | Merge; current checkpoint/manifest model is not an independent indexing engine. | -| `radroots_trade` | `radroots_trade` | Retain algorithms; remove authority, storage, SQL, transport, and duplicate TradeId. | -| `radroots_authority` | `radroots_identity + radroots_event::contract + radroots_signing` | Split account/public-key ownership, author-role contracts, and signing/authorization SPI. | -| `radroots_transport` | `radroots_transport` | Retain and redesign as extensible source/sink SPI. | -| `radroots_transport_nostr` | `radroots_transport_nostr` | Retain adapter; remove storage and sync orchestration. | -| `radroots_transport_reticulum` | `private preview` | Withhold until a real adapter passes the transport conformance suite. | -| `radroots_nostr` | `radroots_nostr` | Retain protocol conversion; remove live relay client and broad upstream aliases. | -| `radroots_nostr_connect` | `radroots_nostr_connect` | Retain as independent bidirectional NIP-46 protocol boundary. | -| `radroots_nostr_accounts` | `radroots_identity + radroots_secrets + radroots_storage + radroots_sdk` | Split mixed account, vault, persistence, and manager responsibilities. | -| `radroots_nostr_signer` | `radroots_signing + radroots_nostr_connect + Myc-private state` | Do not publish current service-state package. | -| `radroots_nostr_runtime` | `radroots_transport_nostr + radroots_sync` | Merge live relay runtime into adapter/orchestration layers. | -| `radroots_nostrdb` | `private; possible future radroots_storage_nostrdb` | Withhold until the storage SPI and external consumers justify a backend package. | -| `radroots_event_store` | `radroots_storage + radroots_storage_sqlite` | Split backend-neutral contracts from SQLite implementation. | -| `radroots_outbox` | `radroots_storage + radroots_storage_sqlite` | Merge as one persistence capability with atomic operation commits. | -| `radroots_runtime_store` | `radroots_storage or host-private state` | Retire broad name and classify each table by owner. | -| `radroots_sql_core` | `radroots_storage_sqlite private internals` | Remove raw SQL/JSON executor from public API. | -| `radroots_secret_vault` | `radroots_secrets` | Merge provider/wrapping SPI. | -| `radroots_protected_store` | `radroots_secrets` | Merge encrypted-envelope semantics. | -| `radroots_geocoder` | `radroots_geonames` | Rename to the actual concrete provider. | -| `radroots_runtime` | `radroots_sync + radroots_storage + host-private tooling` | Dismantle mixed config/signals/logging/queue/transport package. | -| `radroots_log` | `no replacement package` | Libraries emit tracing; hosts install subscribers. | -| `radroots_net` | `radroots_transport + radroots_sync + radroots_sdk` | Retire broad duplicated network/runtime package. | -| `radroots_runtime_paths` | `private host utility` | Do not place host path policy in the SDK registry closure. | -| `radroots_runtime_manager` | `private host tooling` | Runtime installation and process lifecycle remain host-owned. | -| `radroots_runtime_distribution` | `private host tooling` | Artifact distribution is not a public SDK dependency. | -| `radroots_replica_*` | `private/deferred` | Preserve and redesign; no public names until generated CRUD/raw SQL surfaces are replaced. | -| `radroots_mesh_*` | `private preview` | Preserve; publish only a real adapter or protocol with external consumers. | -| `radroots_simplex_*` | `private preview` | Preserve internal decomposition; no crates.io commitment in release v1. | -| `radroots_trade_sp1_*` | `private build/preview` | Keep specialized guest/host packages private. | -| `binding, WASM, FFI, codegen, fixtures, xtask` | `private build/test packages` | Publish generated language artifacts, not Rust build machinery. | - -## 21. Workspace and manifest policy - -```toml -[workspace] -resolver = "3" - -[workspace.package] -edition = "2024" -rust-version = "1.97.1" -license = "MIT OR Apache-2.0" -homepage = "https://radroots.org" -``` - -Each standalone workspace sets its own repository metadata: - -```toml -# radrootslabs/lib -[workspace.package] -repository = "https://github.com/radrootslabs/lib" -``` - -```toml -# radrootslabs/sdk -[workspace.package] -repository = "https://github.com/radrootslabs/sdk" -``` - -Every public package MUST define: - -```toml -[package] -name = "radroots_..." -version = "<repository-governed exact version>" -publish = ["crates-io"] -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -homepage.workspace = true -readme = "README.md" -documentation = "https://docs.rs/radroots_..." -``` - -Additional rules: - -- Use an explicit `include` whitelist. -- Include both license files. -- Every same-repository public dependency uses `path + version`. -- Every dependency from `radrootslabs/sdk` to a package owned by - `radrootslabs/lib` uses a registry version and MUST NOT use a sibling path or - Git override in a release candidate. -- No Git dependency exists in a published normal/build/target/optional closure. -- Public packages avoid build scripts; generated source is checked and freshness-tested. -- Workspace lints forbid unsafe code by default. -- Public API crates deny broken rustdoc links. -- Package metadata lists only accurate keywords/categories. -- docs.rs metadata selects an intentional feature set instead of blindly enabling platform-incompatible features. - -## 22. Versioning and release policy - -### 22.1 Initial versions - -Every Rust crate in `radrootslabs/lib`, including private build, test, preview, -and support crates, is pinned to exactly `0.1.0-alpha`. Every same-repository -dependency requirement is pinned to exactly `=0.1.0-alpha`. This temporary -cohort is frozen until an explicit future authority changes it; neither normal -development nor release preparation may bump it implicitly. - -Every Rust crate in `radrootslabs/sdk`, including bindings, WASM wrappers, -runtime contracts, build tools, `radroots_sdk`, and `radroots`, is likewise -pinned to exactly `0.1.0-alpha`; every internal Radroots dependency uses the -exact `=0.1.0-alpha` requirement. Cross-repository dependencies use that same -frozen cohort without creating a path dependency between the standalone -repositories. This document's “V1” is the architecture specification version, -not a claim that Rust APIs are already 1.0-stable. - -Cargo package versions are independent from versioned wire, conformance, -database, and operation contracts. Changing the library crate cohort MUST NOT -rewrite authenticated historical protocol artifacts or derive Cargo SemVer -from a protocol contract version. - -### 22.2 SemVer groups - -- `radroots` and `radroots_sdk` release in lockstep and `radroots` uses `=X.Y.Z` for the SDK. -- Lower packages version independently after the first release. -- Lower dependencies use ordinary compatible requirements at the actual minimum supported version. -- Exact requirements are reserved for genuinely inseparable package pairs. -- Wire/event/runtime/storage/backup/generated-schema versions are independent of Cargo package versions. -- Every package gets its own changelog section and public API baseline. -- The repository maintains a tested compatibility manifest for the current SDK release. - -### 22.3 Breaking changes before 1.0 - -For `0.y.z` packages: - -- breaking API changes increment `y`; -- compatible fixes/features increment `z`; -- package names and responsibility charters remain permanent; -- moving a public type between packages is breaking and requires an ADR; -- first-party consumers migrate in the same coordinated cutover. - -## 23. Indicative publication order - -The actual order is computed from `cargo metadata`; the expected order is: - -```text -radroots_core -radroots_identity -radroots_blossom -radroots_protocol -radroots_secrets -radroots_geonames -radroots_event -radroots_event_codec -radroots_trade -radroots_signing -radroots_transport -radroots_nostr -radroots_nostr_connect -radroots_storage -radroots_storage_sqlite -radroots_transport_nostr -radroots_sync -radroots_sdk -radroots -``` - -Actual publication waits for each dependency to appear in the crates.io index before publishing dependents. - -## 24. Required CI and release gates - -### 24.1 Workspace - -- `cargo fmt --all -- --check` -- `cargo check --workspace --all-targets` -- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- `cargo test --workspace --all-targets` -- `cargo doc --workspace --no-deps` -- doctests -- generated-output freshness -- protocol/contract/conformance validation -- forbidden identifier and dependency checks - -### 24.2 Feature/target matrix - -For every public package: - -- no default features; -- default features; -- each public feature independently; -- supported feature bundles; -- all features; -- MSRV 1.97.1; -- current stable; -- Linux, macOS, Windows; -- declared no_std target; -- `wasm32-unknown-unknown` where supported; -- native package targets; -- minimal and latest compatible dependencies. - -### 24.3 Public API - -- `cargo-semver-checks`; -- public API baseline; -- rustdoc with warnings denied; -- no undocumented public item exceptions without review; -- examples compile from packaged artifacts; -- no duplicate canonical type paths except deliberate façade reexports; -- no third-party type leakage in generic packages. - -### 24.4 Reliability and security - -- fuzz event, protocol, NIP-46, URL, manifest, backup, and restore parsers; -- malformed and oversized network payloads; -- cancellation before and after commit; -- idempotency replay and conflict; -- outbox claim expiry; -- partial delivery and retry; -- signer timeout/wrong response/auth challenge; -- storage multi-reader/writer/locking; -- migration and corruption failure; -- crash recovery at every commit point; -- backup/restore interruption, traversal, symlink, and hash mismatch; -- projection invalidation/rebuild; -- secret redaction; -- dependency audit, license policy, provenance, SBOM, and advisory checks. - -### 24.5 Coverage policy - -During the active heavy-development refactor, every required workspace crate -owned by `radrootslabs/lib` MUST maintain at least 90% executable-line, -function, region, and branch coverage. Branch measurement remains required -except for a machine-recorded temporary exception where the coverage tool -emits no branch records. Crate-specific numeric thresholds below 90% are -forbidden. The -machine-readable authority for the active gate is `contracts/coverage.toml` in -`radrootslabs/lib`. - -This amendment does not alter the independently governed `radrootslabs/sdk` -coverage contract. The 90% baseline is temporary development policy, not -permission to remove meaningful tests or weaken package-specific conformance -requirements. The 100% threshold is deferred until this refactor stabilizes -and may be reinstated only through an explicit future contract and -specification update. - -### 24.6 Package-realistic release validation - -For every public package: - -1. Resolve the graph with `cargo metadata`. -2. Reject all public-to-private edges across normal, optional, build, target, and reachable-feature dependencies. -3. Run `cargo package --locked`. -4. Inspect the normalized manifest. -5. Inspect `cargo package --list`. -6. Extract the `.crate`. -7. Build, test, and document the extracted package. -8. Run `cargo publish --dry-run --locked`. -9. Publish to an ephemeral/local registry. -10. Build clean external projects against registry artifacts. -11. Build CLI, Studio, FFI/mobile, web packages, services, and indexers against package artifacts. -12. Confirm no sibling path or Git override remains. - -## 25. Source-evidence record - -| Repository | Reviewed SHA | Source | Pressure-test finding | -|---|---|---|---| -| `radrootslabs/lib` | `466f3cc36739179bc17edb9db796530729ba5219` | `Cargo.toml` | Workspace currently contains 49+ packages, uses edition 2024 with resolver 2, and centralizes underscore-named path dependencies. | -| `radrootslabs/lib` | `466f3cc36739179bc17edb9db796530729ba5219` | `contracts/releases/publish_policy.toml` | Current public classification contains broad runtime/storage/tooling crates while SDK-required packages remain internal. | -| `radrootslabs/sdk` | `fd8384aee348034e0c8ea17a868fe7f094770050` | `crates/sdk/Cargo.toml` | SDK feature graph names implementation assembly and directly references private authority, event-store, outbox, transport, and adapter crates. | -| `radrootslabs/lib` | `466f3cc36739179bc17edb9db796530729ba5219` | `crates/authority/src/{actor,authorization,signer}.rs` | Current authority package combines account provenance, event contract roles, signing SPI, authorization, and concrete local signing. | -| `radrootslabs/lib` | `466f3cc36739179bc17edb9db796530729ba5219` | `crates/event/src/ids.rs` | Semantic identifiers are stored as Strings, implement Deref<str>, and include trade/account/network concepts in one module. | -| `radrootslabs/lib` | `466f3cc36739179bc17edb9db796530729ba5219` | `crates/trade/src/identity.rs` | A second TradeId wraps OrderId, conflicting with the canonical event TradeId concept. | -| `radrootslabs/lib` | `466f3cc36739179bc17edb9db796530729ba5219` | `crates/identity/src/identity.rs` | Identity owns raw secret keys, upstream Nostr event values, file formats, generation, and secret export methods. | -| `radrootslabs/lib` | `466f3cc36739179bc17edb9db796530729ba5219` | `crates/transport/src/{kind,transport}.rs` | TransportKind is a closed enum and one monolithic trait requires both fetch and deliver. | -| `radrootslabs/lib` | `466f3cc36739179bc17edb9db796530729ba5219` | `crates/transport_nostr/{Cargo.toml,src/lib.rs}` | Nostr adapter currently couples relay transport to event-store and outbox persistence. | -| `radrootslabs/lib` | `466f3cc36739179bc17edb9db796530729ba5219` | `crates/event_store/src/store.rs and crates/outbox/src/store.rs` | Concrete stores expose SQLx pools/transactions and split related runtime state across separately opened stores. | -| `radrootslabs/lib` | `466f3cc36739179bc17edb9db796530729ba5219` | `crates/runtime/src/lib.rs and crates/log/src/init.rs` | Runtime package combines host concerns; logging installs process-global subscriber state. | -| `radrootslabs/sdk` | `fd8384aee348034e0c8ea17a868fe7f094770050` | `crates/sdk/src/{lib,runtime,studio_store,error}.rs` | SDK root broadly reexports implementation-shaped types, owns studio.sqlite, exposes many public fields, and duplicates error metadata. | - -## 26. Rejected alternatives - -### Publish the current public list - -Rejected because it permanently exposes host tooling and still omits SDK-required private dependencies. - -### Publish every current dependency unchanged - -Rejected because temporary implementation boundaries would become permanent package identities. - -### Collapse lower crates into `radroots_sdk` - -Rejected because domain, protocol, SPI, backend, and adapter packages have independent consumers and semver responsibilities. - -### One `radroots_runtime` package - -Rejected because runtime configuration, process lifecycle, paths, logging, queues, storage, and networking are not one coherent library boundary. - -### Separate packages for every domain or NIP - -Rejected because it creates package proliferation. Only independently substantial protocols with direct consumers—such as Nostr Connect—receive a package. - -### Versioned package names - -Rejected. Versions belong in modules and schema IDs. - -### Public preview placeholder packages - -Rejected. Preview code remains private until the implementation is real and conformance-tested. - -### Public codegen features - -Rejected. Code generation is a private build concern and must not enlarge the runtime registry closure. - -## 27. Cutover sequence - -1. Freeze publication and record this specification as an ADR. -2. Preserve the independent `radrootslabs/lib` and `radrootslabs/sdk` - histories; record the 17/2 package allocation and synchronized contract - hashes without forming or importing a third repository. -3. Switch workspace to resolver 3 and MSRV 1.97.1. -4. Create final snake-case package manifests with `publish = false` during migration. -5. Refactor identity/public-key ownership and remove all secret material. -6. Split authority among identity, event contracts, and signing. -7. Remove the duplicate TradeId and make trade algorithm-only. -8. Create `radroots_protocol`. -9. Create `radroots_secrets`. -10. Create `radroots_storage` and `radroots_storage_sqlite`; migrate event/outbox/journal/private storage. -11. Remove Studio state from SDK storage. -12. Redesign `radroots_transport`; separate source/sink. -13. Narrow `radroots_nostr` and `radroots_transport_nostr`. -14. Refactor and retain `radroots_nostr_connect`. -15. Create `radroots_sync`. -16. Rename/refocus GeoNames. -17. Refactor SDK root, features, errors, lifecycle, and commit semantics. -18. Add the curated `radroots` façade. -19. Migrate every first-party consumer. -20. Run package-realistic validation. -21. Change only the 19 final packages to `publish = ["crates-io"]`. -22. Publish in Cargo-derived order after explicit authorization. - -## 28. Completion and publication decision - -The package identities and boundaries in this specification are final for release V1. - -Publication is authorized only when all of the following are simultaneously true: - -- all 19 packages implement their charters; -- no forbidden responsibility remains; -- Cargo-resolved closure contains only public Radroots packages; -- all feature/target/package/downstream gates pass; -- naming availability and ownership are confirmed; -- current-source licensing and contributor provenance are cleared; -- generated cross-language contracts are coherent; -- no first-party host remains on legacy or sibling-path APIs; -- the actual `.crate` archives have been inspected and tested. - -Until then, the correct status is: - -```text -Architecture: FINAL -Implementation: REQUIRED -Publication: BLOCKED -``` - -## 29. Final reaffirmation - -This is the final recommended Radroots crates surface. - -It preserves real modularity without turning every current workspace folder into a permanent public package. It establishes stable identities for foundational values, identity, event protocol, trade algorithms, signing, transport, secrets, storage, Nostr, synchronization, a concrete geodata provider, the advanced SDK, and the ordinary-user façade. - -It intentionally withholds host utilities, preview transports, generated CRUD/replica code, experimental messaging/proof systems, codegen, FFI machinery, and test support. - -No additional public crate is required for release V1, and no package in the 19-package family is present merely to satisfy Cargo. Each has a durable responsibility, named consumers, a one-way dependency position, and a credible independent SemVer surface. diff --git a/imports/studio_mpl_legacy_core/Cargo.toml b/imports/studio_mpl_legacy_core/Cargo.toml @@ -1,8 +1,5 @@ [workspace] -members = [ - "crates/core", - "tools/uniffi-bindgen", -] +members = ["crates/core", "tools/uniffi-bindgen"] resolver = "2" [workspace.package] diff --git a/supply-chain/config.toml b/supply-chain/config.toml @@ -83,9 +83,9 @@ criteria = "secret-handling-reviewed" [[exemptions.allocator-api2]] version = "0.2.21" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.android_system_properties]] @@ -115,9 +115,9 @@ criteria = "safe-to-deploy" [[exemptions.anyhow]] version = "1.0.102" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.apple-native-keyring-store]] @@ -291,17 +291,17 @@ criteria = "safe-to-deploy" [[exemptions.bitflags]] version = "2.11.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.block-buffer]] version = "0.10.4" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.block-buffer]] @@ -311,9 +311,9 @@ criteria = "safe-to-deploy" [[exemptions.block-padding]] version = "0.3.3" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.blocking]] @@ -327,9 +327,9 @@ criteria = "safe-to-deploy" [[exemptions.bumpalo]] version = "3.20.2" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.bytecount]] @@ -375,9 +375,9 @@ criteria = ["network-parser-reviewed", "secret-handling-reviewed"] [[exemptions.cc]] version = "1.2.57" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.cexpr]] @@ -387,9 +387,9 @@ criteria = "safe-to-deploy" [[exemptions.cfg-if]] version = "1.0.4" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.cfg_aliases]] @@ -415,9 +415,9 @@ criteria = "safe-to-deploy" [[exemptions.cipher]] version = "0.4.4" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.clang-sys]] @@ -483,9 +483,9 @@ criteria = "safe-to-deploy" [[exemptions.cpufeatures]] version = "0.2.17" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.cpufeatures]] @@ -527,9 +527,9 @@ criteria = "safe-to-deploy" [[exemptions.crypto-common]] version = "0.1.7" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.crypto-common]] @@ -587,9 +587,9 @@ criteria = "safe-to-deploy" [[exemptions.digest]] version = "0.10.7" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.digest]] @@ -675,9 +675,9 @@ criteria = "secret-handling-reviewed" [[exemptions.equivalent]] version = "1.0.2" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.errno]] @@ -727,9 +727,9 @@ criteria = "safe-to-deploy" [[exemptions.find-msvc-tools]] version = "0.1.9" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.flatbuffers]] @@ -751,17 +751,17 @@ criteria = "safe-to-deploy" [[exemptions.foldhash]] version = "0.1.5" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.foldhash]] version = "0.2.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.foreign-types]] @@ -839,25 +839,25 @@ criteria = ["network-parser-reviewed", "secret-handling-reviewed"] [[exemptions.generic-array]] version = "0.14.7" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.getrandom]] version = "0.2.17" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.getrandom]] version = "0.3.4" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.getrandom]] @@ -899,17 +899,17 @@ criteria = "safe-to-deploy" [[exemptions.hashbrown]] version = "0.15.5" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.hashbrown]] version = "0.16.1" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.hashbrown]] @@ -923,9 +923,9 @@ criteria = "safe-to-deploy" [[exemptions.heck]] version = "0.5.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.hermit-abi]] @@ -947,9 +947,9 @@ criteria = "secret-handling-reviewed" [[exemptions.hmac]] version = "0.12.1" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.home]] @@ -1027,9 +1027,9 @@ criteria = "network-parser-reviewed" [[exemptions.id-arena]] version = "2.3.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.idna]] @@ -1047,17 +1047,17 @@ criteria = "network-parser-reviewed" [[exemptions.indexmap]] version = "2.13.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.inout]] version = "0.1.4" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.instant]] @@ -1083,9 +1083,9 @@ criteria = "safe-to-deploy" [[exemptions.itoa]] version = "1.0.18" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.jiff-tzdb]] @@ -1095,17 +1095,17 @@ criteria = "safe-to-deploy" [[exemptions.jobserver]] version = "0.1.34" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.js-sys]] version = "0.3.91" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.jsonschema]] @@ -1151,17 +1151,17 @@ criteria = "safe-to-deploy" [[exemptions.leb128fmt]] version = "0.1.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.libc]] version = "0.2.183" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.libdbus-sys]] @@ -1219,9 +1219,9 @@ criteria = "safe-to-deploy" [[exemptions.log]] version = "0.4.29" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.lru]] @@ -1239,9 +1239,9 @@ criteria = "safe-to-deploy" [[exemptions.memchr]] version = "2.8.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.memoffset]] @@ -1371,9 +1371,9 @@ criteria = "safe-to-deploy" [[exemptions.once_cell]] version = "1.21.4" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.once_cell_polyfill]] @@ -1495,9 +1495,9 @@ criteria = "network-parser-reviewed" [[exemptions.prettyplease]] version = "0.2.37" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.proc-macro-crate]] @@ -1507,10 +1507,10 @@ criteria = "secret-handling-reviewed" [[exemptions.proc-macro2]] version = "1.0.106" criteria = [ - "build-execution-reviewed", - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "build-execution-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.quinn]] @@ -1528,18 +1528,18 @@ criteria = "safe-to-deploy" [[exemptions.quote]] version = "1.0.45" criteria = [ - "build-execution-reviewed", - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "build-execution-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.r-efi]] version = "5.3.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.r-efi]] @@ -1573,9 +1573,9 @@ criteria = "safe-to-deploy" [[exemptions.rand_core]] version = "0.6.4" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.rand_core]] @@ -1709,9 +1709,9 @@ criteria = "network-parser-reviewed" [[exemptions.rustversion]] version = "1.0.22" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.ryu]] @@ -1781,17 +1781,17 @@ criteria = "secret-handling-reviewed" [[exemptions.semver]] version = "1.0.27" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.serde]] version = "1.0.228" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.serde-wasm-bindgen]] @@ -1801,25 +1801,25 @@ criteria = "safe-to-deploy" [[exemptions.serde_core]] version = "1.0.228" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.serde_derive]] version = "1.0.228" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.serde_json]] version = "1.0.149" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.serde_repr]] @@ -1845,17 +1845,17 @@ criteria = "network-parser-reviewed" [[exemptions.sha2]] version = "0.10.9" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.sha2-asm]] version = "0.6.4" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.sha3]] @@ -1873,9 +1873,9 @@ criteria = "safe-to-deploy" [[exemptions.shlex]] version = "1.3.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.signal-hook-registry]] @@ -1973,9 +1973,9 @@ criteria = "safe-to-deploy" [[exemptions.subtle]] version = "2.6.1" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.symlink]] @@ -1985,10 +1985,10 @@ criteria = "safe-to-deploy" [[exemptions.syn]] version = "2.0.117" criteria = [ - "build-execution-reviewed", - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "build-execution-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.syn]] @@ -2178,9 +2178,9 @@ criteria = "safe-to-deploy" [[exemptions.typenum]] version = "1.20.1" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.uds_windows]] @@ -2198,10 +2198,10 @@ criteria = "safe-to-deploy" [[exemptions.unicode-ident]] version = "1.0.24" criteria = [ - "build-execution-reviewed", - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "build-execution-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.unicode-normalization]] @@ -2211,9 +2211,9 @@ criteria = "network-parser-reviewed" [[exemptions.unicode-xid]] version = "0.2.6" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.uniffi]] @@ -2335,9 +2335,9 @@ criteria = "secret-handling-reviewed" [[exemptions.version_check]] version = "0.9.5" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.vsimd]] @@ -2355,17 +2355,17 @@ criteria = "safe-to-deploy" [[exemptions.wasi]] version = "0.11.1+wasi-snapshot-preview1" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wasip2]] version = "1.0.2+wasi-0.2.9" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wasip3]] @@ -2375,9 +2375,9 @@ criteria = "secret-handling-reviewed" [[exemptions.wasm-bindgen]] version = "0.2.114" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wasm-bindgen-futures]] @@ -2387,25 +2387,25 @@ criteria = "network-parser-reviewed" [[exemptions.wasm-bindgen-macro]] version = "0.2.114" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wasm-bindgen-macro-support]] version = "0.2.114" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wasm-bindgen-shared]] version = "0.2.114" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wasm-bindgen-test]] @@ -2423,25 +2423,25 @@ criteria = "safe-to-run" [[exemptions.wasm-encoder]] version = "0.244.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wasm-metadata]] version = "0.244.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wasmparser]] version = "0.244.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.web-sys]] @@ -2611,49 +2611,49 @@ criteria = "secret-handling-reviewed" [[exemptions.wit-bindgen]] version = "0.51.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wit-bindgen-core]] version = "0.51.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wit-bindgen-rust]] version = "0.51.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wit-bindgen-rust-macro]] version = "0.51.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wit-component]] version = "0.244.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.wit-parser]] version = "0.244.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.writeable]] @@ -2727,17 +2727,17 @@ criteria = "network-parser-reviewed" [[exemptions.zeroize]] version = "1.9.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.zeroize_derive]] version = "1.5.0" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.zerotrie]] @@ -2763,9 +2763,9 @@ criteria = "safe-to-deploy" [[exemptions.zmij]] version = "1.0.21" criteria = [ - "crypto-reviewed", - "network-parser-reviewed", - "secret-handling-reviewed", + "crypto-reviewed", + "network-parser-reviewed", + "secret-handling-reviewed", ] [[exemptions.zopfli]] diff --git a/tools/xtask/README b/tools/xtask/README @@ -9,6 +9,9 @@ tasks for the `radroots` core libraries. release workflows; * `contract`, `coverage`, `dto-roots`, `release`, and `hygiene` command families for core-library governance; + * configurable deterministic prototype-contract reporting through + `hygiene prototype-contracts`, with explicit narrow allowlists and a future + strict enforcement mode; * deterministic `dto-roots --write|--check` generation for every source-manifest package in the workspace DTO authority; * command-dispatch code used for contract, coverage, hygiene, and release diff --git a/tools/xtask/src/architecture.rs b/tools/xtask/src/architecture.rs @@ -9,9 +9,10 @@ use serde::Deserialize; mod api_leakage; mod core_contract; mod dependency_boundary; +mod retired_compatibility; -const DEVIATIONS_RELATIVE: &str = "docs/implementation/deviations.toml"; -const ARCHITECTURE_RELATIVE: &str = "docs/specs/radroots_crates_release_v1.toml"; +const DEVIATIONS_RELATIVE: &str = "contracts/architecture/deviations.toml"; +const ARCHITECTURE_RELATIVE: &str = "contracts/crates/release_v1/radroots_crates_release_v1.toml"; const ARCHITECTURE_ID: &str = "radroots.crates.release.v1"; const PUBLIC_HOMEPAGE: &str = "https://radroots.org"; const PUBLIC_README: &str = "README.md"; @@ -168,6 +169,7 @@ pub fn validate(workspace_root: &Path) -> Result<(), String> { core_contract::validate(workspace_root)?; api_leakage::validate_policy_catalog(workspace_root, &architecture_packages)?; dependency_boundary::validate_policy_catalog(workspace_root, &architecture_packages)?; + retired_compatibility::validate(workspace_root)?; validate_workspace_toolchain(workspace_root, &architecture)?; validate_public_package_metadata(workspace_root, &architecture)?; validate_no_production_sibling_paths(workspace_root)?; @@ -1105,7 +1107,7 @@ fn validate_spec_anchor( let (relative, fragment) = anchor .split_once('#') .map_or((anchor, None), |(path, fragment)| (path, Some(fragment))); - if relative.trim().is_empty() || fragment.is_some_and(|value| value.trim().is_empty()) { + if relative.trim().is_empty() || fragment.is_none_or(|value| value.trim().is_empty()) { return Err(format!( "deviation {deviation_id} has invalid spec anchor {anchor}" )); @@ -1121,14 +1123,64 @@ fn validate_spec_anchor( "deviation {deviation_id} spec anchor must be repository-relative: {anchor}" )); } - if !relative.starts_with("docs/specs/") || !workspace_root.join(path).is_file() { + if relative != ARCHITECTURE_RELATIVE { return Err(format!( - "deviation {deviation_id} spec anchor does not resolve to a local spec: {anchor}" + "deviation {deviation_id} spec anchor must target {ARCHITECTURE_RELATIVE}: {anchor}" + )); + } + let machine_path = workspace_root.join(path); + let raw = fs::read_to_string(&machine_path) + .map_err(|error| format!("read {}: {error}", machine_path.display()))?; + let machine = toml::from_str::<toml::Value>(&raw) + .map_err(|error| format!("parse {}: {error}", machine_path.display()))?; + let selector = fragment.expect("fragment is required above"); + if !machine_selector_exists(&machine, selector) { + return Err(format!( + "deviation {deviation_id} spec anchor has unknown machine selector {selector}" )); } Ok(()) } +fn machine_selector_exists(machine: &toml::Value, selector: &str) -> bool { + let segments = selector.split('.').collect::<Vec<_>>(); + if segments.iter().any(|segment| segment.is_empty()) { + return false; + } + let root = match machine.as_table() { + Some(root) => root, + None => return false, + }; + match segments.as_slice() { + ["repositories", repository] => root + .get("repositories") + .and_then(toml::Value::as_table) + .and_then(|repositories| repositories.get(*repository)) + .is_some_and(toml::Value::is_table), + ["repository_policy"] | ["release_policy"] => { + root.get(segments[0]).is_some_and(toml::Value::is_table) + } + ["quality_policy", "coverage"] => root + .get("quality_policy") + .and_then(toml::Value::as_table) + .and_then(|quality| quality.get("coverage")) + .is_some_and(toml::Value::is_table), + ["package", package] => root + .get("package") + .and_then(toml::Value::as_array) + .is_some_and(|packages| { + packages.iter().any(|candidate| { + candidate + .as_table() + .and_then(|table| table.get("name")) + .and_then(toml::Value::as_str) + == Some(*package) + }) + }), + _ => false, + } +} + fn require_text(deviation_id: &str, field: &str, value: &str) -> Result<(), String> { if value.trim().is_empty() { return Err(format!( @@ -1184,10 +1236,10 @@ mod tests { .expect("clock") .as_nanos(); let root = std::env::temp_dir().join(format!("radroots_architecture_{label}_{nonce}")); - fs::create_dir_all(root.join("docs/specs")).expect("create spec root"); + fs::create_dir_all(root.join("contracts/crates/release_v1")).expect("create spec root"); fs::write( - root.join("docs/specs/radroots_crates_release_v1.md"), - "# Architecture\n", + root.join("contracts/crates/release_v1/radroots_crates_release_v1.toml"), + "spec_id = \"radroots.crates.release.v1\"\n\n[repositories.sdk]\nurl = \"https://github.com/radrootslabs/sdk\"\n", ) .expect("write spec"); root @@ -1203,7 +1255,7 @@ date = "2026-07-27" status = "active" approval = "Explicit user correction dated 2026-07-27." affected_steps = ["015", "016"] -spec_anchors = ["docs/specs/radroots_crates_release_v1.md#repository-topology"] +spec_anchors = ["contracts/crates/release_v1/radroots_crates_release_v1.toml#repositories.sdk"] source_evidence = ["The approved architecture assigns packages to the existing lib and sdk repositories."] replacement_action = "Keep both standalone repositories and verify them independently." verification = ["Repository-local architecture validation passes."] @@ -1253,7 +1305,7 @@ adr_required = false fn rejects_incomplete_active_deviation() { let root = test_root("incomplete"); let incomplete = complete_ledger().replace( - "spec_anchors = [\"docs/specs/radroots_crates_release_v1.md#repository-topology\"]", + "spec_anchors = [\"contracts/crates/release_v1/radroots_crates_release_v1.toml#repositories.sdk\"]", "spec_anchors = []", ); let error = validate_ledger(&root, "radroots.crates.release.v1", &incomplete) @@ -1263,6 +1315,22 @@ adr_required = false } #[test] + fn deviation_anchors_require_resolving_machine_selectors() { + let root = test_root("machine_selector"); + let invalid = complete_ledger().replace("#repositories.sdk", "#garbage"); + let error = validate_ledger(&root, "radroots.crates.release.v1", &invalid) + .expect_err("arbitrary fragment must fail"); + assert!(error.contains("unknown machine selector garbage")); + + let markdown_slug = + complete_ledger().replace("#repositories.sdk", "#20-current-to-target-migration-map"); + let error = validate_ledger(&root, "radroots.crates.release.v1", &markdown_slug) + .expect_err("Markdown heading slug must fail"); + assert!(error.contains("unknown machine selector")); + let _ = fs::remove_dir_all(root); + } + + #[test] fn workspace_membership_requires_every_local_package_root() { let root = test_root("workspace_members"); for path in ["crates/a", "tools/xtask"] { diff --git a/tools/xtask/src/architecture/api_leakage.rs b/tools/xtask/src/architecture/api_leakage.rs @@ -12,9 +12,38 @@ use syn::{ }; const API_BOUNDARIES_RELATIVE: &str = "contracts/releases/api_boundaries.toml"; +const API_DECISION_RELATIVE: &str = + "contracts/architecture/decisions/public_api_leakage_migration_baseline.v1.toml"; +const API_DECISION_ID: &str = "radroots.public_api_leakage_migration_baseline.v1"; const SPEC_ID: &str = "radroots.crates.release.v1"; const POLICY_SCHEMA_VERSION: u16 = 1; -const CURRENT_STEP: u16 = 25; +const DECISION_SCHEMA_VERSION: u16 = 1; +const CURRENT_STEP: u16 = 313; +const HISTORICAL_EXCEPTION_IDS: [&str; 8] = [ + "RCRV1-API-001", + "RCRV1-API-002", + "RCRV1-API-003", + "RCRV1-API-004", + "RCRV1-API-005", + "RCRV1-API-006", + "RCRV1-API-007", + "RCRV1-API-008", +]; +const FORBIDDEN_EXPANSION: [&str; 8] = [ + "broader_aliases", + "keyring", + "new_items", + "new_upstream_paths", + "platform_specific_types", + "reqwest", + "sqlx", + "tokio", +]; +const REMOVAL_MILESTONES: [(&str, u16); 3] = [ + ("radroots_identity", 42), + ("radroots_nostr", 124), + ("radroots_nostr_connect", 140), +]; #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] @@ -43,12 +72,34 @@ struct ApiException { forbidden_path: String, items: Vec<String>, observed_paths: Vec<String>, - adr: String, + decision: String, removal_step: u16, rationale: String, } #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct ApiLeakageDecision { + schema_version: u16, + decision_id: String, + status: String, + accepted_date: String, + completed_step: u16, + publication_authorized: bool, + exception_ids: Vec<String>, + active_exception_ids: Vec<String>, + forbidden_expansion: Vec<String>, + removal_milestone: Vec<ApiRemovalMilestone>, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct ApiRemovalMilestone { + package: String, + step: u16, +} + +#[derive(Debug, Deserialize)] struct CargoMetadata { packages: Vec<CargoPackage>, workspace_members: Vec<String>, @@ -151,6 +202,7 @@ fn validate_policy( "{API_BOUNDARIES_RELATIVE} spec_id must be {SPEC_ID}" )); } + let decision = load_and_validate_decision(workspace_root)?; let forbidden = sorted_unique( "forbidden_public_paths", @@ -293,7 +345,7 @@ fn validate_policy( exception.id )); } - validate_adr(workspace_root, exception)?; + validate_decision_reference(exception, &decision)?; for item in &exception.items { let key = ( exception.package.as_str(), @@ -309,6 +361,16 @@ fn validate_policy( } } } + let active_exception_ids = decision + .active_exception_ids + .iter() + .map(String::as_str) + .collect::<BTreeSet<_>>(); + if exception_ids != active_exception_ids { + return Err(format!( + "{API_BOUNDARIES_RELATIVE} active exception ids must exactly match {API_DECISION_RELATIVE}" + )); + } Ok(()) } @@ -352,32 +414,87 @@ fn validate_relative_source(id: &str, source: &str) -> Result<(), String> { Ok(()) } -fn validate_adr(workspace_root: &Path, exception: &ApiException) -> Result<(), String> { - let path = Path::new(&exception.adr); - if path.is_absolute() - || !exception.adr.starts_with("docs/decisions/") +fn load_and_validate_decision(workspace_root: &Path) -> Result<ApiLeakageDecision, String> { + let path = workspace_root.join(API_DECISION_RELATIVE); + let raw = + fs::read_to_string(&path).map_err(|error| format!("read {}: {error}", path.display()))?; + let decision = toml::from_str::<ApiLeakageDecision>(&raw) + .map_err(|error| format!("parse {}: {error}", path.display()))?; + + if decision.schema_version != DECISION_SCHEMA_VERSION + || decision.decision_id != API_DECISION_ID + || decision.status != "accepted_historical" + || decision.accepted_date != "2026-07-27" + || decision.completed_step != CURRENT_STEP + || decision.publication_authorized + { + return Err(format!( + "{API_DECISION_RELATIVE} identity, lifecycle, or publication policy drifted" + )); + } + if decision + .exception_ids + .iter() + .map(String::as_str) + .ne(HISTORICAL_EXCEPTION_IDS) + { + return Err(format!( + "{API_DECISION_RELATIVE} must preserve the exact historical exception ids" + )); + } + if !decision.active_exception_ids.is_empty() { + return Err(format!( + "{API_DECISION_RELATIVE} must not authorize active exceptions after Step {CURRENT_STEP}" + )); + } + if decision + .forbidden_expansion + .iter() + .map(String::as_str) + .ne(FORBIDDEN_EXPANSION) + { + return Err(format!( + "{API_DECISION_RELATIVE} forbidden expansion set drifted" + )); + } + if decision + .removal_milestone + .iter() + .map(|milestone| (milestone.package.as_str(), milestone.step)) + .ne(REMOVAL_MILESTONES) + { + return Err(format!( + "{API_DECISION_RELATIVE} removal milestones drifted" + )); + } + Ok(decision) +} + +fn validate_decision_reference( + exception: &ApiException, + decision: &ApiLeakageDecision, +) -> Result<(), String> { + let path = Path::new(&exception.decision); + if exception.decision != API_DECISION_RELATIVE + || path.is_absolute() || path .components() .any(|component| !matches!(component, Component::Normal(_))) - || path.extension().and_then(|extension| extension.to_str()) != Some("md") + || path.extension().and_then(|extension| extension.to_str()) != Some("toml") { return Err(format!( - "{API_BOUNDARIES_RELATIVE} exception {} ADR must be a normalized docs/decisions/*.md path", + "{API_BOUNDARIES_RELATIVE} exception {} decision must be {API_DECISION_RELATIVE}", exception.id )); } - let full = workspace_root.join(path); - let raw = fs::read_to_string(&full).map_err(|error| { - format!( - "{API_BOUNDARIES_RELATIVE} exception {} ADR {} is not readable: {error}", - exception.id, - full.display() - ) - })?; - if !raw.contains(&exception.id) { + if !decision + .active_exception_ids + .iter() + .any(|id| id == &exception.id) + { return Err(format!( - "{API_BOUNDARIES_RELATIVE} exception {} ADR {} must cite the exception id", - exception.id, exception.adr + "{API_BOUNDARIES_RELATIVE} exception {} is not active in {API_DECISION_RELATIVE}", + exception.id )); } Ok(()) @@ -1264,15 +1381,18 @@ fn module_label(module: &[String]) -> String { #[cfg(test)] mod tests { use super::{ - ApiBoundaryPolicy, ApiException, CargoMetadata, exception_matches, scan_workspace, + API_DECISION_RELATIVE, ApiBoundaryPolicy, ApiException, CargoMetadata, scan_workspace, validate_policy, }; use serde::Deserialize; use std::{collections::BTreeSet, fs}; const POLICY: &str = include_str!("../../../../contracts/releases/api_boundaries.toml"); + const DECISION: &str = include_str!( + "../../../../contracts/architecture/decisions/public_api_leakage_migration_baseline.v1.toml" + ); const ARCHITECTURE: &str = - include_str!("../../../../docs/specs/radroots_crates_release_v1.toml"); + include_str!("../../../../contracts/crates/release_v1/radroots_crates_release_v1.toml"); const GENERIC_SQLX: &str = include_str!("../../tests/fixtures/api-leakage/generic-sqlx.rs"); const GENERIC_RENAMED_TOKIO: &str = include_str!("../../tests/fixtures/api-leakage/generic-renamed-tokio.rs"); @@ -1280,8 +1400,6 @@ mod tests { include_str!("../../tests/fixtures/api-leakage/allowed-concrete-adapter.rs"); const PRIVATE_IMPLEMENTATION: &str = include_str!("../../tests/fixtures/api-leakage/private-implementation.rs"); - const ADR_EXCEPTION: &str = include_str!("../../tests/fixtures/api-leakage/adr-exception.rs"); - #[derive(Deserialize)] struct ArchitectureCatalog { package: Vec<ArchitecturePackage>, @@ -1345,19 +1463,22 @@ mod tests { .expect("scan fixture") } - fn write_baseline_adr(root: &std::path::Path) { - fs::create_dir_all(root.join("docs/decisions")).expect("baseline ADR directory"); + fn write_baseline_decision(root: &std::path::Path) { + fs::create_dir_all(root.join("contracts/architecture/decisions")) + .expect("baseline decision directory"); fs::write( - root.join("docs/decisions/0001-public-api-leakage-migration-baseline.md"), - "RCRV1-API-001 RCRV1-API-002 RCRV1-API-003 RCRV1-API-004 RCRV1-API-005 RCRV1-API-006 RCRV1-API-007 RCRV1-API-008\n", + root.join( + "contracts/architecture/decisions/public_api_leakage_migration_baseline.v1.toml", + ), + DECISION, ) - .expect("baseline ADR"); + .expect("baseline decision"); } #[test] fn policy_covers_exact_architecture_catalog() { let root = tempfile::TempDir::new().expect("policy root"); - write_baseline_adr(root.path()); + write_baseline_decision(root.path()); validate_policy(root.path(), &policy(), &expected_packages()) .expect("policy without exceptions"); } @@ -1393,15 +1514,9 @@ mod tests { } #[test] - fn exact_item_exception_requires_resolving_adr() { + fn invented_exception_is_rejected_by_the_completed_decision() { let root = tempfile::TempDir::new().expect("exception root"); - write_baseline_adr(root.path()); - fs::create_dir_all(root.path().join("docs/decisions")).expect("ADR directory"); - fs::write( - root.path().join("docs/decisions/0001-test.md"), - "# Test\n\nRCRV1-API-999\n", - ) - .expect("ADR"); + write_baseline_decision(root.path()); let mut policy = policy(); policy.exception.push(ApiException { id: "RCRV1-API-999".to_owned(), @@ -1410,24 +1525,56 @@ mod tests { forbidden_path: "reqwest".to_owned(), items: vec!["temporary_client".to_owned()], observed_paths: vec!["reqwest::Client".to_owned()], - adr: "docs/decisions/0001-test.md".to_owned(), - removal_step: 226, + decision: API_DECISION_RELATIVE.to_owned(), + removal_step: 400, rationale: "test-only exception".to_owned(), }); - validate_policy(root.path(), &policy, &expected_packages()).expect("resolving ADR"); - let finding = scan_workspace( - root.path(), - &policy, - &fixture_metadata(root.path(), "radroots_sdk", ADR_EXCEPTION), - ) - .expect("scan exception fixture") - .pop() - .expect("finding"); - assert!(exception_matches(&policy.exception, &finding)); + let error = validate_policy(root.path(), &policy, &expected_packages()) + .expect_err("invented exception must fail"); + assert!(error.contains("is not active")); + } - fs::remove_file(root.path().join("docs/decisions/0001-test.md")).expect("remove ADR"); + #[test] + fn expired_historical_exception_cannot_return() { + let root = tempfile::TempDir::new().expect("expired exception root"); + write_baseline_decision(root.path()); + let mut policy = policy(); + policy.exception.push(ApiException { + id: "RCRV1-API-006".to_owned(), + package: "radroots_nostr_connect".to_owned(), + source: "src/client.rs".to_owned(), + forbidden_path: "nostr".to_owned(), + items: vec!["client::RadrootsNostrConnectClientTarget".to_owned()], + observed_paths: vec!["nostr::PublicKey".to_owned()], + decision: API_DECISION_RELATIVE.to_owned(), + removal_step: 313, + rationale: "retired historical exception".to_owned(), + }); let error = validate_policy(root.path(), &policy, &expected_packages()) - .expect_err("missing ADR must fail"); - assert!(error.contains("is not readable")); + .expect_err("expired historical exception must fail"); + assert!(error.contains("expired at Step 313")); + } + + #[test] + fn malformed_or_publication_authorizing_decision_fails_closed() { + let root = tempfile::TempDir::new().expect("decision validation root"); + write_baseline_decision(root.path()); + let decision_path = root.path().join(API_DECISION_RELATIVE); + fs::write(&decision_path, "not valid toml = [\n").expect("malformed decision"); + let malformed = validate_policy(root.path(), &policy(), &expected_packages()) + .expect_err("malformed decision must fail"); + assert!(malformed.contains("parse")); + + fs::write( + &decision_path, + DECISION.replace( + "publication_authorized = false", + "publication_authorized = true", + ), + ) + .expect("publication-authorizing decision"); + let authorized = validate_policy(root.path(), &policy(), &expected_packages()) + .expect_err("publication authorization must fail"); + assert!(authorized.contains("publication policy drifted")); } } diff --git a/tools/xtask/src/architecture/core_contract.rs b/tools/xtask/src/architecture/core_contract.rs @@ -6,7 +6,7 @@ use std::{ use syn::{Item, UseTree, Visibility}; -const ARCHITECTURE_RELATIVE: &str = "docs/specs/radroots_crates_release_v1.toml"; +const ARCHITECTURE_RELATIVE: &str = "contracts/crates/release_v1/radroots_crates_release_v1.toml"; const CORE_MANIFEST_RELATIVE: &str = "crates/core/Cargo.toml"; const CORE_LIB_RELATIVE: &str = "crates/core/src/lib.rs"; @@ -485,11 +485,12 @@ serde_json = { workspace = true } fn fixture() -> tempfile::TempDir { let root = tempdir().expect("temporary workspace"); - fs::create_dir_all(root.path().join("docs/specs")).expect("spec directory"); + fs::create_dir_all(root.path().join("contracts/crates/release_v1")) + .expect("spec directory"); fs::create_dir_all(root.path().join("crates/core/src")).expect("core directory"); fs::write( root.path() - .join("docs/specs/radroots_crates_release_v1.toml"), + .join("contracts/crates/release_v1/radroots_crates_release_v1.toml"), SPEC, ) .expect("architecture spec"); diff --git a/tools/xtask/src/architecture/dependency_boundary.rs b/tools/xtask/src/architecture/dependency_boundary.rs @@ -570,7 +570,7 @@ mod tests { const POLICY: &str = include_str!("../../../../contracts/releases/package_tiers.toml"); const ARCHITECTURE: &str = - include_str!("../../../../docs/specs/radroots_crates_release_v1.toml"); + include_str!("../../../../contracts/crates/release_v1/radroots_crates_release_v1.toml"); const DOMAIN_TO_STORAGE: &str = include_str!("../../tests/fixtures/dependency-boundaries/domain-to-storage.json"); const SPI_TO_ADAPTER: &str = diff --git a/tools/xtask/src/architecture/retired_compatibility.rs b/tools/xtask/src/architecture/retired_compatibility.rs @@ -0,0 +1,150 @@ +use std::{collections::BTreeMap, fs, path::Path}; + +use serde::Deserialize; + +const CONTRACT_RELATIVE: &str = "contracts/architecture/retired_compatibility.v1.toml"; +const CONTRACT_ID: &str = "radroots.retired_compatibility.v1"; +const SCHEMA_VERSION: u16 = 1; + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct RetiredCompatibility { + schema_version: u16, + contract_id: String, + status: String, + retired_bridge: Vec<RetiredBridge>, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct RetiredBridge { + id: String, + final_owners: Vec<String>, + removal_step: u16, +} + +pub(super) fn validate(workspace_root: &Path) -> Result<(), String> { + let path = workspace_root.join(CONTRACT_RELATIVE); + let raw = + fs::read_to_string(&path).map_err(|error| format!("read {}: {error}", path.display()))?; + validate_raw(&raw).map_err(|error| format!("{CONTRACT_RELATIVE}: {error}")) +} + +fn validate_raw(raw: &str) -> Result<(), String> { + let contract = toml::from_str::<RetiredCompatibility>(raw) + .map_err(|error| format!("invalid TOML: {error}"))?; + if contract.schema_version != SCHEMA_VERSION + || contract.contract_id != CONTRACT_ID + || contract.status != "enforced" + { + return Err("identity or lifecycle drifted".to_owned()); + } + + let mut actual = BTreeMap::new(); + for bridge in contract.retired_bridge { + if bridge.id.trim().is_empty() || bridge.final_owners.is_empty() { + return Err("bridge ids and final owners must not be empty".to_owned()); + } + if bridge + .final_owners + .windows(2) + .any(|pair| pair[0] >= pair[1]) + { + return Err(format!( + "bridge {} final owners must be sorted and unique", + bridge.id + )); + } + if actual + .insert( + bridge.id.clone(), + (bridge.final_owners, bridge.removal_step), + ) + .is_some() + { + return Err(format!("bridge {} is duplicated", bridge.id)); + } + } + + let expected = BTreeMap::from([ + ( + "nostrdb_runtime_adapter".to_owned(), + (vec!["radroots_storage_sqlite".to_owned()], 301), + ), + ( + "radroots_authority".to_owned(), + (vec!["radroots_signing".to_owned()], 313), + ), + ( + "radroots_geocoder".to_owned(), + (vec!["radroots_geonames".to_owned()], 313), + ), + ( + "radroots_net".to_owned(), + (vec!["radroots_transport".to_owned()], 301), + ), + ( + "radroots_nostr_connect_hidden_prelude".to_owned(), + (vec!["radroots_nostr_connect".to_owned()], 313), + ), + ( + "radroots_nostr_connect_prefixed_client_bridge".to_owned(), + (vec!["radroots_nostr_connect".to_owned()], 313), + ), + ( + "radroots_nostr_runtime".to_owned(), + (vec!["radroots_transport_nostr".to_owned()], 301), + ), + ( + "radroots_nostr_signer".to_owned(), + ( + vec![ + "radroots_nostr_connect".to_owned(), + "radroots_signing".to_owned(), + ], + 313, + ), + ), + ]); + if actual != expected { + return Err("retired bridge inventory, final owners, or removal steps drifted".to_owned()); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::validate_raw; + + const CONTRACT: &str = + include_str!("../../../../contracts/architecture/retired_compatibility.v1.toml"); + + #[test] + fn current_retirement_contract_is_complete() { + validate_raw(CONTRACT).expect("complete retirement contract"); + } + + #[test] + fn malformed_or_incomplete_retirement_contract_fails_closed() { + let malformed = validate_raw("not toml = [\n").expect_err("malformed contract must fail"); + assert!(malformed.contains("invalid TOML")); + + let geocoder = r#"[[retired_bridge]] +id = "radroots_geocoder" +final_owners = ["radroots_geonames"] +removal_step = 313 + +"#; + let incomplete = validate_raw(&CONTRACT.replace(geocoder, "")) + .expect_err("missing geocoder retirement must fail"); + assert!(incomplete.contains("inventory")); + + let no_prefixed_bridge = CONTRACT.replace( + "radroots_nostr_connect_prefixed_client_bridge", + "radroots_nostr_connect_unknown_bridge", + ); + let prefixed = validate_raw(&no_prefixed_bridge) + .expect_err("missing prefixed-client retirement must fail"); + assert!(prefixed.contains("inventory")); + } +} diff --git a/tools/xtask/src/build_control.rs b/tools/xtask/src/build_control.rs @@ -12,7 +12,7 @@ use sha2::{Digest, Sha256}; const SOURCE_LOCK_NAME: &str = "radroots.lib.source-lock.v1.toml"; const CONSUMER_MARKER: &str = ".radroots-consumer-root"; -const CATALOG_RELATIVE: &str = "contracts/crates/catalog.v1.toml"; +const CATALOG_RELATIVE: &str = "contracts/crates/catalog.v2.toml"; const REPOSITORY: &str = "https://github.com/radrootslabs/lib"; const ARCHITECTURE: &str = "radroots.crates.release.v2"; const VERSION: &str = "0.1.0-alpha"; @@ -93,8 +93,11 @@ impl ConsumerRoot { .map_err(|error| format!("consumer marker is not UTF-8: {error}"))? .trim() .to_owned(); - if !matches!(product.as_str(), "sdk" | "mobile" | "studio") { - return Err("consumer marker must contain sdk, mobile, or studio".to_owned()); + if !matches!( + product.as_str(), + "sdk" | "mobile" | "studio" | "myc" | "rhi" + ) { + return Err("consumer marker must contain sdk, mobile, studio, myc, or rhi".to_owned()); } let source_lock_path = canonical.join(SOURCE_LOCK_NAME); let source_lock = parse_source_lock(&source_lock_path)?; @@ -1084,7 +1087,7 @@ mod tests { fs::create_dir_all(source.join("contracts/crates")).expect("contracts"); fs::write( source.join(CATALOG_RELATIVE), - "schema = \"radroots.workspace.catalog.v1\"\n", + "schema = \"radroots.workspace.catalog.v2\"\n", ) .expect("catalog"); fs::create_dir_all(source.join("src")).expect("source crate"); @@ -1163,6 +1166,16 @@ mod tests { } #[test] + fn source_lock_accepts_services_without_creating_artifact_routes() { + for product in ["myc", "rhi"] { + let fixture = Fixture::new(product); + let consumer = ConsumerRoot::open(&fixture.consumer).expect("valid service consumer"); + assert_eq!(consumer.product, product); + assert!(validate_artifact_route(product, "linux", "rust").is_err()); + } + } + + #[test] fn source_lock_supports_a_contained_nested_lockfile() { let mut fixture = Fixture::new("studio"); let core = fixture.consumer.join("core"); diff --git a/tools/xtask/src/catalog.rs b/tools/xtask/src/catalog.rs @@ -12,14 +12,14 @@ use crate::contract::artifact_bundle::{ GeneratedArtifact, read_regular_file, with_artifact_bundle_transaction, }; -const CATALOG_RELATIVE: &str = "contracts/crates/catalog.v1.toml"; +const CATALOG_RELATIVE: &str = "contracts/crates/catalog.v2.toml"; const RELEASE_RELATIVE: &str = "contracts/crates/release.v2.toml"; const CONSOLIDATION_RELATIVE: &str = "contracts/consolidation/architecture.v1.toml"; const GROUPS_RELATIVE: &str = "contracts/crates/generated/package_groups.v1.toml"; const PLATFORMS_RELATIVE: &str = "contracts/crates/generated/platform_inventory.v1.toml"; const RELEASE_INVENTORY_RELATIVE: &str = "contracts/crates/generated/release_inventory.v2.toml"; const COVERAGE_RELATIVE: &str = "contracts/coverage.toml"; -const CATALOG_SCHEMA: &str = "radroots.workspace.catalog.v1"; +const CATALOG_SCHEMA: &str = "radroots.workspace.catalog.v2"; const RELEASE_ID: &str = "radroots.crates.release.v2"; const CONSOLIDATION_ID: &str = "radroots.rust.consolidation.v1"; const VERSION: &str = "0.1.0-alpha"; @@ -39,6 +39,7 @@ struct Catalog { package_count: usize, digest_algorithm: String, source_tree_digest_algorithm: String, + native_introduction_tree_digest_algorithm: String, source_provenance_policy: String, provenance_correction_contract: String, retired_packages: Vec<String>, @@ -60,10 +61,12 @@ struct CatalogPackage { groups: Vec<String>, owners: Vec<String>, permitted_dependency_tiers: Vec<String>, - source_repository: String, - source_revision: String, - source_path: String, - source_tree_sha256: String, + provenance_kind: String, + source_repository: Option<String>, + source_revision: Option<String>, + source_path: Option<String>, + source_tree_sha256: Option<String>, + introduction_tree_sha256: Option<String>, compatibility: Vec<String>, removal_gate: Option<String>, replaces: Vec<String>, @@ -82,6 +85,7 @@ struct ReleaseV2 { publication_authorized: bool, public_packages: Vec<String>, v1_artifact: Vec<V1Artifact>, + v1_retired_human_artifact: Vec<RetiredV1HumanArtifact>, } #[derive(Debug, Deserialize)] @@ -93,6 +97,13 @@ struct V1Artifact { #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] +struct RetiredV1HumanArtifact { + former_path: String, + sha256: String, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] struct ConsolidationV1 { schema_version: u16, consolidation_id: String, @@ -251,7 +262,7 @@ fn parse_toml<T: for<'de> Deserialize<'de>>(relative: &str, bytes: &[u8]) -> Res } fn validate_catalog(catalog: &Catalog) -> Result<(), String> { - if catalog.schema_version != 1 + if catalog.schema_version != 2 || catalog.schema != CATALOG_SCHEMA || catalog.architecture != RELEASE_ID || catalog.consolidation != CONSOLIDATION_ID @@ -263,7 +274,8 @@ fn validate_catalog(catalog: &Catalog) -> Result<(), String> { || catalog.package_count != catalog.package.len() || catalog.digest_algorithm != "sha256-raw-bytes-v1" || catalog.source_tree_digest_algorithm != "sha256-git-ls-tree-r-v1" - || catalog.source_provenance_policy != "immutable_after_source_retirement" + || catalog.native_introduction_tree_digest_algorithm != "sha256-git-tree-records-z-v1" + || catalog.source_provenance_policy != "imported_revision_tree_or_native_introduction_tree" || catalog.provenance_correction_contract != "approved_correction_record_required" { return Err("catalog identity, toolchain, or cardinality drifted".to_owned()); @@ -361,20 +373,7 @@ fn validate_catalog(catalog: &Catalog) -> Result<(), String> { { return Err(format!("GPL package {} has an invalid class", package.name)); } - if package.source_repository - != format!( - "https://github.com/radrootslabs/{}", - source_repository_name(&package.source_repository)? - ) - { - return Err(format!( - "package {} source repository is noncanonical", - package.name - )); - } - validate_oid(&package.source_revision, "source revision")?; - validate_relative_path(&package.source_path)?; - validate_sha256(&package.source_tree_sha256, "source tree digest")?; + validate_package_provenance(package)?; for replaced in &package.replaces { validate_package_identity(replaced, "replaced package")?; } @@ -466,6 +465,80 @@ fn validate_catalog(catalog: &Catalog) -> Result<(), String> { Ok(()) } +fn validate_package_provenance(package: &CatalogPackage) -> Result<(), String> { + match package.provenance_kind.as_str() { + "imported" => { + let source_repository = package.source_repository.as_deref().ok_or_else(|| { + format!("imported package {} lacks source repository", package.name) + })?; + let source_revision = package.source_revision.as_deref().ok_or_else(|| { + format!("imported package {} lacks source revision", package.name) + })?; + let source_path = package + .source_path + .as_deref() + .ok_or_else(|| format!("imported package {} lacks source path", package.name))?; + let source_tree_sha256 = package.source_tree_sha256.as_deref().ok_or_else(|| { + format!("imported package {} lacks source tree digest", package.name) + })?; + if package.introduction_tree_sha256.is_some() { + return Err(format!( + "imported package {} declares native introduction provenance", + package.name + )); + } + if source_repository + != format!( + "https://github.com/radrootslabs/{}", + source_repository_name(source_repository)? + ) + { + return Err(format!( + "package {} source repository is noncanonical", + package.name + )); + } + validate_oid(source_revision, "source revision")?; + validate_relative_path(source_path)?; + validate_sha256(source_tree_sha256, "source tree digest") + } + "native" => { + if package.source_repository.is_some() + || package.source_revision.is_some() + || package.source_path.is_some() + || package.source_tree_sha256.is_some() + { + return Err(format!( + "native package {} must not declare imported provenance", + package.name + )); + } + let introduction_tree_sha256 = + package.introduction_tree_sha256.as_deref().ok_or_else(|| { + format!( + "native package {} lacks introduction tree digest", + package.name + ) + })?; + validate_sha256(introduction_tree_sha256, "introduction tree digest")?; + if package.state != "active" + || package.publish + || package.visibility == "public_release" + { + return Err(format!( + "native package {} must be active and unpublished", + package.name + )); + } + Ok(()) + } + _ => Err(format!( + "package {} has unknown provenance kind {}", + package.name, package.provenance_kind + )), + } +} + fn validate_coverage_authority(catalog: &Catalog, coverage: &CoveragePolicy) -> Result<(), String> { let catalog_required = catalog .package @@ -502,7 +575,7 @@ fn validate_release( catalog: &Catalog, workspace_root: &Path, ) -> Result<(), String> { - if release.schema_version != 1 + if release.schema_version != 2 || release.spec_id != RELEASE_ID || release.status != "approved_not_published" || release.supersedes_without_mutation != "radroots.crates.release.v1" @@ -537,14 +610,24 @@ fn validate_release( } } let expected = BTreeSet::from([ - "docs/specs/radroots_crates_release_v1.dot", - "docs/specs/radroots_crates_release_v1.md", - "docs/specs/radroots_crates_release_v1.sha256", - "docs/specs/radroots_crates_release_v1.toml", - "docs/specs/radroots_crates_release_v1_inventory.csv", + "contracts/crates/release_v1/radroots_crates_release_v1.dot", + "contracts/crates/release_v1/radroots_crates_release_v1.sha256", + "contracts/crates/release_v1/radroots_crates_release_v1.toml", + "contracts/crates/release_v1/radroots_crates_release_v1_inventory.csv", ]); if paths != expected { - return Err("release v2 must pin every historical v1 authority artifact".to_owned()); + return Err("release v2 must pin every historical v1 machine artifact".to_owned()); + } + if release.v1_retired_human_artifact.len() != 1 { + return Err("release v2 must record the retired v1 human artifact".to_owned()); + } + let retired = &release.v1_retired_human_artifact[0]; + validate_relative_path(&retired.former_path)?; + validate_sha256(&retired.sha256, "retired v1 human artifact digest")?; + if retired.former_path != "docs/specs/radroots_crates_release_v1.md" + || retired.sha256 != "ea2c1f0f5c53fae56a247ae7519b065c9a0d62dafb998b75a48075f4a875b5eb" + { + return Err("release v2 retired v1 human artifact drifted".to_owned()); } Ok(()) } @@ -806,38 +889,204 @@ fn validate_active_source_provenance( catalog: &Catalog, workspace_root: &Path, ) -> Result<(), String> { - for package in catalog.package.iter().filter(|package| { - package.state == "active" - && package.source_repository == "https://github.com/radrootslabs/lib" - }) { - let output = Command::new("git") - .args([ - "ls-tree", - "-r", - &package.source_revision, - "--", - &package.source_path, - ]) - .current_dir(workspace_root) - .output() - .map_err(|error| format!("run git ls-tree for {}: {error}", package.name))?; - if !output.status.success() { - return Err(format!( - "source revision for {} is unavailable: {}", - package.name, - String::from_utf8_lossy(&output.stderr).trim() - )); - } - if sha256(&output.stdout) != package.source_tree_sha256 { - return Err(format!( - "source tree provenance drifted for {}", - package.name - )); + for package in catalog + .package + .iter() + .filter(|package| package.state == "active") + { + match package.provenance_kind.as_str() { + "imported" + if package.source_repository.as_deref() + == Some("https://github.com/radrootslabs/lib") => + { + validate_imported_source_provenance(package, workspace_root)?; + } + "native" => validate_native_source_provenance(package, workspace_root)?, + _ => {} } } Ok(()) } +fn validate_imported_source_provenance( + package: &CatalogPackage, + workspace_root: &Path, +) -> Result<(), String> { + let source_revision = package + .source_revision + .as_deref() + .ok_or_else(|| format!("imported package {} lacks source revision", package.name))?; + let source_path = package + .source_path + .as_deref() + .ok_or_else(|| format!("imported package {} lacks source path", package.name))?; + let expected = package + .source_tree_sha256 + .as_deref() + .ok_or_else(|| format!("imported package {} lacks source tree digest", package.name))?; + let output = Command::new("git") + .args(["ls-tree", "-r", source_revision, "--", source_path]) + .current_dir(workspace_root) + .output() + .map_err(|error| format!("run git ls-tree for {}: {error}", package.name))?; + if !output.status.success() { + return Err(format!( + "source revision for {} is unavailable: {}", + package.name, + String::from_utf8_lossy(&output.stderr).trim() + )); + } + if sha256(&output.stdout) != expected { + return Err(format!( + "source tree provenance drifted for {}", + package.name + )); + } + Ok(()) +} + +fn validate_native_source_provenance( + package: &CatalogPackage, + workspace_root: &Path, +) -> Result<(), String> { + let expected = package + .introduction_tree_sha256 + .as_deref() + .ok_or_else(|| format!("native package {} lacks introduction digest", package.name))?; + let introducing_commit = native_introducing_commit(workspace_root, &package.path)?; + let actual = if let Some(commit) = introducing_commit { + committed_tree_digest(workspace_root, &commit, &package.path)? + } else { + staged_tree_digest(workspace_root, &package.path)? + }; + if actual != expected { + return Err(format!( + "native introduction tree provenance drifted for {}", + package.name + )); + } + Ok(()) +} + +fn native_introducing_commit( + workspace_root: &Path, + package_path: &str, +) -> Result<Option<String>, String> { + let output = Command::new("git") + .args([ + "log", + "--format=%H", + "--diff-filter=A", + "--reverse", + "--no-renames", + "HEAD", + "--", + package_path, + ]) + .current_dir(workspace_root) + .output() + .map_err(|error| format!("derive native introduction commit: {error}"))?; + if !output.status.success() { + return Err(format!( + "derive native introduction commit for {package_path}: {}", + String::from_utf8_lossy(&output.stderr).trim() + )); + } + let stdout = std::str::from_utf8(&output.stdout) + .map_err(|error| format!("native introduction history is not UTF-8: {error}"))?; + let Some(commit) = stdout.lines().next() else { + return Ok(None); + }; + validate_oid(commit, "native introducing commit")?; + Ok(Some(commit.to_owned())) +} + +fn committed_tree_digest( + workspace_root: &Path, + commit: &str, + package_path: &str, +) -> Result<String, String> { + let output = Command::new("git") + .args(["ls-tree", "-r", "-z", commit, "--", package_path]) + .current_dir(workspace_root) + .output() + .map_err(|error| format!("read native introduction tree: {error}"))?; + if !output.status.success() { + return Err(format!( + "read native introduction tree for {package_path}: {}", + String::from_utf8_lossy(&output.stderr).trim() + )); + } + if output.stdout.is_empty() { + return Err(format!( + "native introducing commit {commit} has no tree at {package_path}" + )); + } + Ok(sha256(&output.stdout)) +} + +fn staged_tree_digest(workspace_root: &Path, package_path: &str) -> Result<String, String> { + let output = Command::new("git") + .args(["ls-files", "--stage", "-z", "--", package_path]) + .current_dir(workspace_root) + .output() + .map_err(|error| format!("read staged native tree: {error}"))?; + if !output.status.success() { + return Err(format!( + "read staged native tree for {package_path}: {}", + String::from_utf8_lossy(&output.stderr).trim() + )); + } + let records = canonical_staged_tree_records(&output.stdout)?; + if records.is_empty() { + return Err(format!( + "native package {package_path} has no committed introduction or staged tree" + )); + } + Ok(sha256(&records)) +} + +fn canonical_staged_tree_records(input: &[u8]) -> Result<Vec<u8>, String> { + let mut output = Vec::new(); + for record in input + .split(|byte| *byte == 0) + .filter(|record| !record.is_empty()) + { + let tab = record + .iter() + .position(|byte| *byte == b'\t') + .ok_or_else(|| "staged tree record lacks a path separator".to_owned())?; + let header = std::str::from_utf8(&record[..tab]) + .map_err(|error| format!("staged tree record header is not UTF-8: {error}"))?; + let fields = header.split(' ').collect::<Vec<_>>(); + if fields.len() != 3 || fields[2] != "0" { + return Err("staged native tree must contain only stage-zero records".to_owned()); + } + let mode = fields[0]; + let oid = fields[1]; + if mode.len() != 6 || !mode.bytes().all(|byte| matches!(byte, b'0'..=b'7')) { + return Err("staged native tree contains an invalid mode".to_owned()); + } + validate_oid(oid, "staged native object")?; + if oid.bytes().all(|byte| byte == b'0') { + return Err("staged native tree contains an intent-to-add object".to_owned()); + } + if record[tab + 1..].is_empty() { + return Err("staged native tree contains an empty path".to_owned()); + } + let object_type = if mode == "160000" { "commit" } else { "blob" }; + output.extend_from_slice(mode.as_bytes()); + output.push(b' '); + output.extend_from_slice(object_type.as_bytes()); + output.push(b' '); + output.extend_from_slice(oid.as_bytes()); + output.push(b'\t'); + output.extend_from_slice(&record[tab + 1..]); + output.push(0); + } + Ok(output) +} + fn render_projections(catalog: &Catalog, digest: &str) -> Vec<GeneratedArtifact> { let mut groups = BTreeMap::<&str, Vec<&str>>::new(); let mut active_groups = BTreeMap::<&str, Vec<&str>>::new(); @@ -1095,11 +1344,60 @@ fn sha256(bytes: &[u8]) -> String { #[cfg(test)] mod tests { use super::*; + use tempfile::TempDir; fn checked_in_catalog() -> Catalog { parse_file(&crate::workspace_root(), CATALOG_RELATIVE).expect("checked-in catalog") } + fn git(root: &Path, args: &[&str]) { + let output = Command::new("git") + .args(args) + .current_dir(root) + .output() + .expect("run git fixture command"); + assert!( + output.status.success(), + "git {} failed: {}", + args.join(" "), + String::from_utf8_lossy(&output.stderr) + ); + } + + fn git_fixture() -> TempDir { + let root = tempfile::tempdir().expect("temporary git fixture"); + git(root.path(), &["init", "--quiet"]); + git(root.path(), &["config", "user.name", "Catalog Test"]); + git( + root.path(), + &["config", "user.email", "catalog-test@example.invalid"], + ); + fs::write(root.path().join("README.md"), "# Fixture\n").expect("baseline file"); + git(root.path(), &["add", "README.md"]); + git(root.path(), &["commit", "--quiet", "-m", "baseline"]); + root + } + + fn native_package<'a>( + catalog: &'a mut Catalog, + path: &str, + digest: &str, + ) -> &'a mut CatalogPackage { + let package = catalog + .package + .iter_mut() + .find(|package| package.name == "radroots_nostrdb") + .expect("private package fixture"); + package.path = path.to_owned(); + package.provenance_kind = "native".to_owned(); + package.source_repository = None; + package.source_revision = None; + package.source_path = None; + package.source_tree_sha256 = None; + package.introduction_tree_sha256 = Some(digest.to_owned()); + package + } + #[test] fn public_inventory_is_exact() { assert_eq!(expected_public_packages().len(), 19); @@ -1117,6 +1415,114 @@ mod tests { } #[test] + fn provenance_kinds_are_disjoint_and_native_is_unpublished() { + let mut catalog = checked_in_catalog(); + let imported = catalog + .package + .iter_mut() + .find(|package| package.name == "radroots_core") + .expect("imported fixture"); + imported.source_revision = None; + assert!(validate_package_provenance(imported).is_err()); + + let mut catalog = checked_in_catalog(); + let native = native_package(&mut catalog, "crates/nostrdb", &"a".repeat(64)); + assert!(validate_package_provenance(native).is_ok()); + native.source_revision = Some("a".repeat(40)); + assert!(validate_package_provenance(native).is_err()); + + let mut catalog = checked_in_catalog(); + let public = catalog + .package + .iter_mut() + .find(|package| package.name == "radroots_core") + .expect("public fixture"); + public.provenance_kind = "native".to_owned(); + public.source_repository = None; + public.source_revision = None; + public.source_path = None; + public.source_tree_sha256 = None; + public.introduction_tree_sha256 = Some("a".repeat(64)); + assert!(validate_package_provenance(public).is_err()); + } + + #[test] + fn native_provenance_matches_staged_then_derived_introduction_tree() { + let root = git_fixture(); + let package_root = root.path().join("crates/native_fixture"); + fs::create_dir_all(&package_root).expect("native package root"); + fs::write( + package_root.join("Cargo.toml"), + "[package]\nname='fixture'\n", + ) + .expect("native manifest"); + fs::write(package_root.join("lib.rs"), "pub fn initial() {}\n").expect("native source"); + git(root.path(), &["add", "crates/native_fixture"]); + + let staged = + staged_tree_digest(root.path(), "crates/native_fixture").expect("staged tree digest"); + assert_eq!( + native_introducing_commit(root.path(), "crates/native_fixture") + .expect("precommit history"), + None + ); + let mut catalog = checked_in_catalog(); + let package = native_package(&mut catalog, "crates/native_fixture", &staged); + validate_native_source_provenance(package, root.path()).expect("staged provenance"); + + fs::write(package_root.join("lib.rs"), "unstaged change\n").expect("unstaged change"); + validate_native_source_provenance(package, root.path()) + .expect("only the staged introduction is authoritative before commit"); + git( + root.path(), + &["commit", "--quiet", "-m", "add native package"], + ); + + let introducing = native_introducing_commit(root.path(), "crates/native_fixture") + .expect("committed history") + .expect("introducing commit"); + assert_eq!( + committed_tree_digest(root.path(), &introducing, "crates/native_fixture") + .expect("committed introduction digest"), + staged + ); + validate_native_source_provenance(package, root.path()) + .expect("derived committed provenance"); + + git(root.path(), &["add", "crates/native_fixture/lib.rs"]); + git( + root.path(), + &["commit", "--quiet", "-m", "change native package"], + ); + validate_native_source_provenance(package, root.path()) + .expect("later changes do not rewrite introduction provenance"); + package.introduction_tree_sha256 = Some("b".repeat(64)); + assert!(validate_native_source_provenance(package, root.path()).is_err()); + } + + #[test] + fn native_precommit_provenance_requires_stage_zero_index_records() { + let root = git_fixture(); + fs::create_dir_all(root.path().join("crates/native_fixture")).expect("native package root"); + fs::write( + root.path().join("crates/native_fixture/lib.rs"), + "pub fn unstaged() {}\n", + ) + .expect("unstaged native source"); + assert!(staged_tree_digest(root.path(), "crates/native_fixture").is_err()); + + let oid = "a".repeat(40); + let conflicted = format!("100644 {oid} 1\tcrates/native_fixture/lib.rs\0"); + assert!(canonical_staged_tree_records(conflicted.as_bytes()).is_err()); + + let intent_to_add = format!( + "100644 {} 0\tcrates/native_fixture/lib.rs\0", + "0".repeat(40) + ); + assert!(canonical_staged_tree_records(intent_to_add.as_bytes()).is_err()); + } + + #[test] fn generated_maps_are_sorted_and_digest_bound() { let mut values = BTreeMap::from([("sdk", vec!["z", "a", "a"])]); sort_map_values(&mut values); diff --git a/tools/xtask/src/contract.rs b/tools/xtask/src/contract.rs @@ -155,7 +155,7 @@ const KNOWLEDGE_BETA_CONTRACT_IDS: [&str; 3] = [ "radroots.knowledge.change_proposal.v1", "radroots.knowledge.contribution_attestation.v1", ]; -const EVENT_BOUNDARY_MATRIX_ENV: &str = "RADROOTS_EVENT_BOUNDARY_MATRIX"; +const EVENT_BOUNDARY_MATRIX_RELATIVE: &str = "contracts/event_boundary_matrix.md"; const COVERAGE_REQUIRED_THRESHOLD: f64 = 90.0; const COVERAGE_REQUIRED_THRESHOLD_LABEL: &str = "90/90/90/90"; const COVERAGE_REPORT_EPSILON: f64 = 0.000_001; @@ -1271,11 +1271,6 @@ const FOOD_AVAILABILITY_VECTOR_EXPECTATIONS: [(&str, &str); 40] = [ "food_availability.validate_revision.valid", ), ]; -const EVENT_BOUNDARY_MATRIX_RELATIVES: [&str; 2] = [ - "contracts/event_boundary_matrix.md", - "docs/platform/canonical/open_source/radroots_v1_spec/02_public_contract_and_runtime/08_event_boundary_matrix.md", -]; - #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct ContractManifest { @@ -3212,37 +3207,16 @@ fn parse_json<T: for<'de> Deserialize<'de>>(path: &Path) -> Result<T, String> { } } -fn resolve_event_boundary_matrix_path_with_override( - workspace_root: &Path, - event_boundary_override: Option<PathBuf>, -) -> Result<PathBuf, String> { - if let Some(path) = event_boundary_override { - if !path.is_file() { - return Err(format!( - "{EVENT_BOUNDARY_MATRIX_ENV} points to a missing canonical event matrix file: {}", - path.display() - )); - } - return Ok(path); +fn resolve_event_boundary_matrix_path(workspace_root: &Path) -> Result<PathBuf, String> { + let candidate = workspace_root.join(EVENT_BOUNDARY_MATRIX_RELATIVE); + if candidate.is_file() { + return Ok(candidate); } - - for ancestor in workspace_root.ancestors() { - for relative in EVENT_BOUNDARY_MATRIX_RELATIVES { - let candidate = ancestor.join(relative); - if candidate.is_file() { - return Ok(candidate); - } - } - } - resolve_missing_event_boundary_matrix_path(workspace_root) } fn missing_event_boundary_matrix_error() -> String { - format!( - "canonical event matrix not found; set {EVENT_BOUNDARY_MATRIX_ENV} or provide one of: {}", - EVENT_BOUNDARY_MATRIX_RELATIVES.join(", ") - ) + format!("canonical event matrix not found at {EVENT_BOUNDARY_MATRIX_RELATIVE}") } #[cfg(not(test))] @@ -3389,13 +3363,11 @@ fn validate_event_boundary_source_witness( Ok(()) } -fn validate_canonical_event_boundary_with_override( +fn validate_canonical_event_boundary_at_path( workspace_root: &Path, - event_boundary_override: Option<PathBuf>, + matrix_path: &Path, ) -> Result<(), String> { - let matrix_path = - resolve_event_boundary_matrix_path_with_override(workspace_root, event_boundary_override)?; - let rows = parse_event_boundary_matrix(&matrix_path)?; + let rows = parse_event_boundary_matrix(matrix_path)?; let expected_domains = CANONICAL_EVENT_BOUNDARY_EXPECTATIONS .iter() .map(|row| row.domain.to_string()) @@ -3460,7 +3432,8 @@ fn validate_canonical_event_boundary_with_override( } pub fn validate_canonical_event_boundary(workspace_root: &Path) -> Result<(), String> { - validate_canonical_event_boundary_with_override(workspace_root, None) + let matrix_path = resolve_event_boundary_matrix_path(workspace_root)?; + validate_canonical_event_boundary_at_path(workspace_root, &matrix_path) } fn contract_root(workspace_root: &Path) -> PathBuf { @@ -3812,7 +3785,8 @@ fn validate_workspace_version_lockstep( ) -> Result<(), String> { let workspace_manifest = parse_toml::<WorkspaceVersionCargoManifest>(&workspace_root.join("Cargo.toml"))?; - let architecture_path = workspace_root.join("docs/specs/radroots_crates_release_v1.toml"); + let architecture_path = + workspace_root.join("contracts/crates/release_v1/radroots_crates_release_v1.toml"); let governed_version = if architecture_path.is_file() { parse_toml::<CratesReleaseArchitecture>(&architecture_path)? .repositories @@ -8533,7 +8507,8 @@ fn validate_v1_release_policy( return Ok(None); } - let architecture_path = workspace_root.join("docs/specs/radroots_crates_release_v1.toml"); + let architecture_path = + workspace_root.join("contracts/crates/release_v1/radroots_crates_release_v1.toml"); let architecture = parse_toml::<CratesReleaseArchitecture>(&architecture_path)?; let expected_approved = collect_unique_set( &architecture @@ -9583,7 +9558,7 @@ mod tests { fn create_synthetic_workspace(prefix: &str) -> PathBuf { let root = temp_root(prefix); write_file( - &root.join("docs/specs/radroots_crates_release_v1.toml"), + &root.join("contracts/crates/release_v1/radroots_crates_release_v1.toml"), r#"spec_id = "radroots.crates.release.v1" package_count = 2 @@ -11023,7 +10998,7 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"] assert!(lock_error.contains("Cargo.lock package radroots_a version")); write_file( - &root.join("docs/specs/radroots_crates_release_v1.toml"), + &root.join("contracts/crates/release_v1/radroots_crates_release_v1.toml"), r#"spec_id = "radroots.crates.release.v1" package_count = 0 package = [] @@ -11532,8 +11507,7 @@ packages = [] #[test] fn canonical_event_boundary_reports_row_drift() { let root = workspace_root(); - let matrix_path = - resolve_event_boundary_matrix_path_with_override(&root, None).expect("matrix path"); + let matrix_path = resolve_event_boundary_matrix_path(&root).expect("matrix path"); let raw = fs::read_to_string(&matrix_path).expect("read matrix"); let drifted = raw.replacen( "| message | 14 | Message |", @@ -11544,7 +11518,7 @@ packages = [] let override_path = temp.join("spec-coverage.md"); write_file(&override_path, &drifted); - let err = validate_canonical_event_boundary_with_override(&root, Some(override_path)) + let err = validate_canonical_event_boundary_at_path(&root, &override_path) .expect_err("message kind drift should fail"); assert!(err.contains("message kind drift")); @@ -11554,8 +11528,7 @@ packages = [] #[test] fn canonical_event_boundary_rejects_deletion_operation_drift() { let root = workspace_root(); - let matrix_path = - resolve_event_boundary_matrix_path_with_override(&root, None).expect("matrix path"); + let matrix_path = resolve_event_boundary_matrix_path(&root).expect("matrix path"); let raw = fs::read_to_string(&matrix_path).expect("read matrix"); let (preamble, table) = raw .split_once("## Coverage matrix") @@ -11572,7 +11545,7 @@ packages = [] let override_path = temp.join("spec-coverage.md"); write_file(&override_path, &drifted); - let error = validate_canonical_event_boundary_with_override(&root, Some(override_path)) + let error = validate_canonical_event_boundary_at_path(&root, &override_path) .expect_err("deletion operation drift must fail"); assert!(error.contains("deletion_request rpc drift"), "{error}"); @@ -11580,6 +11553,22 @@ packages = [] } #[test] + fn event_boundary_never_falls_back_to_parent_human_documentation() { + let parent = temp_root("event_boundary_parent_docs"); + let capsule = parent.join("oss/lib"); + fs::create_dir_all(&capsule).expect("capsule root"); + let parent_doc = parent.join( + "docs/platform/canonical/open_source/radroots_v1_spec/02_public_contract_and_runtime/08_event_boundary_matrix.md", + ); + write_file(&parent_doc, &synthetic_event_boundary_matrix()); + + let error = resolve_event_boundary_matrix_path(&capsule) + .expect_err("parent human documentation must not become contract input"); + assert!(error.contains(EVENT_BOUNDARY_MATRIX_RELATIVE)); + let _ = fs::remove_dir_all(parent); + } + + #[test] fn validate_synthetic_operation_contract_bundle() { let root = create_synthetic_workspace("operation_contract_bundle"); add_operation_contract_files(&root); @@ -12884,7 +12873,7 @@ crates = ["radroots_a"] architecture.push_str(&format!("\n[[package]]\nname = \"{name}\"\n")); } write_file( - &root.join("docs/specs/radroots_crates_release_v1.toml"), + &root.join("contracts/crates/release_v1/radroots_crates_release_v1.toml"), &architecture, ); diff --git a/tools/xtask/src/hygiene.rs b/tools/xtask/src/hygiene.rs @@ -1,5 +1,130 @@ +use serde::Deserialize; +use std::collections::HashSet; use std::fs; +use std::io::Read; use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +const PROTOTYPE_CONTRACT_CONFIG_PATH: &str = "contracts/hygiene/prototype-contracts.v1.toml"; +const PROTOTYPE_CONTRACT_SCHEMA: &str = "radroots.prototype-contract-source-guard.v1"; +const PROTOTYPE_MAX_CONFIG_BYTES: u64 = 1024 * 1024; +const PROTOTYPE_MAX_CONFIG_STRING_BYTES: usize = 1024; +const PROTOTYPE_MAX_CONFIG_PATHS: usize = 256; +const PROTOTYPE_MAX_CONFIG_EXTENSIONS: usize = 128; +const PROTOTYPE_MAX_CONFIG_PATTERNS: usize = 1024; +const PROTOTYPE_MAX_CONFIG_ALLOWLIST: usize = 4096; +const PROTOTYPE_MAX_REASON_BYTES: usize = 512; +const PROTOTYPE_MAX_CONFIGURED_SCAN_ENTRIES: usize = 100_000; +const PROTOTYPE_MAX_CONFIGURED_INVENTORY_BYTES: usize = 64 * 1024 * 1024; +const PROTOTYPE_MAX_CONFIGURED_FILE_BYTES: u64 = 64 * 1024 * 1024; +const PROTOTYPE_MAX_CONFIGURED_MATCHES: usize = 100_000; +const PROTOTYPE_MAX_CONFIGURED_REPORT_LINES: usize = 10_000; +const PROTOTYPE_MAX_GIT_STDERR_BYTES: usize = 8 * 1024; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +enum PrototypeGuardMode { + ReportOnly, + Strict, +} + +impl PrototypeGuardMode { + const fn as_str(self) -> &'static str { + match self { + Self::ReportOnly => "report_only", + Self::Strict => "strict", + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +enum PrototypeMatchKind { + Substring, + WordPrefix, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct PrototypeGuardConfig { + schema: String, + mode: PrototypeGuardMode, + scan: PrototypeScanConfig, + limits: PrototypeGuardLimits, + pattern: Vec<PrototypePattern>, + #[serde(default)] + allow: Vec<PrototypeAllow>, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct PrototypeScanConfig { + roots: Vec<String>, + path_roots: Vec<String>, + path_excludes: Vec<String>, + extensions: Vec<String>, + extensionless_names: Vec<String>, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct PrototypeGuardLimits { + max_scan_entries: usize, + max_inventory_bytes: usize, + max_file_bytes: u64, + max_matches: usize, + max_reported_findings: usize, + max_reported_allowlisted: usize, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct PrototypePattern { + id: String, + needle: String, + match_kind: PrototypeMatchKind, + description: String, + #[serde(default)] + match_path: bool, + #[serde(default)] + path_prefixes: Vec<String>, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct PrototypeAllow { + pattern_id: String, + path: String, + line_contains: String, + reason: String, +} + +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +enum PrototypeFindingOrigin { + Path, + Content, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct PrototypeFinding { + pattern_id: String, + path: String, + origin: PrototypeFindingOrigin, + line: Option<usize>, + excerpt: String, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct PrototypeAllowedMatch { + finding: PrototypeFinding, + reason: String, +} + +#[derive(Debug, Default, Eq, PartialEq)] +struct PrototypeGuardReport { + findings: Vec<PrototypeFinding>, + allowed: Vec<PrototypeAllowedMatch>, +} const BINDING_DEPENDENCIES: &[&str] = &[ "serde-wasm-bindgen", @@ -125,10 +250,1044 @@ const RETIRED_LISTING_CONTRACT_ID: &str = "radroots.listing.published.v1"; pub fn run(args: &[String], root: &Path) -> Result<(), String> { match args.first().map(String::as_str) { Some("forbidden-identifiers") => validate_forbidden_identifiers(root), + Some("prototype-contracts") => run_prototype_contract_guard(&args[1..], root), _ => Err("unknown hygiene subcommand".to_string()), } } +fn run_prototype_contract_guard(args: &[String], root: &Path) -> Result<(), String> { + let (config_path, mode_override) = parse_prototype_guard_args(args)?; + let config = load_prototype_guard_config(root, &config_path)?; + let mode = mode_override.unwrap_or(config.mode); + let report = scan_prototype_contracts(root, &config)?; + print_prototype_guard_report(mode, &report, &config.limits); + if mode == PrototypeGuardMode::Strict && !report.findings.is_empty() { + return Err(format!( + "prototype contract source guard found {} non-allowlisted match(es)", + report.findings.len() + )); + } + Ok(()) +} + +fn parse_prototype_guard_args( + args: &[String], +) -> Result<(PathBuf, Option<PrototypeGuardMode>), String> { + let mut config_path = PathBuf::from(PROTOTYPE_CONTRACT_CONFIG_PATH); + let mut mode = None; + let mut index = 0; + while index < args.len() { + match args[index].as_str() { + "--config" => { + let Some(value) = args.get(index + 1) else { + return Err("prototype-contracts --config requires a path".to_string()); + }; + validate_repo_relative_path(value, "prototype guard config path")?; + config_path = PathBuf::from(value); + index += 2; + } + "--strict" => { + set_prototype_mode(&mut mode, PrototypeGuardMode::Strict)?; + index += 1; + } + "--report-only" => { + set_prototype_mode(&mut mode, PrototypeGuardMode::ReportOnly)?; + index += 1; + } + value => return Err(format!("unknown prototype-contracts argument: {value}")), + } + } + Ok((config_path, mode)) +} + +fn set_prototype_mode( + current: &mut Option<PrototypeGuardMode>, + requested: PrototypeGuardMode, +) -> Result<(), String> { + if current.replace(requested).is_some() { + return Err("prototype-contracts accepts only one mode override".to_string()); + } + Ok(()) +} + +fn load_prototype_guard_config( + root: &Path, + relative_path: &Path, +) -> Result<PrototypeGuardConfig, String> { + validate_repo_relative_path( + &relative_path.to_string_lossy(), + "prototype guard config path", + )?; + let path = root.join(relative_path); + reject_symlinked_path_components(root, relative_path, "prototype guard config path")?; + let metadata = fs::symlink_metadata(&path) + .map_err(|error| format!("inspect prototype guard config {}: {error}", path.display()))?; + if metadata.file_type().is_symlink() || !metadata.is_file() { + return Err(format!( + "prototype guard config must be a regular non-symlink file: {}", + path.display() + )); + } + let display = path.display().to_string(); + let source = read_bounded_prototype_input(&path, &display, PROTOTYPE_MAX_CONFIG_BYTES)?; + let config: PrototypeGuardConfig = toml::from_str(&source) + .map_err(|error| format!("parse prototype guard config {}: {error}", path.display()))?; + validate_prototype_guard_config(&config)?; + Ok(config) +} + +fn validate_prototype_guard_config(config: &PrototypeGuardConfig) -> Result<(), String> { + if config.schema != PROTOTYPE_CONTRACT_SCHEMA { + return Err(format!( + "prototype guard schema must be {PROTOTYPE_CONTRACT_SCHEMA}" + )); + } + if config.scan.roots.is_empty() { + return Err("prototype guard scan roots must not be empty".to_string()); + } + if config.scan.path_roots.is_empty() { + return Err("prototype guard path scan roots must not be empty".to_string()); + } + if config.scan.extensions.is_empty() { + return Err("prototype guard extensions must not be empty".to_string()); + } + if config.limits.max_scan_entries == 0 + || config.limits.max_inventory_bytes == 0 + || config.limits.max_file_bytes == 0 + || config.limits.max_matches == 0 + || config.limits.max_reported_findings == 0 + || config.limits.max_reported_allowlisted == 0 + || config.limits.max_reported_findings > config.limits.max_matches + || config.limits.max_reported_allowlisted > config.limits.max_matches + || config.limits.max_scan_entries > PROTOTYPE_MAX_CONFIGURED_SCAN_ENTRIES + || config.limits.max_inventory_bytes > PROTOTYPE_MAX_CONFIGURED_INVENTORY_BYTES + || config.limits.max_file_bytes > PROTOTYPE_MAX_CONFIGURED_FILE_BYTES + || config.limits.max_matches > PROTOTYPE_MAX_CONFIGURED_MATCHES + || config.limits.max_reported_findings > PROTOTYPE_MAX_CONFIGURED_REPORT_LINES + || config.limits.max_reported_allowlisted > PROTOTYPE_MAX_CONFIGURED_REPORT_LINES + { + return Err( + "prototype guard limits must be positive, report limits must not exceed max_matches, and every value must remain within the compiled resource ceiling" + .to_string(), + ); + } + if config.pattern.is_empty() { + return Err("prototype guard patterns must not be empty".to_string()); + } + if config.scan.roots.len() > PROTOTYPE_MAX_CONFIG_PATHS + || config.scan.path_roots.len() > PROTOTYPE_MAX_CONFIG_PATHS + || config.scan.path_excludes.len() > PROTOTYPE_MAX_CONFIG_PATHS + || config.scan.extensions.len() > PROTOTYPE_MAX_CONFIG_EXTENSIONS + || config.scan.extensionless_names.len() > PROTOTYPE_MAX_CONFIG_EXTENSIONS + || config.pattern.len() > PROTOTYPE_MAX_CONFIG_PATTERNS + || config.allow.len() > PROTOTYPE_MAX_CONFIG_ALLOWLIST + { + return Err("prototype guard configuration collection exceeds compiled limit".to_string()); + } + + let mut roots = HashSet::new(); + for root in &config.scan.roots { + validate_repo_relative_path(root, "prototype guard scan root")?; + if !roots.insert(root.as_str()) { + return Err(format!("duplicate prototype guard scan root: {root}")); + } + } + + let mut path_roots = HashSet::new(); + for root in &config.scan.path_roots { + validate_repo_relative_or_root_path(root, "prototype guard path scan root")?; + if !path_roots.insert(root.as_str()) { + return Err(format!("duplicate prototype guard path scan root: {root}")); + } + } + + let mut path_excludes = HashSet::new(); + for excluded in &config.scan.path_excludes { + validate_repo_relative_path(excluded, "prototype guard path exclusion")?; + if !config + .scan + .path_roots + .iter() + .any(|root| root == "." || repository_path_is_within(excluded, root)) + { + return Err(format!( + "prototype guard path exclusion is outside path scan roots: {excluded}" + )); + } + if !path_excludes.insert(excluded.as_str()) { + return Err(format!( + "duplicate prototype guard path exclusion: {excluded}" + )); + } + } + + let mut extensions = HashSet::new(); + for extension in &config.scan.extensions { + if extension.is_empty() + || extension.len() > 32 + || extension.starts_with('.') + || !extension + .chars() + .all(|character| character.is_ascii_alphanumeric()) + { + return Err(format!( + "invalid prototype guard extension (omit the dot): {extension:?}" + )); + } + if !extensions.insert(extension.as_str()) { + return Err(format!("duplicate prototype guard extension: {extension}")); + } + } + + let mut extensionless_names = HashSet::new(); + for name in &config.scan.extensionless_names { + if name.is_empty() + || name.len() > 128 + || !name + .chars() + .all(|character| character.is_ascii_alphanumeric() || "._-".contains(character)) + { + return Err(format!( + "invalid prototype guard extensionless file name: {name:?}" + )); + } + if !extensionless_names.insert(name.as_str()) { + return Err(format!( + "duplicate prototype guard extensionless file name: {name}" + )); + } + } + + let mut pattern_ids = HashSet::new(); + for pattern in &config.pattern { + if pattern.id.is_empty() + || pattern.id.len() > 64 + || !pattern.id.chars().all(|character| { + character.is_ascii_lowercase() || character.is_ascii_digit() || character == '-' + }) + { + return Err(format!( + "invalid prototype guard pattern id: {:?}", + pattern.id + )); + } + if !pattern_ids.insert(pattern.id.as_str()) { + return Err(format!( + "duplicate prototype guard pattern id: {}", + pattern.id + )); + } + if pattern.needle.is_empty() + || pattern.needle.len() > PROTOTYPE_MAX_CONFIG_STRING_BYTES + || pattern.needle.chars().any(char::is_control) + { + return Err(format!( + "prototype guard pattern {} has an invalid needle", + pattern.id + )); + } + if pattern.match_kind == PrototypeMatchKind::WordPrefix && !pattern.needle.is_ascii() { + return Err(format!( + "prototype guard word-prefix pattern {} must use an ASCII needle", + pattern.id + )); + } + if pattern.description.trim().is_empty() + || pattern.description.len() > PROTOTYPE_MAX_CONFIG_STRING_BYTES + || pattern.description.chars().any(char::is_control) + { + return Err(format!( + "prototype guard pattern {} requires a description", + pattern.id + )); + } + let mut prefixes = HashSet::new(); + for prefix in &pattern.path_prefixes { + validate_repo_relative_path(prefix, "prototype guard pattern path prefix")?; + if !config + .scan + .roots + .iter() + .any(|root| repository_path_is_within(prefix, root)) + { + return Err(format!( + "prototype guard pattern {} path prefix is outside scan roots: {prefix}", + pattern.id + )); + } + if !prefixes.insert(prefix.as_str()) { + return Err(format!( + "duplicate path prefix for prototype guard pattern {}: {prefix}", + pattern.id + )); + } + } + } + + let mut allow_keys = HashSet::new(); + for allowed in &config.allow { + if !pattern_ids.contains(allowed.pattern_id.as_str()) { + return Err(format!( + "prototype guard allowlist references unknown pattern: {:?}", + allowed.pattern_id + )); + } + validate_repo_relative_path(&allowed.path, "prototype guard allowlist path")?; + if !config + .scan + .roots + .iter() + .any(|root| repository_path_is_within(&allowed.path, root)) + { + return Err(format!( + "prototype guard allowlist path is outside scan roots: {}", + allowed.path + )); + } + let pattern = config + .pattern + .iter() + .find(|pattern| pattern.id == allowed.pattern_id) + .expect("validated pattern id must resolve"); + if !pattern.path_prefixes.is_empty() + && !pattern + .path_prefixes + .iter() + .any(|prefix| repository_path_is_within(&allowed.path, prefix)) + { + return Err(format!( + "prototype guard allowlist path {} is outside pattern {} path prefixes", + allowed.path, allowed.pattern_id + )); + } + if allowed.line_contains.is_empty() + || allowed.line_contains.len() > PROTOTYPE_MAX_CONFIG_STRING_BYTES + || allowed.line_contains.chars().any(char::is_control) + { + return Err(format!( + "prototype guard allowlist for {} requires one line fragment", + allowed.pattern_id + )); + } + if allowed.reason.trim().is_empty() + || allowed.reason.len() > PROTOTYPE_MAX_REASON_BYTES + || allowed.reason.chars().any(char::is_control) + { + return Err(format!( + "prototype guard allowlist for {} requires a reason", + allowed.pattern_id + )); + } + let key = ( + allowed.pattern_id.as_str(), + allowed.path.as_str(), + allowed.line_contains.as_str(), + ); + if !allow_keys.insert(key) { + return Err(format!( + "duplicate prototype guard allowlist entry: {} {}", + allowed.pattern_id, allowed.path + )); + } + } + Ok(()) +} + +fn validate_repo_relative_path(value: &str, label: &str) -> Result<(), String> { + let path = Path::new(value); + if value.is_empty() + || value.len() > PROTOTYPE_MAX_CONFIG_STRING_BYTES + || value.chars().any(char::is_control) + || value.contains('\\') + || value.contains(':') + || path.is_absolute() + || path + .components() + .any(|component| !matches!(component, std::path::Component::Normal(_))) + { + return Err(format!( + "{label} must be a normalized repository-relative path: {value:?}" + )); + } + Ok(()) +} + +fn validate_repo_relative_or_root_path(value: &str, label: &str) -> Result<(), String> { + if value == "." { + return Ok(()); + } + validate_repo_relative_path(value, label) +} + +fn reject_symlinked_path_components( + root: &Path, + relative_path: &Path, + label: &str, +) -> Result<(), String> { + let mut candidate = root.to_path_buf(); + for component in relative_path.components() { + let std::path::Component::Normal(component) = component else { + return Err(format!( + "{label} must contain only normalized path components: {}", + relative_path.display() + )); + }; + candidate.push(component); + let metadata = fs::symlink_metadata(&candidate).map_err(|error| { + format!("inspect {label} component {}: {error}", candidate.display()) + })?; + if metadata.file_type().is_symlink() { + return Err(format!( + "{label} must not contain a symlinked component: {}", + candidate.display() + )); + } + } + Ok(()) +} + +fn repository_path_is_within(path: &str, prefix: &str) -> bool { + path == prefix + || path + .strip_prefix(prefix) + .is_some_and(|rest| rest.starts_with('/')) +} + +fn scan_prototype_contracts( + root: &Path, + config: &PrototypeGuardConfig, +) -> Result<PrototypeGuardReport, String> { + let extensions: HashSet<&str> = config.scan.extensions.iter().map(String::as_str).collect(); + let extensionless_names: HashSet<&str> = config + .scan + .extensionless_names + .iter() + .map(String::as_str) + .collect(); + let mut inputs = PrototypeInputs::default(); + for relative_root in &config.scan.roots { + reject_symlinked_path_components( + root, + Path::new(relative_root), + "prototype guard scan root", + )?; + } + for relative_root in &config.scan.path_roots { + let relative_path = Path::new(relative_root); + if relative_root != "." { + reject_symlinked_path_components( + root, + relative_path, + "prototype guard path scan root", + )?; + } + } + if let Some(governed_paths) = git_governed_paths( + root, + config.limits.max_scan_entries, + config.limits.max_inventory_bytes, + )? { + for candidate in governed_paths { + let relative = prototype_display_path(root, &candidate)?; + if path_is_excluded(&relative, &config.scan.path_excludes) { + continue; + } + if path_is_within_any_root(&relative, &config.scan.path_roots) { + inputs.paths.push(candidate.clone()); + } + if path_is_within_any_root(&relative, &config.scan.roots) + && is_prototype_text_input(&candidate, &extensions, &extensionless_names) + { + inputs.files.push(candidate); + } + } + } else { + for relative_root in &config.scan.roots { + collect_prototype_inputs( + root, + &root.join(relative_root), + &extensions, + &extensionless_names, + &config.scan.path_excludes, + config.limits.max_scan_entries, + &mut inputs, + )?; + } + for relative_root in &config.scan.path_roots { + collect_prototype_paths( + root, + &root.join(relative_root), + &config.scan.path_excludes, + config.limits.max_scan_entries, + &mut inputs.paths, + )?; + } + } + inputs.files.sort(); + inputs.files.dedup(); + inputs.paths.sort(); + inputs.paths.dedup(); + if inputs.paths.len() > config.limits.max_scan_entries { + return Err(format!( + "prototype guard scan contains {} entries, limit is {}", + inputs.paths.len(), + config.limits.max_scan_entries + )); + } + + let mut report = PrototypeGuardReport::default(); + let mut allow_match_counts = vec![0_usize; config.allow.len()]; + for candidate in inputs.paths { + let path = prototype_display_path(root, &candidate)?; + for pattern in config.pattern.iter().filter(|pattern| pattern.match_path) { + if !prototype_pattern_matches(&path, &path, pattern) { + continue; + } + record_prototype_match( + config, + &mut report, + &mut allow_match_counts, + PrototypeFinding { + pattern_id: pattern.id.clone(), + path: path.clone(), + origin: PrototypeFindingOrigin::Path, + line: None, + excerpt: bounded_excerpt(&path), + }, + &path, + )?; + } + } + for file in inputs.files { + let path = prototype_display_path(root, &file)?; + let source = read_bounded_prototype_input(&file, &path, config.limits.max_file_bytes)?; + for (line_index, line) in source.lines().enumerate() { + for pattern in &config.pattern { + if !prototype_pattern_matches(&path, line, pattern) { + continue; + } + let finding = PrototypeFinding { + pattern_id: pattern.id.clone(), + path: path.clone(), + origin: PrototypeFindingOrigin::Content, + line: Some(line_index + 1), + excerpt: bounded_excerpt(line), + }; + record_prototype_match( + config, + &mut report, + &mut allow_match_counts, + finding, + line, + )?; + } + } + } + for (allowed, match_count) in config.allow.iter().zip(allow_match_counts) { + if match_count != 1 { + return Err(format!( + "prototype guard allowlist entry must match exactly one line (matched {match_count}): {} {} contains {:?}", + allowed.pattern_id, allowed.path, allowed.line_contains + )); + } + } + report.findings.sort_by(|left, right| { + (&left.path, left.origin, left.line, &left.pattern_id).cmp(&( + &right.path, + right.origin, + right.line, + &right.pattern_id, + )) + }); + report.allowed.sort_by(|left, right| { + ( + &left.finding.path, + left.finding.origin, + left.finding.line, + &left.finding.pattern_id, + ) + .cmp(&( + &right.finding.path, + right.finding.origin, + right.finding.line, + &right.finding.pattern_id, + )) + }); + Ok(report) +} + +fn git_governed_paths( + root: &Path, + max_scan_entries: usize, + max_inventory_bytes: usize, +) -> Result<Option<Vec<PathBuf>>, String> { + if !root.join(".git").exists() { + return Ok(None); + } + let mut child = Command::new("git") + .arg("-C") + .arg(root) + .args([ + "ls-files", + "-z", + "--cached", + "--others", + "--exclude-standard", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|error| format!("run git source inventory for prototype guard: {error}"))?; + + let stdout = child + .stdout + .take() + .ok_or_else(|| "Git source inventory stdout was not captured".to_string())?; + let stderr = child + .stderr + .take() + .ok_or_else(|| "Git source inventory stderr was not captured".to_string())?; + let stderr_reader = std::thread::spawn(move || read_bounded_and_drain(stderr)); + let inventory = parse_git_inventory(root, stdout, max_scan_entries, max_inventory_bytes); + if inventory.is_err() { + let _ = child.kill(); + } + let status = child + .wait() + .map_err(|error| format!("wait for Git source inventory: {error}"))?; + let stderr = stderr_reader + .join() + .map_err(|_| "Git source inventory stderr reader panicked".to_string())? + .map_err(|error| format!("read Git source inventory stderr: {error}"))?; + let paths = inventory?; + if !status.success() { + return Err(format!( + "Git source inventory for prototype guard failed: {}", + escape_report_text(String::from_utf8_lossy(&stderr).trim()) + )); + } + Ok(Some(paths)) +} + +fn read_bounded_and_drain(mut reader: impl Read) -> std::io::Result<Vec<u8>> { + let mut captured = Vec::new(); + let mut buffer = [0_u8; 4096]; + loop { + let read = reader.read(&mut buffer)?; + if read == 0 { + return Ok(captured); + } + let remaining = PROTOTYPE_MAX_GIT_STDERR_BYTES.saturating_sub(captured.len()); + captured.extend_from_slice(&buffer[..read.min(remaining)]); + } +} + +fn parse_git_inventory( + root: &Path, + mut reader: impl Read, + max_scan_entries: usize, + max_inventory_bytes: usize, +) -> Result<Vec<PathBuf>, String> { + let mut paths = Vec::new(); + let mut raw_path = Vec::new(); + let mut total_bytes = 0_usize; + let mut buffer = [0_u8; 4096]; + loop { + let read = reader + .read(&mut buffer) + .map_err(|error| format!("read Git source inventory: {error}"))?; + if read == 0 { + break; + } + total_bytes = total_bytes.checked_add(read).ok_or_else(|| { + "prototype guard Git source inventory byte count overflowed".to_string() + })?; + if total_bytes > max_inventory_bytes { + return Err(format!( + "prototype guard Git source inventory exceeds configured byte limit {max_inventory_bytes}" + )); + } + for byte in &buffer[..read] { + if *byte != 0 { + if raw_path.len() >= PROTOTYPE_MAX_CONFIG_STRING_BYTES { + return Err(format!( + "prototype guard Git source path exceeds compiled byte limit {PROTOTYPE_MAX_CONFIG_STRING_BYTES}" + )); + } + raw_path.push(*byte); + continue; + } + if raw_path.is_empty() { + continue; + } + if paths.len() >= max_scan_entries { + return Err(format!( + "prototype guard Git source inventory exceeds configured entry limit {max_scan_entries}" + )); + } + let relative = std::str::from_utf8(&raw_path) + .map_err(|error| format!("prototype guard Git path is not UTF-8: {error}"))?; + validate_repo_relative_path(relative, "prototype guard Git source path")?; + let candidate = root.join(relative); + match fs::symlink_metadata(&candidate) { + Ok(_) => { + reject_symlinked_path_components( + root, + Path::new(relative), + "prototype guard Git source path", + )?; + paths.push(candidate); + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => { + return Err(format!( + "inspect prototype guard Git source {}: {error}", + candidate.display() + )); + } + } + raw_path.clear(); + } + } + if !raw_path.is_empty() { + return Err( + "prototype guard Git source inventory ended without a NUL delimiter".to_string(), + ); + } + paths.sort(); + paths.dedup(); + Ok(paths) +} + +fn path_is_within_any_root(path: &str, roots: &[String]) -> bool { + roots + .iter() + .any(|root| root == "." || repository_path_is_within(path, root)) +} + +fn path_is_excluded(path: &str, excluded_prefixes: &[String]) -> bool { + excluded_prefixes + .iter() + .any(|prefix| repository_path_is_within(path, prefix)) +} + +fn prototype_display_path(root: &Path, path: &Path) -> Result<String, String> { + let relative = path.strip_prefix(root).map_err(|_| { + format!( + "prototype guard path is outside repository root: {}", + path.display() + ) + })?; + if relative.as_os_str().is_empty() { + return Ok(".".to_string()); + } + let mut components = Vec::new(); + for component in relative.components() { + let std::path::Component::Normal(component) = component else { + return Err("prototype guard path must contain only normal components".to_string()); + }; + components.push( + component + .to_str() + .ok_or_else(|| "prototype guard path is not UTF-8".to_string())?, + ); + } + let relative = components.join("/"); + validate_repo_relative_path(&relative, "prototype guard source path")?; + Ok(relative) +} + +fn is_prototype_text_input( + path: &Path, + extensions: &HashSet<&str>, + extensionless_names: &HashSet<&str>, +) -> bool { + let extension_matches = path + .extension() + .and_then(|extension| extension.to_str()) + .is_some_and(|extension| extensions.contains(extension)); + let extensionless_matches = path.extension().is_none() + && path + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| extensionless_names.contains(name)); + extension_matches || extensionless_matches +} + +fn record_prototype_match( + config: &PrototypeGuardConfig, + report: &mut PrototypeGuardReport, + allow_match_counts: &mut [usize], + finding: PrototypeFinding, + matched_text: &str, +) -> Result<(), String> { + let match_count = report.findings.len() + report.allowed.len(); + if match_count >= config.limits.max_matches { + return Err(format!( + "prototype guard match count exceeds configured limit {}", + config.limits.max_matches + )); + } + if let Some((allow_index, allowed)) = config.allow.iter().enumerate().find(|(_, allowed)| { + allowed.pattern_id == finding.pattern_id + && allowed.path == finding.path + && matched_text.contains(&allowed.line_contains) + }) { + allow_match_counts[allow_index] += 1; + report.allowed.push(PrototypeAllowedMatch { + finding, + reason: allowed.reason.clone(), + }); + } else { + report.findings.push(finding); + } + Ok(()) +} + +fn read_bounded_prototype_input( + path: &Path, + display_path: &str, + max_file_bytes: u64, +) -> Result<String, String> { + let file = fs::File::open(path) + .map_err(|error| format!("open prototype guard input {display_path}: {error}"))?; + let mut bytes = Vec::new(); + file.take(max_file_bytes + 1) + .read_to_end(&mut bytes) + .map_err(|error| format!("read prototype guard input {display_path}: {error}"))?; + if bytes.len() as u64 > max_file_bytes { + return Err(format!( + "prototype guard input exceeds {max_file_bytes} bytes: {display_path}" + )); + } + String::from_utf8(bytes) + .map_err(|error| format!("prototype guard input is not UTF-8 {display_path}: {error}")) +} + +#[derive(Default)] +struct PrototypeInputs { + files: Vec<PathBuf>, + paths: Vec<PathBuf>, +} + +fn collect_prototype_inputs( + root: &Path, + path: &Path, + extensions: &HashSet<&str>, + extensionless_names: &HashSet<&str>, + excluded_prefixes: &[String], + max_scan_entries: usize, + inputs: &mut PrototypeInputs, +) -> Result<(), String> { + let relative = prototype_display_path(root, path)?; + if path_is_excluded(&relative, excluded_prefixes) { + return Ok(()); + } + let metadata = fs::symlink_metadata(path).map_err(|error| { + format!( + "inspect required prototype guard path {}: {error}", + path.display() + ) + })?; + if metadata.file_type().is_symlink() { + return Err(format!( + "prototype guard refuses symlinked scan input: {}", + path.display() + )); + } + if inputs.paths.len() >= max_scan_entries { + return Err(format!( + "prototype guard scan exceeds configured entry limit {max_scan_entries}" + )); + } + inputs.paths.push(path.to_path_buf()); + if metadata.is_file() { + if is_prototype_text_input(path, extensions, extensionless_names) { + inputs.files.push(path.to_path_buf()); + } + return Ok(()); + } + if !metadata.is_dir() { + return Ok(()); + } + let entries = fs::read_dir(path) + .map_err(|error| format!("read prototype guard directory {}: {error}", path.display()))?; + for entry in entries { + let entry = entry.map_err(|error| { + format!( + "read prototype guard entry under {}: {error}", + path.display() + ) + })?; + collect_prototype_inputs( + root, + &entry.path(), + extensions, + extensionless_names, + excluded_prefixes, + max_scan_entries, + inputs, + )?; + } + Ok(()) +} + +fn collect_prototype_paths( + root: &Path, + path: &Path, + excluded_prefixes: &[String], + max_scan_entries: usize, + paths: &mut Vec<PathBuf>, +) -> Result<(), String> { + let relative = prototype_display_path(root, path)?; + if excluded_prefixes + .iter() + .any(|prefix| repository_path_is_within(&relative, prefix)) + { + return Ok(()); + } + let metadata = fs::symlink_metadata(path).map_err(|error| { + format!( + "inspect required prototype guard path {}: {error}", + path.display() + ) + })?; + if metadata.file_type().is_symlink() { + return Err(format!( + "prototype guard refuses symlinked scan input: {}", + path.display() + )); + } + if paths.len() >= max_scan_entries { + return Err(format!( + "prototype guard scan exceeds configured entry limit {max_scan_entries}" + )); + } + paths.push(path.to_path_buf()); + if !metadata.is_dir() { + return Ok(()); + } + let entries = fs::read_dir(path) + .map_err(|error| format!("read prototype guard directory {}: {error}", path.display()))?; + for entry in entries { + let entry = entry.map_err(|error| { + format!( + "read prototype guard entry under {}: {error}", + path.display() + ) + })?; + collect_prototype_paths( + root, + &entry.path(), + excluded_prefixes, + max_scan_entries, + paths, + )?; + } + Ok(()) +} + +fn prototype_pattern_matches(path: &str, line: &str, pattern: &PrototypePattern) -> bool { + let path_matches = pattern.path_prefixes.is_empty() + || pattern + .path_prefixes + .iter() + .any(|prefix| repository_path_is_within(path, prefix)); + path_matches + && match pattern.match_kind { + PrototypeMatchKind::Substring => line.contains(&pattern.needle), + PrototypeMatchKind::WordPrefix => { + let folded_line = line.to_ascii_lowercase(); + let folded_needle = pattern.needle.to_ascii_lowercase(); + folded_line.match_indices(&folded_needle).any(|(index, _)| { + folded_line[..index] + .chars() + .next_back() + .is_none_or(|character| !is_prototype_identifier_continue(character)) + }) + } + } +} + +fn is_prototype_identifier_continue(character: char) -> bool { + character.is_alphanumeric() || character == '_' +} + +fn bounded_excerpt(line: &str) -> String { + const LIMIT: usize = 240; + let escaped = escape_report_text(line.trim()); + if escaped.chars().count() <= LIMIT { + return escaped; + } + let mut excerpt: String = escaped.chars().take(LIMIT - 3).collect(); + excerpt.push_str("..."); + excerpt +} + +fn escape_report_text(value: &str) -> String { + let mut escaped = String::with_capacity(value.len()); + for character in value.chars() { + if character.is_control() { + escaped.extend(character.escape_default()); + } else { + escaped.push(character); + } + } + escaped +} + +fn print_prototype_guard_report( + mode: PrototypeGuardMode, + report: &PrototypeGuardReport, + limits: &PrototypeGuardLimits, +) { + println!( + "prototype contract source guard: mode={} findings={} allowlisted={}", + mode.as_str(), + report.findings.len(), + report.allowed.len() + ); + for finding in report.findings.iter().take(limits.max_reported_findings) { + print_prototype_finding("finding", finding, None); + } + if report.findings.len() > limits.max_reported_findings { + println!( + "... {} additional finding(s) omitted by report limit", + report.findings.len() - limits.max_reported_findings + ); + } + for allowed in report.allowed.iter().take(limits.max_reported_allowlisted) { + print_prototype_finding("allowlisted", &allowed.finding, Some(&allowed.reason)); + } + if report.allowed.len() > limits.max_reported_allowlisted { + println!( + "... {} additional allowlisted match(es) omitted by report limit", + report.allowed.len() - limits.max_reported_allowlisted + ); + } +} + +fn print_prototype_finding(label: &str, finding: &PrototypeFinding, reason: Option<&str>) { + let path = escape_report_text(&finding.path); + let location = finding + .line + .map_or_else(|| format!("{path} [path]"), |line| format!("{path}:{line}")); + if let Some(reason) = reason { + let reason = escape_report_text(reason); + println!( + "{label} {} {location}: {} ({reason})", + finding.pattern_id, + escape_report_text(&finding.excerpt) + ); + } else { + println!( + "{label} {} {location}: {}", + finding.pattern_id, + escape_report_text(&finding.excerpt) + ); + } +} + pub fn validate_forbidden_identifiers(root: &Path) -> Result<(), String> { let mut failures = Vec::new(); let consolidation_active = consolidation_is_active(root); @@ -887,6 +2046,510 @@ mod tests { let _ = fs::remove_dir_all(dirty_root); } + fn prototype_config( + mode: &str, + pattern_id: &str, + needle: &str, + match_kind: &str, + allow: Option<(&str, &str, &str)>, + ) -> String { + let allow = allow.map_or_else(String::new, |(path, line_contains, reason)| { + format!( + r#" +[[allow]] +pattern_id = "{pattern_id}" +path = "{path}" +line_contains = "{line_contains}" +reason = "{reason}" +"# + ) + }); + format!( + r#"schema = "{PROTOTYPE_CONTRACT_SCHEMA}" +mode = "{mode}" + +[scan] +roots = ["src", "docs"] +path_roots = ["."] +path_excludes = [".git", "target"] +extensions = ["capnp", "md", "rs", "toml", "ts"] +extensionless_names = [".gitignore", "README"] + +[limits] +max_scan_entries = 100 +max_inventory_bytes = 4096 +max_file_bytes = 1024 +max_matches = 16 +max_reported_findings = 4 +max_reported_allowlisted = 4 + +[[pattern]] +id = "{pattern_id}" +needle = "{needle}" +match_kind = "{match_kind}" +description = "test prototype pattern" +{allow}"# + ) + } + + #[test] + fn prototype_guard_reports_matches_and_narrow_allowlists() { + let root = unique_temp_dir("prototype_report"); + let needle = ["config", ".env"].concat(); + write_file( + &root, + "contracts/test-prototype-guard.toml", + &prototype_config( + "report_only", + "config-environment", + &needle, + "substring", + Some(( + "docs/history.md", + "historical fixture", + "Historical fixture text is not an active configuration path.", + )), + ), + ); + write_file( + &root, + "src/config.rs", + &format!("const PROTOTYPE: &str = \"{needle}\";\n"), + ); + write_file( + &root, + "docs/history.md", + &format!("historical fixture: {needle}\nactive example: {needle}\n"), + ); + + let config = + load_prototype_guard_config(&root, Path::new("contracts/test-prototype-guard.toml")) + .expect("load prototype guard config"); + let report = scan_prototype_contracts(&root, &config).expect("scan prototype contracts"); + assert_eq!(report.findings.len(), 2); + assert_eq!(report.allowed.len(), 1); + assert_eq!(report.findings[0].path, "docs/history.md"); + assert_eq!(report.findings[0].origin, PrototypeFindingOrigin::Content); + assert_eq!(report.findings[1].path, "src/config.rs"); + assert_eq!(report.allowed[0].finding.path, "docs/history.md"); + + run_prototype_contract_guard( + &[ + "--config".to_string(), + "contracts/test-prototype-guard.toml".to_string(), + ], + &root, + ) + .expect("report-only prototype guard"); + let strict_error = run_prototype_contract_guard( + &[ + "--config".to_string(), + "contracts/test-prototype-guard.toml".to_string(), + "--strict".to_string(), + ], + &root, + ) + .expect_err("strict prototype guard rejects findings"); + assert!(strict_error.contains("2 non-allowlisted match(es)")); + let _ = fs::remove_dir_all(root); + } + + #[test] + fn prototype_guard_word_prefix_avoids_embedded_false_positives() { + let root = unique_temp_dir("prototype_word_prefix"); + let prefix = ["com", "pat"].concat(); + write_file( + &root, + "contracts/test-prototype-guard.toml", + &prototype_config( + "strict", + "compatibility-concept", + &prefix, + "word_prefix", + Some(( + "docs/interoperability.md", + "compatible peer", + "External interoperability is not a compatibility implementation path.", + )), + ), + ); + write_file( + &root, + "docs/interoperability.md", + "incompatible input\ncompatible peer\nCompatReader\nÉcompatReader\n", + ); + write_file(&root, "src/clean.rs", "fn current_contract() {}\n"); + + let config = + load_prototype_guard_config(&root, Path::new("contracts/test-prototype-guard.toml")) + .expect("load prototype guard config"); + let report = scan_prototype_contracts(&root, &config).expect("scan prototype contracts"); + assert_eq!(report.findings.len(), 1); + assert_eq!(report.findings[0].line, Some(3)); + assert_eq!(report.findings[0].excerpt, "CompatReader"); + assert_eq!(report.allowed.len(), 1); + assert_eq!(report.allowed[0].finding.line, Some(2)); + + let unsafe_path = + parse_prototype_guard_args(&["--config".to_string(), "../outside.toml".to_string()]) + .expect_err("parent traversal must fail"); + assert!(unsafe_path.contains("normalized repository-relative path")); + let duplicate_mode = + parse_prototype_guard_args(&["--strict".to_string(), "--report-only".to_string()]) + .expect_err("duplicate mode must fail"); + assert!(duplicate_mode.contains("only one mode override")); + let _ = fs::remove_dir_all(root); + } + + #[test] + fn prototype_guard_rejects_broad_or_stale_allowlists_and_symlinks() { + let root = unique_temp_dir("prototype_allowlist_integrity"); + let needle = ["import", "_json"].concat(); + write_file( + &root, + "contracts/test-prototype-guard.toml", + &prototype_config( + "report_only", + "state-import-identifier", + &needle, + "substring", + Some(( + "src/import.rs", + "import", + "A test allowance that is intentionally too broad.", + )), + ), + ); + write_file( + &root, + "src/import.rs", + &format!("fn {needle}() {{}}\nfn second_{needle}() {{}}\n"), + ); + write_file(&root, "docs/README", "Current contract.\n"); + let config = + load_prototype_guard_config(&root, Path::new("contracts/test-prototype-guard.toml")) + .expect("load prototype guard config"); + let broad_error = scan_prototype_contracts(&root, &config) + .expect_err("one allowance must not authorize multiple matching lines"); + assert!(broad_error.contains("must match exactly one line (matched 2)")); + + write_file(&root, "src/import.rs", "fn current_name() {}\n"); + let stale_error = + scan_prototype_contracts(&root, &config).expect_err("stale allowance must fail closed"); + assert!(stale_error.contains("must match exactly one line (matched 0)")); + + #[cfg(unix)] + { + use std::os::unix::fs::symlink; + + let outside = unique_temp_dir("prototype_symlink_target"); + write_file(&outside, "forbidden.rs", &format!("fn {needle}() {{}}\n")); + symlink(outside.join("forbidden.rs"), root.join("src/linked.rs")) + .expect("create scan symlink"); + let symlink_error = scan_prototype_contracts(&root, &config) + .expect_err("symlinked source must fail closed"); + assert!(symlink_error.contains("refuses symlinked scan input")); + fs::remove_file(root.join("src/linked.rs")).expect("remove direct scan symlink"); + + symlink(&outside, root.join("linked-root")).expect("create intermediate scan symlink"); + let linked_root_source = prototype_config( + "report_only", + "state-import-identifier", + &needle, + "substring", + None, + ) + .replace("roots = [\"src\", \"docs\"]", "roots = [\"linked-root\"]") + .replace("path_roots = [\".\"]", "path_roots = [\"docs\"]") + .replace( + "path_excludes = [\".git\", \"target\"]", + "path_excludes = []", + ); + write_file( + &root, + "contracts/linked-root-guard.toml", + &linked_root_source, + ); + let linked_root_config = + load_prototype_guard_config(&root, Path::new("contracts/linked-root-guard.toml")) + .expect("load intermediate symlink scan config"); + let linked_root_error = scan_prototype_contracts(&root, &linked_root_config) + .expect_err("intermediate scan-root symlink must fail closed"); + assert!(linked_root_error.contains("must not contain a symlinked component")); + + fs::create_dir_all(root.join("configs")).expect("create config parent"); + symlink(&outside, root.join("configs/linked")) + .expect("create intermediate config symlink"); + write_file(&outside, "guard.toml", &linked_root_source); + let linked_config_error = + load_prototype_guard_config(&root, Path::new("configs/linked/guard.toml")) + .expect_err("intermediate config symlink must fail closed"); + assert!(linked_config_error.contains("must not contain a symlinked component")); + let _ = fs::remove_dir_all(outside); + } + let _ = fs::remove_dir_all(root); + } + + #[test] + fn prototype_guard_scans_paths_non_rust_inputs_and_required_roots() { + let root = unique_temp_dir("prototype_path_and_fixture_scan"); + let path_needle = ["identity", ".example.json"].concat(); + let content_needle = ["allow", "_generate_identity"].concat(); + let env_path_needle = [".env", ".example"].concat(); + let worker_path_needle = ["workers", "/rhi"].concat(); + let config_source = prototype_config( + "report_only", + "identity-example-path", + &path_needle, + "substring", + None, + ) + .replace( + "roots = [\"src\", \"docs\"]", + "roots = [\"src\", \"docs\", \".gitignore\"]", + ) + .replace( + "description = \"test prototype pattern\"", + "description = \"test prototype pattern\"\nmatch_path = true", + ) + &format!( + r#" +[[pattern]] +id = "identity-generation-content" +needle = "{content_needle}" +match_kind = "substring" +description = "test non-Rust fixture pattern" + +[[pattern]] +id = "environment-example-path" +needle = "{env_path_needle}" +match_kind = "substring" +description = "test environment example path" +match_path = true + +[[pattern]] +id = "worker-directory-path" +needle = "{worker_path_needle}" +match_kind = "substring" +description = "test worker directory path" +match_path = true +"#, + ); + write_file(&root, "contracts/test-prototype-guard.toml", &config_source); + let identity_path = format!("src/{path_needle}"); + let env_path = env_path_needle.clone(); + let worker_path = format!("src/{worker_path_needle}"); + write_file(&root, &identity_path, "{}\n"); + write_file(&root, &env_path, "CURRENT_SETTING=true\n"); + write_file(&root, ".gitignore", &format!("# {content_needle}\n")); + fs::create_dir_all(root.join(&worker_path)).expect("create forbidden worker path"); + write_file( + &root, + "src/generated.ts", + &format!("export const flag = \"{content_needle}\";\n"), + ); + write_file(&root, "src/service.capnp", &format!("# {content_needle}\n")); + write_file(&root, "docs/README", &format!("{content_needle}\n")); + + let config = + load_prototype_guard_config(&root, Path::new("contracts/test-prototype-guard.toml")) + .expect("load prototype guard config"); + let report = scan_prototype_contracts(&root, &config).expect("scan active textual inputs"); + assert_eq!(report.findings.len(), 7); + for path in [&env_path, &identity_path, &worker_path] { + assert!(report.findings.iter().any(|finding| { + finding.path == *path + && finding.origin == PrototypeFindingOrigin::Path + && finding.line.is_none() + })); + } + for path in [ + ".gitignore", + "docs/README", + "src/generated.ts", + "src/service.capnp", + ] { + assert!(report.findings.iter().any(|finding| { + finding.path == path && finding.origin == PrototypeFindingOrigin::Content + })); + } + + fs::remove_dir_all(root.join("docs")).expect("remove required scan root"); + let missing_error = scan_prototype_contracts(&root, &config) + .expect_err("a missing required scan root must fail closed"); + assert!(missing_error.contains("prototype guard scan root component")); + let _ = fs::remove_dir_all(root); + } + + #[cfg(unix)] + #[test] + fn prototype_guard_git_inventory_ignores_workstation_symlinks() { + use std::os::unix::fs::symlink; + + let root = unique_temp_dir("prototype_git_inventory"); + let needle = [".env", ".example"].concat(); + let config_source = prototype_config( + "report_only", + "environment-example-path", + &needle, + "substring", + None, + ) + .replace( + "description = \"test prototype pattern\"", + "description = \"test prototype pattern\"\nmatch_path = true", + ); + write_file(&root, "contracts/test-prototype-guard.toml", &config_source); + write_file(&root, "src/current.rs", "fn current_contract() {}\n"); + write_file(&root, "docs/README", "Current contract.\n"); + let ignore = format!(".direnv/\nresult\n.env.*\n!{needle}\n"); + write_file(&root, ".gitignore", &ignore); + write_file(&root, &needle, "CURRENT_SETTING=true\n"); + let init = Command::new("git") + .args(["init", "-q"]) + .current_dir(&root) + .status() + .expect("run git init"); + assert!(init.success()); + + let config = + load_prototype_guard_config(&root, Path::new("contracts/test-prototype-guard.toml")) + .expect("load prototype guard config"); + let before = scan_prototype_contracts(&root, &config).expect("scan governed Git source"); + assert_eq!(before.findings.len(), 1); + assert_eq!(before.findings[0].path, needle); + assert_eq!(before.findings[0].origin, PrototypeFindingOrigin::Path); + + let control_path = "src/control\n\u{1b}.rs"; + write_file(&root, control_path, "fn current_contract() {}\n"); + let control_error = scan_prototype_contracts(&root, &config) + .expect_err("control characters in Git paths must fail closed"); + assert_eq!(control_error.lines().count(), 1); + assert!(!control_error.contains('\u{1b}')); + assert!(control_error.contains("control\\n\\u{1b}.rs")); + fs::remove_file(root.join(control_path)).expect("remove control-character path"); + + let outside = unique_temp_dir("prototype_ignored_symlink_target"); + write_file(&outside, "ignored.rs", "Current ignored cache.\n"); + fs::create_dir_all(root.join(".direnv")).expect("create ignored environment cache"); + symlink(outside.join("ignored.rs"), root.join(".direnv/linked.rs")) + .expect("create ignored environment symlink"); + symlink(&outside, root.join("result")).expect("create ignored Nix result symlink"); + + let after = scan_prototype_contracts(&root, &config) + .expect("ignored workstation symlinks must not enter the source inventory"); + assert_eq!(after, before); + let _ = fs::remove_dir_all(outside); + let _ = fs::remove_dir_all(root); + } + + #[test] + fn prototype_guard_git_inventory_parser_enforces_byte_and_entry_bounds() { + let root = unique_temp_dir("prototype_git_inventory_bounds"); + write_file(&root, "src/one.rs", "fn one() {}\n"); + write_file(&root, "src/two.rs", "fn two() {}\n"); + let inventory = b"src/one.rs\0src/two.rs\0"; + + let parsed = parse_git_inventory(&root, &inventory[..], 2, inventory.len()) + .expect("bounded inventory must parse"); + assert_eq!(parsed.len(), 2); + + let byte_error = parse_git_inventory(&root, &inventory[..], 2, inventory.len() - 1) + .expect_err("inventory bytes above the configured ceiling must fail"); + assert!(byte_error.contains("configured byte limit")); + + let entry_error = parse_git_inventory(&root, &inventory[..], 1, inventory.len()) + .expect_err("inventory entries above the configured ceiling must fail"); + assert!(entry_error.contains("configured entry limit 1")); + + let delimiter_error = + parse_git_inventory(&root, &inventory[..inventory.len() - 1], 2, 4096) + .expect_err("unterminated Git inventory must fail"); + assert!(delimiter_error.contains("without a NUL delimiter")); + + let escaped = bounded_excerpt("safe\tvalue\u{1b}[31m"); + assert_eq!(escaped, "safe\\tvalue\\u{1b}[31m"); + assert!(!escaped.chars().any(char::is_control)); + let _ = fs::remove_dir_all(root); + } + + #[test] + fn prototype_guard_bounds_configuration_bytes_counts_and_reasons() { + let root = unique_temp_dir("prototype_config_bounds"); + let config_path = "contracts/test-prototype-guard.toml"; + write_file( + &root, + config_path, + &"x".repeat(PROTOTYPE_MAX_CONFIG_BYTES as usize + 1), + ); + let bytes_error = load_prototype_guard_config(&root, Path::new(config_path)) + .expect_err("oversized configuration must fail before parsing"); + assert!(bytes_error.contains("exceeds 1048576 bytes")); + + let needle = ["config", ".env"].concat(); + let oversized_reason = "r".repeat(PROTOTYPE_MAX_REASON_BYTES + 1); + let reason_config = prototype_config( + "report_only", + "config-environment", + &needle, + "substring", + Some(("src/config.rs", "prototype", &oversized_reason)), + ); + write_file(&root, config_path, &reason_config); + let reason_error = load_prototype_guard_config(&root, Path::new(config_path)) + .expect_err("oversized printed reason must fail validation"); + assert!(reason_error.contains("requires a reason")); + + let mut pattern_config = + prototype_config("report_only", "pattern-0", &needle, "substring", None); + for index in 1..=PROTOTYPE_MAX_CONFIG_PATTERNS { + pattern_config.push_str(&format!( + r#" +[[pattern]] +id = "pattern-{index}" +needle = "current-{index}" +match_kind = "substring" +description = "bounded pattern" +"#, + )); + } + write_file(&root, config_path, &pattern_config); + let count_error = load_prototype_guard_config(&root, Path::new(config_path)) + .expect_err("excessive pattern count must fail validation"); + assert!(count_error.contains("configuration collection exceeds compiled limit")); + let _ = fs::remove_dir_all(root); + } + + #[test] + fn prototype_guard_enforces_file_and_match_resource_bounds() { + let root = unique_temp_dir("prototype_resource_bounds"); + let needle = ["config", ".env"].concat(); + write_file( + &root, + "contracts/test-prototype-guard.toml", + &prototype_config( + "report_only", + "config-environment", + &needle, + "substring", + None, + ), + ); + write_file(&root, "docs/README", "Current contract.\n"); + write_file(&root, "src/large.rs", &"x".repeat(1025)); + let config = + load_prototype_guard_config(&root, Path::new("contracts/test-prototype-guard.toml")) + .expect("load prototype guard config"); + let size_error = scan_prototype_contracts(&root, &config) + .expect_err("oversized source input must fail closed"); + assert!(size_error.contains("exceeds 1024 bytes")); + + write_file(&root, "src/large.rs", &format!("{needle}\n").repeat(17)); + let match_error = scan_prototype_contracts(&root, &config) + .expect_err("excessive matches must fail closed"); + assert!(match_error.contains("match count exceeds configured limit 16")); + let _ = fs::remove_dir_all(root); + } + #[test] fn run_dispatches_forbidden_identifiers() { let root = unique_temp_dir("run"); diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs @@ -299,6 +299,9 @@ fn usage() { " cargo xtask coverage refresh-summary [--reports-root <dir>] [--out <file>] [--status-out <file>]" ); eprintln!(" cargo xtask hygiene forbidden-identifiers"); + eprintln!( + " cargo xtask hygiene prototype-contracts [--config <repo-relative-path>] [--strict|--report-only]" + ); eprintln!(" cargo xtask source-lock --consumer-root <absolute-directory>"); eprintln!( " cargo xtask source materialize --consumer-root <absolute-directory> --cache-root <absolute-directory> --mode <prefetch|offline>" @@ -782,6 +785,8 @@ mod tests { run(&["coverage".to_string(), "help".to_string()]).expect("root run coverage"); run(&["hygiene".to_string(), "forbidden-identifiers".to_string()]) .expect("hygiene forbidden identifiers"); + run(&["hygiene".to_string(), "prototype-contracts".to_string()]) + .expect("report prototype contracts"); let _ = fs::remove_dir_all(out_dir); } diff --git a/tools/xtask/src/release_qualification.rs b/tools/xtask/src/release_qualification.rs @@ -50,7 +50,7 @@ fn rustflags_with_warnings_denied() -> OsString { } fn feature_matrix(workspace_root: &Path) -> Result<Vec<CargoInvocation>, String> { - let path = workspace_root.join("contracts/crates/catalog.v1.toml"); + let path = workspace_root.join("contracts/crates/catalog.v2.toml"); let raw = fs::read_to_string(&path) .map_err(|error| format!("failed to read {}: {error}", path.display()))?; let catalog = toml::from_str::<Catalog>(&raw) diff --git a/tools/xtask/src/target_qualification.rs b/tools/xtask/src/target_qualification.rs @@ -73,7 +73,7 @@ fn load(workspace_root: &Path) -> Result<(TargetMatrix, Vec<String>), String> { .map_err(|error| format!("failed to parse {}: {error}", matrix_path.display()))?; validate(&matrix)?; - let catalog_path = workspace_root.join("contracts/crates/catalog.v1.toml"); + let catalog_path = workspace_root.join("contracts/crates/catalog.v2.toml"); let catalog = toml::from_str::<Catalog>(&read(&catalog_path)?) .map_err(|error| format!("failed to parse {}: {error}", catalog_path.display()))?; let mut packages = catalog diff --git a/tools/xtask/tests/fixtures/api-leakage/adr-exception.rs b/tools/xtask/tests/fixtures/api-leakage/adr-exception.rs @@ -1,3 +0,0 @@ -pub fn temporary_client() -> reqwest::Client { - unreachable!() -} diff --git a/tools/xtask/tests/services_hardening_host_decision.rs b/tools/xtask/tests/services_hardening_host_decision.rs @@ -0,0 +1,239 @@ +#![forbid(unsafe_code)] + +use serde_json::Value; + +const HOST_DECISION: &str = + include_str!("../../../contracts/architecture/decisions/services_hardening_host.v1.json"); + +fn decision() -> Value { + serde_json::from_str(HOST_DECISION).expect("host decision must be valid JSON") +} + +#[test] +fn admin_routes_envelopes_and_exit_codes_are_unique_and_exact() { + let value = decision(); + assert_eq!( + value["schema"], + "radroots.services-hardening.host-decisions.v1" + ); + assert_eq!(value["decision_state"], "reserved_preimplementation"); + assert_eq!( + value["local_admin"]["transport"], + "http_1_1_over_unix_domain_socket" + ); + assert_eq!(value["local_admin"]["base_path"], "/v1"); + assert_eq!(value["local_admin"]["tcp_admin"], false); + + assert_eq!( + value["local_admin"]["mutation_request_envelope"], + serde_json::json!({ + "required_fields": ["contract_version", "operation_id", "request"], + "optional_fields": ["correlation_id"], + "contract_version": 1, + "operation_id_semantics": "caller_stable_idempotency_identity", + "correlation_id_semantics": "caller_safe_trace_identity_or_daemon_generated_when_absent", + "identical_operation_id_reuse": "return_original_committed_result", + "different_request_operation_id_reuse": { + "admin_error_code": "operation_id_conflict", + "cli_exit": 5 + } + }) + ); + assert_eq!(value["local_admin"]["cors"], false); + assert_eq!(value["local_admin"]["browser_authentication"], false); + assert_eq!( + value["local_admin"]["unknown_major_version"], + "unsupported_contract_version" + ); + assert_eq!(value["local_admin"]["unknown_route"], "route_not_found"); + assert_eq!(value["local_admin"]["duplicate_json_fields_rejected"], true); + assert_eq!(value["local_admin"]["unknown_json_fields_rejected"], true); + assert_eq!( + value["local_admin"]["success_response_envelope"], + serde_json::json!({ + "required_fields": ["contract_version", "ok", "correlation_id", "result"], + "contract_version": 1, + "ok": true + }) + ); + assert_eq!( + value["local_admin"]["failure_response_envelope"], + serde_json::json!({ + "required_fields": ["contract_version", "ok", "correlation_id", "error"], + "error_required_fields": ["code", "message"], + "contract_version": 1, + "ok": false + }) + ); + assert_eq!( + value["local_admin"]["output_safety"], + serde_json::json!({ + "request_body_max_utf8_bytes": 65_536, + "response_body_max_utf8_bytes": 1_048_576, + "operation_id_max_utf8_bytes": 128, + "correlation_id_max_utf8_bytes": 128, + "error_code_max_utf8_bytes": 64, + "error_message_max_utf8_bytes": 256, + "redaction_required": true, + "forbidden_material": [ + "secret_or_credential_material", + "private_identity_material", + "decrypted_payload", + "raw_absolute_or_resolved_path", + "raw_sql_or_database_error", + "raw_provider_error", + "raw_relay_or_network_error", + "source_error_chain" + ] + }) + ); + + let routes = value["local_admin"]["common_route_suffixes"] + .as_array() + .expect("common routes"); + assert_eq!( + routes, + serde_json::json!([ + { "method": "GET", "path": "/status", "operation_suffix": "status.get", "request_model": "empty", "response_model": "service_status_v1" }, + { "method": "GET", "path": "/config/effective", "operation_suffix": "config.effective.get", "request_model": "empty", "response_model": "effective_config_v1" }, + { "method": "GET", "path": "/identity/status", "operation_suffix": "identity.status.get", "request_model": "identity_status_query_v1", "response_model": "identity_status_v1" }, + { "method": "POST", "path": "/identity/rekey", "operation_suffix": "identity.rekey", "request_model": "identity_rekey_request_v1", "response_model": "identity_mutation_receipt_v1" }, + { "method": "POST", "path": "/identity/replace", "operation_suffix": "identity.replace", "request_model": "identity_replace_request_v1", "response_model": "identity_mutation_receipt_v1" }, + { "method": "GET", "path": "/state/status", "operation_suffix": "state.status.get", "request_model": "empty", "response_model": "state_status_v1" }, + { "method": "POST", "path": "/state/backup", "operation_suffix": "state.backup.create", "request_model": "state_backup_request_v1", "response_model": "state_backup_receipt_v1" }, + { "method": "GET", "path": "/metrics/snapshot", "operation_suffix": "metrics.snapshot.get", "request_model": "empty", "response_model": "metrics_snapshot_v1" } + ]) + .as_array() + .unwrap() + ); + + assert_eq!( + value["exit_codes"], + serde_json::json!([ + { "code": 0, "name": "success", "meaning": "successful command or completed graceful first-signal shutdown" }, + { "code": 1, "name": "unexpected_internal", "meaning": "unexpected invariant, critical task, or internal failure" }, + { "code": 2, "name": "input_or_configuration", "meaning": "CLI, config, validation, or unsupported contract input" }, + { "code": 3, "name": "service_or_dependency_unavailable", "meaning": "daemon, required provider, relay, source, or local dependency unavailable" }, + { "code": 4, "name": "state_or_identity_unavailable", "meaning": "state, schema, lock, credential, or identity unavailable" }, + { "code": 5, "name": "operation_rejected_or_conflict", "meaning": "authorization rejection, idempotency conflict, stale generation, or domain conflict" }, + { "code": 6, "name": "doctor_required_check_failed", "meaning": "one or more required doctor checks failed or timed out" } + ]) + ); +} + +#[test] +fn readiness_peer_authorization_and_native_support_fail_closed() { + let value = decision(); + assert_eq!( + value["tcp_operations"]["routes"], + serde_json::json!([ + { "method": "GET", "path": "/livez", "source": "cached_supervisor_state" }, + { "method": "GET", "path": "/readyz", "source": "cached_readiness_state" }, + { "method": "GET", "path": "/metrics", "source": "cached_bounded_metrics_snapshot" } + ]) + ); + assert_eq!(value["tcp_operations"]["active_probe_per_request"], false); + assert_eq!(value["tcp_operations"]["additional_routes"], false); + assert_eq!(value["systemd"]["sd_notify_v1"], false); + assert_eq!(value["systemd"]["service_type"], "simple"); + assert_eq!( + value["systemd"]["readiness_authority"], + "cached_http_readyz" + ); + assert_eq!( + value["systemd"]["process_running_does_not_imply_ready"], + true + ); + assert_eq!( + value["peer_authorization"]["linux_service_host"], + serde_json::json!({ + "credential_api": "SO_PEERCRED", + "required": true, + "allow": ["peer_uid_equals_daemon_euid", "peer_primary_gid_equals_configured_admin_gid"], + "credential_unavailable": "deny", + "parent_mode_without_admin_gid": "0700", + "socket_mode_without_admin_gid": "0600", + "parent_mode_with_admin_gid": "0750", + "socket_mode_with_admin_gid": "0660" + }) + ); + assert_eq!( + value["peer_authorization"]["macos_interactive"], + serde_json::json!({ + "credential_api": "none_v1", + "required": false, + "authority": "filesystem_owner_permissions_only", + "parent_mode": "0700", + "socket_mode": "0600", + "peer_credential_equivalence_claim": false + }) + ); + assert_eq!( + value["peer_authorization"]["other_platforms"], + serde_json::json!({ "admin_support": "unsupported_v1" }) + ); + assert_eq!( + value["doctor"], + serde_json::json!({ + "schema": "radroots.service.doctor.v1", + "contract_version": 1, + "required_fields": ["contract_version", "service", "instance", "status", "checks"], + "check_required_fields": ["id", "status", "required", "deadline_ms", "summary", "remediation_code"], + "statuses": ["pass", "fail", "timeout", "skipped"], + "required_skipped": "forbidden", + "aggregate_statuses": ["pass", "degraded", "fail"], + "aggregation": { + "required_fail_or_timeout": "fail", + "optional_fail_timeout_or_skipped": "degraded", + "otherwise": "pass" + }, + "optional_nonpass_exit": 0, + "summary_max_utf8_bytes": 256, + "raw_error_or_path_allowed": false, + "required_fail_or_timeout_exit": 6 + }) + ); + assert_eq!( + value["forced_signal_exit"], + "operating_system_128_plus_signal_not_remapped" + ); + assert_eq!( + value["bare_rust_linux"]["qualification_base"], + "debian_bookworm_slim_digest_pinned_per_receipt" + ); + assert_eq!( + value["bare_rust_linux"]["architectures"], + serde_json::json!(["x86_64", "aarch64"]) + ); + assert_eq!( + value["bare_rust_linux"]["rust_install"], + "rustup_profile_minimal_exact_repository_toolchain" + ); + assert_eq!( + value["bare_rust_linux"]["apt_packages"], + serde_json::json!(["build-essential", "ca-certificates", "git"]) + ); + assert_eq!( + value["bare_rust_linux"]["not_required_by_final_graph"], + serde_json::json!([ + "clang", + "libclang-dev", + "libsodium-dev", + "libsqlite3-dev", + "libssl-dev", + "pkg-config" + ]) + ); + assert_eq!(value["bare_rust_linux"]["sqlite"], "bundled"); + assert_eq!(value["bare_rust_linux"]["tls"], "rustls"); + assert_eq!( + value["bare_rust_linux"]["proof"], + serde_json::json!([ + "fresh_digest_pinned_base_for_each_architecture", + "install_only_declared_apt_packages_and_exact_rust_toolchain", + "locked_format_check_test_clippy_rustdoc_release_build", + "repeat_with_network_disabled_from_governed_vendor_bundle", + "fail_if_undeclared_native_package_is_installed_or_linked" + ]) + ); +}