Files
ironclaw/Cargo.toml
Benjamin Kurrek cf3226b3f7 feat(web-push): browser push notifications + PWA — the web app as a first-party notification channel (#7398)
* feat(web-push): browser push notifications + PWA — the web app as a first-party notification channel

The web app becomes a real, selectable notification route for automations,
at parity with Slack/Telegram: a bundled first-party channel extension
(web-push) delivers W3C Web Push (RFC 8030/8291/8292) to the user's
enrolled browsers through the existing catalog → notification-channel set →
notifier → delivery coordinator → channel adapter → policy-enforced egress
chain, with zero new routing machinery and the two-lane delivery contract
untouched. The WebUI ships as an installable PWA (root-scope service
worker with push + notification-click deep links; manifest and icons
already existed), and the automations page's notification-channels panel
replaces the always-on "web app" placeholder with a real toggleable row
plus a per-browser enroll/disable flow.

New crates: ironclaw_web_push (domain: subscription records + CAS store,
RFC 8291 aes128gcm encryption pinned to the RFC's Appendix A vector, VAPID
key-material generation, transport-free request planning, channel identity
grammar, late-bound runtime slot) and ironclaw_web_push_extension (channel
package: manifest with vapid_authorization egress injection, adapter with
404/410 pruning and honest Sent-without-ref evidence, personal-DM codec,
owner-scoped catalog provider). One generic host addition: the
RuntimeCredentialTarget::VapidAuthorization egress injection kind — the
host signs the RFC 8292 ES256 JWT at the existing credential chokepoint
with the audience derived from the request's own push-service origin;
adapters never see key bytes. VAPID material is auto-generated and seeded
at composition boot. Enrollment is an authenticated product surface (three
new /api/webchat/v2/web-push routes) with descriptors declared in
ironclaw_product_contracts::web_push per the transport/product boundary,
and endpoints validate against the manifest-declared push-service hosts.

Also fixes a boot bug the new integration tests exposed:
DeploymentChannelBinding rejected outbound-only channels, which would have
failed the runtime build at serve.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(web-push): address review — redact VAPID secret, byte-budget payload, account-scoped enrollment, sanitized store errors

Triage of the CodeRabbit + IronLoop review on #7398:

- Secret exposure (Critical): hand-written redacting `Debug` on
  `VapidCredentialMaterialV1` and `GeneratedVapidKeyMaterial` so the ES256
  private key can never reach a log, panic, or `{:?}`.
- VAPID material (High): composition now ensures-then-reads-back the canonical
  stored keypair so multi-replica cold-start converges on one signing key and
  the advertised applicationServerKey matches it; material shape is validated
  at boot and again before egress signing.
- Payload budget (Medium): notification body is trimmed by serialized-JSON
  bytes, not character count, so multi-byte content can't blow the
  single-record push budget (+ regression test).
- Error hygiene (Medium): `WebPushError::Store` is a fixed sanitized category;
  the backend cause is logged server-side, never rendered into the boundary
  error.
- Account-scoped enrollment (Medium): status projects an `endpoint_digest`
  (SHA-256 hex) so a shared browser profile distinguishes "enrolled here" from
  "enrolled for another account" without the endpoint URL leaving the backend;
  the frontend correlates on it and only offers destructive disable when
  verified.
- Deep-link/tag grammar enforced in the owning crate; SW validates same-origin
  before navigating; subject parsed via `url`; concurrent-writer CAS test;
  registry key derived from the extension-id constant; parse-cause preserved;
  doc/count corrections.

The manifest keeps the `web_push_vapid` field (a channel egress credential
handle must be declared in [admin_configuration]); it stays host-seeded and
not operator-supplied, with the rotation caveat documented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(web-push): keep SW registration out of the initial /chat bundle; raise the chat budget for the feature's i18n copy

The merge-lane bundle-budget gate (skipped on the PR lane) flagged the initial
/chat JS at 218.2 KB against the 217.0 KB gzip budget. `main.tsx` boot-imported
`registerServiceWorker` from `lib/web-push.ts`, which dragged the enrollment
lib's api-client and WebCrypto imports into the initial chunk. Extracted the
dependency-free `lib/register-sw.ts` for boot; the enrollment API stays in
`web-push.ts`, imported only by the already-lazy automations route. That
recovered the eager-code weight (218.2 -> 217.4 KB); the residual is the
feature's new `en.ts` fallback-pack strings, so the /chat budget is raised
217.0 -> 218.0 KB with rationale, matching how prior features (hosted MCP,
Router 8) handled eager localized copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(web-push): register the browser channel in the journey coverage gate

The merge-lane product-surface evidence gate (test_journey_coverage.py, skipped
on the PR lane) requires every outbound channel manifest to name exact journey
evidence. web-push declares `outbound = true`, so it entered the required
delivery-target set with none. Added:

- `JourneyDeliveryTarget.WEB_PUSH` and a `ProductJourneyCase` citing the
  existing `blocked_fire_pushes_web_push_notice_to_enrolled_browser`
  integration test, with unthreaded (`thread_anchor=None`) delivery-address
  evidence — browser push addresses a per-browser endpoint capability URL,
  not a conversation thread.
- `assert_web_push_delivery_evidence` in delivery_user_journeys.rs, shaped as
  the gate's citability check requires (literal `expected_conversation_id`
  gating the count, `expected_thread_anchor = None`), called from that test.

