@oneie/claude 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/agents/w1-recon.md +9 -4
  2. package/agents/w2-decide.md +51 -28
  3. package/agents/w3-edit.md +70 -78
  4. package/agents/w4-verify.md +164 -69
  5. package/commands/browser.md +98 -35
  6. package/commands/cc-connect.md +6 -0
  7. package/commands/chat.md +13 -1
  8. package/commands/close.md +45 -19
  9. package/commands/create.md +20 -10
  10. package/commands/db-sync.md +5 -1
  11. package/commands/deploy.md +174 -87
  12. package/commands/do-autonomous.md +31 -20
  13. package/commands/do-improve.md +4 -4
  14. package/commands/do-show.md +4 -3
  15. package/commands/do.md +53 -13
  16. package/commands/go.md +75 -0
  17. package/commands/improve.md +4 -2
  18. package/commands/kill.md +6 -8
  19. package/commands/notify.md +15 -2
  20. package/commands/oo-push.md +24 -9
  21. package/commands/rag.md +6 -0
  22. package/commands/release.md +46 -25
  23. package/commands/restart.md +5 -4
  24. package/commands/see.md +45 -35
  25. package/commands/skill-create.md +38 -5
  26. package/commands/sync.md +21 -11
  27. package/hooks/scripts/auto-continue.sh +70 -0
  28. package/hooks/scripts/branch-pin.sh +85 -0
  29. package/hooks/scripts/git-add-guard.sh +16 -5
  30. package/hooks/scripts/post-edit-check.sh +16 -0
  31. package/hooks/scripts/session-start.sh +13 -8
  32. package/hooks/scripts/sync-priority-todo.sh +5 -0
  33. package/package.json +1 -1
  34. package/rules/api.md +9 -0
  35. package/rules/design.md +13 -3
  36. package/rules/documentation.md +30 -71
  37. package/rules/engine.md +8 -1
  38. package/rules/ui.md +5 -1
  39. package/scripts/__pycache__/do-rank.cpython-313.pyc +0 -0
  40. package/scripts/browser-check.mjs +44 -103
  41. package/scripts/cc-events-proof.sh +13 -12
  42. package/scripts/chrome.mjs +381 -0
  43. package/scripts/do-analyze.sh +109 -4
  44. package/scripts/do-auto.sh +350 -43
  45. package/scripts/do-consumer-sweep.sh +72 -0
  46. package/scripts/do-derives-check.sh +88 -0
  47. package/scripts/do-fleet.sh +50 -1
  48. package/scripts/do-killswitch-audit.py +33 -14
  49. package/scripts/do-promise-lint.sh +150 -4
  50. package/scripts/do-promise-settle.sh +131 -9
  51. package/scripts/do-prove.sh +197 -17
  52. package/scripts/do-rank.py +606 -20
  53. package/scripts/do-rubric.py +21 -2
  54. package/scripts/do-signal.sh +157 -17
  55. package/scripts/do-smoke.sh +9 -0
  56. package/scripts/do-substrate-check.sh +49 -0
  57. package/scripts/do-tasks-wire-check.sh +81 -0
  58. package/scripts/do-tier.sh +90 -5
  59. package/scripts/do-ui-gate.sh +332 -0
  60. package/scripts/do-untracked-gate.sh +231 -0
  61. package/scripts/do-walk.sh +344 -0
  62. package/scripts/factory-check.sh +747 -0
  63. package/scripts/factory-repo.sh +1361 -0
  64. package/scripts/factory-tasks-check.sh +85 -0
  65. package/scripts/outcome-pull.ts +279 -0
  66. package/scripts/promise-manifest.mjs +167 -0
  67. package/scripts/reconcile-allow.txt +5 -0
  68. package/scripts/rubric-weights.json +64 -0
  69. package/scripts/spine-canary.sh +20 -0
  70. package/scripts/typedb-cluster-status.sh +128 -0
  71. package/scripts/typedb-env.sh +182 -0
  72. package/scripts/typedb-flap-recorder.sh +85 -0
  73. package/scripts/typedb-probes/containment-probe.py +88 -0
  74. package/scripts/typedb-probes/panic-probe-version.py +92 -0
  75. package/scripts/typedb-probes/panic-probe.py +77 -0
  76. package/scripts/typedb-scratch.sh +73 -0
  77. package/scripts/urls-lint.sh +58 -0
  78. package/scripts/w4-rubric.ts +96 -12
  79. package/scripts/wf-check.mjs +75 -0
  80. package/skills/ai-ui/SKILL.md +1 -1
  81. package/skills/astro/SKILL.md +177 -132
  82. package/skills/astro/add-content-collection.md +29 -13
  83. package/skills/astro/check-build.md +19 -13
  84. package/skills/astro/create-component.md +36 -9
  85. package/skills/astro/create-page.md +39 -10
  86. package/skills/astro/optimize-performance.md +36 -8
  87. package/skills/cli/SKILL.md +254 -220
  88. package/skills/dev/SKILL.md +1 -1
  89. package/skills/directory-autofill/SKILL.md +68 -0
  90. package/skills/docs/SKILL.md +4 -4
  91. package/skills/mcp/SKILL.md +186 -156
  92. package/skills/promise-make/SKILL.md +2 -2
  93. package/skills/puck/SKILL.md +465 -250
  94. package/skills/react19/SKILL.md +196 -296
  95. package/skills/reactflow/SKILL.md +387 -412
  96. package/skills/sdk/SKILL.md +167 -106
  97. package/skills/shadcn/SKILL.md +245 -201
  98. package/skills/signal/SKILL.md +5 -0
  99. package/skills/sui/SKILL.md +6 -1
  100. package/skills/tutorial/SKILL.md +15 -0
  101. package/skills/typedb/SKILL.md +427 -217
  102. package/skills/typedb/examples/python-patterns.py +11 -4
  103. package/skills/typedb/examples/query-patterns.tql +8 -3
  104. package/skills/typedb/examples/schema-patterns.tql +4 -2
  105. package/skills/typedb/reference/migration-2x-3x.md +19 -1
  106. package/skills/typedb/reference/python-driver.md +17 -1
  107. package/skills/voice/SKILL.md +117 -0
  108. package/skills/writer/SKILL.md +29 -12
  109. package/templates/template-feature.md +26 -1
  110. package/templates/template-tests.md +4 -0
  111. package/templates/template-todo.md +65 -10
@@ -1,9 +1,29 @@
1
- # TypeDB 3.0 Complete Reference Skill
1
+ ---
2
+ name: typedb
3
+ description: Write, debug, and deploy TypeQL against the ONE substrate on TypeDB 3.12.1. Use when editing any `.tql` file under `schema/` (one.tql, roles.tql, do.tql, reason.tql, router.tql, factory.tql, work-contract.tql, migrations/), writing a `fun`, composing a match/insert/update/put pipeline, reading or writing through the `/v1/` HTTP API, or diagnosing a TypeDB error code (TQL0, REP1, REP4, REP44, FIN4, FUN5, SVL2, COW5, WCP4, INF11) or a query that hangs. Triggers — "write a TypeQL query", "add a fun", "change the schema", "deploy the schema", "this TypeDB query times out", "why is this .tql rejected", "match/fetch/reduce syntax", "TypeDB 2.x vs 3.x".
4
+ ---
2
5
 
3
- > **Version**: TypeDB 3.x (3.0+)
4
- > **Last Updated**: 2026-04-20
6
+ # TypeDB 3.x Complete Reference Skill
7
+
8
+ > **Version**: TypeDB **3.12.1** — prod (TypeDB Cloud) AND the local OrbStack
9
+ > container both run 3.12.1 as of 2026-07-29. Profiles: `.claude/typedb/{dev,prod}.env`,
10
+ > switched by `.claude/scripts/typedb-env.sh`. Never assume 3.0 or 3.8.x behaviour.
11
+ > **Last Updated**: 2026-08-02 (syntax claims re-probed read-only against live 3.12.1)
5
12
  > **Purpose**: Comprehensive TypeDB/TypeQL knowledge for Claude Code
