TypeDB — the brain, never the request path
The rule that decides most reviews
TypeDB is the brain of record and is never on the request path. It syncs to
JSON in RAM (BrainDO /brain/* + a KV snapshot) and a receiver reads the
snapshot. Writes go fire-and-forget or through the hourly sync/ worker. The
same door answers in 26–58ms from the snapshot and 1.2–1.6s as a live
query. A receiver you add or touch reads KV / /brain/* JSON, never
typedbQuery. resolvers/factory.ts is the deliberate exception: there the
graph write IS the deliverable, so it throws on failure.
Instrument: bash .claude/scripts/signal-watch.sh — a receiver that reaches for
the brain reads red instead of merely feeling slow.
The deployed stack is not every .tql in the repo
Measured: one.tql roles.tql do.tql reason.tql router.tql channels.tql.
world.tql is NOT loaded. A fun declared in an undeployed file is
reachable through fn:run's allowlist and still answers upstream_400 —
which reads exactly like a broken query and is not one. Check the stack
(.claude/scripts/typedb-env.sh) before debugging a fun that "does not work".
Same trap in the field names: one.tql names a signal's payload field
payload; world.tql names it data. Code written against data under a
.catch(() => {}) fails silently forever.
Writing a fun
- Functions only over the six locked dimensions. A fun that needs a seventh
dimension is a fun that is wrong.
- A
$beat-style parameter is passed as the FULL pair ("\"beat\":\"want\""),
never assembled by string concat — the payload is matched with contains.
- Every id you interpolate must be TQL-safe by construction, not by escaping.
- Register the fun in the allowlist or
fn:run refuses it by name, which is a
clearer failure than a silent empty result — prefer it.
An absent answer is not a benign answer
The house bug in this repo: a fun over data nobody writes returns 0 or empty,
and a caller reads that as "fine". story_beats_told answered 0 for every story
because three funs read a "beat" convention with zero writers. When you add a
fun, say in one line what it answers when the data does not exist yet, and make
that answer distinguishable from a real zero.
Cannot run
§ An absent answer is not a benign answer is the house bug. Applied to yourself,
it has a name: say cannot-run rather than return the empty result.
- You do not know which stack is deployed. Measured, the deployed set is
one.tql roles.tql do.tql reason.tql router.tql channels.tql and world.tql
is not in it. A fun in an undeployed file answers upstream_400, which reads
exactly like a broken query. Until .claude/scripts/typedb-env.sh has told you
which stack you are on, a failing fun is undiagnosed, not diagnosed.
- A missing fun and an undeployed schema are the same bytes. The
fn-allowlist.ts gate masks deployment status: a fun absent from the
allowlist is unreachable from every surface whether or not the schema carrying
it ever landed. fn:run <name> returning rows, or returning REP4, is the only
thing that separates them, and either answer settles it — so halt and say
which was not done rather than reporting a schema as live. The same refusal
covers "is this migration applied": the numbered file on disk is evidence it
was written, never that it ran.
- A zero has no writer behind it.
story_beats_told answered 0 for every
story because three funs read a "beat" convention nothing wrote. If you
cannot point to the code that writes the rows your query counts, 0 is
cannot-run and must never be reported as "none". An empty result is not a
clean graph, and in a child-protection surface "no concerns recorded" and "we
could not ask" must never be the same answer.
- The two field names are in play.
one.tql calls a signal's payload
payload and world.tql calls it data. Where the caller is under a
.catch(() => {}) you will see silence from both a wrong name and an empty
table — that is indistinguishable, and indistinguishable is cannot-run.
- You were asked to put it on the request path. That is not an unanswerable
question, it is a refused one: the brain is never on the request path, and the
answer is the snapshot. Say so and stop.
Return { ok: false, reason: "<which of those, and the line that says so>" }
and close with warn.