Verified: test_journey_coverage.py (68), test_product_surface_coverage.py +
test_provider_capability_inventory.py (30), and the cited integration test all
green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(web-push): rustfmt the journey-evidence helper

`cargo fmt --all -- --check` (the first step of the Fast deterministic checks
lane, PR-lane-skipped) flagged the iterator chain in
`assert_web_push_delivery_evidence`. Formatting only, no behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(web-push): present the channel as "Web UI" and hide it from the install catalog

The web-app's browser-push channel is host infrastructure, not a
browse-and-install integration, so:

- Rename the extension/channel to "Web UI" (manifest name + channel
  display_name + first-party bundle label; description reworded).
- Hide it from the install catalog (/extensions, /extensions/registry, and the
  Settings channels list) via an explicit built-in-host-surface classification
  in the product lifecycle projection — deliberately keyed by id, not inferred
  from channel direction, so it stays correct as the web-app channel later
  gains inbound/outbound. Its outbound notification target (/outbound/targets)
  is a separate registry and is unaffected, so it remains a selectable
  notification channel.

Also finalizes the enrollment UI carried from this session:

- Align the "This browser" device block with the channel cards (drop the stray
  left indent).
- Lazy-mount the web-push device hook so its status query fires only when a
  web-push row is present, not on every automations view.
- Rename the misleading webOnlyHelper i18n key to noSelectionHelper across all
  locale packs (the string already dropped the retired "stays in the web app"
  claim).
- Manifest icons declare purpose "any maskable" so Chrome offers the PWA
  install prompt.

Regression coverage extends the production web-push integration test to assert
it is absent from the install endpoints yet present in /outbound/targets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(web-push): fix stale helper name in install-catalog filter comment

CodeRabbit flagged that the filter comment still named the removed
`is_host_managed_channel` helper and described the old outbound-only
heuristic. Point it at `is_builtin_host_surface` and the id-based
classification the code actually uses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(web-push): pin notification-helper i18n key as noSelectionHelper

The P1 rename webOnlyHelper -> noSelectionHelper missed two test
assertions that pin the i18n key, failing the affected-3 Reborn crate
bucket (Tests (Reborn) rolls it up):
- crates/app/ironclaw_composition/tests/webui_v2_serve.rs (served bundle)
- crates/product/ironclaw_webui/src/webui_v2/static_assets/assets.rs (live source)

Also renames the now-misleading test fn and corrects a stale bundle-budget
comment. Test-only; no production behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(channels): notifications as a first-class channel capability

