Building a Reborn feature
Start by locating the existing ProductSurface descriptor, route, and caller
test. Most WebUI features are already represented by one of these two paths:
read: WebUI handler -> ProductSurface::query -> ProductView descriptor
write: WebUI handler -> ProductSurface::invoke -> capability descriptor
-> query read-back when the result is durable state
The owning crates are:
ironclaw_assistant: product DTOs, the concrete ProductView and
command/capability descriptor instances, and product orchestration.
ironclaw_product_contracts: the ProductSurface contract, the
ProductView/ProductSurfaceCommandDescriptor/ProductCapabilityDescriptor
types, and the ProductSurfaceCaller/BoundProductSurface caller binding.
ironclaw_host_api: shared host-facing error vocabulary
(ProductAdapterError).
ironclaw_webui: route descriptors, handlers, gateway/listener/auth, and
the Vite frontend under frontend/.
ironclaw_composition: production assembly and dependency wiring.
ironclaw_cli: boot and serve command wiring.
Before editing
Run the graph status check once. If it is missing or stale, use targeted
rg searches and verify the result against live code.
bash scripts/codebase-graph.sh status
rg -n "ProductSurface|ProductView|ProductSurfaceCommandDescriptor|ProductCapabilityDescriptor" crates/product/ironclaw_assistant crates/contracts/ironclaw_product_contracts crates/product/ironclaw_webui
rg -n "descriptor|webui_v2_routes|ProductSurface" crates/product/ironclaw_webui/src/webui_v2
Read the owning crate's AGENTS.md, then CLAUDE.md or CONTRACT.md when
present. Find the nearest existing descriptor and copy its narrow pattern.
Default implementation
- Add or reuse a typed
ProductView<Params, Output> in
crates/product/ironclaw_assistant/src/reborn_services.rs or its owning submodule.
- Add or reuse a
ProductSurfaceCommandDescriptor for typed product
commands, or a ProductCapabilityDescriptor for API-only side effects.
- Implement the backing behavior inside
ironclaw_assistant or the owning
service. Keep authorization, approval, persistence, and runtime mediation
in their existing stages.
- Add the route descriptor and thin handler in
ironclaw_webui. Handlers
receive ProductSurfaceCaller and use BoundProductSurface; they do not
reach into composition, stores, dispatchers, or runtime lanes.
- Add the frontend code under
crates/product/ironclaw_webui/frontend/src and use
the existing API client and page patterns.
- Wire only genuinely new production dependencies through composition and
the CLI. Do not add a builder or
Arc field when an existing surface can
carry the operation.
Add an abstraction only when it earns its keep
Do not add a feature-specific port, facade method, DTO family, builder field,
or adapter by default. Add one only when it provides dependency inversion,
two production implementations, a real test seam, a required dyn injection
point, or an enforced security/ownership boundary. Record the reason in the
PR description and run the architecture test for dependency changes.
Automations/triggers work
Trigger/automation domain work (cron/once schedules, run-now, trigger history
and settlement) crosses crates/domains/ironclaw_triggers, its trusted-submit
wiring in crates/app/ironclaw_composition/src/automation/, and the WebUI
automations surface (crates/product/ironclaw_webui/frontend/src/pages/automations/).
Two things to get right before touching this path:
- Sealed ingress is a hard invariant, not a convention. Read root
AGENTS.md → "Host-trusted trigger ingress is sealed by..." before writing
any code here. Product adapters, product workflow, first-party
capabilities, and host-runtime handlers use untrusted inbound requests and
must never mint TrustedInboundTurnRequest or call trusted trigger
submitter factories — only trigger-worker-owned minting and private
conversation-owned trusted construction may. Verify with
rg -n "TrustedInboundTurnRequest|ConversationTrustedTriggerSubmitter" crates/ (today
the only hits are the allowed owner, crates/domains/ironclaw_conversations, plus the
architecture test itself — any hit outside that crate is a prohibited caller); the
boundary is enforced by
untrusted_ingress_paths_cannot_submit_host_trusted_inbound in
crates/app/ironclaw_architecture_tests/tests/reborn_dependency_boundaries.rs.
- Settlement, fire identity, and run-history ordering are specified, not
improvised.
crates/domains/ironclaw_triggers/AGENTS.md and
docs/internal/reborn/contracts/triggers.md are the source of truth — read
both before changing schedule, claim, settlement, or history behavior;
this class of invariant has a history of needing multiple follow-up fixes
to get right, so treat first-pass changes here as review-heavy.
Boundary rules
- WebUI handlers consume
ProductSurface only. ironclaw_assistant imports in
WebUI are limited to wire DTOs and descriptors.
- Composition assembles dependencies; it does not own product policy.
- External input is validated and bounded at the HTTP or adapter boundary.
- Mutations use the capability path and report authoritative evidence; durable
state is read back when the contract requires it.
- Identity and scope come from the authenticated caller, never the request
body.
Verification
cargo test -p ironclaw_assistant
cargo clippy -p ironclaw_assistant --all-targets --all-features -- -D warnings
cargo test -p ironclaw_webui --all-features
cargo clippy -p ironclaw_webui --all-targets --all-features -- -D warnings
cargo test -p ironclaw_architecture_tests # when ownership or dependencies change
pnpm --dir crates/product/ironclaw_webui/frontend test
Use a caller-level test for every new route or side effect. Add a whole-path
integration test when the feature changes turn execution or cross-layer
behavior. Do not add a new test tier solely because a recipe lists it.
1---2name: reborn-feature3description: Use when building or extending a user-facing WebUI feature, endpoint, ProductSurface command/capability, or product-level API surface in the Reborn stack, or when adding/changing trigger and automation domain behavior (schedules, run-now, trigger history).4---56# Building a Reborn feature78Start by locating the existing ProductSurface descriptor, route, and caller9test. Most WebUI features are already represented by one of these two paths:1011```text12read: WebUI handler -> ProductSurface::query -> ProductView descriptor13write: WebUI handler -> ProductSurface::invoke -> capability descriptor14 -> query read-back when the result is durable state15```1617The owning crates are:1819- `ironclaw_assistant`: product DTOs, the concrete `ProductView` and20 command/capability descriptor instances, and product orchestration.21- `ironclaw_product_contracts`: the `ProductSurface` contract, the22 `ProductView`/`ProductSurfaceCommandDescriptor`/`ProductCapabilityDescriptor`23 types, and the `ProductSurfaceCaller`/`BoundProductSurface` caller binding.24- `ironclaw_host_api`: shared host-facing error vocabulary25 (`ProductAdapterError`).26- `ironclaw_webui`: route descriptors, handlers, gateway/listener/auth, and27 the Vite frontend under `frontend/`.28- `ironclaw_composition`: production assembly and dependency wiring.29- `ironclaw_cli`: boot and serve command wiring.3031## Before editing3233Run the graph status check once. If it is missing or stale, use targeted34`rg` searches and verify the result against live code.3536```bash37bash scripts/codebase-graph.sh status38rg -n "ProductSurface|ProductView|ProductSurfaceCommandDescriptor|ProductCapabilityDescriptor" crates/product/ironclaw_assistant crates/contracts/ironclaw_product_contracts crates/product/ironclaw_webui39rg -n "descriptor|webui_v2_routes|ProductSurface" crates/product/ironclaw_webui/src/webui_v240```4142Read the owning crate's `AGENTS.md`, then `CLAUDE.md` or `CONTRACT.md` when43present. Find the nearest existing descriptor and copy its narrow pattern.4445## Default implementation46471. Add or reuse a typed `ProductView<Params, Output>` in48 `crates/product/ironclaw_assistant/src/reborn_services.rs` or its owning submodule.492. Add or reuse a `ProductSurfaceCommandDescriptor` for typed product50 commands, or a `ProductCapabilityDescriptor` for API-only side effects.513. Implement the backing behavior inside `ironclaw_assistant` or the owning52 service. Keep authorization, approval, persistence, and runtime mediation53 in their existing stages.544. Add the route descriptor and thin handler in `ironclaw_webui`. Handlers55 receive `ProductSurfaceCaller` and use `BoundProductSurface`; they do not56 reach into composition, stores, dispatchers, or runtime lanes.575. Add the frontend code under `crates/product/ironclaw_webui/frontend/src` and use58 the existing API client and page patterns.596. Wire only genuinely new production dependencies through composition and60 the CLI. Do not add a builder or `Arc` field when an existing surface can61 carry the operation.6263## Add an abstraction only when it earns its keep6465Do not add a feature-specific port, facade method, DTO family, builder field,66or adapter by default. Add one only when it provides dependency inversion,67two production implementations, a real test seam, a required `dyn` injection68point, or an enforced security/ownership boundary. Record the reason in the69PR description and run the architecture test for dependency changes.7071## Automations/triggers work7273Trigger/automation domain work (cron/once schedules, run-now, trigger history74and settlement) crosses `crates/domains/ironclaw_triggers`, its trusted-submit75wiring in `crates/app/ironclaw_composition/src/automation/`, and the WebUI76automations surface (`crates/product/ironclaw_webui/frontend/src/pages/automations/`).77Two things to get right before touching this path:7879- **Sealed ingress is a hard invariant, not a convention.** Read root80 `AGENTS.md` → "Host-trusted trigger ingress is sealed by..." before writing81 any code here. Product adapters, product workflow, first-party82 capabilities, and host-runtime handlers use untrusted inbound requests and83 must never mint `TrustedInboundTurnRequest` or call trusted trigger84 submitter factories — only trigger-worker-owned minting and private85 conversation-owned trusted construction may. Verify with86 `rg -n "TrustedInboundTurnRequest|ConversationTrustedTriggerSubmitter" crates/` (today87 the only hits are the allowed owner, `crates/domains/ironclaw_conversations`, plus the88 architecture test itself — any hit outside that crate is a prohibited caller); the89 boundary is enforced by90 `untrusted_ingress_paths_cannot_submit_host_trusted_inbound` in91 `crates/app/ironclaw_architecture_tests/tests/reborn_dependency_boundaries.rs`.92- **Settlement, fire identity, and run-history ordering are specified, not93 improvised.** `crates/domains/ironclaw_triggers/AGENTS.md` and94 `docs/internal/reborn/contracts/triggers.md` are the source of truth — read95 both before changing schedule, claim, settlement, or history behavior;96 this class of invariant has a history of needing multiple follow-up fixes97 to get right, so treat first-pass changes here as review-heavy.9899## Boundary rules100101- WebUI handlers consume `ProductSurface` only. `ironclaw_assistant` imports in102 WebUI are limited to wire DTOs and descriptors.103- Composition assembles dependencies; it does not own product policy.104- External input is validated and bounded at the HTTP or adapter boundary.105- Mutations use the capability path and report authoritative evidence; durable106 state is read back when the contract requires it.107- Identity and scope come from the authenticated caller, never the request108 body.109110## Verification111112```bash113cargo test -p ironclaw_assistant114cargo clippy -p ironclaw_assistant --all-targets --all-features -- -D warnings115cargo test -p ironclaw_webui --all-features116cargo clippy -p ironclaw_webui --all-targets --all-features -- -D warnings117cargo test -p ironclaw_architecture_tests # when ownership or dependencies change118pnpm --dir crates/product/ironclaw_webui/frontend test119```120121Use a caller-level test for every new route or side effect. Add a whole-path122integration test when the feature changes turn execution or cross-layer123behavior. Do not add a new test tier solely because a recipe lists it.