Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 45 additions & 23 deletions crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,13 @@ fn reborn_cli_binary_crate_stays_separate_from_v1_root() {

let runtime_dir = root.join("crates/ironclaw_reborn_cli/src/runtime");
let mut cli_runtime_source = String::new();
collect_runtime_rs(&runtime_dir, &mut cli_runtime_source);
let cli_runtime_files = collect_runtime_rs(&runtime_dir, &mut cli_runtime_source);
assert!(
cli_runtime_files > 0,
"expected the Reborn CLI runtime module tree at {}; found no .rs files, so this scan \
would be enforcing nothing",
runtime_dir.display()
);
assert!(
cli_runtime_source.contains("build_reborn_runtime"),
"Reborn CLI should enter the assembled runtime through ironclaw_reborn_composition::build_reborn_runtime"
Expand Down Expand Up @@ -1008,9 +1014,23 @@ fn reborn_host_runtime_services_do_not_expose_lower_substrate_handles() {
let services =
std::fs::read_to_string(root.join("crates/ironclaw_host_runtime/src/services.rs"))
.expect("host runtime services.rs must be readable");
let obligations =
std::fs::read_to_string(root.join("crates/ironclaw_host_runtime/src/obligations.rs"))
.expect("host runtime obligations.rs must be readable");
// WS3 split `obligations.rs` into `obligations/{mod,handler,staged_handoffs,
// process_store,tests}.rs` (one module per chartered owner). This gate is
// about the *escape hatches* the obligation code may not make public, and
// those can now be written in any of those modules — so it scans the whole
// directory rather than one file. The file count is asserted so a future
// move that empties the directory fails here instead of scanning nothing
// and reporting success (the loud-vs-silent distinction WS10 tracks).
let obligations_dir = root.join("crates/ironclaw_host_runtime/src/obligations");
let mut obligations = String::new();
let obligations_files = collect_runtime_rs(&obligations_dir, &mut obligations);
assert!(
obligations_files >= 4,
"expected the host-runtime obligation owners under {} (mod + the three chartered \
modules at minimum); found {obligations_files} .rs files, so this gate would be \
scanning nothing",
obligations_dir.display()
);
let host_runtime_contract =
std::fs::read_to_string(root.join("docs/reborn/contracts/host-runtime.md"))
.expect("host runtime contract must be readable");
Expand Down Expand Up @@ -4586,35 +4606,37 @@ fn assert_no_normal_workspace_deps<'a>(
}

/// Recursively concatenate every `.rs` file under `dir` into `out`,
/// descending into subdirectories. Matches the recursion pattern used by
/// `collect_forbidden_*` walkers above so future boundary checks over
/// `runtime/` can reuse the same helper. Used by
/// `reborn_cli_binary_crate_stays_separate_from_v1_root` to scan the
/// entire `runtime/` module tree for forbidden imports.
fn collect_runtime_rs(dir: &std::path::Path, out: &mut String) {
for entry in std::fs::read_dir(dir).unwrap_or_else(|err| {
panic!(
"Reborn CLI runtime directory must be readable at {}: {err}",
dir.display()
)
}) {
/// descending into subdirectories, and return how many files were read.
/// Matches the recursion pattern used by `collect_forbidden_*` walkers above so
/// future boundary checks over a module tree can reuse the same helper. Used by
/// `reborn_cli_binary_crate_stays_separate_from_v1_root` to scan the entire
/// `runtime/` module tree for forbidden imports, and by
/// `reborn_host_runtime_services_do_not_expose_lower_substrate_handles` to scan
/// the obligation owners.
///
/// The count is returned so a caller can assert it actually read something: a
/// path-keyed scan that silently walks an empty directory reports success while
/// enforcing nothing.
fn collect_runtime_rs(dir: &std::path::Path, out: &mut String) -> usize {
let mut files = 0usize;
for entry in std::fs::read_dir(dir)
.unwrap_or_else(|err| panic!("directory must be readable at {}: {err}", dir.display()))
{
let path = entry.expect("dir entry").path();
if path.is_dir() {
collect_runtime_rs(&path, out);
files += collect_runtime_rs(&path, out);
continue;
}
if path.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
let content = std::fs::read_to_string(&path).unwrap_or_else(|err| {
panic!(
"Reborn CLI runtime file {} unreadable: {err}",
path.display()
)
});
let content = std::fs::read_to_string(&path)
.unwrap_or_else(|err| panic!("file {} unreadable: {err}", path.display()));
out.push_str(&content);
out.push('\n');
files += 1;
}
files
}

fn collect_forbidden_runtime_network_uses(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,13 @@ const FROZEN_PATH_COUNTS: &[FrozenPathCount] = &[
path: "crates/ironclaw_host_runtime/src/sandbox_process/credential_firewall.rs",
count: 1,
},
// WS3 split `obligations.rs` into one module per chartered owner; the
// single frozen test-support method travelled to the staged-handoff owner.
// Repointed, not re-baselined: the count is unchanged at 1.
FrozenPathCount {
category: "test-support",
item_kind: "method",
path: "crates/ironclaw_host_runtime/src/obligations.rs",
path: "crates/ironclaw_host_runtime/src/obligations/staged_handoffs.rs",
count: 1,
},
FrozenPathCount {
Expand Down
6 changes: 5 additions & 1 deletion crates/ironclaw_host_runtime/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
- Capability surface: the capability-surface policy `CapabilitySurfacePolicy`/`VisibleCapability`/`VisibleCapabilityAccess` (`surface`); the hot capability catalog `HotCapabilityCatalog`/`HotCapabilityRecord`/`publish_hot_capability_catalog` (`capability_catalog`).
- First-party capabilities: the `FirstPartyCapabilityRegistry`/handler/request/result (`first_party`) and the builtin tool set `BuiltinFirstPartyTools` with capability IDs (echo/time/json/http/shell/read_file/write_file/list_dir/glob/grep/apply_patch) and `builtin_first_party_handlers`/`_package` (`first_party_tools`).
- Host-owned extension contract discovery: `default_host_api_contract_registry`, `default_host_port_catalog`, `discover_extensions_with_default_host_api_contracts*` (`extension_contracts`). Product-specific manifest contracts are added by the owning composition/product layer.
- Obligation handling: `BuiltinObligationHandler`/`BuiltinObligationServices`, `ProcessObligationLifecycleStore`, and the secret-injection/network-policy stores (`obligations`).
- Obligation handling (`obligations`), split along its **three chartered owners** so no single file fuses them again (PROPOSAL §6.5.9, CHECKLIST WS3). Put new obligation code in the owner it belongs to, never in `mod.rs`:
- `obligations::handler` — which obligations apply and what each one does before/after dispatch: `BuiltinObligationHandler`, the `CapabilityObligationHandler` impl, and the audit / redaction / resource-ceiling / mount validation behind them.
- `obligations::staged_handoffs` — material staged for a *later* consumer: `RuntimeSecretInjectionStore`, `NetworkObligationPolicyStore`, and the `RuntimeCredentialAccountResolver` port that feeds them.
- `obligations::process_store` — `ProcessObligationLifecycleStore`: discarding staged handoffs and reconciling or releasing a prepared reservation once a capability process has started.
- `obligations::mod` holds only `BuiltinObligationServices`, the assembly seam that binds the three for composition, and is deliberately the only place naming all three at once.
- The runtime process port `RuntimeProcessPort`/`HostProcessPort` + command execution types (`process_port`) and memory-context builders (`memory_context`).
- Production validation of the `TurnRunWakeNotifier` handle consumed by `ironclaw_runner` (`ProductionWiringComponent::TurnRunWakeNotifier`, `src/services/production_wiring.rs`); scheduler/executor ownership lives in that runner-side crate.
- Low-level mediation by composing `ironclaw_network`/`ironclaw_secrets`/`ironclaw_resources` (egress, redaction, secret leases, accounting) — never duplicating that logic in runtime crates.
Expand Down
1 change: 1 addition & 0 deletions crates/ironclaw_host_runtime/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Keep runtime-specific request shapes in the runtime crates; adapters should translate into host API contracts and delegate here.
- Compose low-level services such as `ironclaw_network` and `ironclaw_secrets`; do not duplicate URL parsing, DNS checks, private-IP filtering, HTTP clients, secret stores, or redaction logic in runtime crates.
- Host HTTP egress lives under `src/egress/`: keep request validation/sanitization, credential-source resolution, staged network-policy lookup, staged secret injection, transport dispatch, response sanitization, and response-body storage as separate pipeline steps instead of rebuilding a monolithic service method.
- Obligation code lives under `src/obligations/` and stays in its owner: `handler` decides and executes obligations, `staged_handoffs` owns the secret/network material staged for a later consumer, `process_store` owns post-start cleanup and reservation reconciliation. `mod.rs` holds only `BuiltinObligationServices` — the assembly seam. A change that needs all three at once is a signal the split is being undone, not a reason to add code to `mod.rs`. Access needed **only** by a sibling owner is `pub(super)` — `RuntimeSecretInjectionStore::{has_for_capability, prune_expired}` and `NetworkObligationPolicyStore::contains` — so widening one of those is a deliberate edit. `pub(crate)` on the staged-handoff stores is *not* a violation of that rule: `insert`/`take`/`clone_material`/`get`/`discard_for_capability` are also called from `src/egress/**`, which is host-runtime composition outside `obligations/` and is the reason those stores exist. The test is what the caller set actually is, not which keyword appears: if a method's only callers are inside `obligations/`, it is `pub(super)`.
- Production host HTTP egress must be constructed with staged `NetworkObligationPolicyStore` and `RuntimeSecretInjectionStore` handoffs. Request-carried policy and direct `SecretStoreLease` sources are legacy/test compatibility paths only.
- Preserve the accounting invariant: `network_egress_bytes` is outbound request bytes only, with response bytes tracked separately.
- Keep raw secret material inside the narrow lease/injection path. Reject runtime-supplied manual credentials, scan raw and percent-decoded URL forms, redact leased values from runtime-visible errors and responses, strip sensitive response headers, and block credential-shaped runtime requests/responses before they reach external services or runtime callers.
Expand Down
Loading
Loading