Separates "notification target" from "final-reply / model-delivery target"
so the web app is a notification channel (blocked-automation notices) without
being a place the model or a run's final reply is delivered to. Folds the
notifications-capability work into the web-push PR (#7398).

Model / declarative:
- ChannelDescriptor gains a `notifications` capability (serde default), so a
  manifest can independently declare inbound / outbound / notifications. The
  web-push manifest declares `notifications = true` (kept `outbound = true`
  for now: it is what registers the channel's delivery binding; outbound
  thread-creation is a later capability). Pinned by manifest_lockstep.

Delivery authority (the load-bearing separation):
- DeliveryTargetCapabilities gains a `notifications` bool, and a new
  `OutboundDeliveryTargetProvider::resolve_notification_target` resolves a
  target the caller may receive blocked-automation notices on, gated on
  `notifications` (not `final_replies`). Default impl + registry/mutable-registry
  overrides; the generic channel provider reuses its id-resolution.
- web-push target caps flip to `final_replies: false, notifications: true`:
  browser push is where a run's reply already lands, never a destination the
  model/final-reply path delivers *to*.
- The notification-channel picker (`/outbound/targets`) and the notifier +
  set-validation resolution now gate on the notifications capability; the
  model-facing delivery list (`builtin.outbound_delivery_targets_list`) narrows
  to `final_replies`, so a notification-only target (browser push) is invisible
  to the model until it gains outbound delivery.

Why this shape: production blocked-automation notices run through
TriggeredRunDeliveryDriver -> DeliveryCoordinator -> WebPushChannelAdapter and
select targets by capability resolution; the `ThreadNotificationPolicy` /
`progress` push-plan in ironclaw_outbound is dormant (no production writer or
caller), so gating on `progress` would not have changed delivery. Slack and
Telegram are both final-reply and notification targets and are unaffected.

Regression coverage: web-push resolves as a notification target but NOT as a
final-reply/model target (targets.rs); existing notifier + notification-channel
integration journeys stay green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(channels): cargo fmt + drop vendor names from generic-code comments

- cargo fmt reflow in notification_channel_resolution.rs.
- Reword comments in ironclaw_composition/runtime.rs and
  ironclaw_extension_host/channel_outbound_targets.rs so generic code does
  not name Slack/Telegram (reborn_extension_specificity gate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs+test(channels): fix notifications doc + serde-default coverage

Addresses CodeRabbit on the notifications-capability commit:
- channel.rs: reword ChannelDescriptor.notifications doc so it no longer claims
  the web app declines outbound (the manifest keeps outbound = true for the
  delivery binding). Net-zero line count (contracts size ceiling unchanged).
- delivery_resolution.rs: assert notifications defaults false, and add a
  legacy-payload (omitted notifications) deserialize test for #[serde(default)].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(outbound): decouple notification-picker list from model-delivery list

Addresses the CodeRabbit "Major": final_replies and notifications were coupled
through one shared base filter. Now RebornOutboundDeliveryTargetCapabilities
carries a wire-level `notifications`; list_outbound_delivery_targets returns the
union (final_replies || notifications); the notification picker
(build_outbound_delivery_targets_view) filters notifications and the model list
(list_outbound_delivery_targets_for_model) filters final_replies — independently.
A final-reply-only target is now visible to the model but not the picker; a
notification-only target (web-push) shows in the picker but never to the model.

Verified: assistant, ironclaw_webui, frontend (1208), delivery_user_journeys (25),
web-push round-trip, architecture suite (41). Chose "in #7398" per the overnight
directive to fix review comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(outbound): cover divergent notifications/final_replies at the filter seam

Addresses CodeRabbit's follow-up: the unit capability-filter seam couldn't
construct divergent targets (the fixture set notifications = final_replies).
Adds `target_entry_with_caps` (independent notifications) and a divergent-combo
test asserting the base list is the union — a final-reply-only target and a
notification-only target both survive, a neither-capable target is excluded.
Existing coincident-case tests unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 11:13:26 +00:00

552 lines
26 KiB
TOML

[workspace]
members = [".", "crates/contracts/ironclaw_common", "crates/substrates/ironclaw_observability", "crates/contracts/ironclaw_host_api", "crates/product/ironclaw_host_ingress", "crates/substrates/ironclaw_libsql_runtime", "crates/substrates/ironclaw_filesystem", "crates/domains/ironclaw_attachments", "crates/domains/ironclaw_extractors", "crates/domains/ironclaw_memory", "crates/extensions/packages/memory-native", "crates/extensions/packages/mem0", "crates/events/ironclaw_event_log", "crates/events/ironclaw_event_projections", "crates/events/ironclaw_event_streams", "crates/events/ironclaw_event_store", "crates/extensions/ironclaw_extension_registry", "crates/extensions/ironclaw_extension_host", "crates/extensions/ironclaw_extension_manager", "crates/kernel/ironclaw_processes", "crates/lanes/ironclaw_sandbox", "crates/lanes/ironclaw_mcp", "crates/lanes/ironclaw_wasm", "crates/lanes/ironclaw_wasm_limiter", "crates/kernel/ironclaw_capabilities", "crates/substrates/ironclaw_secrets", "crates/substrates/ironclaw_network", "crates/kernel/ironclaw_host_runtime", "crates/kernel/ironclaw_runtime_policy", "crates/kernel/ironclaw_authorization", "crates/kernel/ironclaw_approvals", "crates/kernel/ironclaw_resources", "crates/domains/ironclaw_auth", "crates/kernel/ironclaw_trust", "crates/kernel/ironclaw_turns", "crates/contracts/ironclaw_loop_contracts", "crates/contracts/ironclaw_extension_contracts", "crates/contracts/ironclaw_product_contracts", "crates/loop/ironclaw_agent_loop", "crates/domains/ironclaw_threads", "crates/contracts/ironclaw_prompt_envelope", "crates/loop/ironclaw_hooks", "crates/loop/ironclaw_loop_host", "crates/loop/ironclaw_turn_runner", "crates/app/ironclaw_config", "crates/product/ironclaw_operator", "crates/app/ironclaw_composition", "crates/domains/ironclaw_identity", "crates/extensions/ironclaw_extension_support", "crates/app/ironclaw_cli", "crates/domains/ironclaw_trace_commons", "crates/product/ironclaw_webui", "crates/product/ironclaw_openai_compat", "crates/domains/ironclaw_conversations", "crates/product/ironclaw_assistant", "crates/extensions/packages/telegram", "crates/extensions/packages/slack", "crates/extensions/packages/web-push", "crates/domains/ironclaw_outbound", "crates/domains/ironclaw_triggers", "crates/domains/ironclaw_web_push", "crates/app/ironclaw_architecture_tests", "crates/substrates/ironclaw_safety", "crates/domains/ironclaw_skills", "crates/domains/ironclaw_llm", "tools/ironclaw_stress"]
default-members = ["crates/app/ironclaw_cli"]
exclude = [
# Standalone helper binary, `[workspace]`-rooted and never built here (it
# needs libclang + a C toolchain). Its §5 home is `tools/`, beside the
# stress harness — WS7 relocated it out of `crates/`, which after the
# family moves holds exactly the ten family directories (PROPOSAL §5,
# §12.13 D-O).
"tools/ironclaw_silk_decoder",
# The root `fuzz/` package was deleted in WS8: it declared
# `[dependencies.ironclaw] path = ".."` and fuzzed `ironclaw::safety` /
# `ironclaw::tools`, but the root package has had no lib target since the
# v1 monolith was removed, so it could not resolve. `ironclaw_safety/fuzz`
# is a separate, live fuzz package and stays.
"crates/substrates/ironclaw_safety/fuzz",
]
[workspace.package]
rust-version = "1.96"
[workspace.dependencies]
# WASM sandbox for untrusted tool execution.
# Floor 47.0.3: minimum current major patch containing the fixes for the
# Wasmtime advisories RUSTSEC-2026-0149, RUSTSEC-2026-0188,
# RUSTSEC-2026-0222, and RUSTSEC-2026-0223.
wasmtime = { version = "47.0.3", features = ["component-model"] }
# Only the WASI p2 host API is linked (`p2::add_to_linker_sync`). Keeping
# defaults off avoids compiling unused p0/p1 WITX proc macros under remote CI
# sccache workers, where registry sidecar WITX files are not present.
wasmtime-wasi = { version = "47.0.3", default-features = false, features = ["p2"] }
# Maintainability guardrails against dead/speculative public API
# (the "Theme 1" anti-pattern from the maintainability audit: accessors,
# `_dyn` variants, and getters with zero callers that needlessly expand the
# stable surface). These are `warn`, not `deny`, and are *opted into*
# per-crate via `[lints] workspace = true` so they do not flood the build
# with pre-existing noise across the whole workspace. Currently scoped to the
# hook/reborn crates.
#
# - `unreachable_pub`: flags `pub` items that are not reachable outside their
# crate — the classic "this should be `pub(crate)`" smell.
# - `dead_code`: warn on never-used items (this is the rustc default, declared
# here so it survives any future `#![allow(dead_code)]` creep at the crate root).
[workspace.lints.rust]
unreachable_pub = "warn"
dead_code = "warn"
# A `Result`/`#[must_use]` value that is silently discarded is a swallowed
# error. `unused_must_use` is warn-by-default; deny it so a dropped `Result`
# fails the build instead of hiding a failure. Verified zero current fires
# across the workspace, so this only guards against *future* silent drops.
#
# Companion clippy lints that catch the other swallow idioms are deferred to
# follow-up PRs because they need a real cleanup first (measured on this tree):
# * `clippy::let_underscore_must_use` — 67 sites (`let _ = <must_use>`), a mix
# of safe discards and genuine swallows (e.g. an ignored async delete).
# * `clippy::map_err_ignore` — ~861 sites; a blanket deny is wrong here since
# `.claude/rules/error-handling.md` permits `map_err` to a *specific* typed
# error and only forbids cause-dropping ones. Needs a scoped sweep.
unused_must_use = "deny"
[package]
# Tier B (see docs/internal/plans/2026-07-02-reborn-internal-module-refactor.md §8):
# the v1 legacy monolith (`src/`, package `ironclaw_legacy`, binary
# `ironclaw-legacy`) has been deleted. This package now exists solely to host
# the Reborn integration test suite (`tests/integration/*`, the `reborn_*`
# `[[test]]` targets below, and their shared `[dev-dependencies]`) — it has no
# library or binary target of its own.
name = "ironclaw_integration_tests"
publish = false
version = "0.1.0"
edition = "2024"
rust-version.workspace = true
license = "MIT OR Apache-2.0"
homepage = "https://github.com/nearai/ironclaw"
repository = "https://github.com/nearai/ironclaw"
[package.metadata.ironclaw]
layer = "app"
[package.metadata.dist]
dist = false
[dev-dependencies]
# Common utility crates the Reborn integration tests (`tests/integration/*`,
# `tests/reborn_*.rs`) reference directly — previously satisfied via the now-
# deleted v1 `[dependencies]` block, since regular deps were visible to test
# binaries too.
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
async-trait = "0.1"
thiserror = "2"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["v4", "v5", "serde"] }
secrecy = { version = "0.10", features = ["serde"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
axum = { version = "0.8", features = ["ws"] }
tower = { version = "0.5", features = ["util"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "rustls-tls-native-roots", "stream"] }
base64 = "0.22"
rust_decimal = { version = "1", features = ["serde", "serde-with-str"] }
regex = "1"
sha2 = "0.11"
hex = "0.4"
url = "2"
toml = "1.1"
zip = { version = "8", default-features = false, features = ["deflate"] }
libsql = { version = "0.9", default-features = false, features = ["core", "replication", "remote", "tls"] }
# `test-support` exposes the witness-gated protocol-auth test seam and the
# messaging conformance helpers used by the integration fixtures. Declared
# explicitly because cfg(test) and sibling feature unification do not
# propagate reliably into an integration-test dependency graph.
ironclaw_host_api = { path = "crates/contracts/ironclaw_host_api", version = "0.1.0", features = ["test-support"] }
ironclaw_memory = { path = "crates/domains/ironclaw_memory", version = "0.1.0" }
# The production-backend memory integration scenario binds the native provider
# over the same libSQL composite used by memory tool dispatch.
ironclaw_memory_native = { path = "crates/extensions/packages/memory-native", version = "0.1.0" }
ironclaw_host_ingress = { path = "crates/product/ironclaw_host_ingress", version = "0.1.0" }
ironclaw_runtime_policy = { path = "crates/kernel/ironclaw_runtime_policy", version = "0.1.0" }
ironclaw_common = { path = "crates/contracts/ironclaw_common" }
ironclaw_safety = { path = "crates/substrates/ironclaw_safety" }
ironclaw_trace_commons = { path = "crates/domains/ironclaw_trace_commons", version = "0.1.0" }
# The Reborn integration harness provisions Postgres testcontainers in every
# feature lane (StorageMode::Postgres skips at runtime when unavailable), so
# the pool deps are unconditional dev-dependencies (the `postgres` feature is
# a marker with no dependency gating).
deadpool-postgres = "0.14"
tokio-postgres = { version = "0.7", features = ["with-uuid-1", "with-chrono-0_4", "with-serde_json-1"] }
# The generic-extension ingress/delivery integration suites verify webhook
# HMAC recipes and collect axum response bodies.
hmac = "0.13"
http-body-util = "0.1"
tokio-test = "0.4"
tracing-test = "0.2"
testcontainers-modules = { version = "0.12", features = ["postgres"] }
pretty_assertions = "1"
tempfile = "3"
insta = { version = "1.46.3", features = ["yaml"] }
# Pull the LLM crate's `testing` feature in only for tests so `StubLlm` and
# fault-injection helpers don't ship in release binaries.
ironclaw_llm = { path = "crates/domains/ironclaw_llm", version = "0.1.0", features = ["test-support"] }
ironclaw_agent_loop = { path = "crates/loop/ironclaw_agent_loop", version = "0.1.0", features = ["test-support"] }
ironclaw_approvals = { path = "crates/kernel/ironclaw_approvals", version = "0.1.0", features = ["test-support"] }
# Pull the auth crate's `test-support` feature in only for tests so the
# `test_support::conformance` OAuth-flow suite (used by the
# `reborn_integration_oauth_connect` test) doesn't ship in release binaries.
ironclaw_auth = { path = "crates/domains/ironclaw_auth", version = "0.1.0", features = ["test-support"] }
# C-ATTACH: `InboundAttachment` for `submit_turn_with_image_attachment`.
ironclaw_attachments = { path = "crates/domains/ironclaw_attachments", version = "0.1.0" }
# `test-support` enables the in-memory-backed capability-lease store constructor
# for the integration-test harness (cfg(test) does not propagate into deps).
ironclaw_authorization = { path = "crates/kernel/ironclaw_authorization", version = "0.1.0", features = ["test-support"] }
# Integration harness builds a fresh in-memory `ReplayPayloadStore`
# fallback for harnesses without shared approval parts (§5.3 Stage 2a-i).
ironclaw_capabilities = { path = "crates/kernel/ironclaw_capabilities", version = "0.1.0" }
ironclaw_extension_registry = { path = "crates/extensions/ironclaw_extension_registry", version = "0.1.0" }
# Extension-runtime P2: the invented-vendor fixture factory the integration
# harness registers (`NativeExtensionFactory`), plus scripted channel fakes.
ironclaw_extension_host = { path = "crates/extensions/ironclaw_extension_host", version = "0.1.0", features = ["test-support"] }
# WS2.4: the lifecycle service bundle the hosted-MCP registration integration
# test drives moved to the manager with the product face it builds.
ironclaw_extension_manager = { path = "crates/extensions/ironclaw_extension_manager", version = "0.1.0", features = ["test-support"] }
ironclaw_filesystem = { path = "crates/substrates/ironclaw_filesystem", version = "0.1.0" }
ironclaw_mcp = { path = "crates/lanes/ironclaw_mcp", version = "0.1.0" }
ironclaw_extension_support = { path = "crates/extensions/ironclaw_extension_support", version = "0.1.0" }
# W6-COLD-SPOTS: `CommunicationPreferenceRecord`/`CommunicationPreferenceKey`
# for the outbound-store-durability reopen test.
ironclaw_outbound = { path = "crates/domains/ironclaw_outbound", version = "0.1.0", features = ["test-support"] }
# Recording hook doubles + `HookDispatcherBuilderFactory` builders for the
# C-HOOKS / E-HOOK-INFRA int-tier coverage (tests/integration/support/hooks.rs).
ironclaw_hooks = { path = "crates/loop/ironclaw_hooks", version = "0.1.0" }
ironclaw_host_runtime = { path = "crates/kernel/ironclaw_host_runtime", version = "0.1.0" }
ironclaw_network = { path = "crates/substrates/ironclaw_network", version = "0.1.0" }
ironclaw_processes = { path = "crates/kernel/ironclaw_processes", version = "0.1.0", features = ["test-support"] }
ironclaw_resources = { path = "crates/kernel/ironclaw_resources", version = "0.1.0", features = ["test-support"] }
ironclaw_secrets = { path = "crates/substrates/ironclaw_secrets", version = "0.1.0" }
ironclaw_assistant = { path = "crates/product/ironclaw_assistant", version = "0.1.0", features = ["test-support"] }
ironclaw_turn_runner = { path = "crates/loop/ironclaw_turn_runner", version = "0.1.0", features = ["test-support"] }
# `ironclaw_webui` + `ironclaw_identity` sit on the int-tier coverage
# lane (identity_resolution_smoke / webui_v2_router_smoke); they compile into
# every int-tier build, behaviorally inert for suites that never call them.
# W5-SLACK-PAIR: the Slack host surface grows the lcov denominator by ~42k
# lines of slack_*.rs modules (previously invisible, not 0%) and unlocks
# composing them in int-tier tests. All 17 of those modules are recompiled
# into every int-tier test binary — a real, one-time build-time cost for the
# lane, behaviorally inert for suites that never reference Slack types.
ironclaw_composition = { path = "crates/app/ironclaw_composition", version = "0.1.0", features = ["test-support"] }
ironclaw_loop_host = { path = "crates/loop/ironclaw_loop_host", features = ["test-support"] }
# Direct dev-dep: composition deliberately does NOT re-export the bare
# `webui_v2_router`/`WebUiV2State` (facade-only rule), and the router smoke
# drives the bare router, not the `webui_v2_app` wrapper. The v2 route surface
# merged into `ironclaw_webui` (as its public `webui_v2` module).
ironclaw_webui = { path = "crates/product/ironclaw_webui", version = "0.1.0" }
# Direct dev-dep for the same reason as ironclaw_webui above: composition's
# facade-only rule means it re-exports no SLACK_V2_ADAPTER_ID/SLACK_USER_ACTOR_KIND-style
# constants; Slack pairing/actor-resolution int-tier tests need them directly.
ironclaw_slack_extension = { path = "crates/extensions/packages/slack", version = "0.1.0" }
ironclaw_telegram_extension = { path = "crates/extensions/packages/telegram", version = "0.1.0" }
ironclaw_web_push = { path = "crates/domains/ironclaw_web_push", version = "0.1.0" }
ironclaw_web_push_extension = { path = "crates/extensions/packages/web-push", version = "0.1.0" }
ironclaw_config = { path = "crates/app/ironclaw_config", version = "0.1.0" }
ironclaw_openai_compat = { path = "crates/product/ironclaw_openai_compat", version = "0.1.0"}
ironclaw_threads = { path = "crates/domains/ironclaw_threads", version = "0.1.0" }
# `test-support` unlocks the shared in-memory process-journal-backed turn
# runtime used by integration tests. cfg(test) does not propagate from an
# integration-test binary into its deps, so the feature is explicit here.
ironclaw_loop_contracts = { path = "crates/contracts/ironclaw_loop_contracts", version = "0.1.0" }
ironclaw_extension_contracts = { path = "crates/contracts/ironclaw_extension_contracts", version = "0.1.0" }
ironclaw_product_contracts = { path = "crates/contracts/ironclaw_product_contracts", version = "0.1.0" }
ironclaw_turns = { path = "crates/kernel/ironclaw_turns", version = "0.1.0", features = ["test-support"] }
# `InMemoryDurableEventLog` for the W5-WEBUI-API-1 SSE scenario's test-local
# `ProjectionStream` wiring (`build_webui_event_stream_for_test`).
ironclaw_event_log = { path = "crates/events/ironclaw_event_log", version = "0.1.0" }
# Binary Reborn E2E recovery scenarios replay the milestone-backed durable log
# through the production event projection service.
ironclaw_event_projections = { path = "crates/events/ironclaw_event_projections", version = "0.1.0" }
# `test-support` unlocks `TrustedTriggerSubmitRequest::new_for_test` (E-TRIGGERED-SUBMIT
# harness seam) — cfg(test) does not propagate from an integration-test binary into its deps.
ironclaw_triggers = { path = "crates/domains/ironclaw_triggers", version = "0.1.0", features = ["test-support"] }
# Trigger-conversation binding/session services for the E-TRIGGERED-SUBMIT harness seam —
# mirrors the conversation-services type production's own local-dev build wires for the
# trusted-trigger submit path (ironclaw_composition::runtime.rs).
ironclaw_conversations = { path = "crates/domains/ironclaw_conversations", version = "0.1.0" }
ironclaw_trust = { path = "crates/kernel/ironclaw_trust", version = "0.1.0" }
ironclaw_wasm = { path = "crates/lanes/ironclaw_wasm", version = "0.1.0" }
# `ironclaw_outbound/test-support` provides the in-memory-backed
# `OutboundStateStore` the triggered-delivery outcome-seam int-tier
# proof injects (asserting a real `TriggeredRunDeliveryDriver`'s recorded
# outcome via the same public store trait the composition factory accepts).
# Named `#[case]` parametrization for the storage-backend matrix tests (slice 3).
rstest = "0.23"
[features]
# Opt-in feature for especially heavy integration-test targets that run in a
# dedicated CI job instead of the default Rust test matrix.
integration = []
[[test]]
name = "reborn_group_approvals"
path = "tests/integration/group_approvals/main.rs"
[[test]]
name = "reborn_group_memory"
path = "tests/integration/group_memory/main.rs"
[[test]]
name = "reborn_group_extensions"
path = "tests/integration/group_extensions/main.rs"
[[test]]
name = "reborn_group_triggers"
path = "tests/integration/group_triggers/main.rs"
[[test]]
name = "reborn_group_multiuser"
path = "tests/integration/group_multiuser/main.rs"
[[test]]
name = "reborn_group_skills"
path = "tests/integration/group_skills/main.rs"
[[test]]
name = "reborn_generated_gate_sequences"
path = "tests/integration/generated_gate_sequences.rs"
[[test]]
name = "reborn_generated_restart_sequences"
path = "tests/integration/generated_restart_sequences.rs"
[[test]]
name = "reborn_group_journeys"
path = "tests/integration/group_journeys/main.rs"
[[test]]
name = "reborn_integration_oauth_refresh"
path = "tests/integration/auth/oauth_refresh.rs"
[[test]]
name = "reborn_integration_attach"
path = "tests/integration/attach.rs"
[[test]]
name = "reborn_integration_sandbox_shell_turn"
path = "tests/integration/reborn_sandbox_shell_turn.rs"
[[test]]
name = "reborn_integration_subagent_await_edge"
path = "tests/integration/subagent_await_edge.rs"
[[test]]
name = "reborn_integration_auth_failure"
path = "tests/integration/auth/auth_failure.rs"
[[test]]
name = "reborn_integration_auth_gate"
path = "tests/integration/auth/auth_gate.rs"
[[test]]
name = "reborn_integration_backend_matrix"
path = "tests/integration/backend_matrix.rs"
[[test]]
name = "reborn_integration_budget"
path = "tests/integration/budget.rs"
[[test]]
name = "reborn_integration_cancel"
path = "tests/integration/cancel.rs"
[[test]]
name = "reborn_integration_steering"
path = "tests/integration/steering.rs"
[[test]]
name = "reborn_integration_model_recovery"
path = "tests/integration/model_recovery.rs"
[[test]]
name = "reborn_integration_terminal_warning"
path = "tests/integration/terminal_warning.rs"
[[test]]
name = "reborn_integration_channel_connection_projection"
path = "tests/integration/channel_connection_projection.rs"
[[test]]
name = "reborn_integration_comm_context"
path = "tests/integration/comm_context.rs"
[[test]]
name = "reborn_integration_durable"
path = "tests/integration/durable.rs"
[[test]]
name = "reborn_integration_delivery_user_journeys"
path = "tests/integration/delivery_user_journeys.rs"
[[test]]
name = "reborn_integration_extension_runtime"
path = "tests/integration/extension_runtime.rs"
[[test]]
name = "reborn_integration_extension_ingress"
path = "tests/integration/extension_ingress.rs"
[[test]]
name = "reborn_integration_extension_delivery"
path = "tests/integration/extension_delivery.rs"
[[test]]
name = "reborn_integration_extension_visibility"
path = "tests/integration/extension_visibility.rs"
[[test]]
name = "reborn_integration_extension_user_lifecycle_isolation"
path = "tests/integration/extension_user_lifecycle_isolation.rs"
[[test]]
name = "reborn_integration_golden_payload"
path = "tests/integration/golden_payload.rs"
[[test]]
name = "reborn_integration_greeting"
path = "tests/integration/greeting.rs"
[[test]]
name = "reborn_integration_hooks"
path = "tests/integration/hooks.rs"
[[test]]
name = "reborn_integration_http_matcher"
path = "tests/integration/http_matcher.rs"
[[test]]
name = "reborn_integration_idempotent_replay"
path = "tests/integration/idempotent_replay.rs"
[[test]]
name = "reborn_integration_identity_resolution_smoke"
path = "tests/integration/identity_resolution_smoke.rs"
[[test]]
name = "reborn_integration_lease_wedge"
path = "tests/integration/lease_wedge.rs"
[[test]]
name = "reborn_integration_mcp"
path = "tests/integration/mcp.rs"
[[test]]
name = "reborn_integration_hosted_mcp_registration"
path = "tests/integration/hosted_mcp_registration.rs"
[[test]]
name = "reborn_integration_oauth_connect"
path = "tests/integration/auth/oauth_connect.rs"
[[test]]
name = "reborn_integration_oauth_popup_journeys"
path = "tests/integration/auth/oauth_popup_journeys.rs"
[[test]]
name = "reborn_integration_outbound_store_durability"
path = "tests/integration/outbound_store_durability.rs"
[[test]]
name = "reborn_integration_outbound_target"
path = "tests/integration/outbound_target.rs"
[[test]]
name = "reborn_integration_process_port"
path = "tests/integration/process_port.rs"
[[test]]
name = "reborn_integration_profile"
path = "tests/integration/profile.rs"
[[test]]
name = "reborn_integration_project_create"
path = "tests/integration/project_create.rs"
[[test]]
name = "reborn_integration_real_egress_pipeline"
path = "tests/integration/real_egress_pipeline.rs"
[[test]]
name = "reborn_integration_reopen_resume_through_gate"
path = "tests/integration/auth/reopen_resume_through_gate.rs"
[[test]]
name = "reborn_integration_safety"
path = "tests/integration/safety.rs"
[[test]]
name = "reborn_integration_secret_injection"
path = "tests/integration/secret_injection.rs"
[[test]]
name = "reborn_integration_secrets"
path = "tests/integration/secrets.rs"
[[test]]
name = "reborn_integration_skill_activate"
path = "tests/integration/skill_activate.rs"
[[test]]
name = "reborn_integration_surface_disclosure"
path = "tests/integration/surface_disclosure.rs"
[[test]]
name = "reborn_integration_tool_call"
path = "tests/integration/tool_call.rs"
[[test]]
name = "reborn_integration_tool_disclosure"
path = "tests/integration/tool_disclosure.rs"
[[test]]
name = "reborn_integration_trace_capture"
path = "tests/integration/trace_capture.rs"
[[test]]
name = "reborn_integration_tracecap"
path = "tests/integration/tracecap.rs"
[[test]]
name = "reborn_integration_triggered_submit"
path = "tests/integration/triggered_submit.rs"
[[test]]
name = "reborn_integration_web_access"
path = "tests/integration/web_access.rs"
[[test]]
name = "reborn_integration_webui_v2_product_api"
path = "tests/integration/webui_v2_product_api.rs"
[[test]]
name = "reborn_integration_webui_v2_router_smoke"
path = "tests/integration/webui_v2_router_smoke.rs"
[[test]]
name = "reborn_integration_wiring_parity"
path = "tests/integration/wiring_parity.rs"
[profile.release]
strip = true # Remove debug symbols from release binaries
# The profile that 'cargo dist' will build with
[profile.dist]
inherits = "release"
lto = "thin"
# Config for 'dist'
[workspace.metadata.dist]
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.31.0"
# cargo-dist 0.31 grants `contents: write` to every generated job. Keep the
# checked-in CI customized so only the host job can publish a GitHub Release.
allow-dirty = ["ci"]
# Release only the canonical Reborn CLI.
packages = ["ironclaw"]
# CI backends to support
ci = "github"
# Match the canonical `ironclaw-v*` release tags.
tag-namespace = "ironclaw"
# Install build-only prerequisites before cargo-dist compiles each target.
github-build-setup = "../dist-build-setup.yml"
# The installers to generate for each app
installers = ["shell", "powershell", "msi"]
# Publish jobs to run in CI
publish-jobs = []
# Target platforms to build apps for (Rust target-triple syntax)
targets = [
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
"x86_64-pc-windows-msvc",
]
# The archive format to use for windows builds (defaults .zip)
windows-archive = ".tar.gz"
# The archive format to use for non-windows builds (defaults .tar.xz)
unix-archive = ".tar.gz"
# Which actions to run on pull requests
pr-run-mode = "skip"
# Path that installers should place binaries in
install-path = "CARGO_HOME"
# Whether to install an updater program
install-updater = true
# Cache intermediate build artifacts to speed up the release pipelines
cache-builds = true
[workspace.metadata.dist.github-custom-runners]
aarch64-unknown-linux-gnu = "ubuntu-24.04-arm"
aarch64-unknown-linux-musl = "ubuntu-24.04-arm"
x86_64-unknown-linux-gnu = "ubuntu-22.04"
x86_64-unknown-linux-musl = "ubuntu-22.04"
x86_64-pc-windows-msvc = "windows-2022"
x86_64-apple-darwin = "macos-15-intel"
aarch64-apple-darwin = "macos-14"
[workspace.metadata.dist.github-action-commits]
"actions/checkout" = "df4cb1c069e1874edd31b4311f1884172cec0e10"
"actions/download-artifact" = "37930b1c2abaa49bbe596cd826c3c89aef350131"
"actions/upload-artifact" = "b7c566a772e6b6bfb58ed0dc250532a479d7789f"
"swatinem/rust-cache" = "e18b497796c12c097a38f9edb9d0641fb99eee32"