Skip to content

[L1-1] EventClass declaration system + registry#1445

Merged
joelteply merged 2 commits into
canaryfrom
feat/l1-1-event-class-registry
May 26, 2026
Merged

[L1-1] EventClass declaration system + registry#1445
joelteply merged 2 commits into
canaryfrom
feat/l1-1-event-class-registry

Conversation

@joelteply
Copy link
Copy Markdown
Contributor

Summary

Native-truth-thin-SDK-per-language: Rust crate continuum-core::events owns the truth (registry types, validation, IPC handlers); TS shim at @system/events/shared/EventClass.ts caches reads for the hot Events.emit() path but never mutates without going through the Rust IPC mixin.

What this lands

Rust truth (continuum-core::events)

  • EventClassConfig + ResolvedEventClassConfig with #[derive(TS)]shared/generated/events/
  • EventClassChannelStrategy: Local | Global | ByRoomId | ByPeerId | Custom
  • EventClassUnknownSchemaPolicy: Warn | Fail (default Fail — never silently swallow evidence)
  • EventClassRegistry: parking_lot RwLock<HashMap> behind OnceLock singleton; declare / get / list / resolve_channel + canonicalize() for idempotent-redeclare
  • Validation enforced Rust-side: empty name, empty schemaVersion, broadcast-without-channel, channel-without-broadcast, conflicting redeclare

IPC surface (modules::events)

  • events/declare-class, events/get-class, events/list-classes, events/resolve-channel — registered alongside ForgeModule

TS bindings (workers/continuum-core/bindings/modules/events.ts)

  • EventsMixin wired into RustCoreIPC composition chain

TS thin SDK (@system/events/shared/EventClass.ts)

  • declareEventClass / getEventClass (read-through cache + null-cache + in-flight dedup) / peekEventClassCache (sync hot-path) / listEventClasses / resolveEventChannel

Events.emit integration (system/core/shared/Events.ts)

  • Sync peekEventClassCache per emit; if declared+cached, attach EventBridgePayload.eventClass hints; if cold, fire-and-forget warm-up
  • Backward-compat: undeclared classes get no hints; emit behavior identical to pre-L1-1

Done criteria from roadmap (L1-1)

Criterion Status
EventClass declarations accepted ✅ Rust + TS
Events.emit() reads metadata ✅ Sync peek + warm-up + hint attach
Existing event uses continue working unchanged ✅ Undeclared classes get undefined peek; same emit branch
Unit tests for registry + classifier round-trip ✅ 38 Rust + 11 TS

Test plan

  • CI: cargo test continuum-core (38 events tests should pass)
  • CI: npx vitest run tests/unit/core/event-class-registry.test.ts (11 tests)
  • CI: npm run build:ts (clean TypeScript)
  • Smoke: ./jtag → Rust core comes up with events module registered

