Files
neuron/packages/core/src/event-bus-schema.ts
T
will.anderson aae7f883cd feat(core): event bus seed - envelope schema, durable topic log, OELBR kernel
- EventBus: dumb wire, publish/subscribe, failures surface
- Envelope: four schema planes, inject-only headers, content-addressed
- TopicLog: owned SQLite ledger, replay + by-type/time/causality
- Seed facade: durable=ledger+wire, ephemeral=wire only, fold-for-state
- Rung 1 kernel: OELBR loop, DAG plans, durable cancellation tokens
- Neuron v0 executable spec source; specs/event-bus.md citizen zero
- Standing orders: design-before-code doctrine installed
2026-08-22 12:18:35 -05:00

124 lines
4.4 KiB
TypeScript

export * as EventBusSchema from "./event-bus-schema"
/**
* The envelope, fully specified. Pure data - no behavior, no transport
* assumptions. Self-describing: body carries (or references) the schema
* that decodes it. Headers are an ordered append-only list; inject-only.
*
* Schema planes:
* 0 envelope format itself (pinned root, amended loudly)
* 1 header values (per-header ref)
* 2 body contract (type + artifact rules)
* 3 payload (per message-type; nested objects too)
*/
import { Schema } from "effect"
// ---------- Schema reference (used by every plane) ----------
/**
* Content-addressed schema reference. ID is the hash of the schema
* definition itself; resolvers fetch by hash from a registry or read
* inline for small/self-contained payloads.
*/
export class SchemaRef extends Schema.Class<SchemaRef>("SchemaRef")({
/** content hash (sha256 of canonical schema definition). */
id: Schema.String,
/** Optional inline definition so messages decode with zero registry access. */
inline: Schema.optional(Schema.String),
/** Media/format hint, e.g. "json-schema", "effect-schema", "protobuf". */
format: Schema.optional(Schema.String),
}) {}
// ---------- Header (Plane 1) ----------
/**
* One injected header. Immutable once written.
* Each header carries its own value-schema reference (Plane 1).
*/
export class MessageHeader extends Schema.Class<MessageHeader>("MessageHeader")({
key: Schema.String,
value: Schema.String,
author: Schema.String,
at: Schema.Number,
/** Plane 1: schema governing THIS header's value. Resolves dynamically. */
schema: Schema.optional(SchemaRef),
}) {}
// ---------- Body (Planes 2 + 3) ----------
/** Artifacts are part of the record they belong to: refs plus hashes. */
export class Artifact extends Schema.Class<Artifact>("Artifact")({
ref: Schema.String,
hash: Schema.optional(Schema.String),
kind: Schema.optional(Schema.String),
}) {}
export class Body extends Schema.Class<Body>("Body")({
/** Event type, e.g. "plan.step.completed". */
type: Schema.String,
/** PLANE 3: this message-type's unique payload, JSON-encoded. */
payloadJSON: Schema.optional(Schema.String),
/** PLANE 3: schema for the payload shape above. */
payloadSchema: Schema.optional(SchemaRef),
/** PLANE 2: schema of the body contract itself. */
schema: Schema.optional(SchemaRef),
artifacts: Schema.Array(Artifact),
}) {}
// ---------- Envelope (Plane 0) ----------
export const Envelope = Schema.Struct({
/** Orchestration / topic handle. Durable or ephemeral per Topic. */
topic: Schema.String,
/** Position within the topic's append-only sequence (assigned by log). */
seq: Schema.Number,
/** Causal spine: seq of the message this one responds to or extends. */
parentSeq: Schema.optional(Schema.Number),
/** Identity: hash of canonical form. Two identical messages are one. */
id: Schema.String,
/** Who published: token id, orchestrator id, stage name. */
author: Schema.String,
/** Epoch ms at publish. */
at: Schema.Number,
/** Ordered inject-only context chain. Append-only across every hop. */
headers: Schema.Array(MessageHeader),
body: Body,
})
export type Envelope = typeof Envelope.Type
/** Plane 0 pins itself: the wire format amends loudly or not at all. */
export const EnvelopeSchemaRef = SchemaRef.make({
id: "sha256:event-bus-envelope-v1",
format: "effect-schema",
})
// ---------- Reading rules ----------
/** current(headers, key): last-wins fold over injected headers. */
export function current(headers: ReadonlyArray<MessageHeader>, key: string): MessageHeader | undefined {
let found: MessageHeader | undefined
for (const h of headers) if (h.key === key) found = h
return found
}
/** history(headers, key): full evolution in injection order. */
export function history(headers: ReadonlyArray<MessageHeader>, key: string): Array<MessageHeader> {
return headers.filter((h) => h.key === key)
}
// ---------- Topics ----------
export const TopicDurability = Schema.Literals(["durable", "ephemeral"])
export class Topic extends Schema.Class<Topic>("Topic")({
id: Schema.String,
durability: TopicDurability,
/** Ephemeral only: keep most recent value available to new subscribers. */
retainLast: Schema.optional(Schema.Boolean),
/** Ephemeral only: auto-dissolve after this many ms idle. */
ttlMs: Schema.optional(Schema.Number),
author: Schema.String,
at: Schema.Number,
}) {}