diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a9092b..3a81e90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,14 +5,15 @@ on: branches: ["main"] pull_request: branches: ["main"] + workflow_dispatch: env: CARGO_TERM_COLOR: always RUST_VERSION: "1.96.0" jobs: - check: - name: Check + ci: + name: just ci runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -21,60 +22,12 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_VERSION }} + components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 - name: Install protobuf compiler run: bash scripts/ci/install-protobuf.sh - - name: Check workspace - run: cargo check --workspace - - test: - name: Test - runs-on: ubuntu-latest - needs: check - steps: - - uses: actions/checkout@v6 - - name: Checkout Reflective sibling dependencies - run: bash scripts/ci/checkout-reflective-siblings.sh - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ env.RUST_VERSION }} - - uses: Swatinem/rust-cache@v2 - - name: Install protobuf compiler - run: bash scripts/ci/install-protobuf.sh - - name: Run tests - run: cargo test --workspace --all-targets - - lint: - name: Lint - runs-on: ubuntu-latest - needs: check - steps: - - uses: actions/checkout@v6 - - name: Checkout Reflective sibling dependencies - run: bash scripts/ci/checkout-reflective-siblings.sh - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ env.RUST_VERSION }} - components: clippy - - uses: Swatinem/rust-cache@v2 - - name: Install protobuf compiler - run: bash scripts/ci/install-protobuf.sh - - name: Clippy - run: cargo clippy --workspace --all-targets -- -D warnings - - format: - name: Format - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Checkout Reflective sibling dependencies - run: bash scripts/ci/checkout-reflective-siblings.sh - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ env.RUST_VERSION }} - components: rustfmt - - name: Check formatting - run: cargo fmt --check + - uses: extractions/setup-just@v3 + - run: just ci # Cross-extension solver e2e — exercises ferrox CP-SAT alongside arbiter + # prism. Requires libortools to be built/installed on the runner, which is diff --git a/AGENTS.md b/AGENTS.md index 3cd7f22..8dc488b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,10 +71,24 @@ Or `just release-check`. Archive the artefacts under `target/security/`, - `kb/Architecture/` — surface diagrams, ports, ADRs - `kb/Building/` — getting-started, release commands - `kb/History/CHANGELOG.md` — release notes -- `kb/Planning/MILESTONES.md` — scheduled delivery +- `kb/Planning/MILESTONES.md` — archived; open work lives in Linear + (team `RFL`, label `module:atelier-showcase`) Every kb/ page carries `source:` frontmatter (`human` / `llm` / `mixed`). +## Floor versions + +This repository targets: + +- Converge >= 3.9.1 +- MSRV 1.96.0 +- Edition 2024 +- `unsafe_code = "forbid"` + +In the root `~/dev/reflective` checkout, selected Converge, Organism, and +Prism crates are path-patched to local sources while unreleased contracts +are exercised. Keep those patches aligned with `Cargo.toml`. + ## What this repo is not - Not a place for foundation contracts. Universal contracts live in diff --git a/CLAUDE.md b/CLAUDE.md index c48d708..031cfc1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,13 +1,12 @@ # Claude Code Entrypoint Read and follow `AGENTS.md` — it is the canonical project documentation. +Open work lives in Linear (team `RFL`, label `module:atelier-showcase`). ## Session Scope -- **Milestones:** `kb/Planning/MILESTONES.md` - **Changelog:** `kb/History/CHANGELOG.md` - **Standard:** [Extension Release Checklist](https://github.com/Reflective-Lab/converge/blob/main/kb/Standards/Extension%20Release%20Checklist.md) — the engineering bar every release must meet -- **Strategic context:** `~/dev/reflective/bedrock-platform/EPIC.md` ## Claude-Specific Notes @@ -16,17 +15,3 @@ Read and follow `AGENTS.md` — it is the canonical project documentation. - Run `just lint` before considering work done. - Run `just release-check` before tagging a release. All five gates must be green. - Never push to main without confirmation. - -## Floor versions - -This repository targets: - -- Converge >= 3.9.1 -- MSRV 1.96.0 -- Edition 2024 -- `unsafe_code = "forbid"` - -In the root `~/dev/reflective` checkout, selected Converge, Organism, and -Prism crates are path-patched to local sources under `../stack/` while -unreleased contracts are exercised. Keep those patches aligned with -`Cargo.toml`. diff --git a/Justfile b/Justfile index eda96ba..3a3395f 100644 --- a/Justfile +++ b/Justfile @@ -10,10 +10,16 @@ set dotenv-load := true # ── Compile gates ────────────────────────────────────────────────────────── # Run all four basic gates -default: check lint test +default: ci + +# Canonical CI aggregate (RP-CI-PARITY): CI runs exactly `just ci`. +ci: fmt-check check lint test + +fmt-check: + cargo fmt --all -- --check check: - cargo check --workspace + cargo check --workspace --all-targets test: cargo test --workspace --all-targets @@ -28,16 +34,15 @@ solver-check: cargo test -p example-solver-policy-allocation --features with-solver --test end_to_end lint: - cargo fmt --check cargo clippy --workspace --all-targets -- -D warnings fmt: - cargo fmt + cargo fmt --all # Auto-fix lint issues where possible fix-lint: cargo clippy --fix --allow-staged --allow-dirty --allow-no-vcs - cargo fmt + cargo fmt --all # ── Quality dashboard ────────────────────────────────────────────────────── # @@ -239,6 +244,14 @@ resource-declarations: # ── The four release-grade gates ─────────────────────────────────────────── # Gate 1: supply-chain audit. Mirrors foundation's security-audit. +# Keep the ignore list in sync with scripts/ci/cargo-audit-blocking.sh. +# 2026-07-02: RUSTSEC-2026-0187 (lopdf 0.38, fix >=0.42) and +# RUSTSEC-2026-0192 (ttf-parser unmaintained, via lopdf) are pinned +# transitively via pdf-extract 0.10 -> organism-intelligence; no fixed +# version reachable until pdf-extract moves to lopdf >=0.42. +# 2026-07-02: RUSTSEC-2026-0194/-0195 (quick-xml 0.38.4 DoS-class, fix +# >=0.41) pinned transitively via object_store 0.12.5 (semver-locked by +# lancedb/surrealdb); no fix path until they move. # Output: # target/security/audit.json (cargo-audit JSON) # target/security/deny.txt (cargo-deny human report) @@ -261,6 +274,10 @@ security-audit: --ignore RUSTSEC-2025-0141 \ --ignore RUSTSEC-2025-0119 \ --ignore RUSTSEC-2026-0002 \ + --ignore RUSTSEC-2026-0187 \ + --ignore RUSTSEC-2026-0192 \ + --ignore RUSTSEC-2026-0194 \ + --ignore RUSTSEC-2026-0195 \ > "${out_dir}/audit.json" || true cargo audit --deny warnings \ --ignore RUSTSEC-2023-0089 \ @@ -272,6 +289,10 @@ security-audit: --ignore RUSTSEC-2025-0141 \ --ignore RUSTSEC-2025-0119 \ --ignore RUSTSEC-2026-0002 \ + --ignore RUSTSEC-2026-0187 \ + --ignore RUSTSEC-2026-0192 \ + --ignore RUSTSEC-2026-0194 \ + --ignore RUSTSEC-2026-0195 \ 2>&1 | tee -a "${summary}" audit_human_status=${PIPESTATUS[0]} echo "" | tee -a "${summary}" @@ -373,6 +394,7 @@ release-check: just coverage PERF_BASELINE="v$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/')" just performance-profile SOAK_DURATION_MIN=5 just soak + just fmt-check just lint just resource-declarations cargo test --workspace diff --git a/deny.toml b/deny.toml index bbdd0da..20d0e35 100644 --- a/deny.toml +++ b/deny.toml @@ -21,6 +21,20 @@ ignore = [ # ratatui → burn-train. The unsound API is reachable only inside # ratatui's internal cache; we do not call it. Mirrors prism's policy. "RUSTSEC-2026-0002", + # lopdf 0.38 stack overflow on deeply nested PDF objects, fix >=0.42. + # Transitive via pdf-extract 0.10 → organism-intelligence; no fixed + # version reachable until pdf-extract moves to lopdf >=0.42. Showcase + # scenarios only parse repo-local sample PDFs, not untrusted input. + # Mirrors scripts/ci/cargo-audit-blocking.sh (2026-07-02). + "RUSTSEC-2026-0187", + # quick-xml 0.38.4 — duplicate-attribute quadratic run time (0194) and + # NsReader namespace-allocation DoS (0195), fixed in >=0.41. Transitive + # via object_store 0.12.5, itself semver-locked by lancedb/surrealdb; + # no fix path until they move. Exposure is XML from the operator- + # configured storage provider's API, not arbitrary attacker input, and + # both advisories are DoS-class. Added 2026-07-02. + "RUSTSEC-2026-0194", + "RUSTSEC-2026-0195", ] [licenses] diff --git a/kb/Planning/MILESTONES.md b/kb/Planning/MILESTONES.md index 8d214fb..6598d93 100644 --- a/kb/Planning/MILESTONES.md +++ b/kb/Planning/MILESTONES.md @@ -1,3 +1,6 @@ +> **Archived 2026-07-02** — active milestone tracking moved to Linear (Reflective team). +> This file is kept for historical context only. Do not add new items here. + --- source: mixed --- @@ -53,6 +56,7 @@ Theatre smell checklist: - The README says "end-to-end" but no boundary break, missing type, or upstream issue emerges. ### Acceptance criteria for any scenario added under v1.1.0 +**Epic:** E7 - [ ] Touches **three or more** Mosaic modules wired through Converge contracts, not bespoke glue. - [ ] Domain-specific enough that the "why this matters" passes atelier's specificity bar (atelier-showcase is one of the few places allowed to speak concretely about domains). @@ -74,6 +78,7 @@ Build the first four v1.1.0 scenarios in the order that maximizes under-exercise Defer **cross-llm-adjudication** until the first four land. It is useful, but it has the highest risk of reading like model-vendor choreography unless the reflexion memory and approval quorum materially change a regulated decision. Keep **ip-counterparty-scoring** as the stretch failure case: only start it when the team is ready to grow skeleton Embassy ports through real missing entity shapes. ### Proposed scenarios +**Epic:** E7 - [x] **sec-edgar-live-filing** — landed 2026-05-22 as the first narrow `REAL LIVE` Mosaic source-observation proof slice. It fetches Apple Inc.'s @@ -113,6 +118,7 @@ Defer **cross-llm-adjudication** until the first four land. It is useful, but it - [ ] **ip-counterparty-scoring** *(stretch — forces skeleton growth)* — Embassy skeletons (`uspto`, `epo`, `openalex`, `arxiv`) → Prism `SimilarityPack` + `RankingPack` → Soter SMT invariant "score ≥ X requires legal sign-off." *Pressure-tests:* the P1 skeleton ports — pulling on them must surface the missing entity shapes and grow them through real use, not paper over them. This is the scenario most likely to fail on first attempt; that failure is the point. ### Definition of done for v1.1.0 +**Epic:** E7 - [ ] At least **four of the seven** proposed scenarios land with `just release-check` clean and coverage at or above v1.0.0's 83.3% floor. - [ ] Each landed scenario carries a Capability-Matrix back-link in its `README.md` naming which functions it pulls. diff --git a/scenarios/helm-realtime-stem-headless/src/cases.rs b/scenarios/helm-realtime-stem-headless/src/cases.rs index d5a3a3e..05f74ca 100644 --- a/scenarios/helm-realtime-stem-headless/src/cases.rs +++ b/scenarios/helm-realtime-stem-headless/src/cases.rs @@ -3,9 +3,7 @@ //! Named interactive cases that stress the Session Intelligence Spine headlessly. -use crate::{ - InteractiveCaseId, RealtimeStemRun, ServerLoopProfile, push_script, -}; +use crate::{InteractiveCaseId, RealtimeStemRun, ServerLoopProfile, push_script}; use helm_session_contracts::urgency::UrgencyIntent; /// Run one named interactive case and return the deterministic artifact. @@ -127,7 +125,11 @@ fn preemptive_pivot() -> RealtimeStemRun { run.complete_local_loop( "cyra", serde_json::json!({"conclusion": "segment-by-need", "dissent_preserved": true}), - Some(("need_more_evidence", "high", "quorum://hypothesis/segmentation")), + Some(( + "need_more_evidence", + "high", + "quorum://hypothesis/segmentation", + )), ); run.tick(4_000); run diff --git a/scenarios/helm-realtime-stem-headless/src/lib.rs b/scenarios/helm-realtime-stem-headless/src/lib.rs index 1cdd011..0e1a24a 100644 --- a/scenarios/helm-realtime-stem-headless/src/lib.rs +++ b/scenarios/helm-realtime-stem-headless/src/lib.rs @@ -123,24 +123,24 @@ pub struct StemEvent { pub payload: Value, } -struct ParticipantSlot { - display_name: String, - role: String, - helm: ClientHelm, - pending_local_spawn: bool, - pending_server_offload: Option, +pub struct ParticipantSlot { + pub display_name: String, + pub role: String, + pub helm: ClientHelm, + pub pending_local_spawn: bool, + pub pending_server_offload: Option, } #[derive(Debug, Clone)] -struct ServerLoopRecord { - server_formation_id: String, - participant_id: String, - formation_type: String, - profile: ServerLoopProfile, - started_at_ms: u64, - completes_at_ms: u64, - client_loop_id: Option, - completed: bool, +pub struct ServerLoopRecord { + pub server_formation_id: String, + pub participant_id: String, + pub formation_type: String, + pub profile: ServerLoopProfile, + pub started_at_ms: u64, + pub completes_at_ms: u64, + pub client_loop_id: Option, + pub completed: bool, } /// Deterministic headless coordinator for multi-user realtime stem scenarios. @@ -154,7 +154,7 @@ pub struct RealtimeStemRun { pub prepared_proposals: Vec, pub admitted_fact_ids: Vec, now_ms: u64, - start_ms: u64, + pub start_ms: u64, default_budget_ms: u64, } @@ -280,11 +280,7 @@ impl RealtimeStemRun { } /// Simulate the native layer spawning a fresh local formation after pause-and-inject. - pub fn spawn_fresh_local_loop( - &mut self, - participant_id: &str, - description: impl Into, - ) { + pub fn spawn_fresh_local_loop(&mut self, participant_id: &str, description: impl Into) { let description = description.into(); let loop_id = { let slot = self @@ -292,9 +288,7 @@ impl RealtimeStemRun { .get_mut(participant_id) .unwrap_or_else(|| panic!("unknown participant: {participant_id}")); let push = push_script( - self.session_id - .as_deref() - .unwrap_or("session"), + self.session_id.as_deref().unwrap_or("session"), 99, self.now_ms + 1, UrgencyIntent::Informational, @@ -452,7 +446,7 @@ impl RealtimeStemRun { } }), }; - slot.helm.formation_completed(&loop_id, output); + slot.helm.formation_completed(&loop_id, output, None); let submissions = slot.helm.drain_submissions(); self.emit( StemEventKind::LocalLoopCompleted, @@ -519,7 +513,8 @@ impl RealtimeStemRun { .first() .map(|view| view.gate_id.clone()) .unwrap_or_else(|| panic!("no pending gate for {participant_id}")); - slot.helm.respond_to_gate(&GateId::from_string(gate_id.clone()), response); + slot.helm + .respond_to_gate(&GateId::from_string(gate_id.clone()), response); let submissions = slot.helm.drain_submissions(); self.emit( StemEventKind::GateResponded, @@ -852,24 +847,21 @@ fn normalize_loop_ids( Value::Object(map) => { let mut out = serde_json::Map::new(); for (key, child) in map { - if key == "loop_id" { - if let Some(id) = child.as_str() { - let normalized = id_map - .entry(id.to_string()) - .or_insert_with(|| { - let label = format!("loop-{next}"); - *next += 1; - label - }) - .clone(); - out.insert(key.clone(), Value::String(normalized)); - continue; - } + if key == "loop_id" + && let Some(id) = child.as_str() + { + let normalized = id_map + .entry(id.to_string()) + .or_insert_with(|| { + let label = format!("loop-{next}"); + *next += 1; + label + }) + .clone(); + out.insert(key.clone(), Value::String(normalized)); + continue; } - out.insert( - key.clone(), - normalize_loop_ids(child, id_map, next), - ); + out.insert(key.clone(), normalize_loop_ids(child, id_map, next)); } Value::Object(out) } @@ -912,7 +904,12 @@ mod tests { json!({"objective": "server"}), ); run.deliver_push("alice", disruptive); - run.ack_server_offload("alice", "srv-1", "dd-analysis", ServerLoopProfile::ShortProbe); + run.ack_server_offload( + "alice", + "srv-1", + "dd-analysis", + ServerLoopProfile::ShortProbe, + ); let views = run.registry_views(); let alice = views.get("alice").expect("alice"); @@ -931,9 +928,10 @@ mod tests { #[test] fn budget_exhaustion_marks_local_loop_failed() { let case = cases::run_case(InteractiveCaseId::BudgetExhaustion); - assert!(case - .events - .iter() - .any(|event| event.kind == StemEventKind::LocalLoopFailed)); + assert!( + case.events + .iter() + .any(|event| event.kind == StemEventKind::LocalLoopFailed) + ); } } diff --git a/scripts/ci/cargo-audit-blocking.sh b/scripts/ci/cargo-audit-blocking.sh index bfac944..e75a8b9 100644 --- a/scripts/ci/cargo-audit-blocking.sh +++ b/scripts/ci/cargo-audit-blocking.sh @@ -4,6 +4,15 @@ set -euo pipefail # Keep this list in sync with the release-grade security-audit recipe. Each # ignore below is an explicit accepted transitive advisory while upstream # owners move. +# +# 2026-07-02: RUSTSEC-2026-0187 (lopdf 0.38 stack overflow, fix >=0.42) and +# RUSTSEC-2026-0192 (ttf-parser unmaintained, pulled in by lopdf) are pinned +# transitively via pdf-extract 0.10 -> organism-intelligence; no fixed +# version reachable until pdf-extract moves to lopdf >=0.42. +# +# 2026-07-02: RUSTSEC-2026-0194/-0195 (quick-xml 0.38.4 DoS-class advisories, +# fix >=0.41) are pinned transitively via object_store 0.12.5, itself +# semver-locked by lancedb/surrealdb; no fix path until they move. cargo audit --deny warnings \ --ignore RUSTSEC-2023-0089 \ --ignore RUSTSEC-2024-0384 \ @@ -13,4 +22,8 @@ cargo audit --deny warnings \ --ignore RUSTSEC-2021-0141 \ --ignore RUSTSEC-2025-0141 \ --ignore RUSTSEC-2025-0119 \ - --ignore RUSTSEC-2026-0002 + --ignore RUSTSEC-2026-0002 \ + --ignore RUSTSEC-2026-0187 \ + --ignore RUSTSEC-2026-0192 \ + --ignore RUSTSEC-2026-0194 \ + --ignore RUSTSEC-2026-0195