6
13
  > **Primary sources**: TypeQL paper (Dorn & Pribadi, PACMMOD 2024, *Best Newcomer Award* at SIGMOD/PODS 2024) · TypeDB lecture series (Vaticle YouTube, 2023–2024) · "Inside TypeDB: The Next Chapter" (Dec 2025) · TypeDB 3.0 roadmap ([GitHub #6764](https://github.com/typedb/typedb/issues/6764))
14
+ >
15
+ > **In-repo canon is `schema/`, not this file.** `schema/one.tql` (480 lines) is the
16
+ > locked ontology; `schema/CLAUDE.md` says which files load, in what order, and which
17
+ > must never co-load. When this skill and a `.tql` disagree, the `.tql` wins.
18
+ >
19
+ > **How this repo actually talks to TypeDB: the `/v1/` HTTP API.** No driver package
20
+ > is installed anywhere in the monorepo. TypeScript goes through
21
+ > `one.ie/web/src/lib/substrate.ts` → the gateway in `api/src/index.ts` → `POST
22
+ > {TYPEDB_URL}/v1/query`; the Python scripts in `backup/scripts/typedb/` use
23
+ > `urllib` against the same endpoint. The *Python Driver* section below is
24
+ > background for reading upstream docs — it is not the path any code here takes.
25
+ >
26
+ > **Do not mock TypeDB in tests.** Real TypeDB or skip (root `CLAUDE.md`).
7
27
 
8
28
  ---
9
29
 
@@ -30,7 +50,7 @@
30
50
  19. [Development Tools & Ecosystem](#development-tools--ecosystem) — Studio, Vibe Querying, Cloud, LSP
31
51
  20. [SQL → TypeQL: Concrete Contrasts](#sql--typeql-concrete-contrasts)
32
52
  21. [Works With /sui](#works-with-sui--the-same-ontology-two-deterministic-fires)
33
- 22. [Production Patterns: Classifier Functions, Thing Collapse, Symmetric Routing](#production-patterns-classifier-functions-thing-collapse-symmetric-routing) — from `src/schema/world.tql`
53
+ 22. [Production Patterns: Classifier Functions, Thing Collapse, Symmetric Routing](#production-patterns-classifier-functions-thing-collapse-symmetric-routing) — from `schema/world.tql`
34
54
  23. [Project-Specific Patterns](#project-specific-patterns)
35
55
 
36
56
  ---
@@ -79,28 +99,48 @@ This is a generalization of Wadler's *Propositions as Types* into **Queries as T
79
99
  - **Type Hierarchies**: Single-inheritance subtyping with `sub`. Works on entities, relations, AND attributes.
80
100
  - **Type Functions**: `fun` declarations replace 2.x `rule`; dependent-type subtyping generalizes Datalog-like reasoning.
81
101
 
82
- ### Connection Details (TypeDB Cloud)
102
+ ### Connection Details (this repo — the `/v1/` HTTP API)
83
103
 
84
- ```python
85
- from typedb.driver import TypeDB, Credentials, DriverOptions, TransactionType
104
+ Two endpoints, both 3.12.1, both database `one`, credentials in
105
+ `.claude/typedb/{dev,prod}.env`:
86
106
 
87
- # Connect to TypeDB Cloud
88
- credentials = Credentials("admin", os.environ["TYPEDB_PASSWORD"])
89
- options = DriverOptions(is_tls_enabled=True)
90
- driver = TypeDB.driver("https://server.cluster.typedb.com:80", credentials, options)
107
+ ```
108
+ prod https://flsiu1-0.cluster.typedb.com:1729 (TypeDB Cloud)
109
+ dev http://127.0.0.1:8000 (local OrbStack container)
110
+ ```
111
+
112
+ `bash .claude/scripts/typedb-env.sh {dev|prod|status|up|down}` rewrites the four
113
+ `TYPEDB_*` lines in `one.ie/web/.env`. Every call is two HTTP requests:
114
+
115
+ ```bash
116
+ TOKEN=$(curl -s -X POST "$TYPEDB_URL/v1/signin" \
117
+ -H 'content-type: application/json' \
118
+ -d "{\"username\":\"$TYPEDB_USERNAME\",\"password\":\"$TYPEDB_PASSWORD\"}" \
119
+ | jq -r .token)
91
120
 
92
- # Open transaction (no sessions in 3.0!)
93
- with driver.transaction("database-name", TransactionType.READ) as tx:
94
- result = tx.query("match $e isa entity; select $e; limit 10;").resolve()
121
+ curl -s -X POST "$TYPEDB_URL/v1/query" \
122
+ -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
123
+ -d '{"query":"match $g isa group; reduce $c = count($g);",
124
+ "databaseName":"one","transactionType":"read","commit":false}'
95
125
  ```
96
126
 
127
+ `transactionType` is `read` | `write` | `schema`; `commit` must be `true` for
128
+ anything but `read`. `GET /v1/databases/one/schema` dumps the live schema —
129
+ the fastest way to answer "is this fun actually deployed?". `GET /v1/version`
130
+ returns the running build.
131
+
132
+ The Python driver (`TypeDB.driver(...)`, `driver.transaction(...)`) is covered in
133
+ the *Python Driver* section for reading upstream docs. **No code in this repo
134
+ uses it** — there is no `typedb-driver` dependency in any `package.json` or
135
+ `requirements.txt`.
136
+
97
137
  ---
98
138
 
99
139
  ## Critical Syntax Rules
100
140
 
101
141
  ### MUST-KNOW RULES
102
142
 
103
- | Feature | Correct TypeDB 3.0 |
143
+ | Feature | Correct TypeDB 3.x (verified on 3.12.1) |
104
144
  |---------|-------------------|
105
145
  | Sessions | **NO SESSIONS** - transactions directly on driver |
106
146
  | Concept API | **DROPPED** — all operations go through TypeQL queries |
@@ -116,6 +156,42 @@ with driver.transaction("database-name", TransactionType.READ) as tx:
116
156
  | List attributes | `attribute emails, value string[];` — 3.x-only syntax for list-valued attributes |
117
157
  | Struct values | `struct address { street: string, city: string };` — 3.x compound values |
118
158
 
159
+ ### VERIFIED against the live cluster (flsiu1-0.cluster.typedb.com)
160
+
161
+ These override anything else in this file — each was probed against the prod
162
+ TypeDB Cloud build (`commit:false` schema txs, or read txs with `with fun`).
163
+ Rows are dated: **2026-07-07** was probed on the pre-bump build; the syntax rows
164
+ marked **re-probed 2026-08-02** were re-run against live **3.12.1** and still
165
+ hold. `with fun … match …` in a `read` transaction is the cheap way to test
166
+ function syntax without touching the schema.
167
+
168
+ | Claim elsewhere in this file | What the live cluster ACTUALLY accepts |
169
+ |---|---|
170
+ | `return first if (...) then X else Y` conditional returns | **REJECTED — re-probed 2026-08-02 on 3.12.1, still rejected.** `match let $x = if (true) then 1 else 2;` → `[TQL0] [TQL03] syntax error: expected PLUS, MINUS, TIMES, DIVIDE, POWER, or MODULO`. There is no conditional *expression* in TypeQL. Encode classifiers as an exhaustive disjunction that binds a `let` per branch, then `return first $var`: `{ $st == "halted"; let $stage = "halted"; } or { … };` — this is exactly what the deployed `do_stage` and `path_status` do. **Beware:** `schema/world.tql`, `schema/sui.tql` and `schema/skins.tql` in this repo are written in the `if/then/else` form and are therefore NOT deployable to the main `one` database — see the note in *Production Patterns* below. `schema/reason.tql` carries the deployable rewrite of `path_status`. |
171
+ | `return $x;` for scalar functions | **REJECTED** — scalar returns require a selector: `return first $x;` (`expected return_single_selector or return_reduce_reduction`) |
172
+ | `fun f() -> { $to: city }` — naming the returned variable in the signature | **REJECTED** (re-probed 2026-08-02) — `[TQL0] syntax error: expected named_type_any`. A stream return type is types only: `-> { city }`, `-> { actor }`, `-> { thing, string }`. Tuple returns without braces (`-> string, integer`) are valid. |
173
+ | stream membership via `contains` — `my_stream_fun($x) contains $y;` | **REJECTED** (re-probed 2026-08-02) — `[REP44] The variable 'y' is required to be bound to a value before it's used`. `contains` is *substring* matching on strings. Iterate a stream with `let $y in my_stream_fun($x);` — the only form the deployed schema uses. |
174
+ | calling a `fun` and then reading attributes off the returned rows | **Works, but it is the wrong tool and it is slow.** Functions are for **COUNTS and scalars** — `return count($x)`, `return first $status`. When you need **rows plus their attributes**, do NOT call the fun and then `has` the results: **inline the fun's body** into your match and select the attributes there. This is the single most common cause of a query that "works locally, hangs in prod". |
175
+ | a `\uXXXX` escape inside a TypeQL string literal | **Killed the server process on 3.8.3** (a PANIC, not an error) — reproducer at `.claude/scripts/typedb-probes/panic-probe.py`, and the reason `backup/scripts/typedb/dump.py` renders every literal with `ensure_ascii=False`. Prod and local moved to **3.12.1 on 2026-07-29 and this has not been re-probed since.** Treat as unresolved: keep emitting raw UTF-8, never `\uXXXX`. The same build also panicked on the same variable used in two roles. |
176
+ | `fun f(threshold: double = 10.0)` default params | **REJECTED** — params need `$` and no defaults: `fun f($threshold: double)` |
177
+ | `define fun` re-applies like types | **REJECTED** (`FUN5 already exists`) — an existing function needs `redefine fun …` (one per tx); `define` is idempotent for types only |
178
+ | `redefine attribute x, value string @values(…)` | Syntax is `redefine x value string @values(…);` — no `attribute` keyword, no comma |
179
+ | value param binding an attribute: `has tag $axis` ($axis: string param) | **REJECTED** (`REP1`) — bind then compare: `has tag $t; $t == $axis;` |
180
+ | role named `as` (e.g. `relates as`) | **REJECTED** — `as` is reserved (role aliasing). A relation using it makes the whole file unparseable |
181
+ | labels are per-kind | Labels are **global across kinds** — `entity channel` cannot coexist with `attribute channel` (`SVL2`) |
182
+ | a fun calling a not-yet-defined fun validates | Cross-fun references only resolve against **committed** functions (`REP4` on commit:false) — commit dependencies first, or define both in one tx. This is why `schema/factory.tql` is one file: a partial multi-file deploy produced prod's `[REP4] Could not resolve function with name 'gaps'` on 2026-07-29. |
183
+ | a fun body that type-checks in isolation deploys | **`FIN4` / `QUA2`** — "The types inferred for the return statement of function 'f' did not match those declared in the signature. Mismatching index: 0". The return type is checked against what the body actually binds, so `-> string` with a body binding an attribute concept fails. Fix the signature or bind a value with `let`. |
184
+ | reading an attribute off a variable the planner infers as a *union* of owner types | **`INF11`** — the union has no member that can own the attribute, and the **whole schema transaction is rejected** at function type-check. This is why the three `*-schema.tql` domain files are absent from prod (`schema/CLAUDE.md`). Constrain the variable's type before reading the attribute. |
185
+ | `return first true` (boolean literal) | **REJECTED — re-probed 2026-08-02 on 3.12.1, still rejected** (`[TQL0] syntax error: expected var`) — bind first: `let $ok = true; return first $ok;`. The live schema contains **zero** `return first <literal>`; every one of its nine distinct return-first forms returns a bound variable (`$ok`, `$stage`, `$status`, `$toxic`, …). The deployed existence-check idiom is `match <the predicate>; let $ok = true; return first $ok;` — see `route_exists`. |
186
+ | `put` with non-key attributes is idempotent | **CRASHES on keyed entities** (`COW5` duplicate-key) when any non-key attribute differs from the existing row — the whole pattern fails to match, put inserts a duplicate @key. Put by key only, then put each invariant attribute as its own stage: `put $m isa actor, has aid "X"; put $m has actor-type "agent";` Never put attributes that vary (e.g. `name`) |
187
+ | `delete $attr of $e;` removes an ownership | **REJECTED** (`REP1 Object vs ThingType`) — and `delete has $attr of $e;` parses but can't feed a computed re-insert. There is NO in-database increment on this build |
188
+ | computed values in writes: `insert $e has strength ($s + 1)` or `has strength $ns` (value var) | **REJECTED** (`REP1 Attribute vs Value`) — writes take LITERALS only. The increment pattern is read-then-write: read current values, compute in the caller, write back with `update $e has strength 7.0;` (update = replace-or-add on card 0..1, no delete needed). See substrate.ts readPathWeights/updatePathWeights |
189
+ | relation insert/match+update: `insert $r (role: $x) isa rel-type, has attr val;` (role-list before `isa`) | **INSERT: REJECTED** (`WCP4 Could not determine the type of the insert variable '_anonymous'`). **MATCH-then-UPDATE: REJECTED** (`REP1 variable cannot be declared as both Object/Thing and ThingType`) the moment that same match feeds an `update`/`delete` stage — a plain match+select with this shape is fine. Verified 2026-07-12 while shipping `path-context` writes (`substrate.ts` `upsertPathContext`, `fade()`, `follow()`). Fix: declare `isa` first, `links` second — `insert $r isa rel-type, links (role: $x), has attr val;` and `match $r isa rel-type, links (role: $x), has attr val; update $r has attr val2;` both work for insert, match, AND update. Only pure match+select tolerates the role-first shorthand; treat isa-first as the universal safe form. |
190
+ | re-asserting `isa <type>` on a variable whose type is already implied by a role constraint elsewhere in the same match (e.g. `$e isa path, links(...)` after `$pc isa path-context, links(followed: $e)` already implies `$e` is a `path` via the schema's `path plays path-context:followed`) | **NOT rejected, but silently forces a full unbound scan** of the re-asserted type before the join — on a relation with real production volume (e.g. `path`, thousands of rows) this reliably **times out** (`"The operation was aborted due to timeout"`) even though the equivalent query with the redundant `isa` dropped returns instantly. Verified 2026-07-12: `match $e isa path, links(source:$a,target:$b); $pc isa path-context, links(followed:$e), has context-tag $ct, has context-strength $cs; ...` timed out; `match $pc isa path-context, has context-tag $ct, has context-strength $cs, links(followed:$e); $e links(source:$a,target:$b); ...` (path-context first, no re-`isa` on `$e`) returned instantly with identical results. Rule: **start the match from the smallest/most-selective relation, and never re-declare a type TypeDB can already infer from a role constraint.** |
191
+ | joining `has <attr> $x` on BOTH role players of the same two-role relation in one match (e.g. `(container: $p, contained: $c) isa containment; $c has tid $id; $p has tid $parent;`) | **NOT rejected, but reliably TIMES OUT** (10s, `"The operation was aborted due to timeout"`) — even on relation types with single-digit row counts platform-wide (`containment`: 9 rows, `blocks`: 2 rows). Reproduced consistently regardless of `limit`, statement order, or dropping a redundant `isa thing` on the role players — this is a distinct defect from the row above (that one is about a *redundant* `isa`; this one triggers even with the minimal, non-redundant `has` shape). Verified 2026-07-16 in `one.ie/web/src/pages/api/things/index.ts` (task board's subtask-parent + blocked-by lookups) — the query had been silently swallowing this 10s hang via `.catch(() => [])` on every single page load. **Each half in isolation is fast** (`$c has tid $id;` alone: ~0.7s; `$p has tid $parent;` alone: ~1s) — it's specifically resolving an attribute on **both** sides in the same query that's poisonous. Fix: split into two queries — (1) `match (container: $p, contained: $c) isa containment; limit N; select $p, $c;` with NO attribute join at all (returns raw entity `iid`s, sub-second), then (2) batch-resolve every involved `iid` to its attribute in ONE disjunction query: `match { $x iid 0x1e...; } or { $x iid 0x1e...; }; $x has tid $id; select $x, $id;` (also sub-second, confirmed via `flattenAnswers` — an entity concept with no `value`/`label` flattens to its bare `iid` string, so `row['p']`/`row['c']` from step 1 are directly usable as iid literals in step 2, no re-quoting). |
192
+
193
+ Deploy path: `POST {TYPEDB_URL}/v1/signin` → `POST /v1/query` with `transactionType:"schema"`, validate `commit:false` first. Working deployer pattern: `one.ie/web/scripts/deploy-chat-schema.ts`.
194
+
119
195
  ### Transaction Pattern (No Sessions!)
120
196
 
121
197
  ```python
@@ -635,13 +711,14 @@ fun friend_count($user: user) -> integer:
635
711
  match ($user, $friend) isa friendship;
636
712
  return count($friend);
637
713
 
638
- # Recursive function (transitive closure)
639
- fun reachable($from: node) -> { node }:
714
+ # Recursive function (transitive closure).
715
+ # `node` is a DEAD NAME in this repo — use the real domain type.
716
+ fun reachable($from: city) -> { city }:
640
717
  match
641
- { $_ isa edge, links (from: $from, to: $to); }
718
+ { $_ isa flight, links (from: $from, to: $to); }
642
719
  or {
643
720
  let $mid in reachable($from);
644
- $_ isa edge, links (from: $mid, to: $to);
721
+ $_ isa flight, links (from: $mid, to: $to);
645
722
  };
646
723
  return { $to };
647
724
  ```
@@ -675,7 +752,8 @@ select $name, $age;
675
752
  with
676
753
  fun is_adult($u: user) -> boolean:
677
754
  match $u has age $a; $a >= 18;
678
- return first true;
755
+ let $ok = true; # bind — `return first true` is a TQL0 syntax error
756
+ return first $ok;
679
757
  match
680
758
  $u isa user;
681
759
  let $adult = is_adult($u);
@@ -683,128 +761,137 @@ match
683
761
  select $u;
684
762
  ```
685
763
 
764
+ A `with fun` block runs inside a plain **read** transaction, which makes it the
765
+ cheapest way to test function syntax against a live server without opening a
766
+ schema transaction. Every re-probe in the VERIFIED table above was done this way.
767
+
768
+ Each ad-hoc function needs **its own `with`** — `with fun a(…): …; fun b(…): …;`
769
+ is a parse error (`expected query_pipeline or WITH`). Write
770
+ `with fun a(…): …; with fun b(…): …; match …` and `b` may call `a`.
771
+
686
772
  ---
687
773
 
688
774
  ## Inference Rules (Emergence Patterns)
689
775
 
690
- TypeDB inference rules derive NEW facts automatically from existing data. This is the foundation for emergent intelligence.
776
+ **There are no rules in TypeDB 3.x.** The `rule <name>: when { } then { };`
777
+ construct was removed with the 2.x line, and the live 3.12.1 schema dump contains
778
+ zero of them. Everything this section used to show as a rule is written as a
779
+ `fun` today. The translation is mechanical and lossy in exactly one place:
691
780
 
692
- ### Basic Classification Rule
781
+ | 2.x rule | 3.x function |
782
+ |---|---|
783
+ | `when { … }` | the `match` body |
784
+ | `then { $e has tier "elite"; }` | a `let $tier = "elite";` binding + `return first $tier` |
785
+ | fires automatically, writes derived facts into the graph | evaluated **on call**; derives nothing until something calls it |
786
+ | chained rules (rule B reads rule A's output) | function composition — B calls A, or (faster) B inlines A's body |
787
+ | rule firing order / priority | the top-down order of the disjunction branches, visible in the source |
788
+ | `?val = $a / $b` value variables | `let $val = $a / $b;` — all variables use `$` |
693
789
 
694
- ```typeql
695
- define
790
+ The loss is the automatic part: a rule *materialised* derived facts, a function
791
+ *computes* them per query. Nothing in the graph changes until a caller asks.
696
792
 
697
- rule elite-pattern:
698
- when {
699
- $e isa signal-edge,
700
- has win-rate $wr,
701
- has trail-pheromone $tp,
702
- has traversal-count $tc;
703
- $wr >= 0.75;
704
- $tp >= 70.0;
705
- $tc >= 50;
706
- } then {
707
- $e has tier "elite";
708
- };
709
- ```
793
+ ### Basic classification
710
794
 
711
- ### Danger Detection
795
+ The 2.x "elite pattern" rule becomes a classifier that returns the tier. Note the
796
+ shape: **an exhaustive disjunction, each branch binding the same `let`, closed by
797
+ `return first $var`.** There is no `if/then/else` expression in TypeQL — see the
798
+ VERIFIED table.
712
799
 
713
800
  ```typeql
714
801
  define
715
802
 
716
- rule danger-zone:
717
- when {
718
- $e isa signal-edge,
719
- has win-rate $wr,
720
- has alarm-pheromone $ap;
721
- $wr < 0.40;
722
- $ap >= 25.0;
723
- } then {
724
- $e has tier "danger";
725
- };
803
+ fun edge_tier($e: path) -> string:
804
+ match
805
+ $e has strength $s, has resistance $r, has traversals $t;
806
+ { $s >= 70.0; $t >= 50;
807
+ let $tier = "elite"; }
808
+ or { $r >= 25.0; not { $s >= 70.0; $t >= 50; };
809
+ let $tier = "danger"; }
810
+ or { not { $s >= 70.0; $t >= 50; };
811
+ not { $r >= 25.0; };
812
+ let $tier = "active"; };
813
+ return first $tier;
726
814
  ```
727
815
 
728
- ### Chained Rules (Hardening)
816
+ Every branch after the first must **negate the earlier branches' conditions**.
817
+ A disjunction is not a cascade: without the `not { … }` guards, a path matching
818
+ two branches yields two rows and `return first` picks an arbitrary one. This is
819
+ the single most common bug when porting a chained rule set. `path_status` in
820
+ `schema/reason.tql:63` is the deployed five-branch example — read it before
821
+ writing your own.
729
822
 
730
- ```typeql
731
- define
823
+ ### Chained rules become composition
732
824
 
733
- # This rule depends on elite-pattern rule firing first
734
- rule hardening-candidate:
735
- when {
736
- $e isa signal-edge,
737
- has tier "elite", # DERIVED from elite-pattern rule
738
- has traversal-count $tc,
739
- has trail-pheromone $tp;
740
- $tc >= 100;
741
- $tp >= 80.0;
742
- } then {
743
- $e has hardening-ready true;
744
- };
745
- ```
746
-
747
- ### Rule with Disjunction (OR)
825
+ A rule that read another rule's derived fact becomes a function that calls it:
748
826
 
749
827
  ```typeql
750
828
  define
751
829
 
752
- rule high-funding-opportunity:
753
- when {
754
- $f isa funding-snapshot, has funding-rate $rate;
755
- { $rate > 0.001; } or { $rate < -0.001; };
756
- } then {
757
- $f has severity "high";
758
- };
830
+ fun hardening_ready($e: path) -> boolean:
831
+ match
832
+ $e has traversals $t, has strength $s;
833
+ $t >= 100; $s >= 80.0;
834
+ let $tier = edge_tier($e);
835
+ $tier == "elite";
836
+ let $ok = true;
837
+ return first $ok;
759
838
  ```
760
839
 
761
- ### Rule with Computed Value
840
+ For anything hot, **inline the body instead of calling** — a call is an
841
+ optimisation barrier, and the deployed `preflight` inlines its three pre-checks
842
+ rather than calling `can_receive`/`is_safe`/`within_budget` for exactly this
843
+ reason.
844
+
845
+ ### Disjunction over a single value
762
846
 
763
847
  ```typeql
764
848
  define
765
849
 
766
- rule reliable-detector:
767
- when {
768
- $d isa detector,
769
- has total-predictions $tp,
770
- has correct-predictions $cp;
771
- $tp >= 20;
772
- ?acc = $cp / $tp;
773
- ?acc >= 0.60;
774
- } then {
775
- $d has pheromone-level ?acc;
776
- };
850
+ fun path_pressure($p: path) -> string:
851
+ match
852
+ $p has resistance $r;
853
+ { $r > 25.0; let $sev = "high"; }
854
+ or { $r <= 25.0; let $sev = "normal"; };
855
+ return first $sev;
777
856
  ```
778
857
 
779
- ### Superhighway Detection
858
+ ### Computed values
859
+
860
+ `?acc = $cp / $tp` becomes `let $acc = $cp / $tp;`. The result is returned, not
861
+ written back to the instance — **a function cannot write.**
780
862
 
781
863
  ```typeql
782
864
  define
783
865
 
784
- rule superhighway-edge:
785
- when {
786
- $e isa signal-edge,
787
- has trail-pheromone $tp,
788
- has traversal-count $tc;
789
- $tp >= 85.0;
790
- $tc >= 100;
791
- } then {
792
- $e has is-superhighway true;
793
- };
866
+ fun delivery_rate($p: path) -> double:
867
+ match
868
+ $p has attempts $a, has deliveries $d;
869
+ $a >= 20;
870
+ let $rate = $d / $a;
871
+ return first $rate;
794
872
  ```
795
873
 
796
- ### Querying Derived Facts
874
+ ### Querying derived values
797
875
 
798
- ```typeql
799
- # tier = "elite" is DERIVED by inference rule, not inserted
800
- match $e isa signal-edge, has tier "elite";
801
- select $e;
876
+ In 2.x you matched the materialised fact. In 3.x you call the function and
877
+ compare its result:
802
878
 
803
- # Find hardening candidates (derived from chained rules)
804
- match $e isa signal-edge, has hardening-ready true;
879
+ ```typeql
880
+ # 2.x: match $e isa signal-edge, has tier "elite"; <- the fact was written
881
+ # 3.x: the tier is computed at query time
882
+ match
883
+ $e isa path;
884
+ let $tier = edge_tier($e);
885
+ $tier == "elite";
805
886
  select $e;
806
887
  ```
807
888
 
889
+ **If you need the derived value to persist** — because you want to index it,
890
+ sort a large set by it, or hand it to a non-TypeQL consumer — compute it in the
891
+ caller and write it back with `update`. The substrate does this for path weights
892
+ (`readPathWeights` / `updatePathWeights` in `one.ie/web/src/lib/substrate.ts`),
893
+ because there is no in-database increment on this build (VERIFIED table).
894
+
808
895
  ---
809
896
 
810
897
  ## Patterns
@@ -1659,6 +1746,15 @@ The `[...]` brackets around `$resource.event-timestamp` tell `fetch` this is a m
1659
1746
 
1660
1747
  These are 3.0+ features that change how you model data. Previously unavailable in 2.x.
1661
1748
 
1749
+ > **Three of these are UNVERIFIED here.** `struct`, list value types
1750
+ > (`value string[]`) and `@index` have **zero occurrences** in `schema/*.tql`
1751
+ > and **zero** in the live `GET /v1/databases/one/schema` dump, and confirming
1752
+ > them needs a schema transaction, which is a write. Treat the `struct`, *List
1753
+ > Attributes* and *Relation Indexing* subsections below as upstream-documentation
1754
+ > summaries, not as things known to work on 3.12.1. Probe with a `commit:false`
1755
+ > schema tx before relying on any of them. `@cascade` and `@subkey` are in the
1756
+ > same position.
1757
+
1662
1758
  ### Cascading Delete
1663
1759
 
1664
1760
  Delete semantics are explicit via `@cascade` on relation roles. When the annotated role player is deleted, the relation itself is also deleted.
@@ -1749,28 +1845,46 @@ select $other;
1749
1845
  ### Functions (replace 2.x `rule`)
1750
1846
 
1751
1847
  Rules are gone. Functions are the only way to express derivation logic in 3.x.
1848
+ See *Inference Rules (Emergence Patterns)* above for the full rule→fun
1849
+ translation table.
1752
1850
 
1753
1851
  ```typeql
1754
1852
  define
1755
- fun reachable-cities($from: city) -> { $to: city } :
1756
- match
1757
- ($from, $to) isa flight;
1853
+ # Return type names TYPES ONLY. `-> { $to: city }` is a TQL0 syntax error
1854
+ # ("expected named_type_any").
1855
+ fun reachable-cities($from: city) -> { city }:
1856
+ match ($from, $to) isa flight;
1758
1857
  return { $to };
1759
1858
 
1760
- # Transitively: fun with recursive call (bounded by type system)
1761
- fun reachable-transitively($from: city) -> { $to: city } :
1859
+ # Transitively: fun with recursive call (bounded by the type system).
1860
+ # Iterate a stream with `let $x in f(...)` NOT `f(...) contains $x`,
1861
+ # which is substring matching and fails with REP44.
1862
+ fun reachable-transitively($from: city) -> { city }:
1762
1863
  match
1763
1864
  { ($from, $direct) isa flight; }
1764
1865
  or
1765
- { reachable-cities($from) contains $mid;
1766
- reachable-transitively($mid) contains $direct; };
1866
+ { let $mid in reachable-cities($from);
1867
+ let $direct in reachable-transitively($mid); };
1767
1868
  return { $direct };
1768
1869
 
1769
- # Use in a query
1870
+ # Use in a query — `in`, not `=`, for a stream
1770
1871
  match
1771
1872
  $nyc isa city, has name "New York";
1772
- let $dests = reachable-transitively($nyc);
1773
- select $dests;
1873
+ let $dest in reachable-transitively($nyc);
1874
+ select $dest;
1875
+ ```
1876
+
1877
+ This is exactly the shape of the deployed `descendants-of` and `reachable`
1878
+ functions — see `schema/reason.tql` and `schema/roles.tql`:
1879
+
1880
+ ```typeql
1881
+ fun descendants-of($g: group) -> { group }:
1882
+ match
1883
+ { (parent: $g, child: $descendant) isa hierarchy; }
1884
+ or
1885
+ { (parent: $g, child: $mid) isa hierarchy;
1886
+ let $descendant in descendants-of($mid); };
1887
+ return { $descendant };
1774
1888
  ```
1775
1889
 
1776
1890
  Functions can return streams (`{ ... }`), single values (`scalar`), or structs. They compose in pipelines and let the planner see through the abstraction.
@@ -1905,21 +2019,21 @@ RETURN path
1905
2019
  ```typeql
1906
2020
  # TypeQL — reachability is a type function, not a path operator
1907
2021
  define
1908
- fun flight-reachable($from: city, $hops: integer) -> { $to: city } :
2022
+ fun flight-reachable($from: city, $hops: integer) -> { city }:
1909
2023
  match
1910
2024
  { $hops == 1; ($from, $to) isa flight; }
1911
2025
  or
1912
2026
  { $hops > 1;
1913
2027
  ($from, $mid) isa flight;
1914
- let $rest = flight-reachable($mid, $hops - 1);
1915
- $to == $rest; };
2028
+ let $next = $hops - 1;
2029
+ let $to in flight-reachable($mid, $next); };
1916
2030
  return { $to };
1917
2031
 
1918
2032
  match
1919
2033
  $nyc isa city, has name "New York";
1920
- let $cities = flight-reachable($nyc, 3);
1921
- $cities has name "London";
1922
- select $cities;
2034
+ let $city in flight-reachable($nyc, 3);
2035
+ $city has name "London";
2036
+ select $city;
1923
2037
  ```
1924
2038
 
1925
2039
  In Neo4j, variable-length path is a query-language primitive. In TypeQL, it's a **user-definable function** — which means any reachability logic (weighted, filtered by attribute, cross-type) is equally expressible without new syntax.
@@ -1928,11 +2042,22 @@ In Neo4j, variable-length path is a query-language primitive. In TypeQL, it's a
1928
2042
 
1929
2043
  ## Works With /sui — The Same Ontology, Two Deterministic Fires
1930
2044
 
1931
- `src/schema/sui.tql:1` already put it best: **"The same ontology. Two deterministic fires."** TypeDB is the learning, classification fire (hypotheses, frontiers, tags cheap to write, rich to query). Move is the permanent, economic fire (path revenue, escrow, treasury — expensive to write, cheap to trust). The runtime is the fast nervous system between them. Both skills speak the same vocabulary by design — `strength`, `resistance`, `revenue`, `path`, `unit` — so the bridge is a **1:1 rename, not a translation**.
2045
+ > **STALEthis section describes a layout the repo no longer has. Verify
2046
+ > before acting on any path or function name in it (checked 2026-08-02).**
2047
+ > `src/engine/bridge.ts`, `src/move/one/sources/one.move` and `src/lib/sui.ts`
2048
+ > **do not exist**, and no file in the monorepo defines `mirrorMark`,
2049
+ > `mirrorHarden` or `absorb`. The Move sources live under `pay/contracts/sui/`
2050
+ > and the Sui runtime under `pay/backend/src/chains/sui.ts` +
2051
+ > `one.ie/web/src/lib/resolvers/sui.ts`. `struct Colony` is **not** in the
2052
+ > current `one.move`. The *conceptual* crosswalk below (Move struct ⇌ TQL
2053
+ > attribute, shared `strength`/`resistance` names) still holds and is why the
2054
+ > section is kept; the file/function inventory does not.
2055
+
2056
+ **"The same ontology. Two deterministic fires."** TypeDB is the learning, classification fire (hypotheses, frontiers, tags — cheap to write, rich to query). Move is the permanent, economic fire (path revenue, escrow, treasury — expensive to write, cheap to trust). The runtime is the fast nervous system between them. Both skills speak the same vocabulary by design — `strength`, `resistance`, `revenue`, `path`, `actor` — so the bridge is a **1:1 rename, not a translation**.
1932
2057
 
1933
2058
  ### Canonical crosswalk
1934
2059
 
1935
- `src/schema/sui.tql` (336 lines) is the Rosetta Stone — every Move struct has a matching TQL entity, every Move function has a matching TQL `fun`. Read it when names or shapes drift. The runtime schema is `src/schema/world.tql`; `sui.tql` is the parallel declaration that proves the two layers agree. The canonical ontology (6 dimensions, stable) is `src/schema/one.tql`.
2060
+ `schema/sui.tql` (475 lines) is the Rosetta Stone — every Move struct has a matching TQL entity, every Move function has a matching TQL `fun`. Read it when names or shapes drift. **It is codegen input, not a loadable schema** `schema/codegen/codegen.ts` parses it standalone to emit Move; it is never loaded into TypeDB, and it is written in the `if/then/else` form the server rejects. The canonical ontology (6 dimensions, stable) is `schema/one.tql` (480 lines).
1936
2061
 
1937
2062
  ### Attribute mapping (Move struct ⇌ TypeDB attribute)
1938
2063
 
@@ -1949,11 +2074,11 @@ In Neo4j, variable-length path is a query-language primitive. In TypeQL, it's a
1949
2074
  | `Highway.id` (address) | `path.sui-highway-id` | address | string | Sui → TQL on `mirrorHarden()` |
1950
2075
  | `Signal.payload` (vector<u8>) | `signal.data` | bytes | string | one-way, usually TQL-only |
1951
2076
 
1952
- **Name drift to know about:** Move still has `struct Colony` (one.move:71). TypeDB moved to `entity group` per `text/dictionary.md`, but the Move contract hasn't been migrated yet because that requires a package upgrade. When reading bridge code, treat Move `Colony` as TQL `group`.
2077
+ **Name drift to know about:** older Move sources carried a `struct Colony`; TypeDB moved to `entity group` per `text/dictionary.md`. `colony` is a **dead name** (root `CLAUDE.md`) and is **no longer present** in `pay/contracts/sui/.../one.move` (checked 2026-08-02). If you meet it in an old artifact, read it as TQL `group`.
1953
2078
 
1954
2079
  **Load-bearing invariant:** `strength` and `resistance` share the same name in both layers. If you rename one, rename both — `bridge.ts` is a pass-through, there's no translation logic. Type-width (`u64` ↔ `double`) is handled by JSON serialization at the bridge; don't write TQL queries that assume sub-integer precision on these columns.
1955
2080
 
1956
- ### Bridge contract (`src/engine/bridge.ts`, 479 lines)
2081
+ ### Bridge contract (module no longer present — shape reference only)
1957
2082
 
1958
2083
  | Function | Fires when | Maps |
1959
2084
  |-----------------------------------|-----------------------------|------------------------------------------------------------------------|
@@ -1990,84 +2115,116 @@ select $p, $hw;
1990
2115
  ### When to load /sui alongside this skill
1991
2116
 
1992
2117
  - Adding a field to a Move struct that needs off-chain query — the TQL attribute must match
1993
- - Touching `src/move/one/sources/one.move` path/signal logic — `src/schema/one.tql` and `text/dictionary.md` are the source of truth for names
1994
- - Debugging why `absorb()` isn't writing to TypeDB — check `world.tql` accepts the attribute type
1995
- - Writing a TQL `fun` that needs an on-chain twin — see `src/schema/sui.tql` for parallel function signatures
1996
- - Querying `actor.wallet` values — they're derived by `addressFor(uid)` in `src/lib/sui.ts`, not always stored
2118
+ - Touching Move path/signal logic under `pay/contracts/sui/` — `schema/one.tql` and `text/dictionary.md` are the source of truth for names
2119
+ - Debugging why an on-chain absorb isn't writing to TypeDB — check the loaded schema accepts the attribute type
2120
+ - Writing a TQL `fun` that needs an on-chain twin — see `schema/sui.tql` for parallel function signatures
2121
+ - Querying `actor.wallet` values — they're derived from the Sui resolvers (`one.ie/web/src/lib/resolvers/sui.ts`), not always stored
1997
2122
 
1998
2123
  ---
1999
2124
 
2000
2125
  ## Production Patterns: Classifier Functions, Thing Collapse, Symmetric Routing
2001
2126
 
2002
- These three patterns come from **`src/schema/world.tql`** — the ONE substrate's live runtime schema — and are worth learning because they turn abstract ideas ("Queries as Types", polymorphism, role interfaces) into code that's actually short, composable, and fast to query. Cross-reference: `packages/typedb-inference-patterns/` has the lesson-by-lesson version; this section is the distillation.
2127
+ These patterns come from **`schema/world.tql`** and are worth learning because they turn abstract ideas ("Queries as Types", polymorphism, role interfaces) into code that's actually short, composable, and fast to query.
2128
+
2129
+ > **Read the code below for the *shape*, and never paste it.** Two corrections
2130
+ > that this section got wrong for a long time:
2131
+ >
2132
+ > 1. **`world.tql` is not the live runtime schema of the main database.** Per
2133
+ > `schema/CLAUDE.md` it is the **brain schema for the standalone `api/`
2134
+ > BrainDO database only** — it declares `task-value`/`task-effort` as strings
2135
+ > where `one.tql` declares doubles, so co-loading the two fails on a
2136
+ > value-type conflict. The main `one` database's canon is
2137
+ > `one.tql` + `roles.tql` + `do.tql` + `reason.tql` + `router.tql` +
2138
+ > `factory.tql` + `work-contract.tql` + `chat.tql`.
2139
+ > 2. **`world.tql` is written in `return first if … then … else …`, which the
2140
+ > live 3.12.1 server rejects with `TQL0`** (VERIFIED table). It is therefore
2141
+ > not deployable as written. `schema/reason.tql` carries the rewrite that
2142
+ > *is* deployed. Below, every function is shown in the **deployable**
2143
+ > disjunction form, with the `world.tql` original noted where it differs.
2144
+ >
2145
+ > Attribute names differ across the two too: `success-rate`, `sample-count` and
2146
+ > `activity-score` exist in `world.tql` and **not** in the live `one` database.
2147
+ > Check `GET /v1/databases/one/schema` before assuming an attribute is there.
2003
2148
 
2004
2149
  ### Pattern 1 — The Deterministic Sandwich as a Function Chain
2005
2150
 
2006
- A **deterministic sandwich** wraps a probabilistic operation (usually an LLM call) in a pre-check and a post-check, so the indeterminism is bounded on both sides. In TypeQL 3.x, every slice of the sandwich is a **typed function** returning either `first true` (boolean pass) or a stream.
2151
+ A **deterministic sandwich** wraps a probabilistic operation (usually an LLM call) in a pre-check and a post-check, so the indeterminism is bounded on both sides. In TypeQL 3.x, every slice of the sandwich is a **typed function** returning either a bound boolean or a stream.
2007
2152
 
2008
2153
  ```typeql
2009
- # src/schema/world.tql:536567the sandwich, verbatim shape
2154
+ # schema/world.tql:586614shape only; signatures corrected to `actor`
2155
+ # (world.tql says `actor`, an older draft of this skill said `unit`, a dead name)
2010
2156
 
2011
2157
  # PRE: Can this receiver handle this skill? (capability check)
2012
- fun can_receive($u: unit, $sk: skill) -> boolean:
2158
+ fun can_receive($u: actor, $sk: skill) -> boolean:
2013
2159
  match (provider: $u, offered: $sk) isa capability;
2014
- return first true;
2160
+ let $ok = true;
2161
+ return first $ok;
2015
2162
 
2016
2163
  # PRE: Is the path to this receiver safe? (not toxic)
2017
- fun is_safe($from: unit, $to: unit) -> boolean:
2018
- match (source: $from, target: $to) isa path,
2019
- has strength $s, has resistance $a;
2020
- return first if ($a > $s and $a >= 10.0) then false else true;
2164
+ fun is_safe($from: actor, $to: actor) -> boolean:
2165
+ match
2166
+ (source: $from, target: $to) isa path,
2167
+ has strength $s, has resistance $r;
2168
+ { $r > $s; $r >= 10.0; let $ok = false; }
2169
+ or { not { $r > $s; $r >= 10.0; }; let $ok = true; };
2170
+ return first $ok;
2021
2171
 
2022
2172
  # PRE: Is the signal within budget?
2023
- fun within_budget($u: unit, $sk: skill, $amount: double) -> boolean:
2024
- match (provider: $u, offered: $sk) isa capability, has price $p;
2025
- return first if ($amount >= $p) then true else false;
2173
+ fun within_budget($u: actor, $sk: skill, $amount: double) -> boolean:
2174
+ match
2175
+ (provider: $u, offered: $sk) isa capability, has price $p;
2176
+ { $amount >= $p; let $ok = true; }
2177
+ or { $amount < $p; let $ok = false; };
2178
+ return first $ok;
2026
2179
 
2027
- # POST: Does the referenced unit still exist?
2180
+ # POST: Does the referenced actor still exist?
2028
2181
  fun unit_exists($uid: string) -> boolean:
2029
2182
  match $u isa actor, has aid $uid;
2030
- return first true;
2031
-
2032
- # POST: Is a unit performing well enough to trust its output?
2033
- fun is_trustworthy($u: unit) -> boolean:
2034
- match $u has success-rate $sr, has sample-count $sc;
2035
- return first if ($sr >= 0.50 or $sc < 10) then true else false;
2183
+ let $ok = true;
2184
+ return first $ok;
2036
2185
 
2037
2186
  # COMPOSED: All the PRE checks as a single type assertion
2038
- fun preflight($from: unit, $to: unit, $sk: skill) -> boolean:
2039
- match (provider: $to, offered: $sk) isa capability;
2040
- (source: $from, target: $to) isa path,
2041
- has strength $s, has resistance $a;
2042
- return first if ($a > $s and $a >= 10.0) then false else true;
2187
+ fun preflight($from: actor, $to: actor, $sk: skill) -> boolean:
2188
+ match
2189
+ (provider: $to, offered: $sk) isa capability;
2190
+ (source: $from, target: $to) isa path,
2191
+ has strength $s, has resistance $r;
2192
+ { $r > $s; $r >= 10.0; let $ok = false; }
2193
+ or { not { $r > $s; $r >= 10.0; }; let $ok = true; };
2194
+ return first $ok;
2043
2195
  ```
2044
2196
 
2045
2197
  **Why this is elegant:**
2046
2198
 
2047
2199
  1. **Each check is a type, not a subroutine.** `is_safe($from, $to)` declares the type "this path is safe". The planner decides whether to evaluate it by scanning strength/resistance, by checking an index, or by proving it vacuously. You never write "first look up strength, then compare".
2048
2200
 
2049
- 2. **`return first if ... then ... else ...`** is the TypeDB 3.x idiom for boolean classifiers. The body is a single conditional expression. No side effects, no cascading rules, no rule-firing order to debug.
2201
+ 2. **An exhaustive disjunction binding one `let` per branch, closed by `return first $var`,** is the TypeDB 3.x idiom for boolean and string classifiers there is no conditional expression in the language. Each branch after the first must negate the earlier ones, or a row matching two branches makes `return first` arbitrary.
2050
2202
 
2051
- 3. **Composition is just another function.** `preflight()` inlines the match patterns of its children rather than calling them — this lets the planner see the whole constraint set and pick the cheapest plan. (Calling three separate functions would force three sequential lookups.)
2203
+ 3. **Composition is just another function.** `preflight()` inlines the match patterns of its children rather than calling them — this lets the planner see the whole constraint set and pick the cheapest plan. (Calling three separate functions would force three sequential lookups.) This is the same "inline the body" rule as *functions are for counts*.
2052
2204
 
2053
- 4. **Negative space stays declarative.** `is_trustworthy` returns `true` when `sr >= 0.50 OR sc < 10` new agents are trusted by default because we have no evidence against them. That's a domain rule encoded as a type, not as a runtime `if`.
2205
+ 4. **Negative space stays declarative.** A trust check that passes when `success-rate >= 0.50 OR sample-count < 10` trusts new agents by default, because there is no evidence against them. That's a domain rule encoded as a type, not as a runtime `if`. (`is_trustworthy` reads `success-rate`/`sample-count` — BrainDO-only attributes, so it is a `world.tql` function and has no counterpart in the main database.)
2054
2206
 
2055
2207
  ### Pattern 2 — The `thing` Collapse (Polymorphism as Entity-Level Union)
2056
2208
 
2057
- Instead of modeling plan, cycle, task, and skill as four separate entities, the canonical ontology (`src/schema/one.tql:47–84`) **collapses them into one `thing` entity** discriminated by a `thing-type` attribute. Attributes specific to each kind (`task-status`, `cycles-planned`, `goal`) live on the shared entity, unused for non-matching kinds.
2209
+ Instead of modeling plan, cycle, task, and skill as four separate entities, the canonical ontology (`schema/one.tql:96+`) **collapses them into one `thing` entity** discriminated by a `thing-type` attribute. Attributes specific to each kind (`task-status`, `cycles-planned`, `goal`) live on the shared entity, unused for non-matching kinds.
2058
2210
 
2059
2211
  ```typeql
2212
+ # Abridged — schema/one.tql is the source, and it is much longer than this.
2060
2213
  entity thing,
2061
2214
  owns tid @key,
2062
2215
  owns name,
2063
- owns thing-type, # "skill" | "task" | "plan" | "service"
2216
+ owns thing-type, # skill|task|token|service|plan|step|corpus|
2217
+ # creative-asset|do-cycle|do-plan — see @values
2064
2218
  owns price,
2065
2219
  owns tag @card(0..),
2066
2220
  # Task-only (meaningful when thing-type='task')
2067
- owns task-status, # open/blocked/picked/done/verified/failed/dissolved
2221
+ owns task-status, # open/blocked/picked/done/verified/failed/dissolved
2068
2222
  owns task-effort,
2069
2223
  owns task-value,
2070
2224
  owns exit-condition,
2225
+ # Workflow steps (meaningful when thing-type='step')
2226
+ owns step-kind, # trigger|tool|skill|agent|condition|human|delay|sell
2227
+ owns step-config,
2071
2228
  # Plan-only (meaningful when thing-type='plan')
2072
2229
  owns goal,
2073
2230
  owns cycles-planned,
@@ -2092,6 +2249,11 @@ entity thing,
2092
2249
  plays production:produced;
2093
2250
  ```
2094
2251
 
2252
+ **Vocabulary law:** every `@values` enum lives in `one.tql` AND must be mirrored
2253
+ by a migration before prod writes rely on a new value. Adding a convention value
2254
+ in a doc or a `.ts` without widening the deployed `@values` means the write is
2255
+ rejected at the constraint (`schema/CLAUDE.md`).
2256
+
2095
2257
  **When to use it:**
2096
2258
 
2097
2259
  - The concepts share >50% of their attributes and all their relations.
@@ -2112,44 +2274,41 @@ fun open_tasks() -> { thing } :
2112
2274
  $t isa thing, has thing-type "task", has task-status "open";
2113
2275
  return { $t };
2114
2276
 
2115
- # Polymorphic priority: works across kinds because all own priority-score
2277
+ # Polymorphic priority: works across kinds because all own task-priority
2116
2278
  fun top_by_priority($kind: string) -> { thing } :
2117
2279
  match
2118
- $t isa thing, has thing-type $kind, has priority-score $p;
2280
+ $t isa thing, has thing-type $kind, has task-priority $p;
2119
2281
  sort $p desc; limit 10;
2120
2282
  return { $t };
2121
2283
  ```
2122
2284
 
2123
- Compare this with the maximalist `src/schema/world.tql` (787 lines, separate `task` entity with its own `task-id`, `task-status`, `priority-formula`, etc.). The **skinny ontology** (`one.tql`) uses the collapse for flexibility; the **runtime schema** (`world.tql`) uses separate entities for performance and strict typing. Both are valid — the choice depends on how much schema change you expect.
2285
+ Compare this with the maximalist `schema/world.tql` (844 lines, separate `task` entity with its own `task-id`, `task-status`, priority formula, etc.). The **skinny ontology** (`one.tql`, 480 lines) uses the collapse for flexibility; the **BrainDO schema** (`world.tql`) uses separate entities for performance and strict typing. Both are valid — the choice depends on how much schema change you expect. They cannot be loaded into the same database.
2124
2286
 
2125
2287
  ### Pattern 3 — Symmetric Routing via Shared-Variable Unification
2126
2288
 
2127
2289
  When you have a relation "X matches Y on tag", you usually need both directions — "what Y's match this X" AND "what X's match this Y". In SQL this is two separate queries. In TypeQL, both queries share the same match pattern; only the return changes.
2128
2290
 
2129
2291
  ```typeql
2130
- # src/schema/world.tql:752775 — verbatim symmetric pair
2292
+ # schema/world.tql:805822 — verbatim symmetric pair.
2293
+ # Note every signature says `actor`, never `unit` (a dead name).
2131
2294
 
2132
- # "What tasks can this unit work on?" (unit → tasks)
2133
- fun tasks_for_unit($u: unit) -> { task } :
2134
- match
2135
- $u has tag $tag;
2136
- $t isa task, has tag $tag,
2137
- has done false, has task-status "open";
2295
+ # "What tasks can this actor work on?" (actor → tasks)
2296
+ fun tasks_for_unit($u: actor) -> { task }:
2297
+ match $u has tag $tag;
2298
+ $t isa task, has tag $tag, has done false, has task-status "open";
2138
2299
  return { $t };
2139
2300
 
2140
- # "Which units can do this task?" (task → units)
2141
- fun actors_for_task($t: task) -> { unit } :
2142
- match
2143
- $t has tag $tag;
2144
- $u isa actor, has tag $tag, has status "active";
2301
+ # "Which actors can do this task?" (task → actors)
2302
+ fun actors_for_task($t: task) -> { actor }:
2303
+ match $t has tag $tag;
2304
+ $u isa actor, has tag $tag, has status "active";
2145
2305
  return { $u };
2146
2306
 
2147
- # "Best unit for this task": tag overlap × pheromone strength
2148
- fun best_unit_for_task($t: task) -> unit :
2149
- match
2150
- $t has tag $tag;
2151
- $u isa actor, has tag $tag, has status "active";
2152
- (source: $any, target: $u) isa path, has strength $s;
2307
+ # "Best actor for this task": tag overlap × path strength
2308
+ fun best_unit_for_task($t: task) -> actor:
2309
+ match $t has tag $tag;
2310
+ $u isa actor, has tag $tag, has status "active";
2311
+ (source: $any, target: $u) isa path, has strength $s;
2153
2312
  sort $s desc; limit 1;
2154
2313
  return $u;
2155
2314
  ```
@@ -2158,7 +2317,7 @@ fun best_unit_for_task($t: task) -> unit :
2158
2317
 
2159
2318
  - The `$tag` variable is shared between `$u has tag $tag` and `$t has tag $tag`. TypeQL **unifies** these — there must exist at least one tag value that both sides agree on. No `JOIN ON` clause, no foreign key; the type constraint IS the join.
2160
2319
 
2161
- - The pattern is symmetric because the *type* of "unit-task matches on tag" doesn't care about direction. Only the *projection* (`return { $t }` vs `return { $u }`) picks a side.
2320
+ - The pattern is symmetric because the *type* of "actor-task matches on tag" doesn't care about direction. Only the *projection* (`return { $t }` vs `return { $u }`) picks a side.
2162
2321
 
2163
2322
  - `best_unit_for_task` composes the matching pattern with a *third* constraint (pheromone strength on an incoming path). Notice `(source: $any, target: $u)` — `$any` is bound but unconstrained; we don't care WHO marked the path, only that SOMEONE marked it. That's a free variable in the type.
2164
2323
 
@@ -2166,19 +2325,39 @@ fun best_unit_for_task($t: task) -> unit :
2166
2325
 
2167
2326
  One more worth lifting out. The substrate labels every path with one of five statuses using a single function whose body is a nested conditional:
2168
2327
 
2169
- ```typeql
2170
- # src/schema/world.tql:523–530
2171
- fun path_status($e: path) -> string:
2172
- match $e has strength $s, has resistance $a, has traversals $t;
2173
- return first
2174
- if ($a > $s and $a >= 10.0) then "toxic"
2175
- else if ($s >= 50.0) then "highway"
2176
- else if ($s >= 10.0 and $s < 50.0 and $t < 10) then "fresh"
2177
- else if ($s > 0.0 and $s < 5.0) then "fading"
2178
- else "active";
2179
- ```
2328
+ `schema/world.tql:570–577` writes it as an `if/then/else` cascade. **That form does
2329
+ not parse on the live server** — this is the deployed rewrite, and it is the
2330
+ single best worked example of translating a cascade into TypeQL:
2180
2331
 
2181
- The rules compose top-down — `toxic` wins over `highway` if both apply, `highway` wins over `fresh`. In 2.x this would be five chained `rule`s with priority annotations. In 3.x it's one function, and the cascade order is visible in the source. Debugging path status is now a single function call.
2332
+ ```typeql
2333
+ # schema/reason.tql:63–79 — the DEPLOYED five-branch classifier
2334
+ fun path_status($p: path) -> string:
2335
+ match
2336
+ $p has strength $s, has resistance $r, has traversals $t;
2337
+ { $r > $s; $r >= 10.0;
2338
+ let $status = "toxic"; }
2339
+ or { $s >= 50.0; not { $r > $s; $r >= 10.0; };
2340
+ let $status = "highway"; }
2341
+ or { $s >= 10.0; $s < 50.0; $t < 10;
2342
+ not { $r > $s; $r >= 10.0; };
2343
+ let $status = "fresh"; }
2344
+ or { $s > 0.0; $s < 5.0; not { $r > $s; $r >= 10.0; };
2345
+ let $status = "fading"; }
2346
+ or { not { $r > $s; $r >= 10.0; };
2347
+ not { $s >= 50.0; };
2348
+ not { $s >= 10.0; $s < 50.0; $t < 10; };
2349
+ not { $s > 0.0; $s < 5.0; };
2350
+ let $status = "active"; };
2351
+ return first $status;
2352
+ ```
2353
+
2354
+ The precedence that `else if` gave you for free — `toxic` beats `highway`,
2355
+ `highway` beats `fresh` — **you now write by hand** as a `not { … }` guard in
2356
+ every later branch. That verbosity is the whole cost of the missing conditional
2357
+ expression, and skipping a guard is a silent bug: a path matching two branches
2358
+ produces two rows and `return first` picks one arbitrarily. In 2.x this was five
2359
+ chained `rule`s with priority annotations; the cascade order is at least still
2360
+ visible in the source.
2182
2361
 
2183
2362
  **Adjacent pattern — reading the label:**
2184
2363
 
@@ -2190,12 +2369,35 @@ match
2190
2369
  select $p, $s;
2191
2370
  ```
2192
2371
 
2193
- The result reads like English: "paths whose status is highway". The function is a verb (`path_status(p)`) that returns a type-tagged string. This is the L2 "quality rule" from `packages/typedb-inference-patterns/LOOPS.md` — classification without explicit rule firing.
2372
+ The result reads like English: "paths whose status is highway". The function is a verb (`path_status(p)`) that returns a type-tagged string — classification without explicit rule firing.
2373
+
2374
+ This is also the boundary where **functions are for counts** bites. `path_status`
2375
+ returns a scalar, so calling it per row is what it is for. If instead you wanted
2376
+ *the paths and their strengths and their tags*, do not call a stream fun and then
2377
+ `has` the results — inline the fun's match body and select the attributes in one
2378
+ pipeline. The call form is what turns a sub-second query into a 10-second timeout.
2194
2379
 
2195
2380
  ---
2196
2381
 
2197
2382
  ## Project-Specific Patterns
2198
2383
 
2384
+ > **These examples are from a trading prototype, not from this repo.**
2385
+ > `signal-edge`, `live-prediction`, `edge-trail-level`, `win-count`,
2386
+ > `from-state-id`, `prediction-id` and friends exist in **no** `.tql` file here
2387
+ > and in **no** live database. `trail` is also a dead name (root `CLAUDE.md`).
2388
+ > They are kept as generic modelling shapes; for a real query against the ONE
2389
+ > substrate use `path` / `strength` / `resistance` / `traversals` and the
2390
+ > functions listed in `schema/CLAUDE.md`.
2391
+ >
2392
+ > **The read-modify-write examples below are the rejected pattern.** Writes take
2393
+ > **literals only** — `insert $e has strength ($s + 1)` and `insert $e has
2394
+ > strength $ns` (a value variable) both fail with `REP1 Attribute vs Value`, and
2395
+ > there is no in-database increment on this build. The shape that works is:
2396
+ > read the current value, compute it in the caller, write it back with
2397
+ > `update $e has strength 7.0;` (`update` is replace-or-add on `@card(0..1)` —
2398
+ > no delete needed). See `readPathWeights` / `updatePathWeights` in
2399
+ > `one.ie/web/src/lib/substrate.ts`.
2400
+
2199
2401
  ### Signal-Edge Operations (Trading)
2200
2402
 
2201
2403
  ```typeql
@@ -2230,12 +2432,9 @@ select $id, $level, $pnl;
2230
2432
  sort $pnl desc;
2231
2433
  limit 20;
2232
2434
 
2233
- # Update edge statistics
2234
- match
2235
- $e isa signal-edge, has edge-id "edge-001";
2236
- $e has win-count $old_wins;
2237
- delete $old_wins;
2238
- insert $e has win-count 15;
2435
+ # Update edge statistics — literal only, and `update` needs no delete
2436
+ match $e isa signal-edge, has edge-id "edge-001";
2437
+ update $e has win-count 15;
2239
2438
  ```
2240
2439
 
2241
2440
  ### Pheromone Trail Patterns
@@ -2255,12 +2454,15 @@ select $id, $level;
2255
2454
  sort $level desc;
2256
2455
  limit 10;
2257
2456
 
2258
- # Decay all pheromones by 10%
2259
- match
2260
- $e isa signal-edge, has edge-trail-level $old;
2261
- let $new = $old * 0.9;
2262
- delete $old;
2263
- insert $e has edge-trail-level $new;
2457
+ # Decay by 10% — THERE IS NO IN-DATABASE FORM OF THIS.
2458
+ # `let $new = $old * 0.9; delete $old; insert $e has edge-trail-level $new;`
2459
+ # is REJECTED (REP1 Attribute vs Value): writes take literals only.
2460
+ # Step 1 read the current values:
2461
+ match $e isa signal-edge, has edge-id $id, has edge-trail-level $old;
2462
+ select $id, $old;
2463
+
2464
+ # Step 2 — multiply in the caller, then write one literal per row:
2465
+ # update $e has edge-trail-level 22.95;
2264
2466
  ```
2265
2467
 
2266
2468
  ### Live Prediction Tracking
@@ -2307,15 +2509,13 @@ reduce $wins = count groupby $name;
2307
2509
 
2308
2510
  ```
2309
2511
  ┌─────────────────────────────────────────────────────────────┐
2310
- │ TypeDB 3.0 Quick Reference
2512
+ │ TypeDB 3.12.1 Quick Reference
2311
2513
  ├─────────────────────────────────────────────────────────────┤
2312
- │ CONNECT
2313
- driver = TypeDB.driver(url, Credentials(u, p), Options)
2314
-
2315
- TRANSACTIONS (no sessions!)
2316
- with driver.transaction(db, TransactionType.READ) as tx:
2317
- │ result = tx.query("...").resolve() │
2318
- │ tx.commit() # only for WRITE/SCHEMA │
2514
+ │ CONNECT (this repo: HTTP, no driver)
2515
+ POST /v1/signin -> token
2516
+ POST /v1/query {query, databaseName,
2517
+ transactionType, commit}
2518
+ GET /v1/version · GET /v1/databases/one/schema
2319
2519
  │ │
2320
2520
  │ TRANSACTION TYPES │
2321
2521
  │ READ - read only, concurrent │
@@ -2331,7 +2531,7 @@ reduce $wins = count groupby $name;
2331
2531
  │ integer (not long!), double, decimal, boolean │
2332
2532
  │ string, date, datetime, datetime-tz, duration │
2333
2533
  │ │
2334
- │ DELETE SYNTAX (3.0) │
2534
+ │ DELETE SYNTAX (3.x) │
2335
2535
  │ delete $attr; # delete attribute │
2336
2536
  │ delete has $attr of $e; # delete ownership │
2337
2537
  │ delete $e; # delete entity/relation │
@@ -2358,10 +2558,20 @@ reduce $wins = count groupby $name;
2358
2558
 
2359
2559
  ### In-repo
2360
2560
 
2361
- - `src/schema/one.tql` — canonical 6-dimension ontology (stable, 272 lines). See *Pattern 2 The `thing` Collapse* for the polymorphic-union move it demonstrates.
2362
- - `src/schema/world.tql` — live substrate schema (787 lines) with classifier functions, deterministic-sandwich pre/post-checks, symmetric-routing pair. Source for the *Production Patterns* section above.
2363
- - `src/schema/sui.tql` — on-chain mirror of the same ontology. Proves the model bridges to a value-bearing substrate (Move). See the *Works With /sui* section above.
2364
- - `packages/typedb-inference-patterns/`lesson-by-lesson version of the 6 patterns that `world.tql` distills. The `standalone/*.tql` files are PRE-3.x (banner-marked); the `runtime/colony.ts` is 3.x-compliant; the README is the rule→fun translation guide.
2561
+ - **`schema/CLAUDE.md` — read this first.** It is the authority on which `.tql` files load, in what order, which must never co-load, the `@values` vocabulary law, and the codegen pipeline. It outranks this skill on all of that.
2562
+ - `schema/one.tql` (480 lines) canonical 6-dimension ontology, locked. See *Pattern 2 The `thing` Collapse*.
2563
+ - `schema/roles.tql` (184) the authority walk: `self-or-ancestors-of` · `open-ancestors-of` · `controls` · `can` · `funding-of` · `brand-of`. All six are live in prod.
2564
+ - `schema/reason.tql`inference layer, functions only. **The deployable `path_status`** and the recursive-closure examples (`reachable`, `strong-reach`, `route_exists`).
2565
+ - `schema/do.tql` — build-engine layer, functions only. `do_stage` is the reference disjunction classifier.
2566
+ - `schema/router.tql` (206) — universal signal router; `receivers-reachable-by` is live in prod.
2567
+ - `schema/factory.tql` (993) — the factory ladder. **One file is one schema transaction on purpose:** a fun may only reference *committed* funs (`REP4`), so a partial deploy breaks it. Prod carried 39 of 43 funs as of 2026-07-30; `next` + the cost trio ship in `migrations/0045_factory_next.tql` and are **not yet live** (confirmed absent 2026-08-02).
2568
+ - `schema/world.tql` (844 lines) — **standalone BrainDO schema, not the main database, and not deployable as written** (`if/then/else`). Source for *Production Patterns* above; read for shape only.
2569
+ - `schema/sui.tql` (475 lines) — Move mirror. Codegen input, never loaded.
2570
+ - `schema/migrations/` — 24 entries. Migration numbering is live through `0045`.
2571
+ - `one.ie/web/src/lib/substrate.ts` — how TypeScript actually queries: gateway → `POST /v1/query`. `readPathWeights` / `updatePathWeights` are the read-compute-write increment pattern.
2572
+ - `backup/scripts/typedb/dump.py` · `replay.py` — Python against `/v1/` via `urllib`, no driver. Also the source of the `(?![\w-])` role-matching fix and the `ensure_ascii=False` rule.
2573
+ - `.claude/scripts/typedb-env.sh` · `.claude/typedb/{dev,prod}.env` — the dev/prod switch and the version pin.
2574
+ - `.claude/scripts/typedb-probes/` — the 3.8.3 server-panic reproducers.
2365
2575
  - `.claude/skills/typedb/reference/research-notes-2026-04.md` — source notes behind this skill's 2026-04 refinement: PACMMOD paper, 3.0 roadmap, Vaticle lectures, "Inside TypeDB: The Next Chapter".
2366
2576
  - `.claude/skills/typedb/reference/migration-2x-3x.md` — mechanical 2.x→3.x translations (sessions, Concept API, rule→fun, long→integer, delete syntax).
2367
2577
  - `.claude/skills/typedb/reference/python-driver.md` — deeper Python-driver reference than the summary in this file.