Skip to content

feat(runtime,CBAR-PIECE-2): ArtifactKey + ArtifactSelector + Cadence types (PR-1)#1321

Merged
joelteply merged 1 commit into
canaryfrom
feat/runtime-artifact-types-cbar-piece-2
May 16, 2026
Merged

feat(runtime,CBAR-PIECE-2): ArtifactKey + ArtifactSelector + Cadence types (PR-1)#1321
joelteply merged 1 commit into
canaryfrom
feat/runtime-artifact-types-cbar-piece-2

Conversation

@joelteply
Copy link
Copy Markdown
Contributor

Summary

Pure-types slice of CBAR-SUBSTRATE missing piece 2 (claimed 15:38Z, unblocked by #1319). Adds the typed wire shape ServiceModule will adopt in PR-2 and the runtime will dispatch on in PR-3.

Same cadence as rate_proposals / generate_recipe PR-1: pure data layer lands first, independently mergeable, with full test coverage, before any runtime wiring.

Types

  • ArtifactKey(String) — newtype, transparent serde, no closed enum. Per CLAUDE.md anti-pattern rules + Joel's "we do not hardcode" directive. Same shape as inference_capability::InferenceKind (feat(grid-inference-routing): PR-1 InferenceCapability + probe + NodeCapabilityRegistry #1315).
  • ArtifactSelector::{Exact, Prefix} — what a subscriber wants. String match + string-prefix only; glob/regex deliberately omitted since matcher is the hot path.
  • Cadence::{Periodic, EventDriven, OnArtifact, Mixed} — supervised wake policy. No Default impl, no OnDemand variant — broker/supervisor decides cadence per dynamic-hardware-detect rule.

What this PR is NOT

  • No ServiceModule trait changes (PR-2)
  • No ModuleConfig field additions (PR-2 — Optional so existing modules don't break; opt-in)
  • No runtime dispatch wiring (PR-3)

Test plan

  • 12/12 unit tests pass: cargo test --features metal,accelerate runtime::artifact_handle
  • ts-rs exports verified: shared/generated/runtime/{ArtifactKey,ArtifactSelector,Cadence}.ts
  • Test focus: transparent serde, internally-tagged enum shape, selector hot-path semantics (Exact doesn't prefix-match, Prefix handles empty + degenerate), Cadence projection (tick_interval returns None for non-periodic, wants_artifact_wakes covers the right variants), full roundtrip every variant
  • CI green

Coordination

Stacked conceptually under codex's Lane D claim (PersonaTurnFrame proof, 19:23Z) — Lane D will subscribe via these types in a later PR. airc-8a5e's CBAR-PIECE-5 (Qwen GPU residency gate) is independent. All three slices physically independent — PR-2 picks up these types when Phase 0 trunk doc lands.

🤖 Generated with Claude Code

…types (PR-1)

Pure-types slice of CBAR-SUBSTRATE missing piece 2 (claimed 15:38Z,
unblocked by #1319). Adds the typed wire shape `ServiceModule` will
adopt in PR-2 (Optional fields on ModuleConfig + default
`on_artifact_available` method) and the runtime will dispatch on in
PR-3 (artifact event delivery on cadence).

Same cadence as rate_proposals / generate_recipe PR-1: pure data layer
lands independently mergeable, with full test coverage, before any
runtime wiring. PR-2 stacks the trait extension on this; PR-3 wires
the dispatcher.

Types

- `ArtifactKey(String)` — newtype, transparent serde, no closed enum.
  Modules register their own kinds at boot per CLAUDE.md anti-pattern
  rules + Joel's "we do not hardcode" directive. Same shape as
  `inference_capability::InferenceKind` (codex's #1315 PR-1).
- `ArtifactSelector::{Exact, Prefix}` — what a subscriber wants. Exact
  string match + string-prefix only. Glob/regex deliberately omitted
  — the matcher is the runtime's hot path (walked every publish);
  string-prefix is cheap + covers the cases we have.
- `Cadence::{Periodic, EventDriven, OnArtifact, Mixed}` — supervised
  wake policy. interval_ms over the wire so TS doesn't deal with bigint
  Duration. No `Default` impl, no `OnDemand` variant — broker/supervisor
  decides cadence per the dynamic-hardware-detect rule, every
  registered module has an explicit policy.

What this PR is NOT

- No `ServiceModule` trait changes yet (PR-2)
- No `ModuleConfig` field additions yet (PR-2 — Optional so existing
  modules don't break; opt-in)
- No runtime dispatch wiring (PR-3)

12/12 unit tests (cargo test --features metal,accelerate
runtime::artifact_handle). ts-rs exports verified to
shared/generated/runtime/{ArtifactKey,ArtifactSelector,Cadence}.ts.
Test focus: serde wire shape (transparent / internally-tagged),
selector hot-path semantics (Exact doesn't prefix-match,
Prefix handles empty + degenerate cases), Cadence projection
(tick_interval returns None for non-periodic, wants_artifact_wakes
covers the right variants), full roundtrip every variant.

Stacked under codex's Lane D claim (PersonaTurnFrame proof, 19:23Z)
and airc-8a5e's CBAR-PIECE-5 signal. All three slices independent —
PR-2 picks up these types when Phase 0 trunk doc lands; Lane D and
PIECE-5 don't physically depend on PR-2.
@joelteply joelteply merged commit f32b3ea into canary May 16, 2026
3 checks passed
@joelteply joelteply deleted the feat/runtime-artifact-types-cbar-piece-2 branch May 16, 2026 19:30
joelteply added a commit that referenced this pull request May 16, 2026
…PR-2) (#1323)

Stacks on #1321 (ArtifactKey + ArtifactSelector + Cadence types).
Adds three default-impl methods to ServiceModule:

- `artifact_subscriptions() -> Vec<ArtifactSelector>` — opt-in list of
  artifact streams this module wants delivery for. Default: empty.
- `cadence() -> Option<Cadence>` — wake-policy override. None preserves
  existing tick_interval semantics. Default: None.
- `on_artifact_available(&key, value) -> Result` — async handler PR-3's
  runtime will call when a producer publishes a matching key. Default:
  no-op Ok.

Non-breaking: every existing module (HealthModule, PressureBrokerModule,
CognitionModule, GpuModule, all 26+ ServiceModule impls in
modules/*.rs) compiles without edits. Opt-in only, same pattern as the
existing `handle_event` / `tick` / `command_schemas` defaults.

What this PR is NOT
- No runtime dispatch wiring yet (PR-3 — runtime calls
  on_artifact_available when a subscription matches a published key)
- No ModuleConfig field additions (artifact_subscriptions lives on the
  trait, not in config — keeps ModuleConfig stable + lets modules
  compute subscriptions from state if needed)
- No existing module opts in yet (Lane D's #1322 PersonaTurnFrame is
  the natural first consumer; once PR-3 lands they can subscribe)

5 tests covering:
- defaults: DefaultsModule sees empty / None / Ok across all 3 methods
- override + trait-object dispatch: OptedInModule sees subscriptions + cadence + custom handler through &dyn ServiceModule
- error propagation: handler Err bubbles up unchanged (PR-3's
  dispatcher will log + continue; pinned shape)
- heterogeneous walk: Vec<Arc<dyn ServiceModule>> with mixed opt-in
  status filters correctly (the exact dispatch shape PR-3 uses)

Validation: cargo test --features metal,accelerate service_module —
5/5 pass. Build clean on continuum-core.

Stacked sequence in flight:
- #1321 (PR-1, types) MERGED
- This (PR-2, trait surface) — opening now
- PR-3 (runtime dispatch wiring) — opens after Lane D consumer pattern
  stabilizes so I can wire to a real subscriber

Co-authored-by: Test <test@test.com>
joelteply added a commit that referenced this pull request May 16, 2026
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: 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
…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>
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>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant