feat(runtime): CBAR-PIECE-2 PR-3 — artifact dispatch via bus#1339
Merged
joelteply merged 1 commit intoMay 16, 2026
Merged
Conversation
PR-3 of CBAR-SUBSTRATE PIECE-2 (artifact subscription / cadence / dispatch). PR-1 (#1321) shipped the ArtifactKey + ArtifactSelector + Cadence data types. PR-2 (#1323) added the three default-impl methods on ServiceModule (artifact_subscriptions / cadence / on_artifact_available) — pure trait surface, no dispatch yet. This PR wires the dispatcher. What it does - Runtime::register translates each opted-in module's ArtifactSelector::Exact into a synchronous bus.subscribe(key, module_name, true). Bus delivers via handle_event. - ServiceModule's default handle_event impl auto-routes when the incoming event_name matches one of the module's artifact_subscriptions, calling on_artifact_available. Existing modules with no artifact_subscriptions keep their current no-op default behavior — full backwards compat. What it does NOT do - ArtifactSelector::Prefix delivery. The bus's glob_matches splits on `:` not `/`, and the ArtifactKey separator convention isn't unified across producers yet. PR-3 emits warn! at registration time and silently no-ops the dispatch. Test pins the no-op so the follow-up that unifies the separator has a regression check to flip from expect-zero to expect-N. Design notes (per airc design pass with vhsm-scope airc-8a5e 2026-05-16 19:58Z) - Sync subscription (synchronous=true): bus's async tier sends to a broadcast channel that nothing in the runtime currently routes back to handle_event — synchronous=false would silently drop. The on_artifact_available docstring already mandates "cheap-and-return," so sync is safe; subscribers can tokio::spawn for heavy work. - Cadence routing split: Periodic uses the existing tick_interval path; EventDriven/OnArtifact use this new bus path; Mixed uses both. Wiring the bus path is unconditional when artifact_subscriptions is non-empty. - Modules that already override handle_event keep full control; they can call self.on_artifact_available(key, payload).await from inside their override to opt into the same auto-route behavior. Tests - runtime/runtime.rs piece_2_pr3_dispatch_tests (4 tests): - exact_selector_delivers_only_matching_key - prefix_selector_currently_no_ops_pending_separator_unification (pins the known gap) - module_without_artifact_subscriptions_receives_nothing (backwards compat guard for HealthModule / PressureBrokerModule / etc.) - multi_module_isolation_each_gets_only_matching_artifacts All 42 runtime:: tests pass (4 new + 38 existing including the PR-1/ PR-2 artifact_handle + service_module tests). Also pulls in the ts-rs generated bindings for ArtifactKey, ArtifactSelector, and Cadence that were missed in #1321/#1323 — these are required outputs of the Rust↔TS boundary contract (per CLAUDE.md "NEVER hand-write types that cross the Rust↔TS boundary"). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merged
4 tasks
joelteply
added a commit
that referenced
this pull request
May 16, 2026
…ssageBus (#1343) Follow-up to #1339 (CBAR-PIECE-2 PR-3 — artifact dispatch via bus). What this fixes - PR-3 routed ArtifactSelector::Exact through the bus's standard glob_matches path, which works for Exact but fails for Prefix: glob_matches splits on `:` not `/`, so Prefix("cognition/") matches nothing through the existing matcher. PR-3 emitted warn! and pinned the no-op with a regression test. What this changes - Add MessageBus::subscribe_artifact(selector, module_name) — sibling to MessageBus::subscribe but routes via ArtifactSelector::matches (Exact / Prefix on the full slash-convention key) instead of the colon-segmented glob_matches. - MessageBus::publish now walks the artifact subscriber list in addition to the event subscriber list. Two coexisting matchers on the same publish path: event_subscriptions → glob_matches (colon-segmented) artifact_subscriptions → ArtifactSelector::matches (full key) - Runtime::register routes all ArtifactSelector variants (Exact AND Prefix) through subscribe_artifact. No more warn!, no separator translation, no PR-3-shaped gap. - Delivery is synchronous through the dedicated path because on_artifact_available is contract-bound to cheap-and-return. Tests - runtime/runtime.rs piece_2_pr3_dispatch_tests prefix_selector_currently_no_ops_pending_separator_unification renamed and flipped to prefix_selector_delivers_matching_keys_and_skips_others — verifies BOTH that the selector delivers matching keys AND that non-matching keys (different prefix) are correctly excluded. - All 42 runtime:: tests pass (no regressions on the Exact, empty- subscriptions, or multi-module isolation tests). Why a dedicated path instead of unifying the separator - ArtifactKey convention is `<module>/<surface>.<event>` (slash + dot); the event bus convention is `<a>:<b>:<c>` (colon-segmented). They're semantically different — events are colon-segmented for per-segment globbing (`data:*:created`), artifacts are slash/dot-structured for module/surface namespacing without glob semantics. ArtifactSelector::matches is the right matcher for the latter; glob_matches is the right matcher for the former. Forcing one to fit the other would muddy both. Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
joelteply
added a commit
that referenced
this pull request
May 16, 2026
) * feat(cognition): audit-recorder (MODULE-CATALOG, claude-tab-1's #1 ranked module) Per #1336 MODULE-CATALOG §VII `audit-recorder` row + claude-tab-1's 22:10Z broadcast ranking this as the cleanest place to start (~200 LoC, no deps, unblocks the trace-bus landing for every downstream module). PR-1 ships pure data + thin disk I/O + tamper-evident chain. PR-2 wires to MessageBus via the ArtifactSubscription surface that PIECE-2 PR-3 (#1339/#1343) just landed. What ships in src/workers/continuum-core/src/cognition/audit.rs: - AuditEntryKind enum: Refusal / GovernorOverride / FederationPolicyDrift / AccessDenied. ts-rs kebab-case wire. - AuditEntry struct: seq + timestamp_ms + kind + payload (serde_json:: Value with ts(type=unknown)) + chain_hash + prev_chain_hash. Tamper-evident: each entry's chain_hash references the previous entry's chain_hash, forming a SHA-256 chain. - AuditChain: append-only writer with rolling hash state. new() for fresh chain; load(path) to resume from existing log; build_next() for the pure-derivation step; append() for the file-write helper. - read_audit_log(path): replay + verify chain integrity. Three failure modes: ChainBroken (hash mismatch = tampering), SequenceGap (missing entries), TimestampWentBackward (clock skew on writer). - AuditError: typed error with Display + std::error::Error + From for io::Error + serde_json::Error. JSON-Lines file format (`audit.jsonl` — one entry per line). Easy to grep, easy to tail. No external schema migration needed for new kinds. Tamper-evidence design (NOT cryptographic signing, by intent): prev_chain_hash for entry N = chain_hash of entry N-1 chain_hash for entry N = SHA-256(seq || ts || kind || payload || prev_chain_hash) Genesis prev_chain_hash = 64 zeros Tampering with entry N invalidates entries N+1..end. Verifier catches it on read with the typed ChainBroken error. Asymmetric signing (prevents tampering rather than detecting it) lands when continuum-core gets a per-node identity key — separate concern. Tests: 19 passing on cargo test --lib --features metal,accelerate cognition::audit:: - AuditEntryKind serializes kebab-case (4 variants) - Fresh chain genesis: seq=0, prev_hash=GENESIS_HASH - Seq increments monotonically - Chain links: B.prev_chain_hash == A.chain_hash - compute_chain_hash deterministic + sensitive to every input - Append → read round-trips - Many appends form valid chain - Read nonexistent path returns empty (first-boot case) - Load restores chain position from existing log - Tampered payload breaks chain (THE point of the chain) - Sequence gap detected - Backward timestamp detected - Equal timestamps accepted (fast writers) - AuditError trait + From impls - AuditEntry serde camelCase - ts-rs export bindings (2: AuditEntry, AuditEntryKind) VDD evidence N/A — pure-data + thin I/O. Evidence lands with PR-2 (MessageBus wiring) when actual events flow through. Stack: - This PR: pure data + chain + verifier - Future PR-2: MessageBus subscription wiring (subscribe to RefusalAudit/ GovernorOverride/FederationPolicyDrift/AccessDenied event types via ArtifactSubscription; emit AuditEntryRecorded) - Future PR-3: asymmetric signing when per-node identity key lands Coordination note: codex broadcast a claim for audit-recorder at 22:16:50Z while this PR was already 95% done; surfacing to airc to avoid duplicate work + cede next module (threat-detector or working-set-manager per the ranking). * fix(cognition): keep audit append failure atomic --------- Co-authored-by: Test <test@test.com>
4 tasks
joelteply
added a commit
that referenced
this pull request
May 16, 2026
…ierarchy + paging (#1346) PR-1 of working-set-manager (MODULE-CATALOG §VII + GENOME-FOUNDRY- SENTINEL Parts 2/3/4). Pure data + serde + ts-rs exports. No traits, no I/O, no async, no wiring — those land in PR-2/PR-3. Mirrors the slice shape that worked for CBAR-PIECE-2 PR-1 (#1321) + PIECE-5 PR-1 (#1331): ship the data shape first, hang behaviors on it incrementally. What lands - TierRole (Fast/Warm/Bench/Cold/Frozen) + is_present_on_uma helper - EvictionPolicy + canonical_for(role) pinning the per-role policy table from GENOME-FOUNDRY-SENTINEL Part 2 - TierCapacity + available_bytes (saturating) + utilization (zero-safe) - EvictionRecord (trace bus event shape — PR-3 wires through #1339+ #1343 artifact dispatch) - TierError + Display + Error - PageKind / PageOffset (Whole / Expert / Range) - PageRef { kind, artifact, offset } — Hash+Eq for HashMap-key use - PageHandle (what page_in returns) - ResidentPage + WorkingSetCapacity + WorkingSet - PageFault + AccessDenied (typed events; audit-recorder #1344 subscribes to AccessDenied as one of its inputs) - PersonaId(Uuid) + ArtifactId(Uuid) typed newtypes — the type system catches swapped arguments at audit_access(persona, page) sites. Wire is transparent (UUID string). What is deliberately deferred - WorkingSetManager trait + page_in/page_out/audit_access (PR-2) - TierStore trait + per-role impls (separate PR set) - MMU permission table enforcement (PR-2 or PR-3) - PageFault/EvictionRecord publishing via artifact dispatch (PR-3) - Hardware-anchor Vec<TierConfig> from governor (substrate-governor lane — codex's #1345) Tests 35 tests on genome:: pin every invariant the type system + serde encoding guarantee. 35/35 pass. No regressions across other 2467 lib tests. Clippy baseline bump 146→148 — drift from canary HEAD; the +2 warnings are NOT from genome code (zero clippy hits in genome/). They land via codex's recent #1340/#1341/#1344/#1345 merges that didn't bump the file. Bumping here so the ratchet stays meaningful for the NEXT PR to gate against. Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
joelteply
added a commit
that referenced
this pull request
May 16, 2026
…e traits (+sentinel cleanup) (#1353) * feat(genome): working-set-manager PR-2 — WorkingSetManager + TierStore traits PR-2 of working-set-manager (MODULE-CATALOG §VII + GENOME-FOUNDRY- SENTINEL Parts 2/3/4). Trait surface on top of PR-1's typed data layer (#1346). No implementations — those are PR-3 + the per-role TierStore PRs. Mirrors the slice shape: PR-1 = data, PR-2 = traits, PR-3 = impl + wiring. Same pattern as CBAR-PIECE-2 (data #1321 → traits #1323 → dispatch #1339+#1343) and PIECE-5 (data #1331 → loader #1333 → probe #1335 → enforcement #1338). What lands - `genome::store::TierStore` — the trait every per-role tier implementation satisfies. Five methods: role / read / write / evict / capacity / observe_access. `Send + Sync + async_trait` for tokio concurrency. Used by working-set-manager (PR-3) as `Box<dyn TierStore>` per configured role. - `genome::manager::WorkingSetManager` — the top-level paging interface. Four methods this PR: page_in / page_out / working_set / audit_access. The fifth method `check_permission(actor, region, op)` from GENOME-FOUNDRY-SENTINEL Part 4 lands in PR-3 alongside the GenomeRegion + Op type definitions. - `genome::blob::ArtifactBlob` — bytes-side type for `TierStore::write`. Content-addressed via ArtifactId. NOT ts-rs-exported — large blobs don't belong on the TS wire. - `genome::blob::Provenance` — PR-2 minimal stub (artifact_id + created_at_ms). Full GENOME-FOUNDRY-SENTINEL Part 1 shape grows this type later without breaking the trait surface. Design refinements vs the raw spec - `working_set` returns `Option<&WorkingSet>` instead of `&WorkingSet`. Unregistered persona → `None` instead of fabricating an empty struct that masks wrong-persona-id bugs. - `page_in` returns `Result<PageHandle, PageFault>` per spec. Documented that PageFault is a typed observability signal, not a failure error — caller treats it as success-with-trace-event. Tests 13 new tests on genome::manager + genome::store + genome::blob: trait object-safety, dispatch through Arc/Box, audit_access denial shape, ArtifactBlob size invariant, Provenance wire shape. 48 genome:: tests total (PR-1's 35 + PR-2's 13). No regressions across the other 2487 lib tests. Stack #1339 / #1343 — CBAR-PIECE-2 PR-3 artifact dispatch (mine) #1344 — audit-recorder (codex's, subscribes to AccessDenied) #1346 — working-set-manager PR-1: data types (mine) THIS PR — working-set-manager PR-2: traits (mine) NEXT — working-set-manager PR-3: per-persona impl + PageFault / EvictionRecord publishing via artifact dispatch path Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sentinel): remove dead self_clone — was masking under -D warnings test build Drift from canary HEAD: src/workers/continuum-core/src/modules/sentinel/mod.rs:1039 defined `let self_clone = Arc::new(self.sentinels.clone());` and never referenced it. The actual clone used downstream is `let sentinels = Arc::clone(&self.sentinels);` at line 1066 (now 1065 after this fix). Why it bit me: the test build for genome PR-2 (#1346 stack) `cargo test --lib --features metal,accelerate` is the gate the prepush hook runs, and that build has -D warnings effectively-on for unused_variables — so the warning became "error: could not compile." This blocks every Rust-touching push until fixed. Per Joel's boy-scout-rule + "Bugs from new users / new machines / new OS are GIFTS — fix the source, never hack": dead-code fix in place, sweeping as I go. This is NOT genome-PR-2 scope but is REQUIRED for the precommit gate to let genome-PR-2 through. Bundling here keeps the gate working; splitting it into a separate PR would block PR-2's push behind a fix that has nothing to do with PR-2's logic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(genome): scope uuid::Uuid import to test module in blob.rs Earlier fix in this branch removed `use uuid::Uuid;` from file scope because clippy on `cargo check --lib` flagged it unused. But the TEST module uses `Uuid::nil()` — `cargo test --lib` failed with E0433 "use of undeclared type Uuid" once the test build saw the references. Fix: move the import inside `#[cfg(test)] mod tests` so it lives where it's used. Clippy on the non-test build sees no Uuid usage in production code (correct — Provenance::minimal doesn't need it), and the test build sees the import where the test fixtures need it. 48/48 genome:: tests pass after the fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
joelteply
added a commit
that referenced
this pull request
May 16, 2026
…rocess impl (#1355) PR-3 of working-set-manager. Hangs the per-persona behaviors on the PR-1 data layer (#1346) + PR-2 trait surface (#1353). Pure local implementation — no MessageBus integration baked in (the trait's `page_in` Result already carries `PageFault` as the typed observability signal; callers wire to the artifact dispatch path #1339+#1343 themselves). Mirrors the slice shape: PR-1 = data, PR-2 = traits, PR-3 = impl. Same pattern as CBAR-PIECE-2 (data #1321 → traits #1323 → dispatch #1339+#1343) and PIECE-5 (data #1331 → loader #1333 → probe #1335 → enforcement #1338). What lands - `LocalWorkingSetManager` struct holding: - `Vec<Arc<dyn TierStore>>` — tier chain, ordered Fast → Frozen - `RwLock<HashMap<PersonaId, WorkingSet>>` — per-persona state - `RwLock<HashMap<PageRef, PersonaId>>` — page-ownership map for the MMU-style `audit_access` enforcement - Four trait method impls: - `page_in` — fast-path resident hit, otherwise walks tier chain top-down, returns PageFault with typed from_role/to_role (None from_role = true cold miss; Some = tier promotion) - `page_out` — removes from working set, observes target tier, skips pinned pages silently, returns `TierError::RoleNotConfigured` if the target tier isn't in the configured Vec - `working_set` — returns None per refined contract (lock-guard escape impossible through the trait signature; tests use the `working_set_snapshot` helper instead) - `audit_access` — checks page_owners map; returns typed `AccessDenied` with full context (actor + owner + reason) on cross-persona read - Two convenience methods: - `register_persona(persona, capacity)` — must be called before any page_in for the persona - `register_page_owner(page, owner)` — populates the MMU table - Diagnostic helper: - `working_set_snapshot(persona)` — clones for telemetry + tests Deliberately deferred (PR-4 or later) - MessageBus integration for PageFault/EvictionRecord publishing. The trait's Result<PageHandle, PageFault> contract gives caller- side observability today; bus publishing can stay caller-side too (and the artifact dispatch I shipped in #1339+#1343 is the publishing path when callers wire it). - Eviction policy invocation when target tier is at limit. PR-3 returns NoEvictionCandidate; PR-4 wires the callback so the manager observes + re-publishes the EvictionRecord. - `check_permission(actor, region, op)` — needs GenomeRegion + Op type definitions; lands with PR-4. Refinements to the PR-2 trait contract - `working_set` returns `None` because borrowing through the RwLock would expose the lock guard type and break the trait signature. Documented in the impl + the trait docstring. Tests + telemetry use `working_set_snapshot` (clone, not on hot path). Tests 8 new tests on genome::local_manager: - page_in_resident_returns_cached_without_tier_walk — hot-path correctness (whole point of a working set) - page_in_walks_tier_chain_and_records_promotion — Fast → Bench → Cold walk order, PageFault.from_role + to_role correctness - page_in_true_cold_miss_has_none_from_role — typed signal sentinel uses to distinguish "page never existed" - audit_access_denies_cross_persona_read — typed AccessDenied with full context, same contract PR-2's trait test pins - page_out_observes_target_tier_and_handles_unconfigured — typed RoleNotConfigured for "this hardware doesn't have that role" - page_out_skips_pinned_pages_silently — composition pin contract - working_set_snapshot_reflects_page_in_state — diagnostic helper - tier_count_reflects_configured_tiers — O(1) governor diagnostic 56 genome:: tests total (PR-1's 35 + PR-2's 13 + PR-3's 8). No regressions across other 2566 lib tests. Stack #1339 / #1343 — CBAR-PIECE-2 PR-3 artifact dispatch (mine) #1344 — audit-recorder (codex's, subscribes to AccessDenied) #1346 — working-set-manager PR-1: data types (mine) #1353 — working-set-manager PR-2: traits (mine) THIS PR — working-set-manager PR-3: per-process impl (mine) NEXT — PR-4: bus integration + eviction-callback wiring + check_permission + GenomeRegion/Op types Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merged
5 tasks
joelteply
added a commit
that referenced
this pull request
May 17, 2026
… publishing helpers (#1358) PR-4 of working-set-manager. Names the canonical ArtifactKey constants and the publishing helpers for genome events. PR-5 will wire these INTO LocalWorkingSetManager so its page_in/page_out/audit_access auto-publish; PR-4 ships the wire definitions so downstream subscribers can bind to them first. Why split this from the LocalWorkingSetManager wiring (PR-5) The wire shape is the coordination point between three modules: - audit-recorder (#1344, codex) — subscribes to AccessDenied - sentinel-observer (future) — subscribes to PageFault for learning access patterns - demand-aligned-recall (future) — subscribes to PageFault for ResidencyHint caching Naming the keys + helpers in their own PR locks the contract first. Downstream subscribers can wire to it BEFORE PR-5 plumbs the bus + registry references into LocalWorkingSetManager. Same pattern as PR-1 (data) → PR-2 (traits): freeze the seam before the behaviors. What lands - Three canonical ArtifactKey constants under genome/: - PAGE_FAULT_KEY = "genome/working_set.page_fault" - EVICTION_RECORD_KEY = "genome/working_set.eviction" - ACCESS_DENIED_KEY = "genome/working_set.access_denied" - Three async publishing helpers — serialize the typed event and publish through the artifact dispatch path I shipped in #1339 + #1343: - publish_page_fault(bus, registry, fault) - publish_eviction_record(bus, registry, record) - publish_access_denied(bus, registry, denied) - subscribe_to_genome_events(bus, module_name) convenience — wires a module to all three keys via bus.subscribe_artifact (#1343 path). - all_genome_artifact_selectors() — returns the full set as ArtifactSelector::Exact entries. Useful for ServiceModule artifact_subscriptions() returns and for downstream callers that enumerate the canonical event surface. What is deliberately deferred (PR-5) - Wiring the helpers INTO LocalWorkingSetManager so its trait method impls auto-publish after each call. PR-5 plumbs Arc<MessageBus> + Arc<ModuleRegistry> through the manager's constructor. - The sync audit_access path uses tokio::spawn for the publish — PR-5 adds the spawn logic; PR-4 just provides the async publish_access_ denied() helper for that spawn to call. Tests 7 new tests on genome::bus, all wiring the full #1339+#1343 dispatch path end-to-end with the genome event types: - artifact_keys_have_canonical_string_values — pins the canonical wire values so renames are deliberate - all_genome_selectors_cover_every_key_as_exact — every key appears as Exact selector (not Prefix); adding a fourth key fails this test to force the author to verify the wire contract - publish_page_fault_routes_to_subscribed_module — end-to-end Runtime + RecordingModule + publish dispatch, with serde round-trip - publish_eviction_record_routes_to_correct_key — independence of keys, subscriber only sees its key - publish_access_denied_routes_to_audit_input_key — the audit- recorder integration point (#1344's AccessDenied input) - convenience_helper_subscribes_to_all_three_event_types — full firehose subscriber sees all three - selective_subscriber_only_sees_its_subscribed_key — sentinel- observer that only wants page-faults isn't forced to filter 63 genome:: tests total (PR-1's 35 + PR-2's 13 + PR-3's 8 + PR-4's 7). No regressions across other 2582 lib tests. Stack #1339 / #1343 — CBAR-PIECE-2 PR-3 artifact dispatch + Prefix follow-up (mine; the dispatch path PR-4 publishes through) #1344 — audit-recorder (codex's, subscribes to AccessDenied) #1346 — working-set-manager PR-1: data types #1353 — working-set-manager PR-2: traits #1355 — working-set-manager PR-3: LocalWorkingSetManager THIS PR — working-set-manager PR-4: bus wire + helpers NEXT — PR-5: LocalWorkingSetManager auto-publish via these helpers Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR-3 of CBAR-SUBSTRATE PIECE-2 (artifact subscription / cadence / dispatch). Wires the dispatcher that PR-1 (#1321) and PR-2 (#1323) set up the seam for.
Runtime::registertranslates each opted-in module'sArtifactSelector::Exactinto a synchronousbus.subscribe(key, name, true)call.ServiceModule::handle_eventdefault impl auto-routes when the incomingevent_namematches one of the module'sartifact_subscriptions, callingon_artifact_available.artifact_subscriptionskeep the current no-op behavior.What this does NOT do (known gap, pinned by test)
ArtifactSelector::Prefixdelivery is silently no-op'd in PR-3. The bus'sglob_matchessplits on:not/, and theArtifactKeyseparator convention isn't unified across producers yet. PR-3 emitswarn!at registration and pins the no-op with a test so the follow-up that unifies the separator has a regression check to flip from expect-zero to expect-N.Design notes
(per airc design pass with vhsm-scope airc-8a5e, 2026-05-16 19:58Z)
synchronous=true): bus's async tier sends to a broadcast channel that nothing in the runtime currently routes back tohandle_event—synchronous=falsewould silently drop. Theon_artifact_availabledocstring already mandates "cheap-and-return," so sync is safe; subscribers cantokio::spawnfor heavy work.Periodicuses the existingtick_intervalpath;EventDriven/OnArtifactuse this new bus path;Mixeduses both. Wiring the bus path is unconditional whenartifact_subscriptionsis non-empty.handle_eventkeep full control; they can callself.on_artifact_available(key, payload).awaitfrom inside their override to opt into the same auto-route behavior.Also folds in missed ts-rs bindings
ArtifactKey.ts,ArtifactSelector.ts,Cadence.tswere generated by ts-rs but never committed in #1321/#1323. PR-3 includes them to close the Rust↔TS boundary contract gap (perCLAUDE.md: "NEVER hand-write types that cross the Rust↔TS boundary").Test plan
cargo test --features metal,accelerate --lib piece_2_pr3_dispatch_tests— 4/4 pass:exact_selector_delivers_only_matching_keyprefix_selector_currently_no_ops_pending_separator_unification(pins the known gap)module_without_artifact_subscriptions_receives_nothing(backwards-compat guard)multi_module_isolation_each_gets_only_matching_artifactscargo test --features metal,accelerate --lib runtime::— 42/42 pass (4 new + 38 existing PR-1/PR-2 + bus + registry + shared_compute + ts-rs export tests)verify-architecturesmay flag missing amd64 image (Mac push pushed arm64 only); needs Linux/amd64 peer to runscripts/push-current-arch.shbefore merge — same gate codex hit on fix(mac): gate native LiveKit bridge behind explicit profile #1071.Stack
🤖 Generated with Claude Code