aves 0.5.0

Typed async application runtime with domain messaging, RPC, and durable workflow automation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
# Aves

Build and automate typed domains with an **async application runtime for Rust**.

Name Aves after Class Aves: the root class containing all birds.
Compose independent domains without moving their business logic into shared infrastructure.
Generate request contracts, RPC adapters, privileged services, and routing from domain declarations.
Add durable Flow automation, configuration, authenticated messaging, and sandboxed script execution.

Use workspace sources while `publish = false` and local dependencies remain.
This guide describes the current source API, not a published crates.io release.

## Installation

Add Aves from another package in this workspace:

```toml
[dependencies]
aves = { path = "../aves" }
```

Adjust the path for your checkout layout.
Use the repository's [development environment](../../AGENTS.md) and pinned dependency versions.

Import the procedural macros through Aves:

```rust
use aves::{bus_requests, bus_responses, domain, ErrorCode};
```

Do not add `aves_macros` merely to access those re-exports.
Aves owns that procedural-macro implementation package under `domain_setup/`.
Declare additional crates directly when your domain or generated code references them.
For example, serialization/schema/RPC code uses Serde, serde_json, Schemars, and rspc.
Use Tokio for asynchronous actors and thiserror for typed failures.
Service declarations also use async-trait in generated code.

Inspect [Kea's manifest](../kea/Cargo.toml) for a concrete domain's dependencies.
Inspect [Corv's manifest](../corv/Cargo.toml) for a domain publishing privileged services.
Do not copy every Aves dependency into your application.
Native Iroh support is currently a runtime dependency, not an optional Cargo feature.

## Quick Start

Follow one complete domain before building your own:

1. Read [Kea's request contracts]../kea/src/messages.rs.
2. Follow [its domain declaration and callbacks]../kea/src/domain.rs.
3. Inspect [its handler wiring]../kea/src/handle.rs.
4. Keep state and work in [its actor]../kea/src/actor.rs.
5. Follow [application composition]../poseidon/src/app.rs to install domains and construction inputs.

The following public-API example creates a typed configuration registration:

```rust
use aves::config::{ConfigError, SectionRegistration};
use serde::{Deserialize, Serialize};

#[derive(Default, Serialize, Deserialize)]
struct WorkerSettings {
  enabled: bool,
}

fn worker_section() -> Result<SectionRegistration, ConfigError> {
  SectionRegistration::new::<WorkerSettings>("worker")
}
```

Publish that registration through `ConfigRegistrationSudoService` during your domain's construction.
Construct Config first, then domains that register sections or read saved configuration.
Supply construction inputs explicitly rather than introducing process-global registries.

The sections below explain the remaining mechanisms and their real source implementations.
Source excerpts retain domain-local names; they are not standalone application files.
Public-API examples were source-reviewed, not compiled or executed for this README.

## Usage

### Choose the right ownership boundary

Put application work in domains and reusable mechanisms in Aves.

| Layer | Owns |
|---|---|
| Runtime | Domain construction, service slots, lifecycle, Auth integration, bus ownership |
| Bus | Authenticated opaque-envelope delivery by domain-marker `TypeId` |
| Domain declaration | Operation metadata, generated routing, service mappings, broadcast projection |
| Handler | Actor construction and request/broadcast forwarding |
| Actor | State, validation, persistence decisions, and domain work |
| Application | Domain composition, construction inputs, root errors/broadcasts, transport factories |

Keep actors, internal state, and implementation modules private.
Re-export only contracts required by real callers.
Do not teach the generic bus about domain names or business operations.

Use a small domain layout:

```text
example/
  mod.rs
  domain.rs
  handle.rs
  actor.rs
  messages.rs
  types.rs
  queries/
```

Use `lib.rs` instead of `mod.rs` when the domain owns its own crate.
Add files only when they have a clear owner and purpose.

### Define requests separately from reply channels

Declare a payload containing operation parameters.
Wrap it with a typed reply channel for internal request/response handling.
Do not embed oneshot senders inside public payload variants.

Request-wrapper excerpt from [Kea](../kea/src/messages.rs):

```rust
pub struct KnowledgeRequest {
  pub payload: KnowledgeRequestPayload,
  pub reply_to: oneshot::Sender<Result<KnowledgeResponse, KnowledgeError>>,
}
```

Declare the payload's marker, context, and error boundaries explicitly.
This library-boundary style avoids relying on inferred names.

Selected payload declaration from the same source:

```rust
#[aves::bus_requests(
  marker = KnowledgeDomain,
  context = BusRpcContext,
  root_error = KnowledgeError,
  domain_error = KnowledgeError
)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum KnowledgeRequestPayload {
  #[operation(
    name = "createKnowledge",
    kind = "mutation",
    response = KnowledgeCreatedOutput,
    response_variant = "KnowledgeCreated"
  )]
  CreateKnowledge { request: CreateKnowledgeRequest },
}
```

This excerpt selects one operation from Kea's complete payload enum.
Define referenced DTOs, errors, and responses in the owning domain.
Use typed identifiers and narrow input types, not stringly typed dispatch envelopes.
Document request fields: schema generation exposes those comments to external callers.

### Generate operation adapters and response projections

Annotate response enums with `#[aves::bus_responses]`.
The macro creates typed output structs and `TryFrom<Response>` projections.
Reference those outputs in request operation metadata.

Selected response declaration from [Kea](../kea/src/messages.rs):

```rust
#[aves::bus_responses]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum KnowledgeResponse {
  KnowledgeCreated { knowledge: KnowledgeRecord },
}
```

For public operations, request generation supplies:

- A typed operation input and its conversion into the payload.
- A typed `send_<operation>` function for authenticated bus ingress.
- A `dispatch_<operation>` helper accepting a `BusRpcContext`.
- JSON dispatch adapters for transport integration.
- Input/output schemas and `RequestOperationDescriptor` metadata.
- An rspc router and operation descriptors adapted to embedding root errors.

Choose `kind = "query"` or `kind = "mutation"` intentionally.
Treat operation names and serialized error identities as client contracts.
Changing a Rust module path does not require changing those contracts.

Inspect [request generation](domain_setup/src/request.rs) and [response generation](domain_setup/src/response.rs).
Use the generated interfaces rather than writing parallel routing tables.
Do not assume every generated helper is re-exported by a domain crate.

### Distinguish publication, serialization, and privilege

Keep these mechanisms separate:

| Declaration | Effect |
|---|---|
| `#[operation(...)]` | Publish generated RPC/operation metadata for an operation |
| `#[privileged(reason = "...")]` | Exclude public generated clients and require a privileged service mapping |
| `#[serde(skip)]` | Exclude a variant or field from its serialized representation |

Do not use `#[serde(skip)]` as a substitute for privileged authorization.
Internal lifecycle messages may be unpublished without being privileged service operations.
Public does not mean unauthenticated: normal Aves admission still applies.

Declare privileged capabilities through named sudo services.
Selected service mappings from [Auth](src/auth/domain.rs):

```rust
pub service AuthSudoService {
  IssueJwt => DelegatedJwtIssued (operation = "issueJwt"),
  IssueRootJwt => JwtIssued (operation = "issueRootJwt"),
  VerifyJwt => Verified (operation = "verifyJwt"),
  RevokeJti => Success (operation = "revokeJti"),
}
```

Place those mappings inside the owning `aves::domain!` declaration.
The complete Auth declaration maps its additional privileged variants too.
Generation enforces privileged mappings and rejects public variants mapped as sudo operations.

Give consumers only the service interfaces they require.
Do not hand them another domain's actor, persistence connection, or broad management authority.
Privileged services remain in-process capabilities, not public RPC backdoors.

### Declare domain routing and broadcasts

Use `aves::domain!` to declare the domain marker and request/response/error boundaries.
Add services and broadcast mappings in the same owner declaration.

Selected routing declaration from [Kea](../kea/src/domain.rs):

```rust
aves::domain! {
  marker KnowledgeDomain;

  request KnowledgeRequestPayload => KnowledgeResponse
  error = KnowledgeError
  transport = SharedInfraError
  target = "knowledge";

  outbound KnowledgeResponse => KnowledgeBroadcast {
    KnowledgeCreated { knowledge: _ } => RecordsChanged,
    KnowledgeUpdated { knowledge: _ } => RecordsChanged,
    KnowledgeDeleted { result: _ } => RecordsChanged,
  }
}
```

Add explicit service blocks when a domain needs named service traits and implementations.
Kea's full source includes additional broadcast mappings.

Let successful responses drive the handler's declared broadcast projection.
Route internal service requests through that handler too.
Bypassing the handler can lose broadcasts even when the actor mutation succeeds.
Keep domain-specific work out of generated routing and transport adapters.

### Wire bounded actor and service channels

Register the domain's marker, request wrapper, and broadcast type with the bus.
Create a separate bounded actor inbox.
Let the generated handler forward admitted requests into that inbox.

Construction excerpt from [Kea's handler](../kea/src/handle.rs):

```rust
let (actor_tasks, actor_tasks_rx) = mpsc::channel(64);
let channels = driver.register::<KnowledgeDomain, KnowledgeRequest, KnowledgeBroadcast>();
let (broadcast_tx, actor_broadcast_rx) = mpsc::channel(64);

let generated_handler = KnowledgeGeneratedHandler::new(
  channels.requests_rx,
  actor_tasks.clone(),
  channels.broadcasts_tx,
  actor_broadcast_rx,
);
```

The enclosing handler provides these domain-local names and imports.
Pass `actor_tasks_rx` to the actor; retain the generated handler for forwarding.
Kea's handler also subscribes to configuration broadcasts.

For a declared service, use its generated channel constructor.
Service-channel excerpt from [Corv](../corv/src/handle.rs):

```rust
let (streaming_service_impl, streaming_service_rx) =
  super::DiscussStreamingServiceImpl::channel(DEFAULT_BUFFER_SIZE);
```

Publish the implementation through the construction facade's service publisher.
Pass its receiver to the generated handler alongside normal bus requests.
Do not construct generated implementation fields or create a second forwarding path.

Read dependencies through `ServiceReader`; publish through `ServicePublisher`.
Readers are late-bound, but immediate `.get()` calls still require an available provider.
Do not assume arbitrary construction order is safe for every domain.

### Implement actor work and typed errors

Keep the actor responsible for its state and domain operations.
Receive a request wrapper, match its payload, and send one typed result.
Use owned task messages for work that must return asynchronously.
Keep network or expensive operations from blocking unrelated actor work where practical.

Use thiserror for domain failures.
Carry reusable infrastructure failures through typed `From` conversions.
Use `aves::utils::errors::SharedInfraError` for shared channel/oneshot plumbing boundaries.
Keep that infrastructure error out of public wire contracts.

Adapt domain errors into the embedding application's root error explicitly.
Preserve structured domain and code identities instead of flattening everything into strings.
Read [Kea's error boundary](../kea/src/types.rs) and [generated call errors](src/runtime/supervisor.rs).

### Compose runtime domains

Use `aves::runtime_domains!` in the embedding application's composition root.
Associate each domain with its router, descriptors, route names, and broadcast projection.
Let generated composition provide lifecycle descriptors, RPC routing, and publication catalogs.

Selected entry from [Poseidon's real composition](../poseidon/src/app.rs):

```rust
ConfigDomain => config_router,
  normal: AVES_CONFIG_OPERATIONS.as_slice(),
  mcp: AVES_CONFIG_OPERATIONS.as_slice(),
  "Sluice Config", "config",
  broadcast: ConfigBroadcast => Config,
```

Place entries inside the macro's `entries` list.
Supply `context`, `root_error`, and `root_broadcast` as shown in the complete source.
Provide the root broadcast conversions required by your domain callbacks.
Do not manually copy a domain's generated operation registry.

Prepare typed inputs with `ConstructionInputs::new().with(value)`.
Request them during construction with `facade.input::<T>()`.
Typical inputs include `ConfigDirectory`, domain boot options, and shared node-registration supplies.
Do not silently create a second supply when domains must share one instance.

### Own the runtime lifecycle

Build `RuntimeOptions` with all required host inputs:

| Field | Responsibility |
|---|---|
| `keyring_service_namespace` | Application-owned namespace for Auth's identity storage |
| `construction_inputs` | Explicit typed inputs for domain constructors |
| `transport_bindings` | Host-provided RPC/MCP listener factories and route metadata |
| `data_root` | Auth peer state and native substrate data root |
| `remote_operation_installer` | Optional installer for generated public peer operations |

Then construct the runtime from the generated lifecycle list.
Application-lifecycle excerpt; application types and `options` come from the composition root:

```rust
let runtime = Runtime::<AppBroadcast>::new(options, AppDomains::lifecycle()).await?;
runtime.start().await?;
let context = runtime.rpc_context();
let broadcasts = runtime.subscribe_broadcasts();
```

Use the actual public APIs: `rpc_context()`, `request_sender()`, and `subscribe_broadcasts()`.
Use `service_reader::<dyn Service>()` when acquiring a runtime-published service.
Do not expose the private `BusHandle` to embedding applications.

Implement `DomainDescriptorFactory<EventBus<Root>>` for each domain marker.
Return `DomainCallbacks` containing `new`, `start`, and `stop` callbacks.
Use `DomainFuture` for asynchronous construction callbacks.
See [Kea's full factory implementation](../kea/src/domain.rs).

Understand the ordering guarantees:

1. Aves constructs built-in Auth and installs authenticated bus context.
2. It constructs supplied domains sequentially in descriptor order.
3. It finalizes the shared bus after construction.
4. `start()` seals configuration registration and runs start callbacks concurrently.
5. `shutdown()` stops Flow first, then other domain callbacks under a shared timeout.
6. It shuts down native Iroh handlers and the bus afterward.

Keep the bus alive while domain stop callbacks send lifecycle requests.
Use `dispatch_lifecycle_request` where the domain uses normal typed lifecycle messages.
Do not invent per-domain global lifecycle senders.

Do not equate sequential construction with ordered service startup.
Start/stop callbacks report their own domain-specific failures.
Construction panics do not provide automatic application-level rollback or compensation.
Keep shutdown behavior explicit and avoid promising cross-domain transactions.

### Supply RPC and MCP transport adapters

Use the authenticated `BusRpcContext` at generated operation boundaries.
Let transport adapters select operation inputs and serialize typed results.
Keep business logic in the domain actor.

`TransportBindings::new` accepts two listener factories and an MCP route catalog.
Each factory receives three arguments:

```text
TcpListener
mpsc::Sender<OpaqueRequestEnvelope>
oneshot::Receiver<()>
```

Return the required boxed listener future from each factory.
Use `aves::utils::transport::run_rpc` for the existing rspc HTTP adapter.
Use Corv's `run_mcp` only when your application includes Corv's MCP implementation.
Aves itself does not collate tools or implement MCP-over-ACP.

See [the real transport bindings](../poseidon/src/app.rs) for complete factory wiring.
The bus owns opening, closing, and stopping supplied listener factories.
An embedding application decides which listeners to request and expose.
Do not assume a production RPC endpoint exists simply because routing is generated.

### Register configuration and live hooks

Own each typed configuration section in the domain that consumes it.
Register before runtime start seals the registry.
Use `getConfigSection` and `saveConfigSection` through the Config domain's public operations.
Load from `config.toml`; do not assume environment or CLI overlays exist automatically.

Extend the Quick Start registration with a typed semantic guard:

```rust
use aves::config::{ConfigError, SectionRegistration};
use serde::{Deserialize, Serialize};

#[derive(Default, Serialize, Deserialize)]
struct QueueSettings {
  max_pending: u32,
}

fn queue_section() -> Result<SectionRegistration, ConfigError> {
  Ok(SectionRegistration::new::<QueueSettings>("queue")?
    .before_write(|_old: QueueSettings, new: QueueSettings| async move {
      if new.max_pending > 1_000 {
        return Err(ConfigError::InvalidValue {
          section: "queue".into(),
          message: "max_pending must not exceed 1000".into(),
        });
      }
      Ok(())
    }))
}
```

Use `on_change` for post-persistence owner notifications.
Its failure does not roll back the already-saved configuration file.
Use `before_write` when rejection must leave the saved configuration unchanged.
Design actor notifications and reload failures around that distinction.

See [Config registration](src/config/types.rs) and [Kea's reload handling](../kea/src/handle.rs).
Kea demonstrates keeping the previous active directory when a reopened root fails.

### Authenticate callers and paired peers

Let built-in Auth establish machine identity and the runtime audience.
Receive the runtime-issued context rather than manufacturing an unauthenticated substitute.
Treat root contexts and sudo-service access as trusted application authority.

`AuthSudoService` provides delegated issuance, verification, revocation, and peer management.
Its `IssueJwtInput` uses `sub`, `cap`, and an RFC3339 `exp` value.
Choose from the current capabilities: `AllBus` and `PullCloudBlobs`.

Use generated public-operation admission and domain checks as implemented.
Do not infer per-operation authorization policy beyond the declared safety boundary.
Do not serialize worker tokens into configuration, logs, or application state.

For remote operations, use the native authenticated Iroh connection and peer admission.
Pairing trust is local, unilateral, file-backed, and non-transitive.
Confirmed machines still need an audience appropriate to the target runtime.
Revocation changes local admission; it does not mutate the remote machine's files.

Inspect [Auth contracts](src/auth/messages.rs), [capabilities](src/auth/types.rs), and [peer runtime architecture](../../docs/trade.sluice/trade.sluice.engineering/architecture/aves/iroh-peer-runtime.md).
Use authenticated native peer operations for supported cross-runtime requests.
Peer transport is not distributed consensus or an application-level shared transaction.

### Automate with Flow

Represent orchestration through Aves Flow rather than adding work to the generic bus.
Use graph combinators and leaf-node registrations to compose owner execution.
Keep domain-specific execution and interpretation behind owner callbacks.

Flow owns an open, validated `NodeKind` and an opaque configuration envelope.
Owners provide `NodeKindRegistration` values through a shared `NodeRegistrationSupply`.
A new backend kind does not require a switch or enum extension in Flow core.

Register owner kinds during construction, before Flow drains the supply.
The supply is a construction input, not a service trait.
Construction-callback excerpt using a `facade` supplied by Aves:

```rust
let supply = facade
  .input::<NodeRegistrationSupply>()
  .cloned()
  .expect("shared node registration supply");
```

Provide the same supply through Flow's boot options and owner-domain construction inputs.
Do not push registrations after Flow has consumed the boot supply.

### Define a custom node kind

The following public-API example defines an immediate, pure custom node.
It records a greeting in terminal state without starting external work.

```rust
use aves::flow::{
  BoundOutputs, CancellationDisposition, FlowRegistrationError, NodeKind,
  NodeKindDescriptor, NodeKindRegistration, NodeLifecycleResult,
  SideEffectDisposition, WorkspacePreparation,
};
use serde::Deserialize;

#[derive(Deserialize)]
struct GreetingConfig {
  message: String,
}

fn greeting_registration() -> Result<NodeKindRegistration, FlowRegistrationError> {
  let descriptor = NodeKindDescriptor {
    kind: NodeKind::parse("GreetingNode")?,
    cancellation: CancellationDisposition::Cancellable,
    side_effects: SideEffectDisposition::Pure,
  };
  Ok(NodeKindRegistration::typed::<GreetingConfig, _, _, _, _, _, _, _>(
    descriptor,
    |config| {
      if config.message.trim().is_empty() {
        return Err(FlowRegistrationError::Configuration {
          message: "message must not be empty".into(),
        });
      }
      Ok(())
    },
    |_context, config| async move {
      Ok(NodeLifecycleResult::Succeeded {
        outputs: BoundOutputs::default(),
        state: serde_json::json!({"message": config.message}),
      })
    },
    |_context, _config| async { Ok(NodeLifecycleResult::Running) },
    |_context, _config| async { Ok(NodeLifecycleResult::Cancelled) },
    None,
    WorkspacePreparation::new(),
  ))
}
```

Push that registration from its owner's constructor:

```rust
supply.push(greeting_registration().expect("valid static greeting registration"));
```

This final line continues the construction excerpt above.
For asynchronous work, return `Running` and report terminal owner results on later ticks.
Do not block or busy-wait inside Flow while waiting for external work.

Handle all lifecycle results: `Running`, `Succeeded`, `Failed`, and `Cancelled`.
Declare `Cancellable` or `NonCancellable` honestly.
Declare `Pure` or `SideEffecting` according to observable effects.
Do not promise cancellation can undo an effect already accepted externally.

See [the registration API](src/flow/registration.rs) and [adding a node kind](../../docs/trade.sluice/trade.sluice.engineering/guides/adding-a-flow-node-kind.md).

### Bind data and prepare workspaces

Use Flow bindings to connect named leaf outputs and resolved blackboard inputs.
Keep owner configuration separate from the Flow-owned node envelope.
Use typed input and output views rather than inventing a second execution graph.

Each `NodeLifecycleContext` carries:

- Correlation IDs and the Flow-owned node envelope.
- Resolved `BoundInputs` and the execution-root directory.
- Flow's authenticated bus context.
- A distinct node-scoped token on start, when issued.
- Execution-owned MCP ports and optional owner state.

The transport context remains Flow-authenticated across start, tick, and cancel.
Do not substitute the start-only node token for transport authentication.
Use it only for the scoped guest or worker capability it represents.

Prepare content roots with `WorkspacePreparation::node_content_roots`.
Supply graph-level seeds through `WorkspacePreparation::graph_seeds` when required.
Flow owns one writable directory per execution at `executions/{execution_id}` under the data root.
Keep checkpoints and restoration in Flow's existing durability mechanism.

Use [Flow dataflow](../../docs/trade.sluice/trade.sluice.engineering/concepts/flow-dataflow.md) and [execution workspaces](../../docs/trade.sluice/trade.sluice.engineering/concepts/flow-execution-workspace.md) for complete contracts.
Do not treat a working-directory setting as general process confinement.

### Choose node owners and trigger sources

Compose the owner domain for each node kind you need:

| Owner | Registered kinds |
|---|---|
| Aves Tasks | `ScriptNode` |
| Corv Discuss | `AgentNode` |
| Tyto Messaging | `MessageNode`, `PermissionNode` |
| Application owners | Additional kinds, such as Poseidon's `OrderNode` |

Let source owners register and validate trigger adapters through Flow's trigger contracts.
Use ordinary public Flow operations such as `flow.start` and `flow.abort`.
Keep privileged retention/runtime control separate from public automation requests.

Flow persists definitions, triggers, execution state, and workspace durability references.
Owners retain responsibility for their external side effects.
Do not infer exactly-once execution or cross-system transactions from graph persistence alone.

Inspect [Flow's public contracts](src/flow/mod.rs) and [pluggable node architecture](../../docs/trade.sluice/trade.sluice.engineering/architecture/aves/pluggable-flow-nodes.md).

### Execute ScriptNode work

Compose `TasksDomain` and supply its required guest-RPC binding factory and registration supply.
Tasks transpiles TypeScript and runs JavaScript through QuickJS inside Wasmtime/WASI.
Use its generated guest RPC surface for normal public operations.
Privileged operations are structurally excluded from those guest bindings.

Expose only the execution directory through the single WASI preopen at guest `/`.
Treat guest filesystem access and guest RPC as separate mechanisms.
Apply the owner-provided finite fuel and timeout limits.
Do not assume an AgentNode's process working directory provides equivalent confinement.

Read [Tasks architecture](../../docs/trade.sluice/trade.sluice.engineering/architecture/aves/tasks-domain.md) and [the script adapter](src/tasks/adapter.rs).
Keep source compilation, execution, and terminal output handling with Tasks.
Keep graph scheduling, persistence, and checkpoint policy with Flow.

### Open databases and apply owner migrations

Use concrete utility paths instead of introducing a utility barrel.
Keep schemas and migration ledgers with the domain that owns the data.

Public-API example for a small application-owned database:

```rust
use std::path::Path;
use aves::utils::database::{
  DatabaseError, Migration, MigrationRunner, open_database_at_path,
};

const MIGRATIONS: &[Migration] = &[Migration {
  version: 1,
  name: "notes",
  sql: "CREATE TABLE IF NOT EXISTS notes (id TEXT PRIMARY KEY, body TEXT NOT NULL);",
}];

async fn migrate_notes(path: &Path) -> Result<(), DatabaseError> {
  let database = open_database_at_path(path).await?;
  MigrationRunner::new("__example_migrations", MIGRATIONS)
    .apply(&database)
    .await?;
  Ok(())
}
```

For production code, embed separately owned SQL assets with `include_str!`.
Use `connect_database` for configured connections and `maintain_database` for maintenance.
Choose `open_database_at_path_with_concurrency` when explicit coordination mode is required.
Do not rename existing ledger tables merely because a crate's name changes.
Do not claim application transactions cover separate services, keyrings, or remote providers.

See [database utilities](src/utils/database.rs), [Kea migrations](../kea/src/migrations.rs), and [Corv migrations](../corv/src/migration.rs).

### Reuse shared mechanisms

Import helpers from their concrete `aves::utils` modules:

| Modules | Responsibility |
|---|---|
| `database` | Database opening, connections, migration runner, maintenance |
| `errors` | Shared typed infrastructure failures |
| `retry` | Retry/backoff mechanisms and owner-selected retry policy |
| `time`, `uuid` | Time conversion and typed UUID utilities |
| `arithmetic`, `bigint`, `integers`, `decimals` | Reusable numerical mechanisms |
| `buffers`, `series`, `rolling` | Bounded buffers and series calculations |
| `transport` | RPC serving and endpoint metadata projection |

Check each module's actual API before creating another helper.
Keep provider SDKs, domain schemas, financial identity, and application policy outside shared utilities.
MCP serving belongs to Corv, not `aves::utils::transport`.

### Test without exposing production internals

Test state transitions, typed validation, serialization, and domain-owned persistence directly.
Use temporary databases and the owner's actual migration registration.
Do not widen actor visibility simply to support a test.

Enable downstream-only test helpers through a dev-dependency:

```toml
[dev-dependencies]
aves = { path = "../aves", features = ["test-support"] }
```

Use `aves::utils::test_support::{MockBus, reply_with}` for existing opaque-envelope fixtures.
Use `ServiceReader::from_published_test_service` for an injected service fixture.
The feature also exposes request-envelope test inspection methods.

Remember that `cfg(test)` does not propagate into dependency crates.
Use the test-support feature rather than enabling test scaffolding in every normal build.
Keep provider integration tests distinct from local parsing or actor tests.

Run commands from the repository root when verification is wanted:

```bash
cargo test --package aves_macros
```

```bash
cargo test --package aves
```

```bash
cargo test --package aves --no-default-features
```

Default features include `keyring` and `wasm-tests`.
Those flags gate relevant tests; disabling them does not remove all production keyring/native dependencies.
Use `test-support` only for downstream test helpers.
Do not interpret source-reviewed README examples as executed integration tests.

### Understand current boundaries

- Use local workspace dependencies until release packaging and publication are explicitly prepared.
- Account for native identity storage and Iroh setup during runtime construction.
- Register Flow kinds at boot, not dynamically after the supply is drained.
- Preserve domain-specific startup and recovery behavior; Aves does not invent owner compensation.
- Handle broadcast lag and bounded-channel backpressure in embedding consumers.
- Treat peer trust as admission, not distributed consensus or shared transactional state.
- Preserve existing protocol and storage identifiers unless a separate migration changes them.

Aves is reusable infrastructure, not an application, frontend framework, or universal provider SDK.
Do not add trading logic, installed personas, or UI lifecycle ownership to the runtime.

### Extend Aves with specialized domains

| Package | Role |
|---|---|
| [Corv]../corv/README.md | Specialized discussion domain: ACP sessions, Teams, AgentNode, MCP |
| [Kea]../kea/README.md | Specialized Knowledge domain: notes, search, Data KV, Canvas |
| [Tyto]../tyto/README.md | Specialized messaging domain: Email/Slack/Telegram and Flow interactions |
| Poseidon | Application composition, trading domains, and application-owned static content |
| Zeus / Hades | Desktop/native bridge and frontend application surfaces |

Compose only the domain capabilities your application requires.
Aves does not depend on Corv, Kea, Tyto, or the trading application.
Specialized domain crates depend on the Aves async application runtime.

## Contributing

Follow [Aves conventions](AGENTS.md), [macro rules](domain_setup/AGENTS.md), and repository engineering standards.
Start with [domain RPC guidance](../../docs/trade.sluice/trade.sluice.engineering/guides/aves-domain-rpc.md).
Read [cross-domain requests](../../docs/trade.sluice/trade.sluice.engineering/guides/cross-domain-requests.md) before adding a second communication mechanism.
Use existing domain and registration patterns before introducing infrastructure abstractions.

## License

See the [Apache License, Version 2.0](../../LICENSE).