Skip to content

Phase 0: productionize build pipeline and bring workspace to green#10

Merged
iduartgomez merged 3 commits intomainfrom
phase-0-foundation
Apr 9, 2026
Merged

Phase 0: productionize build pipeline and bring workspace to green#10
iduartgomez merged 3 commits intomainfrom
phase-0-foundation

Conversation

@iduartgomez
Copy link
Copy Markdown
Contributor

Summary

Phase 0 of the productionization plan. Brings the extracted freenet-email repo from the broken state it was left in after the monorepo split to a fully-buildable workspace with clean check, clippy, and test.

Three commits, progressively layered:

  1. Foundation (4b85d15) — bump freenet-stdlib 0.3.2→0.3.5, port the full web-container contract from freenet-river (with ed25519 signature verification, version monotonicity, and 6 unit tests), fix the inbox contract host test (RSA key size + rand), migrate the workspace to Rust edition 2024, and clean ~44 clippy warnings.
  2. Vendored AFT crates (158ceb5) — copy token-allocation-record and token-generator from freenet-core/modules/antiflood-tokens/ into this repo. They existed only as 0-byte build/ placeholders before, which meant the UI has been uncompilable since the monorepo extraction. Also drops freenet-main-contract / freenet-main-delegate from default features (the #[contract] / #[delegate] macros expand to wasm-only FFI wrappers that break host builds), matching how identity-management is structured.
  3. Build wiring (adc47b7) — cargo make build-contracts now drives fdev build + fdev inspect to produce all 4 WASM artifacts and 3 *_code_hash files the UI embeds via include_bytes!/include_str!. A new ui/build.rs fail-fast checker verifies all 8 artifacts exist before rustc tries to compile the includes, converting cryptic "couldn't read" errors into a structured list of missing files and the cargo make target that produces each one. Also commits a TEST-ONLY EC P-384 keypair under test-contract/identity/ with a security-warning README, matching the freenet-river pattern for reproducible delegate builds.

Status on a clean checkout:

  • `cargo make build-contracts` → produces all 8 artifacts (~30s cold, ~4s cached)
  • `cargo check --workspace` → clean
  • `cargo clippy --workspace --all-targets` → zero warnings
  • `cargo test --workspace` → 17 passed, 1 ignored (legacy integration test with a pre-generated-fixture dependency that doesn't exist in this tree — TODO left in place for Phase 1 to wire up)

Notable design decisions

  • `fdev build` over `cargo build` for contracts/delegates. Matches the existing `include_bytes!` paths (`modules/.../build/freenet/...`) and gives code hash extraction via `fdev inspect` for free. Downside: requires `fdev` installed, but it's already required by the publish pipeline.
  • `CARGO_TARGET_DIR` set explicitly in every build task. `fdev 0.3.200`'s `get_workspace_target_dir()` walks up from its compile-time `CARGO_MANIFEST_DIR` looking for a `[workspace]` `Cargo.toml` and panics if that path no longer exists. The env var bypasses the walk.
  • Test keypair committed to `test-contract/identity/`. The `identity-management` delegate needs a serialized `IdentityParams` (containing a secret key) embedded into the UI at compile time. Without a committed keypair, every clone would produce a different delegate identity, breaking reproducible builds. The README.md documents that this is a test-only identity, the repo is public anyway, and production deployments must generate their own keypair out of band.
  • Dropped `freenet-main-*` from default features on the vendored AFT crates. The `#[contract]`/`#[delegate]` macros expand under those features to `extern "C"` FFI wrappers that reference `WasmLinearMem` and other wasm-only memory helpers, so a plain host `cargo check` fails. `fdev build` enables them explicitly via `--features`.
  • Legacy `token-allocation-record` integration test marked `#[ignore]`. It depends on a pre-generated `build/freenet/contract-state` fixture that lived in the upstream antiflood-tokens test harness and doesn't exist here. TODO left in place to restore when we wire up a state-generation script.

Test plan

  • Fully clean state: remove all `build/` directories, run `cargo make build-contracts`, confirm all 8 artifacts produced
  • `cargo check --workspace` passes
  • `cargo clippy --workspace --all-targets` passes with `-D warnings`
  • `cargo test --workspace` passes (17 passed, 1 ignored with TODO)
  • `ui/build.rs` fail-fast correctly errors with a structured list when artifacts are missing
  • `cargo make build` (full release build including UI via `dx`) — not yet verified in this PR; leaving for CI or a follow-up
  • `cargo make publish` (publish to Freenet) — out of scope for Phase 0

Follow-ups

  • freenet-core cleanup PR to remove `modules/antiflood-tokens/` and `modules/identity-management/` from freenet-core now that they live here. Coming in a separate PR.
  • Phase 1 will address: feature flags for `example-data` / `no-sync` (like freenet-river), E2E tests via Playwright, identity delegate response path reinstatement (the unreachable arm removed in api.rs), state-generation script to unignore the legacy AFT integration test.
  • Phase 2+ per the plan: publishing pipeline with signed releases, CI/CD, docs.

…2024

Groundwork to bring freenet-email toward production readiness:

- Bump freenet-stdlib 0.3.2 → 0.3.5 across workspace
- Port full web-container contract from freenet-river: ed25519
  signature verification, version monotonicity, state delta, 6 unit
  tests (replaces 40-line stub)
- Add freenet-email-core::web_container::WebContainerMetadata
- Fix inbox contract host test: use 2048-bit RSA key (was 32-bit,
  panicked on SHA256 sign); add rsa std dev-dep to re-enable OsRng
- Feature-gate ContractInterface dead-code warnings in inbox crate
- Silence unexpected_cfgs in identity-management (freenet-stdlib
  #[delegate] macro emits cfg(feature = "trace") without declaring it)
- Migrate workspace to Rust edition 2024
- Clippy pass: ~44 auto-fixes (clone_on_copy, needless_borrow,
  repeat_n, get_first, manual_inspect, collapsible_if) plus targeted
  #[allow(dead_code)] and #[allow(clippy::arc_with_non_send_sync)]
  with comments explaining the wasm single-threaded rationale
- Remove unreachable identities delegate response arm in api.rs
  (shadowed by earlier ApplicationMessage arm; flagged for Phase 1)

Note: UI still fails to build because include_bytes!/include_str!
targets for token-allocation-record and token-generator don't exist
yet. Those crates are vendored in the next commit.
…-core

These crates host the anti-flood-token contract and delegate but were
only present as empty build/ placeholders in freenet-email. The UI
references their compiled WASM and code-hash files via include_bytes!
and include_str!, so the UI has been unable to compile since the
monorepo extraction.

Copied wholesale from freenet-core/modules/antiflood-tokens/, with
minimal adjustments for this workspace:

- Added both crates as workspace members in root Cargo.toml
- Dropped `freenet-main-contract` / `freenet-main-delegate` from their
  default features. Those feature gates control whether the #[contract]
  and #[delegate] macros emit the extern "C" FFI wrapper, which is only
  valid when targeting wasm32 (it references WasmLinearMem and other
  host-incompatible memory layouts). `fdev build` will enable them
  explicitly when producing the WASM artifacts.
- Added module-level #[cfg_attr(not(feature = "freenet-main-delegate"),
  allow(dead_code))] to token-generator so host builds don't warn about
  the gated-off helper functions, matching the inbox crate pattern.

Both crates now build cleanly under `cargo check --workspace` on the
host target. The UI still won't compile until task #4 wires up
`fdev build` to produce the WASM artifacts and code hash files that
ui/src/aft.rs and ui/src/api.rs include via include_bytes!/include_str!.
Completes Phase 0 task #4: the workspace now builds end-to-end without
manual fixup. `cargo check --workspace`, `cargo clippy --workspace
--all-targets`, and `cargo test --workspace` all pass (17 tests passing,
1 legacy integration test ignored pending a fixture regeneration story).

Makefile.toml
-------------
Add five cargo-make tasks that drive `fdev build` + `fdev inspect`
for each contract/delegate, writing WASM to the modules/.../build/freenet
paths the UI embeds via include_bytes!, and extracting code hashes into
the *_code_hash text files the UI embeds via include_str!:

  - build-inbox-contract
  - build-token-allocation-record
  - build-token-generator
  - build-identity-management-delegate
  - generate-identity-params

Plus a meta target `build-contracts` that runs all five as dependencies,
now wired into build-ui and dev. Each task sets CARGO_TARGET_DIR
explicitly to work around fdev 0.3.200 panicking in
get_workspace_target_dir() when its compile-time CARGO_MANIFEST_DIR no
longer exists.

ui/build.rs (new)
-----------------
Fail-fast verifier that checks all eight include_bytes!/include_str!
target files exist and are non-empty before rustc tries to compile
src/aft.rs and src/api.rs. On failure, prints a structured error
listing each missing artifact, a human description, and the exact
cargo-make target that produces it. Converts a cryptic rustc
"couldn't read" into an actionable message.

test-contract/identity/
-----------------------
Committed EC P-384 test keypair for the identity-management delegate,
so `cargo make generate-identity-params` is deterministic across clones
and CI runs. README.md documents the security implications:

- The key is a TEST-ONLY identity. Anyone with read access can
  impersonate this identity, which is acceptable because the repo is
  public anyway and the key carries no user data or production
  authority.
- Production deployments must generate their own keypair out of band.

Same pattern as freenet-river's `test-contract/test-keys.toml`.

Small fixes uncovered along the way
-----------------------------------
- token-generator/src/tests.rs: rename `gen` to `gen_` (reserved
  keyword under edition 2024).
- token-allocation-record/tests/test.rs: #[ignore] the legacy
  integration test, which requires a build/freenet/contract-state
  fixture that doesn't exist in this tree (it lived in the upstream
  antiflood-tokens test harness). TODO left in place.

