Skip to content
Merged
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
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,10 @@ path = "tests/integration/web_access.rs"
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"

[[test]]
name = "e2e_thread_scheduling"
required-features = ["libsql", "integration"]
Expand Down
9 changes: 9 additions & 0 deletions tests/integration/support/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ use super::capability_backend::{MOCK_MCP_PROVIDER_ID, RebornCapabilityBackend, S
use super::group::{GroupCapability, GroupSharedStorage, RebornIntegrationGroup};
use super::harness::{HarnessCapabilityRecorder, HarnessTurnBackend, RecordedCapabilityResult};
use super::http_matcher::ScriptedHttpResponse;
use super::planned_runtime_parts_shape::DefaultPlannedRuntimePartsShape;
use super::process::ScriptedProcessResult;
use super::reply::RebornScriptedReply;
use super::scripted_provider::ParkingModelGate;
Expand Down Expand Up @@ -529,6 +530,14 @@ impl RebornIntegrationHarness {
}
}

/// W5-WIRING-PARITY: the Some/None shape of the `DefaultPlannedRuntimeParts`
/// literal this (degenerate one-thread group's) planned runtime was
/// actually built from, captured at `into_group` construction time. See
/// `tests/integration/wiring_parity.rs`.
pub fn planned_runtime_parts_shape(&self) -> DefaultPlannedRuntimePartsShape {
self._shared.planned_runtime_parts_shape
}

