Slash Commands as CRDT Operations
choco advanced 5 min read
ELI5
Typing /foo in the editor is just like typing any other character — it becomes part of the document. The server only notices it once the document syncs, and when it does, it creates a smartblock. There is no special “slash was pressed” telegram.
Technical Deep Dive
Decision: architecture/decisions/ADR-004-slash-commands-crdt-native.md. Slash invocations are CRDT operations on the document, not a domain event with their own envelope.
Flow
sequenceDiagram participant E as cho-co-web editor participant CRDT as Local CRDT layer participant S as choco-sync participant N as NATS (refinement) participant H as choco-consumers smartblock handler participant V as the-vault E->>CRDT: palette.onSelect(slug, args) → block op CRDT->>S: replicate op S->>S: block becomes canonical S->>N: SmartblockCreated(command_slug, args) N->>H: deliver H->>H: validate slug vs server catalog manifest H->>H: resolve federationTargets from federation.yaml H->>V: persist smartblock H->>N: fan-out → webhooks, search, analyticsWhy Not a Domain Event
ADR-004 lists two reasons (ADR-004:18-22):
- Dual-stream dedupe.
SmartblockCreatedalready exists (proto/events/refinement/v1/smartblock.proto) and is wired intoservices/choco-consumers/cmd/server/main.go:436-460. A separateslash.command.invokedwould carry the samecommand_slugone hop earlier and force every consumer to dedupe across two streams describing the same fact. - Outbox at the wrong layer. A fire-and-forget event from the editor needs a client-side outbox + retry to survive offline use. The CRDT log already plays that role for every other write — duplicating it is gratuitous.
Trust Boundary
federationTargets from the client @oompa/sdk catalog is palette UX only. The canonical mapping lives at contracts/oompa/federation.yaml and is codegen’d into Go for the consumer (ADR-004:33-34). The server treats command_slug as an opaque string keyed against the federation manifest.
Surface Universality
Web editor, choco-cli, MCP server, programmatic SDK callers — they all just write CRDT block ops; one handler (smartblock.go) covers every surface (ADR-004:39-40). No surface-specific event channel.
What Stays in @oompa/sdk
Palette UX metadata only: slug, tagline, demoContent, audience, schemaVersion. The 33-entry catalog at packages/oompa-sdk/src/commands/catalog.ts is the source of truth for the editor, not for the server’s allowed list.
Key Terms
- CRDT block op → conflict-free replicated data type operation that mutates a block in the document; replicated by choco-sync.
- command_slug → opaque string identifying the slash command; matched server-side against the federation manifest.
- federation manifest →
contracts/oompa/federation.yaml; codegen’d Go map of slug → federation targets.
Q&A
Q: Where does the editor learn the slug list from?
A: packages/oompa-sdk/src/commands/catalog.ts (33 entries, ADR-004:10). The editor never asks the server.
Q: Can a malicious client invoke a slug with arbitrary federationTargets?
A: No — the server ignores the client’s federationTargets field and resolves from contracts/oompa/federation.yaml (ADR-004:33).
Q: What if a slug exists in the client catalog but not in the server manifest?
A: The smartblock handler validates the slug against the server-side catalog manifest before persistence (ADR-004:32-33); unknown slugs fail validation in the consumer.
Examples
Adding /poll to the palette: register the entry in packages/oompa-sdk/src/commands/catalog.ts (slug, tagline, demoContent, audience), add the slug + federation targets to contracts/oompa/federation.yaml, regenerate the Go map, and extend smartblock.go if the slug needs resolver logic. No new proto, no new NATS event.
neighbors on the map
- NATS Subject Taxonomy wiring a new consumer to the right stream
- FNP CRDT Conflict-Free Merge Semantics understanding CRDT properties and guarantees
- CRDT Operation Message adding a new operation type