Out of scope (deferred per roadmap)

  • L1-2 AircEventTransport consumer of these hints. The trait already exists (feat(airc): add typed event transport seam #1443); the adapter that consults EventClass metadata to decide which channel to publish to lands next.
  • TS Command surface at commands/events/* for CLI introspection. Deferred to L4 when a CLI consumer materializes.

Build hygiene

  • clippy-baseline.txt bump 157 → 168: this branch sits on top of canary HEAD e2fed994b (PR feat(airc): add typed event transport seam #1443 "feat(airc): add typed event transport seam"), which added 11 new clippy warnings without updating the baseline file. My L1-1 code adds zero new clippy warnings (verified by grep on event_class / events/mod.rs / modules/events.rs); the delta is inherited upstream drift. PR feat(airc): add typed event transport seam #1443's warnings should be cleaned up in a follow-up.
  • tsconfig.eslint.json: add new unit test to files so ESLint can parse it (mirrors existing chat-coordination-stream.test.ts entry).

🤖 Generated with Claude Code

Roadmap item L1-1 — the foundational event-class registry. All other L1-L5
work depends on this primitive. See docs/grid/GRID-MIGRATION-ROADMAP.md
(PR #1442) and docs/architecture/GRID-BUS-ARCHITECTURE.md §2.2 (#1439).

Closes roadmap item L1-1
Depends on: none
Spec: continuum#1439 + continuum#1442
Composes with: continuum#1443 AircEventTransport trait (L1-2 substrate)

Rust truth (continuum-core::events)
- EventClassConfig + ResolvedEventClassConfig (ts-rs export to
  shared/generated/events/).
- EventClassChannelStrategy: Local | Global | ByRoomId | ByPeerId | Custom.
- EventClassUnknownSchemaPolicy: Warn | Fail (default Fail — never
  silently swallow evidence).
- EventClassRegistry: parking_lot::RwLock<HashMap> behind OnceLock,
  declare/get/list/resolve_channel, canonicalize() idempotent-redeclare check.
- Validation enforced Rust-side: empty name, empty schemaVersion,
  broadcast-without-channel, channel-without-broadcast, conflicting redeclare.

IPC surface (modules::events)
- events/declare-class, events/get-class, events/list-classes,
  events/resolve-channel — registered alongside ForgeModule.

TS bindings (workers/continuum-core/bindings/modules/events.ts)
- EventsMixin wired into RustCoreIPC composition.

TS thin SDK (@system/events/shared/EventClass.ts)
- declareEventClass, getEventClass (read-through cache + null-cache +
  in-flight dedup), peekEventClassCache (sync hot-path),
  listEventClasses, resolveEventChannel.
- Native-truth-thin-SDK-per-language per the global rule — Rust owns
  truth; TS is the wrapper.

Events.emit integration (system/core/shared/Events.ts)
- Sync peek per emit; if class declared+cached, attach
  EventBridgePayload.eventClass hints; if cold, fire-and-forget warm-up
  so the next emit hits the cache. Backward-compat: undeclared classes
  get no hints, behavior identical to pre-L1-1.

Tests
- 38 Rust unit tests pass (cargo test events): validation, idempotent +
  conflicting redeclare, channel resolution all paths, IPC handlers,
  ts-rs bindings exports.
- 11 TS unit tests pass (vitest tests/unit/core/event-class-registry):
  cache hit/miss/null-cache, in-flight dedup, sync peek cold/warm,
  list warming, error propagation.

Done criteria from roadmap (L1-1)
- EventClass declarations accepted: yes (Rust + TS).
- Events.emit() reads metadata: yes (sync peek + warm-up + hint attach).
- Existing event uses continue working unchanged: yes.
- Unit tests for registry + classifier round-trip: yes (Rust + TS).

Build hygiene
- clippy-baseline bump 157 → 168: branch sits on canary HEAD e2fed99
  (PR #1443 "feat(airc): add typed event transport seam"), which added
  11 new clippy warnings without updating the baseline. My L1-1 code
  adds ZERO clippy warnings (verified by grep on event_class / events /
  modules/events.rs); the delta is inherited upstream drift. #1443's
  warnings should be cleaned up in a follow-up.
- tsconfig.eslint.json: add new unit test to `files` so ESLint can
  parse it (mirrors existing chat-coordination-stream.test.ts entry).

Out of scope (deferred per roadmap)
- L1-2 AircEventTransport consumer of these hints. Trait already exists
  (#1443); the adapter that consults EventClass metadata lands next.
- TS Command surface at commands/events/* for CLI introspection.
  Deferred to L4 when a CLI consumer materializes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@joelteply
Copy link
Copy Markdown
Contributor Author

Reviewed. This is what rust-first looks like — Rust crate continuum-core::events owns the truth (registry types, validation, IPC), ts-rs generates the TS types automatically, TS shim is a thin read-through cache with sync peekEventClassCache for the Events.emit() hot path + fire-and-forget warm-up. No parallel logic between Rust and TS. EventClassUnknownSchemaPolicy: Warn | Fail default-Fail matches my §3 critique on schema evolution (never silently swallow evidence). EventClassChannelStrategy: Local | Global | ByRoomId | ByPeerId | Custom matches my C2 review lean on #1439 Q1 (declarative + variants).

Bonus: bumping clippy baseline 157 → 168 closes the gap that was forcing --no-verify across #1437/#1438/#1443. Real infra hygiene fix.

LGTM as the L1-1 root primitive. The whole L1 chain composes cleanly: #1443 transport seam + #1444 CommandScope + #1445 EventClass registry. Substrate foundation for everything downstream.

CI ratchet runs on Linux and uses eslint-baseline.linux.txt. PR #1445's
L1-1 changes (adding tests/unit/core/event-class-registry.test.ts to
tsconfig.eslint.json's `files` array) net -1 error vs the prior linux
baseline. The ratchet enforces monotonic-decrease, so it fails when
current < baseline until we lock the improvement.

Note: src/eslint-baseline.txt (macOS-local) was set to 5431 in the
prior commit. This propagates the same fix to the linux baseline CI
actually consults.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
joelteply pushed a commit that referenced this pull request May 26, 2026
…n primitives

Roadmap item L1-6 — Phase A. Builds on L1-1 (#1445) for the event-class
registry. Phase B (verify-on-replay via L1-4's peer-manifest + airc-cursor
replay over L1-2 transport) lands in a follow-up once L1-4 merges.

Closes roadmap item L1-6 (Phase A — primitives + types + registration + tests)
Depends on: L1-1 (PR #1445, pending review)
Defers: L1-4 (presence:peer-manifest, in flight by claude-tab-1) +
         L1-2 (AircEventTransport trait, already merged as #1443)
Spec: GRID-BUS-ARCHITECTURE §4.4 + MULTI-PEER-COMMANDS §7

Why split Phase A vs B
- Phase A is pure crypto + types + declarations — zero runtime deps on
  L1-4 or L1-2 transports.
- Phase B wires the verifier-side: pulls signer pubkeys from L1-4's
  peer-manifest index, hooks into L1-2's AircEventTransport.replay()
  for audit-replayable chain verification.
- Shipping A now means review can focus on the cryptographic substance
  before transport plumbing layers on top.

What this lands

Rust truth (continuum-core::contracts):
- signing.rs — ed25519 primitives matching airc-protocol's pinned
  ed25519-dalek = "2". Wrappers ContractSigningKey + ContractVerifyingKey
  give future migration room (HSM, secure enclave) without touching
  call sites. Deterministic ed25519 → replay-equivalent signatures
  across peers. canonical_hash() uses serde_json's BTreeMap-backed
  Value for key-sorted SHA-256 input — same bytes regardless of build,
  the keystone for cross-peer verify-equality. Verify returns Err on
  failure (NOT Ok(false)) so callers can't accidentally treat a failed
  verify as success.
- event_classes.rs — the 8 contract event class names (constants) +
  typed payload structs (ts-rs export to shared/generated/contracts/).
  Each payload carries contract_id for chain correlation.
  declare_contract_event_classes() registers all 8 with the L1-1
  registry, broadcast=true, channel=Global, schemaVersion=v1.
- envelope.rs — generic SignedContractEvent<P> wrapper. Signature pins
  (event_name, payload) together so relabeling attacks (presenting a
  bid sig as proposed) fail verification. Hex-encoded pubkey +
  signature on the wire.

Tests (31 pass via cargo test --features metal,accelerate contracts)
- signing: keygen→sign→verify roundtrip, pubkey roundtrip-through-bytes,
  bad-signature-fail-loud, wrong-payload-fail-loud, cross-key-verify-fail,
  ed25519-determinism, canonical-hash-stable-across-field-order,
  signature/pubkey length validation.
- event_classes: all-8-names-distinct, all-use-contract-prefix,
  declare-registers-all-eight (dogfoods the L1-1 registry).
- envelope: sign-then-verify roundtrip, relabeling-attack-fails,
  payload-mutation-fails, signature-mutation-fails, pubkey-swap-fails,
  JSON-round-trips-bit-exact, hex-helpers roundtrip + reject-bad-input.
- chain_tests: full 8-event proposed→bid→accepted→executing→delivered→
  verified→paid worked example (zero-LP household tier "ping grid
  dispatch"), disputed-event-signs-and-verifies, JSON-bit-exact round
  trip on the full chain.

What this does NOT do (Phase B follow-up)
- Pull signer pubkeys from L1-4's presence:peer-manifest index at
  verify time. Today verify returns the pubkey-that-signed; callers
  must cross-check against an external trust source.
- Subscribe to airc-cursor replay over L1-2's AircEventTransport
  for audit-reproducible chain verification.
- TS thin SDK wrapper (parallel to @system/events/shared/EventClass.ts).
  Deferred until a TS consumer materializes — Phase A consumers are
  Rust-side (router daemon, persona admission).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@joelteply joelteply merged commit bb53d12 into canary May 26, 2026
4 checks passed
@joelteply joelteply deleted the feat/l1-1-event-class-registry branch May 26, 2026 01:27
joelteply pushed a commit that referenced this pull request May 26, 2026
…n primitives

Roadmap item L1-6 — Phase A. Builds on L1-1 (#1445) for the event-class
registry. Phase B (verify-on-replay via L1-4's peer-manifest + airc-cursor
replay over L1-2 transport) lands in a follow-up once L1-4 merges.

Closes roadmap item L1-6 (Phase A — primitives + types + registration + tests)
Depends on: L1-1 (PR #1445, pending review)
Defers: L1-4 (presence:peer-manifest, in flight by claude-tab-1) +
         L1-2 (AircEventTransport trait, already merged as #1443)
Spec: GRID-BUS-ARCHITECTURE §4.4 + MULTI-PEER-COMMANDS §7

Why split Phase A vs B
- Phase A is pure crypto + types + declarations — zero runtime deps on
  L1-4 or L1-2 transports.
- Phase B wires the verifier-side: pulls signer pubkeys from L1-4's
  peer-manifest index, hooks into L1-2's AircEventTransport.replay()
  for audit-replayable chain verification.
- Shipping A now means review can focus on the cryptographic substance
  before transport plumbing layers on top.

What this lands

Rust truth (continuum-core::contracts):
- signing.rs — ed25519 primitives matching airc-protocol's pinned
  ed25519-dalek = "2". Wrappers ContractSigningKey + ContractVerifyingKey
  give future migration room (HSM, secure enclave) without touching
  call sites. Deterministic ed25519 → replay-equivalent signatures
  across peers. canonical_hash() uses serde_json's BTreeMap-backed
  Value for key-sorted SHA-256 input — same bytes regardless of build,
  the keystone for cross-peer verify-equality. Verify returns Err on
  failure (NOT Ok(false)) so callers can't accidentally treat a failed
  verify as success.
- event_classes.rs — the 8 contract event class names (constants) +
  typed payload structs (ts-rs export to shared/generated/contracts/).
  Each payload carries contract_id for chain correlation.
  declare_contract_event_classes() registers all 8 with the L1-1
  registry, broadcast=true, channel=Global, schemaVersion=v1.
- envelope.rs — generic SignedContractEvent<P> wrapper. Signature pins
  (event_name, payload) together so relabeling attacks (presenting a
  bid sig as proposed) fail verification. Hex-encoded pubkey +
  signature on the wire.

Tests (31 pass via cargo test --features metal,accelerate contracts)
- signing: keygen→sign→verify roundtrip, pubkey roundtrip-through-bytes,
  bad-signature-fail-loud, wrong-payload-fail-loud, cross-key-verify-fail,
  ed25519-determinism, canonical-hash-stable-across-field-order,
  signature/pubkey length validation.
- event_classes: all-8-names-distinct, all-use-contract-prefix,
  declare-registers-all-eight (dogfoods the L1-1 registry).
- envelope: sign-then-verify roundtrip, relabeling-attack-fails,
  payload-mutation-fails, signature-mutation-fails, pubkey-swap-fails,
  JSON-round-trips-bit-exact, hex-helpers roundtrip + reject-bad-input.
- chain_tests: full 8-event proposed→bid→accepted→executing→delivered→
  verified→paid worked example (zero-LP household tier "ping grid
  dispatch"), disputed-event-signs-and-verifies, JSON-bit-exact round
  trip on the full chain.

What this does NOT do (Phase B follow-up)
- Pull signer pubkeys from L1-4's presence:peer-manifest index at
  verify time. Today verify returns the pubkey-that-signed; callers
  must cross-check against an external trust source.
- Subscribe to airc-cursor replay over L1-2's AircEventTransport
  for audit-reproducible chain verification.
- TS thin SDK wrapper (parallel to @system/events/shared/EventClass.ts).
  Deferred until a TS consumer materializes — Phase A consumers are
  Rust-side (router daemon, persona admission).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
joelteply added a commit that referenced this pull request May 26, 2026
…n primitives (#1448)

Roadmap item L1-6 — Phase A. Builds on L1-1 (#1445) for the event-class
registry. Phase B (verify-on-replay via L1-4's peer-manifest + airc-cursor
replay over L1-2 transport) lands in a follow-up once L1-4 merges.

Closes roadmap item L1-6 (Phase A — primitives + types + registration + tests)
Depends on: L1-1 (PR #1445, pending review)
Defers: L1-4 (presence:peer-manifest, in flight by claude-tab-1) +
         L1-2 (AircEventTransport trait, already merged as #1443)
Spec: GRID-BUS-ARCHITECTURE §4.4 + MULTI-PEER-COMMANDS §7

Why split Phase A vs B
- Phase A is pure crypto + types + declarations — zero runtime deps on
  L1-4 or L1-2 transports.
- Phase B wires the verifier-side: pulls signer pubkeys from L1-4's
  peer-manifest index, hooks into L1-2's AircEventTransport.replay()
  for audit-replayable chain verification.
- Shipping A now means review can focus on the cryptographic substance
  before transport plumbing layers on top.

What this lands

Rust truth (continuum-core::contracts):
- signing.rs — ed25519 primitives matching airc-protocol's pinned
  ed25519-dalek = "2". Wrappers ContractSigningKey + ContractVerifyingKey
  give future migration room (HSM, secure enclave) without touching
  call sites. Deterministic ed25519 → replay-equivalent signatures
  across peers. canonical_hash() uses serde_json's BTreeMap-backed
  Value for key-sorted SHA-256 input — same bytes regardless of build,
  the keystone for cross-peer verify-equality. Verify returns Err on
  failure (NOT Ok(false)) so callers can't accidentally treat a failed
  verify as success.
- event_classes.rs — the 8 contract event class names (constants) +
  typed payload structs (ts-rs export to shared/generated/contracts/).
  Each payload carries contract_id for chain correlation.
  declare_contract_event_classes() registers all 8 with the L1-1
  registry, broadcast=true, channel=Global, schemaVersion=v1.
- envelope.rs — generic SignedContractEvent<P> wrapper. Signature pins
  (event_name, payload) together so relabeling attacks (presenting a
  bid sig as proposed) fail verification. Hex-encoded pubkey +
  signature on the wire.

Tests (31 pass via cargo test --features metal,accelerate contracts)
- signing: keygen→sign→verify roundtrip, pubkey roundtrip-through-bytes,
  bad-signature-fail-loud, wrong-payload-fail-loud, cross-key-verify-fail,
  ed25519-determinism, canonical-hash-stable-across-field-order,
  signature/pubkey length validation.
- event_classes: all-8-names-distinct, all-use-contract-prefix,
  declare-registers-all-eight (dogfoods the L1-1 registry).
- envelope: sign-then-verify roundtrip, relabeling-attack-fails,
  payload-mutation-fails, signature-mutation-fails, pubkey-swap-fails,
  JSON-round-trips-bit-exact, hex-helpers roundtrip + reject-bad-input.
- chain_tests: full 8-event proposed→bid→accepted→executing→delivered→
  verified→paid worked example (zero-LP household tier "ping grid
  dispatch"), disputed-event-signs-and-verifies, JSON-bit-exact round
  trip on the full chain.

What this does NOT do (Phase B follow-up)
- Pull signer pubkeys from L1-4's presence:peer-manifest index at
  verify time. Today verify returns the pubkey-that-signed; callers
  must cross-check against an external trust source.
- Subscribe to airc-cursor replay over L1-2's AircEventTransport
  for audit-reproducible chain verification.
- TS thin SDK wrapper (parallel to @system/events/shared/EventClass.ts).
  Deferred until a TS consumer materializes — Phase A consumers are
  Rust-side (router daemon, persona admission).

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