/// Submit a user turn and wait for it to complete.
pub async fn submit_turn(&self, text: &str) -> HarnessResult<TurnRunId> {
let run_id = self.submit_turn_async(text).await?;
Expand Down
74 changes: 74 additions & 0 deletions tests/integration/support/extension_surface.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
//! Static Reborn extension capability surface used by binary-E2E tests.
//!
//! **Not production truth.** `EXTENSION_LIFECYCLE_CAPABILITY_IDS` and
//! `BUNDLED_EXTENSION_CAPABILITY_IDS` below are hand-transcribed test-support
//! literals — they duplicate (fully or partially) values that live in a
//! production crate, but are not themselves parsed or imported from one.
//! `tests/integration/wiring_parity.rs`'s capability-id subset check no
//! longer unions either constant into its production-surface RHS; see that
//! file's module doc for why (W5-WIRING-PARITY finding 1). They remain here
//! for the unrelated QA-smoke scripted-scenario assertions
//! (`tests/reborn_qa_smoke_scenarios_e2e.rs`) that still use them as fixed
//! literals to script a harness's own declared surface, not to verify it
//! against production.
//!
//! `bundled_extension_manifest_capability_ids` below IS production truth —
//! it parses the real `manifest.toml` assets bundled extensions ship with,
//! the same way `github::capability_ids()` parses github's.

pub const EXTENSION_SEARCH_CAPABILITY_ID: &str = "builtin.extension_search";
pub const EXTENSION_INSTALL_CAPABILITY_ID: &str = "builtin.extension_install";
Expand Down Expand Up @@ -159,3 +175,61 @@ pub const BUNDLED_EXTENSION_CAPABILITY_IDS: &[&str] = &[
"notion.notion-get-user",
"notion.notion-get-self",
];

/// Bundled first-party extension asset directories under
/// `crates/ironclaw_first_party_extensions/assets/`, parsed by
/// [`bundled_extension_manifest_capability_ids`]. Excludes `github` (parsed
/// separately by `github::capability_ids()`, which this list intentionally
/// does not duplicate) and `slack` (not yet a modeled bundled extension in
/// this harness).
const BUNDLED_EXTENSION_MANIFEST_ASSET_DIRS: &[&str] = &[
"web-access",
"gmail",
"google-calendar",
"google-docs",
"google-sheets",
"google-drive",
"google-slides",
"nearai-mcp",
"notion-mcp",
];

/// Real capability ids declared by every non-github bundled first-party
/// extension's production `manifest.toml` asset — parsed the same way
/// `github::capability_ids()` parses github's
/// (`ExtensionManifest::parse_with_host_api_contracts` over the actual
/// shipped asset file), so this is production truth, not a second
/// hand-transcribed test-only id list like `BUNDLED_EXTENSION_CAPABILITY_IDS`
/// above.
pub fn bundled_extension_manifest_capability_ids()
-> Result<Vec<ironclaw_host_api::CapabilityId>, Box<dyn std::error::Error + Send + Sync>> {
let mut registry = ironclaw_extensions::ExtensionRegistry::new();
for dir_name in BUNDLED_EXTENSION_MANIFEST_ASSET_DIRS {
let asset_root = repo_root()
.join("crates/ironclaw_first_party_extensions/assets")
.join(dir_name);
let manifest = ironclaw_extensions::ExtensionManifest::parse_with_host_api_contracts(
&std::fs::read_to_string(asset_root.join("manifest.toml"))?,
ironclaw_extensions::ManifestSource::HostBundled,
&ironclaw_host_runtime::default_host_port_catalog()?,
&ironclaw_host_runtime::default_host_api_contract_registry()?,
)?;
// The manifest's OWN `id` (not the asset directory name) must match
// the `ExtensionPackage` root's last segment — they differ for
// `nearai-mcp`/`notion-mcp` (manifest id `nearai`/`notion`).
let extension_id = manifest.id.as_str().to_string();
let package = ironclaw_extensions::ExtensionPackage::from_manifest(
manifest,
ironclaw_host_api::VirtualPath::new(format!("/system/extensions/{extension_id}"))?,
)?;
registry.insert(package)?;
}
Ok(registry
.capabilities()
.map(|descriptor| descriptor.id.clone())
.collect())
}

fn repo_root() -> &'static std::path::Path {
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
}
31 changes: 29 additions & 2 deletions tests/integration/support/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ use super::harness::{
HarnessTurnBackend, HostRuntimeCapabilityHarness, RecordingTestCapabilityPort,
StaticCapabilitySurfaceProfileResolver, test_product_scope,
};
use super::planned_runtime_parts_shape::{
DefaultPlannedRuntimePartsShape, harness_planned_runtime_parts_shape,
};
use super::product_workflow::RebornProductWorkflowHarness;
use super::reply::RebornScriptedReply;
use super::scope_gateway::ScopeRegistryGateway;
Expand Down Expand Up @@ -217,6 +220,12 @@ pub(crate) struct GroupSharedStorage {
/// reads the SAME account the loop's accountant seeds. `None` unless
/// budget accounting is wired.
pub(crate) budget_account: Option<ResourceAccount>,
/// W5-WIRING-PARITY: the Some/None shape of the `DefaultPlannedRuntimeParts`
/// literal this group's ONE planned runtime was actually built from,
/// captured at construction (before `build_default_planned_runtime`
/// consumes the struct by value) so a parity test can read back the
/// harness's REAL wiring shape, not a re-derived approximation.
pub(crate) planned_runtime_parts_shape: DefaultPlannedRuntimePartsShape,
}

impl GroupSharedStorage {
Expand Down Expand Up @@ -384,6 +393,14 @@ impl RebornIntegrationGroup {
}
}

/// W5-WIRING-PARITY: the Some/None shape of the `DefaultPlannedRuntimeParts`
/// literal this group's ONE planned runtime was actually built from
/// (`into_group`), captured at construction time before the struct was
/// consumed. See `tests/integration/wiring_parity.rs`.
pub fn planned_runtime_parts_shape(&self) -> DefaultPlannedRuntimePartsShape {
self.shared.planned_runtime_parts_shape
}

/// C-MULTIUSER: grant global always-allow (auto-approve) for a SPECIFIC run
/// owner's `(tenant, user)` scope over the shared CAS-persisted
/// `AutoApproveSettingStore`. In a `multiuser_approvals` group (built with
Expand Down Expand Up @@ -726,7 +743,12 @@ impl RebornIntegrationGroupBuilder {
(None, None, None)
};

let composition = build_default_planned_runtime(DefaultPlannedRuntimeParts {
// W5-WIRING-PARITY: bind the literal to a local before consuming it so
// `harness_planned_runtime_parts_shape` can read the REAL Some/None
// shape this group's runtime is built from — the only place this
// struct value exists before `build_default_planned_runtime` takes it
// by value.
let parts = DefaultPlannedRuntimeParts {
turn_state: turn_state_for_runtime,
thread_service: group_thread_harness.service.clone() as Arc<dyn SessionThreadService>,
thread_scope: group_thread_scope,
Expand Down Expand Up @@ -790,13 +812,17 @@ impl RebornIntegrationGroupBuilder {
// C-COMMCTX: delivery-preference / connected-channel provider (Some
// only when `communication_context_provider()` was set).
communication_context_provider: self.communication_context_provider,
// No RecordingSecurityAuditSink double exists yet (nearai/ironclaw#5640);
// wiring_parity.rs's ALLOWED_DIVERGENCES tracks this field by name, not line.
hook_security_audit_sink: None,
turn_event_sink: composed_turn_event_sink,
attachment_read_port: capability_recorder
.attachment_test_support()
.map(|support| support.read_port),
scheduler_wake_wiring: None,
})?;
};
let planned_runtime_parts_shape = harness_planned_runtime_parts_shape(&parts);
let composition = build_default_planned_runtime(parts)?;

Ok(RebornIntegrationGroup {
shared: Arc::new(GroupSharedStorage {
Expand All @@ -815,6 +841,7 @@ impl RebornIntegrationGroupBuilder {
trace_capture_scope: trace_capture.map(|(_, scope)| scope),
budget_governor,
budget_account,
planned_runtime_parts_shape,
}),
})
}
Expand Down
42 changes: 25 additions & 17 deletions tests/integration/support/harness/profiles/core_builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,30 @@ pub(crate) async fn core_builtin_tools_default() -> HarnessResult<HostRuntimeCap
core_builtin_tools(CoreBuiltinOptions::default()).await
}

/// Real capability ids `core_builtin_tools_from_runtime` registers on the
/// built harness — a single source shared with the wiring-parity
/// capability-id subset check (`tests/integration/wiring_parity.rs`) instead
/// of a second hand-transcribed copy of this list.
pub(crate) fn core_builtin_tools_capability_ids() -> HarnessResult<Vec<CapabilityId>> {
Ok(vec![
CapabilityId::new(TIME_CAPABILITY_ID)?,
CapabilityId::new(JSON_CAPABILITY_ID)?,
CapabilityId::new(HTTP_CAPABILITY_ID)?,
CapabilityId::new(HTTP_SAVE_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_SEARCH_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_WRITE_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_READ_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_TREE_CAPABILITY_ID)?,
CapabilityId::new(PROFILE_SET_CAPABILITY_ID)?,
CapabilityId::new(READ_FILE_CAPABILITY_ID)?,
CapabilityId::new(APPLY_PATCH_CAPABILITY_ID)?,
// `builtin.shell` on the surface so scripted shell calls route
// through the process port (recording by default, live via
// `.with_live_shell()`).
CapabilityId::new(SHELL_CAPABILITY_ID)?,
])
}

fn core_builtin_tools_from_runtime(
root: Arc<tempfile::TempDir>,
workspace_root: PathBuf,
Expand Down Expand Up @@ -174,23 +198,7 @@ fn core_builtin_tools_from_runtime(
.cloned()
.map(|capability_id| (capability_id, memory_mounts.clone()))
.collect(),
capability_ids: vec![
CapabilityId::new(TIME_CAPABILITY_ID)?,
CapabilityId::new(JSON_CAPABILITY_ID)?,
CapabilityId::new(HTTP_CAPABILITY_ID)?,
CapabilityId::new(HTTP_SAVE_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_SEARCH_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_WRITE_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_READ_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_TREE_CAPABILITY_ID)?,
CapabilityId::new(PROFILE_SET_CAPABILITY_ID)?,
CapabilityId::new(READ_FILE_CAPABILITY_ID)?,
CapabilityId::new(APPLY_PATCH_CAPABILITY_ID)?,
// `builtin.shell` on the surface so scripted shell calls route
// through the process port (recording by default, live via
// `.with_live_shell()`).
CapabilityId::new(SHELL_CAPABILITY_ID)?,
],
capability_ids: core_builtin_tools_capability_ids()?,
runtime_kind: RuntimeKind::FirstParty,
effect_kinds: vec![
EffectKind::DispatchCapability,
Expand Down
62 changes: 35 additions & 27 deletions tests/integration/support/harness/profiles/qa_smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,40 @@ use super::super::{
memory_mounts, qa_smoke_mounts,
};

/// Real capability ids `qa_smoke_tools` registers on the built harness — a
/// single source shared with the wiring-parity capability-id subset check
/// (`tests/integration/wiring_parity.rs`) instead of a second
/// hand-transcribed copy of this list.
pub(crate) fn qa_smoke_tools_capability_ids() -> HarnessResult<Vec<CapabilityId>> {
Ok(vec![
CapabilityId::new(ECHO_CAPABILITY_ID)?,
CapabilityId::new(TIME_CAPABILITY_ID)?,
CapabilityId::new(JSON_CAPABILITY_ID)?,
CapabilityId::new(HTTP_CAPABILITY_ID)?,
CapabilityId::new(HTTP_SAVE_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_SEARCH_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_WRITE_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_READ_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_TREE_CAPABILITY_ID)?,
CapabilityId::new(READ_FILE_CAPABILITY_ID)?,
CapabilityId::new(WRITE_FILE_CAPABILITY_ID)?,
CapabilityId::new(LIST_DIR_CAPABILITY_ID)?,
CapabilityId::new(GLOB_CAPABILITY_ID)?,
CapabilityId::new(GREP_CAPABILITY_ID)?,
CapabilityId::new(APPLY_PATCH_CAPABILITY_ID)?,
CapabilityId::new(SHELL_CAPABILITY_ID)?,
CapabilityId::new(SPAWN_SUBAGENT_CAPABILITY_ID)?,
CapabilityId::new(SKILL_LIST_CAPABILITY_ID)?,
CapabilityId::new(SKILL_INSTALL_CAPABILITY_ID)?,
CapabilityId::new(SKILL_REMOVE_CAPABILITY_ID)?,
CapabilityId::new(TRIGGER_CREATE_CAPABILITY_ID)?,
CapabilityId::new(TRIGGER_LIST_CAPABILITY_ID)?,
CapabilityId::new(TRIGGER_PAUSE_CAPABILITY_ID)?,
CapabilityId::new(TRIGGER_RESUME_CAPABILITY_ID)?,
CapabilityId::new(TRIGGER_REMOVE_CAPABILITY_ID)?,
])
}

pub(crate) async fn qa_smoke_tools() -> HarnessResult<HostRuntimeCapabilityHarness> {
let (root, storage_root, workspace_root) = host_runtime_storage_roots()?;
std::fs::create_dir_all(storage_root.join("skills"))?;
Expand Down Expand Up @@ -63,33 +97,7 @@ pub(crate) async fn qa_smoke_tools() -> HarnessResult<HostRuntimeCapabilityHarne
.cloned()
.map(|capability_id| (capability_id, memory_mounts.clone()))
.collect(),
capability_ids: vec![
CapabilityId::new(ECHO_CAPABILITY_ID)?,
CapabilityId::new(TIME_CAPABILITY_ID)?,
CapabilityId::new(JSON_CAPABILITY_ID)?,
CapabilityId::new(HTTP_CAPABILITY_ID)?,
CapabilityId::new(HTTP_SAVE_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_SEARCH_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_WRITE_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_READ_CAPABILITY_ID)?,
CapabilityId::new(MEMORY_TREE_CAPABILITY_ID)?,
CapabilityId::new(READ_FILE_CAPABILITY_ID)?,
CapabilityId::new(WRITE_FILE_CAPABILITY_ID)?,
CapabilityId::new(LIST_DIR_CAPABILITY_ID)?,
CapabilityId::new(GLOB_CAPABILITY_ID)?,
CapabilityId::new(GREP_CAPABILITY_ID)?,
CapabilityId::new(APPLY_PATCH_CAPABILITY_ID)?,
CapabilityId::new(SHELL_CAPABILITY_ID)?,
CapabilityId::new(SPAWN_SUBAGENT_CAPABILITY_ID)?,
CapabilityId::new(SKILL_LIST_CAPABILITY_ID)?,
CapabilityId::new(SKILL_INSTALL_CAPABILITY_ID)?,
CapabilityId::new(SKILL_REMOVE_CAPABILITY_ID)?,
CapabilityId::new(TRIGGER_CREATE_CAPABILITY_ID)?,
CapabilityId::new(TRIGGER_LIST_CAPABILITY_ID)?,
CapabilityId::new(TRIGGER_PAUSE_CAPABILITY_ID)?,
CapabilityId::new(TRIGGER_RESUME_CAPABILITY_ID)?,
CapabilityId::new(TRIGGER_REMOVE_CAPABILITY_ID)?,
],
capability_ids: qa_smoke_tools_capability_ids()?,
runtime_kind: RuntimeKind::FirstParty,
effect_kinds: vec![
EffectKind::DispatchCapability,
Expand Down
16 changes: 12 additions & 4 deletions tests/integration/support/harness/profiles/web_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ use super::super::{
host_runtime_storage_roots, workspace_mounts,
};

/// Real capability ids `web_access_tools` registers on the built harness —
/// a single source shared with the wiring-parity capability-id subset check
/// (`tests/integration/wiring_parity.rs`) instead of a second
/// hand-transcribed copy of this list.
pub(crate) fn web_access_tools_capability_ids() -> HarnessResult<Vec<CapabilityId>> {
Ok(vec![
CapabilityId::new(WEB_SEARCH_CAPABILITY_ID)?,
CapabilityId::new(WEB_GET_CONTENT_CAPABILITY_ID)?,
])
}

/// C-WEBACCESS: wires the real first-party web-access capabilities through production's
/// `WebAccessExecutor`; no credential-injecting authorizer needed (declares zero `runtime_credentials`).
///
Expand Down Expand Up @@ -44,10 +55,7 @@ pub(crate) async fn web_access_tools() -> HarnessResult<HostRuntimeCapabilityHar
workspace_root,
mounts,
capability_mount_overrides: Vec::new(),
capability_ids: vec![
CapabilityId::new(WEB_SEARCH_CAPABILITY_ID)?,
CapabilityId::new(WEB_GET_CONTENT_CAPABILITY_ID)?,
],
capability_ids: web_access_tools_capability_ids()?,
runtime_kind: RuntimeKind::FirstParty,
effect_kinds: vec![EffectKind::DispatchCapability, EffectKind::Network],
network_policy: harness_web_access::exa_mcp_test_network_policy(),
Expand Down
1 change: 1 addition & 0 deletions tests/integration/support/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod hooks;
pub mod http_matcher;
pub mod oauth_flow;
pub mod outbound_preferences;
pub mod planned_runtime_parts_shape;
pub mod process;
pub mod product_workflow;
pub mod project_service_fault;
Expand Down
Loading
Loading