.claude/settings.json: enable the Freenet agent-skills marketplace
plugin so future contributors get the same review/build tooling.
iduartgomez added a commit to freenet/freenet-core that referenced this pull request Apr 9, 2026
These crates have been moved to the freenet-email repository, which is
their only consumer (see freenet/mail#10). They were never
part of this workspace's `members` list — they lived in modules/ as
orphaned, independently-built crates referenced only by freenet-email's
legacy Makefile.

Removed:
  modules/antiflood-tokens/  (contracts/token-allocation-record,
                              delegates/token-generator, interfaces,
                              and the standalone Makefile/workspace)
  modules/identity-management/ (delegate, tool binary, Makefile)

Verified: `cargo check --workspace` compiles 236 crates clean from
this state — no dangling references anywhere in freenet-core.
@iduartgomez iduartgomez merged commit 8caa574 into main Apr 9, 2026
1 check passed
iduartgomez added a commit that referenced this pull request Apr 9, 2026
Replaces the broken `ui-testing` feature flag (which referenced an
`Identity { description }` field that no longer existed and called a
nonexistent `InboxController::load_messages`) with two orthogonal flags
matching the freenet-river pattern:

  - `example-data` seeds the UI with mock identities and 2-3 mock
    messages per identity so the inbox renders with no real state.
  - `no-sync` skips the local-node WebSocket entirely.

`cargo make dev-example` (also new) runs the full offline preview from
a clean clone with zero contract artifacts:

  dx serve --features example-data,no-sync --no-default-features

Wiring details:

  - The sync coroutine in app.rs is now gated on
    `all(use-node, not(no-sync))`. The fallback branch is reused for
    both `not(use-node)` and `no-sync`.

  - `INBOX_CODE_HASH`, `TOKEN_RECORD_CODE_HASH`, and
    `TOKEN_GENERATOR_DELEGATE_CODE_HASH` collapse to empty string
    placeholders under `not(use-node)`. They're only dereferenced from
    contract-bridge code paths that are themselves dead in offline
    mode, so the fallback values are never observed.

  - `ui/build.rs` short-circuits to `return` when `CARGO_FEATURE_NO_SYNC`
    is set or `CARGO_FEATURE_USE_NODE` is unset, so the fail-fast
    artifact check from PR #10 doesn't bloc the offline build.

  - A crate-level
    `cfg_attr(not(use-node), allow(dead_code, unused_imports, ...))`
    suppresses the swathe of dead-code lints triggered when the
    contract-bridge half of `inbox.rs` / `aft.rs` / `api.rs` is
    unreachable. The default `use-node` build remains the source of
    truth for clippy and stays warning-clean.

  - `build-ui-example-no-sync` cargo-make target added for CI/Playwright
    release builds (Phase 3).

Drive-by cleanup of three pre-existing wasm32-target warnings that the
host-target workspace check missed:

  - `pk` unused variable in `contracts/inbox/src/lib.rs::add_message`
    (the surrounding `match` was a no-op debug stub).
  - `StreamExt` unused import in `ui/src/api.rs::WebApi::new`.
  - `mut api` no longer needs to be mutable in the same function.

Verification:
  cargo check --workspace --all-targets                       — clean
  cargo clippy --workspace --all-targets -- -D warnings       — clean
  cargo check -p freenet-email-ui \
    --no-default-features --features example-data,no-sync \
    --target wasm32-unknown-unknown                           — clean
  cargo clippy -p freenet-email-ui (same flags) -- -D warnings — clean

Part of #5.
iduartgomez added a commit that referenced this pull request Apr 9, 2026
…, deser hardening (#11)

* feat(ui): offline dev loop via example-data + no-sync features

Replaces the broken `ui-testing` feature flag (which referenced an
`Identity { description }` field that no longer existed and called a
nonexistent `InboxController::load_messages`) with two orthogonal flags
matching the freenet-river pattern:

  - `example-data` seeds the UI with mock identities and 2-3 mock
    messages per identity so the inbox renders with no real state.
  - `no-sync` skips the local-node WebSocket entirely.

`cargo make dev-example` (also new) runs the full offline preview from
a clean clone with zero contract artifacts:

  dx serve --features example-data,no-sync --no-default-features

Wiring details:

  - The sync coroutine in app.rs is now gated on
    `all(use-node, not(no-sync))`. The fallback branch is reused for
    both `not(use-node)` and `no-sync`.

  - `INBOX_CODE_HASH`, `TOKEN_RECORD_CODE_HASH`, and
    `TOKEN_GENERATOR_DELEGATE_CODE_HASH` collapse to empty string
    placeholders under `not(use-node)`. They're only dereferenced from
    contract-bridge code paths that are themselves dead in offline
    mode, so the fallback values are never observed.

  - `ui/build.rs` short-circuits to `return` when `CARGO_FEATURE_NO_SYNC`
    is set or `CARGO_FEATURE_USE_NODE` is unset, so the fail-fast
    artifact check from PR #10 doesn't bloc the offline build.

  - A crate-level
    `cfg_attr(not(use-node), allow(dead_code, unused_imports, ...))`
    suppresses the swathe of dead-code lints triggered when the
    contract-bridge half of `inbox.rs` / `aft.rs` / `api.rs` is
    unreachable. The default `use-node` build remains the source of
    truth for clippy and stays warning-clean.

  - `build-ui-example-no-sync` cargo-make target added for CI/Playwright
    release builds (Phase 3).

Drive-by cleanup of three pre-existing wasm32-target warnings that the
host-target workspace check missed:

  - `pk` unused variable in `contracts/inbox/src/lib.rs::add_message`
    (the surrounding `match` was a no-op debug stub).
  - `StreamExt` unused import in `ui/src/api.rs::WebApi::new`.
  - `mut api` no longer needs to be mutable in the same function.

Verification:
  cargo check --workspace --all-targets                       — clean
  cargo clippy --workspace --all-targets -- -D warnings       — clean
  cargo check -p freenet-email-ui \
    --no-default-features --features example-data,no-sync \
    --target wasm32-unknown-unknown                           — clean
  cargo clippy -p freenet-email-ui (same flags) -- -D warnings — clean

Part of #5.

* test(inbox): integration tests for ContractInterface round trip

Adds the first real integration test coverage for the inbox contract,
running native Rust against the `--features contract` build (no wasm
FFI). Seven tests across validation, update, and summary/delta:

  validate_state:
    - validate_accepts_signed_empty_inbox
    - validate_rejects_tampered_last_update
        (documents current behavior — `last_update` is not part of the
        signed payload, so the validator currently accepts the change.
        The test exists to surface this fact and to fail loudly if the
        contract is hardened later.)
    - validate_rejects_wrong_owner_signature

  update_state (AddMessages flow):
    - update_accepts_add_message_with_valid_token
        (full path: signed token + matching TokenAllocationRecord
        supplied via `UpdateData::RelatedState`)
    - update_rejects_message_with_unknown_token_record
        (asserts `requires(...)` rather than silent acceptance)
    - update_rejects_token_with_invalid_slot
        (Tier::Min1 + non-zero second → trips `is_valid_slot`)

  summarize_state / get_state_delta:
    - summarize_then_delta_yields_only_new_messages

The tests live under `contracts/inbox/tests/` (a new directory). All
crypto + state-construction scaffolding is centralized in
`tests/common/mod.rs`:

  - `make_keypair`           — fresh 2048-bit RSA pair
  - `make_params`            — serialize InboxParams to Parameters
  - `make_inbox_state`       — build a signed Inbox JSON payload
                              (the public `Inbox::new` is gated behind
                              `wasmbind`, so we hand-roll the wire
                              format and call `SigningKey::sign` on the
                              fixed STATE_UPDATE salt directly)
  - `make_inbox_value`       — same, but returns the JSON value so a
                              caller can tamper with one field before
                              re-serializing
  - `make_token_assignment`  — sign (tier, slot, hash) per the
                              TokenAssignment::signature_content
                              contract documented on the type
  - `make_message`, `make_token_record`, `related_contracts_with`,
    `add_messages_delta`, `related_state_update`, `assignment_hash_for`,
    `token_record_id_for`, `fixed_valid_slot`

Also adds a `cargo make test-inbox` target that runs just the new
integration suite (`-p freenet-email-inbox --features contract --test
integration`) for fast iteration on contract changes.

Verification:
  cargo make test-inbox                                       — 7/7 pass
  cargo clippy -p freenet-email-inbox --features contract \
    --tests -- -D warnings                                    — clean

Part of #5.

* fix(deser): harden identity-management deserialization boundaries

Two `serde_json::from_slice(...).unwrap()` calls in the
`identity-management` delegate's `TryFrom<&[u8]>` impls would have
panicked the delegate process on a malformed payload from the network.
Both now propagate `DelegateError::Deser`.

  - `IdentityManagement::try_from(&[u8])` (lib.rs:107)
  - `IdentityMsg::try_from(&[u8])`        (lib.rs:208)

Adds a `boundary_tests` module with four regression tests that feed
garbage / truncated bytes through both `TryFrom` impls and assert
`Err(DelegateError::Deser(_))`.

Also adds parallel boundary regression tests in
`freenet-aft-interface` (`modules/antiflood-tokens/interfaces`). The
audit cited in #5 named "~50 chrono unwraps" in this file as the
target, but inspection shows all of them sit on hardcoded constants
(tier durations, hardcoded date constants in `get_date`) or
post-check arithmetic — none touch network input. The actual network
boundaries are the `TryFrom<&[u8]>` / `TryFrom<Parameters>` /
`TryFrom<StateDelta>` impls, all of which already returned `Result`.
The new tests pin that fact (`malformed_*`, `truncated_*`,
`empty_*`) so any future regression that swaps a `?` for an `unwrap`
trips them.

Coverage:
  modules/identity-management: 4 new tests
  modules/antiflood-tokens/interfaces: 7 new tests

Verification:
  cargo test -p identity-management        — 4 passed
  cargo test -p freenet-aft-interface      — 13 passed (6 pre-existing
                                              + 7 new)

Part of #5.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant