Skip to content
This repository was archived by the owner on Aug 7, 2026. It is now read-only.
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
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
workflow_dispatch:

env:
CARGO_TERM_COLOR: always
RUST_VERSION: "1.96.0"

jobs:
ci:
name: just ci
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Checkout Reflective sibling dependencies
run: bash scripts/ci/checkout-reflective-siblings.sh
# zsh is the Justfile shell (`set shell`).
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y zsh
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_VERSION }}
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- run: just ci
15 changes: 13 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ set shell := ["zsh", "-cu"]
# Run every quality dimension and print the scoreboard.
default: report

# Canonical CI aggregate (RP-CI-PARITY): CI runs exactly `just ci`.
ci: fmt-check check lint test

# Print the scoreboard for every default dimension.
report:
cargo run --quiet --bin arena -- report
Expand All @@ -24,6 +27,14 @@ contracts:
build:
cargo build --workspace --all-targets

# Type-check the workspace.
check:
cargo check --workspace --all-targets

# Run the full test suite.
test:
cargo test --workspace --all-targets

# Run the arena-metrics unit tests (aggregation/Verdict invariants).
test-metrics:
cargo test -p arena-metrics --all-targets
Expand All @@ -34,8 +45,8 @@ lint:

# Format check.
fmt-check:
cargo fmt --check
cargo fmt --all -- --check

# rustfmt in place.
fmt:
cargo fmt
cargo fmt --all
161 changes: 161 additions & 0 deletions crates/cross-extension-smoke/tests/atelier_showcase_examples.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
//! Atelier showcase cross-module examples.
//!
//! Atelier owns the example agents locally. Arena proves those public example
//! agents still compose with the current Bedrock/Mosaic contract graph.

use atelier_domain::{
AvailabilityRetrievalAgent, ConflictDetectionAgent, DomainRecordPayload, DomainTextPayload,
RequireParticipantAvailability, RequirePositiveDuration, RequireValidSlot,
SlotOptimizationAgent, TimeZoneNormalizationAgent, WorkingHoursConstraintAgent, domain_text,
packs::InvoiceIssuerAgent, payload_contains,
};
use converge_core::suggestors::SeedSuggestor;
use converge_core::{ContextKey, ContextState, Engine, FactPayload};

fn projected(facts: &[converge_core::ContextFact]) -> Vec<(ContextKey, String, serde_json::Value)> {
facts
.iter()
.map(|fact| {
(
fact.key(),
fact.id().as_str().to_string(),
fact.to_wire().expect("fact serializes").payload.payload,
)
})
.collect()
}

#[tokio::test]
async fn atelier_meeting_scheduler_example_converges_through_converge_engine() {
let run = || async {
let mut engine = Engine::new();
engine.register_suggestor(SeedSuggestor::new("participants", "Alice, Bob, Carol"));
engine.register_suggestor(SeedSuggestor::new("duration", "60"));
engine.register_suggestor(AvailabilityRetrievalAgent);
engine.register_suggestor(TimeZoneNormalizationAgent);
engine.register_suggestor(WorkingHoursConstraintAgent);
engine.register_suggestor(SlotOptimizationAgent);
engine.register_suggestor(ConflictDetectionAgent);
engine.register_invariant(RequireParticipantAvailability);
engine.register_invariant(RequirePositiveDuration);
engine.register_invariant(RequireValidSlot);
engine
.run(ContextState::new())
.await
.expect("atelier meeting scheduler should converge")
};

let first = run().await;
let second = run().await;

assert!(first.converged);
assert_eq!(first.cycles, second.cycles);
assert_eq!(
projected(first.context.get(ContextKey::Strategies)),
projected(second.context.get(ContextKey::Strategies)),
"candidate slots should be deterministic across runs"
);
assert_eq!(
projected(first.context.get(ContextKey::Evaluations)),
projected(second.context.get(ContextKey::Evaluations)),
"slot evaluations should be deterministic across runs"
);

let evaluations = first.context.get(ContextKey::Evaluations);
assert_eq!(evaluations.len(), 7);
assert!(
evaluations.iter().any(|fact| {
fact.id() == "eval:1"
&& domain_text(fact).is_some_and(|text| text.contains("RECOMMENDED"))
}),
"Atelier's first recommended slot should survive Converge promotion"
);
}

#[tokio::test]
async fn atelier_money_example_requires_then_accepts_finance_hitl_gate() {
let ready_invoice = r#"{"type":"invoice","state":"ready_to_issue","customer_id":"cust_123","line_items":[{"sku":"svc","amount":12500}],"amount":12500,"currency":"USD"}"#;

let mut without_approval = ContextState::new();
without_approval
.add_input(
ContextKey::Proposals,
"invoice:draft:deal_123",
ready_invoice,
)
.expect("ready invoice fixture should stage");

let mut gated_engine = Engine::new();
gated_engine.register_suggestor(InvoiceIssuerAgent::default());
let gated = gated_engine
.run(without_approval)
.await
.expect("atelier money gate should run without approval");

assert!(gated.converged);
assert!(
gated.context.get(ContextKey::Proposals).iter().any(|fact| {
fact.id() == "invoice:issue_request:invoice:draft:deal_123"
&& payload_contains(fact, "\"required_role\":\"finance_manager\"")
}),
"Atelier money pack should request finance approval before issuing"
);

let mut with_approval = ContextState::new();
with_approval
.add_input(
ContextKey::Proposals,
"invoice:draft:deal_123",
ready_invoice,
)
.expect("ready invoice fixture should stage");
with_approval
.add_input(
ContextKey::Proposals,
"approval:invoice:invoice:draft:deal_123",
r#"{"target_id":"invoice:draft:deal_123","required_role":"finance_manager"}"#,
)
.expect("approval fixture should stage");

let mut approved_engine = Engine::new();
approved_engine.register_suggestor(InvoiceIssuerAgent::default());
let approved = approved_engine
.run(with_approval)
.await
.expect("atelier money gate should run with approval");

assert!(approved.converged);
assert!(
approved
.context
.get(ContextKey::Proposals)
.iter()
.any(|fact| {
fact.id() == "invoice:issued:invoice:draft:deal_123"
&& payload_contains(fact, "\"state\":\"issued\"")
}),
"finance approval should let the Atelier money example issue the invoice"
);
}

#[test]
fn atelier_typed_payloads_keep_public_converge_fact_contract_shape() {
assert_eq!(DomainRecordPayload::FAMILY, "atelier.domain_record");
assert_eq!(DomainTextPayload::FAMILY, "atelier.domain_text");

let provenance = atelier_domain::ATELIER_DOMAIN_PROVENANCE.provenance();
assert_eq!(provenance.as_str(), "atelier-domain");

let record = DomainRecordPayload::new(
"vendor",
serde_json::json!({
"id": "vendor-a",
"compliant": true,
"years_in_business": 7
}),
);

assert_eq!(record.record_type(), "vendor");
assert!(record.bool_field_is("compliant", true));
assert!(record.has_field("years_in_business"));
}
Loading
Loading