@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
@@ -0,0 +1,747 @@
1
+ #!/usr/bin/env bash
2
+ # factory-check.sh — the factory's acceptance checks, against the LIVE substrate.
3
+ #
4
+ # Every check here QUERIES TypeDB. None of them greps a file for a symbol,
5
+ # because a grep passes on a symbol that is defined and never called — and the
6
+ # whole point of this promise is that "built" and "live" are different axes
7
+ # (text/vision.md). A check that cannot go red for the reason it claims to test
8
+ # is not a proof.
9
+ #
10
+ # Usage: factory-check.sh <check> [--verbose]
11
+ # schema-live the ladder is in the DEPLOYED thing-type @values
12
+ # sdk-enum the generated zod enum accepts the ladder (2nd gate)
13
+ # tracer ONE hand-seeded row proves schema->query->surface end to end
14
+ # write-path an objective row created THROUGH A RECEIVER (excludes the tracer)
15
+ # seeded vision.md's rows exist as objective things
16
+ # driver ready-tasks() returns at least one real row
17
+ # gaps-visible uncovered() names the dark wires — absence made visible
18
+ # belief-guard the belief functions refuse to gate while their edge is empty
19
+ # fn-exposed the factory funs are in FN_MAP and on all THREE allowlists
20
+ # fn-entity-args fn:run can actually bind an entity param (the driver funs)
21
+ # fn-one-allowlist web + MCP + CLI read ONE allowlist source, not three copies
22
+ # view-gaps the gaps board queries the ladder AND is honest when empty
23
+ # stream /factory streams what is being built AND can stop it
24
+ # walk-speed the deterministic lifecycle walk passes, with per-stop speed budgets
25
+ # templates text/template-*.md teach the corrected contract; YAML ratchet holds
26
+ # docs plan/vision/docs no longer claim the factory is unarmed
27
+ # all every check above, in order
28
+ # json run them all and write the receipt /factory renders
29
+ # (one.ie/web/src/data/factory-check.json; FACTORY_RECEIPT_OUT
30
+ # overrides). ALWAYS exits 0 — the verdicts are inside the file.
31
+ #
32
+ # Exit: 0 = green · 1 = red (with the reason) · 3 = cannot run (no creds/reach)
33
+ #
34
+ # 1 vs 3 IS THE POINT. A red check failed for a reason you can act on; a 3 means
35
+ # the substrate was unreachable and NOTHING was proven either way. The promise's
36
+ # `proof:` && -join flattens both to non-zero, so a settle that sees non-zero
37
+ # must not assume "broken" — see text/factory.md § A note on "red" vs "cannot
38
+ # run". Never widen a `cannot run` into a `red` to make a chain simpler.
39
+ set -uo pipefail
40
+ ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
41
+ MODE="${1:-all}"
42
+ VERBOSE=0; [[ "${2:-}" == "--verbose" ]] && VERBOSE=1
43
+ say() { [ $VERBOSE -eq 1 ] && echo " $*" >&2; return 0; }
44
+ red() { echo "RED $1" >&2; return 1; }
45
+ # A check that cannot reach its evidence is NOT red — red sends someone to fix
46
+ # the build, cannot-run sends them to fix the environment. Returns 3 so a single
47
+ # -check invocation (what a promise `accept:` runs) keeps the distinction.
48
+ cannot() { echo "CANNOT RUN: $1" >&2; return 3; }
49
+
50
+ # ── receipt (json mode) ───────────────────────────────────────────────────
51
+ # The /factory board renders THIS FILE, never a shell-out: the page runs on
52
+ # Workers, which cannot fork a process, and a board that re-ran thirteen network
53
+ # checks per request would be its own outage. One source of truth for the
54
+ # verdicts — the checker — and a static receipt the page reads.
55
+ #
56
+ # Reasons are sanitized HERE, not on the page. A cannot-run reason that carried
57
+ # $TYPEDB_URL would put infrastructure on a public page, and a page-side scrub
58
+ # would arrive after the value was already committed to disk in this receipt.
59
+ # The sanitizer is python (literal str.replace), NOT sed: a password carrying a
60
+ # sed metacharacter would kill the pipeline, and with pipefail-but-no-errexit the
61
+ # reason would silently come back empty — thirteen rows with no reasons, which is
62
+ # exactly the deliverable failing quietly.
63
+ #
64
+ # Exit is ALWAYS 0 in json mode: writing a receipt is not a verdict. The verdicts
65
+ # live inside it. A caller that wants a verdict runs a check by name.
66
+ CHECK_ORDER="schema-live sdk-enum tracer write-path seeded driver gaps-visible belief-guard fn-exposed fn-entity-args fn-one-allowlist view-gaps stream walk-speed templates docs"
67
+ RECEIPT_OUT="${FACTORY_RECEIPT_OUT:-$ROOT/one.ie/web/src/data/factory-check.json}"
68
+
69
+ json_str() { python3 -c 'import json,sys;print(json.dumps(sys.stdin.read().strip()))'; }
70
+ # NOTE: sanitize takes the reason as $1, NOT piped stdin. `python3 - <<HEREDOC`
71
+ # makes the heredoc BE stdin (the script text) — a second, piped stdin never
72
+ # reaches sys.stdin.read() underneath it, so the reason silently comes back
73
+ # empty. Passing it base64-encoded through argv sidesteps both that trap and
74
+ # any shell-metacharacter risk in a raw reason string reaching argv unescaped.
75
+ sanitize() {
76
+ local b64; b64=$(printf '%s' "$1" | base64 | tr -d '\n')
77
+ python3 - "$ROOT" "${TYPEDB_URL:-}" "${TYPEDB_PASSWORD:-}" "${TOK:-}" "$b64" <<'PY'
78
+ import base64,re,sys
79
+ root,url,pw,tok,b64 = sys.argv[1:6]
80
+ s = base64.b64decode(b64).decode('utf-8', 'replace').strip()
81
+ for needle,repl in ((url,'<substrate>'),(pw,'<redacted>'),(tok,'<redacted>'),(root+'/','')):
82
+ if needle: s = s.replace(needle, repl)
83
+ print(re.sub(r'^(RED\s+|CANNOT RUN:\s*)','',s))
84
+ PY
85
+ }
86
+ # `source` names the RAIL, not just the tool. A board of sixteen green rows means
87
+ # something different depending on whether they were answered by TypeDB Cloud or
88
+ # by a container on the operator's laptop, and this whole promise exists to stop
89
+ # those two being read as the same claim. Host only — never the credentials,
90
+ # which is why this is derived here rather than printing $TYPEDB_URL.
91
+ emit_receipt() {
92
+ local rail host
93
+ host=$(printf '%s' "${TYPEDB_URL:-}" | sed -E 's#^[a-z]+://##; s#[:/].*##')
94
+ case "$host" in
95
+ ''|localhost|127.0.0.1) rail="local substrate" ;;
96
+ *) rail="cloud substrate ${host%%.*}" ;;
97
+ esac
98
+ mkdir -p "$(dirname "$RECEIPT_OUT")"
99
+ printf '{\n "generated_at": "%s",\n "source": "factory-check.sh json · %s",\n "checks": [\n%s\n ]\n}\n' \
100
+ "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$rail" "$1" > "$RECEIPT_OUT"
101
+ echo "receipt: $RECEIPT_OUT ($rail)" >&2
102
+ }
103
+ receipt_all_cannot_run() {
104
+ local body="" c r; r=$(printf '%s' "$1" | json_str)
105
+ for c in $CHECK_ORDER; do
106
+ [ -n "$body" ] && body="$body,"$'\n'
107
+ body="$body {\"name\": \"$c\", \"state\": \"cannot-run\", \"reason\": $r}"
108
+ done
109
+ emit_receipt "$body"
110
+ }
111
+ run_receipt() {
112
+ local body="" c out rc state reason
113
+ for c in $CHECK_ORDER; do
114
+ out=$("check_${c//-/_}" 2>&1 >/dev/null); rc=$?
115
+ case $rc in
116
+ 0) state="ok"; reason="" ;;
117
+ 3) state="cannot-run"; reason="$(sanitize "$out")" ;;
118
+ *) state="red"; reason="$(sanitize "$out")" ;;
119
+ esac
120
+ [ "$state" != "ok" ] && [ -z "$reason" ] && reason="no reason reported"
121
+ [ -n "$body" ] && body="$body,"$'\n'
122
+ body="$body {\"name\": \"$c\", \"state\": \"$state\", \"reason\": $(printf '%s' "$reason" | json_str)}"
123
+ done
124
+ emit_receipt "$body"
125
+ }
126
+
127
+ # ── creds ─────────────────────────────────────────────────────────────────
128
+ # Only the checks that QUERY the production graph need a cluster. `templates`
129
+ # and `docs` read files; `belief-guard` (rewritten 2026-07-28) proves a wire
130
+ # against a SCRATCH database, never `one`. Signing in for those turned a
131
+ # perfectly answerable file check into `CANNOT RUN` during the 2026-07-28
132
+ # TypeDB outage — an availability dependency the check never actually had.
133
+ # `all`/`json` still sign in: most of their checks need it.
134
+ # Verified by inspection 2026-07-28: these call no q(), so a cluster outage must
135
+ # not turn an answerable file check into CANNOT RUN. Re-verify with:
136
+ # sed -n "/^check_<name>()/,/^}/p" $0 | grep -c 'q '
137
+ case "$MODE" in
138
+ templates|docs|belief-guard|sdk-enum|fn-exposed|fn-entity-args|fn-one-allowlist|view-gaps|stream|walk-speed) NEEDS_CLUSTER=0 ;;
139
+ *) NEEDS_CLUSTER=1 ;;
140
+ esac
141
+
142
+ # NOT `set -a && . .env` — that file carries a hyphenated key and an unquoted
143
+ # value, so sourcing it aborts the shell. Pull only what we need.
144
+ #
145
+ # PARSING is unconditional; only the SIGN-IN is gated on NEEDS_CLUSTER. Reading a
146
+ # local file has no availability dependency — that was never the thing an outage
147
+ # broke. Gating the parse too left `belief-guard` (NEEDS_CLUSTER=0, but it hands
148
+ # TYPEDB_URL to vitest) with an unset variable, which under `set -u` aborted the
149
+ # check and surfaced as a RED about the evidence wire.
150
+ eval "$(python3 - "$ROOT/one.ie/web/.env" <<'PY'
151
+ import re,sys,shlex
152
+ for line in open(sys.argv[1], errors="replace"):
153
+ m = re.match(r'^\s*(TYPEDB_(?:URL|DATABASE|USERNAME|PASSWORD))\s*=\s*(.*?)\s*$', line)
154
+ if m: print(f"{m.group(1)}={shlex.quote(m.group(2).strip(chr(34)+chr(39)))}")
155
+ PY
156
+ )"
157
+ : "${TYPEDB_URL:=}" "${TYPEDB_DATABASE:=}" "${TYPEDB_USERNAME:=}" "${TYPEDB_PASSWORD:=}" "${TOK:=}"
158
+
159
+ if [ "$NEEDS_CLUSTER" = 1 ]; then
160
+ if [ -z "$TYPEDB_URL" ]; then
161
+ [ "$MODE" = json ] && { receipt_all_cannot_run "no substrate credentials in this environment"; exit 0; }
162
+ echo "CANNOT RUN: no TYPEDB_URL in one.ie/web/.env" >&2; exit 3
163
+ fi
164
+
165
+ # Same retry contract as q(). A cloud sign-in is the single point every clustered
166
+ # check hangs off, and it is BOTH flaky and slow: measured 2026-07-29, a cold auth
167
+ # against TypeDB Cloud took 4.1s, and a single-shot attempt reported "TypeDB signin
168
+ # failed" while the very next invocation authenticated fine. One transient blip here
169
+ # turns every substrate check in the run into a cannot-run — an infrastructure
170
+ # verdict pronounced on one dropped request. -m 30, not 15, for the cold case.
171
+ for attempt in 1 2 3; do
172
+ TOK=$(curl -s -m 30 -X POST "$TYPEDB_URL/v1/signin" -H 'Content-Type: application/json' \
173
+ -d "{\"username\":\"$TYPEDB_USERNAME\",\"password\":\"$TYPEDB_PASSWORD\"}" \
174
+ | python3 -c 'import sys,json;print(json.load(sys.stdin).get("token",""))' 2>/dev/null)
175
+ [ -n "$TOK" ] && break
176
+ [ "$attempt" -lt 3 ] && sleep "$attempt"
177
+ done
178
+ if [ -z "$TOK" ]; then
179
+ [ "$MODE" = json ] && { receipt_all_cannot_run "substrate unreachable — sign-in failed"; exit 0; }
180
+ echo "CANNOT RUN: TypeDB signin failed at $TYPEDB_URL" >&2; exit 3
181
+ fi
182
+ fi # NEEDS_CLUSTER
183
+
184
+ # read-transaction query → raw JSON on stdout
185
+ #
186
+ # RETRIES, because the code this repo already ships retries. src/lib/substrate.ts
187
+ # documents its own RETRYABLE set — {404, 429, 502, 503, 504} — with the note that
188
+ # TypeDB Cloud "intermittently answers a perfectly valid query with 404 page not
189
+ # found, observed twice in ~60 requests (≈3%), non-deterministic: the SAME query
190
+ # string that 404s succeeds on the next attempt."
191
+ #
192
+ # This checker talks to the cluster DIRECTLY rather than through that helper, so
193
+ # it inherited none of it. At ~3% per query and ~10 queries in an `all` run, the
194
+ # odds of at least one spurious failure are ~26% — and measured 2026-07-29 that is
195
+ # exactly what happened: `all` reported four checks as CANNOT RUN against
196
+ # production, and every one of those four queries answered first-try when run
197
+ # individually seconds later. Three earlier cloud attempts were written up as
198
+ # substrate outages on this same evidence. Two of them were up-windows.
199
+ #
200
+ # Read-only by construction (transactionType is hardcoded "read"), so retrying a
201
+ # 404 carries none of the non-idempotency risk substrate.ts scopes out for writes.
202
+ # Backoff matches substrate.ts: 500ms then 1000ms.
203
+ q() {
204
+ python3 - "$1" "$TYPEDB_DATABASE" > /tmp/fc-q.json <<'PY'
205
+ import json,sys
206
+ print(json.dumps({"databaseName":sys.argv[2],"transactionType":"read","query":sys.argv[1]}))
207
+ PY
208
+ local body code attempt
209
+ for attempt in 1 2 3; do
210
+ body=$(curl -s -m 60 -w $'\n%{http_code}' -X POST "$TYPEDB_URL/v1/query" \
211
+ -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
212
+ --data-binary @/tmp/fc-q.json)
213
+ code="${body##*$'\n'}"; body="${body%$'\n'*}"
214
+ case "$code" in
215
+ 404|429|502|503|504|000|"") [ "$attempt" -lt 3 ] && { sleep "0.$((attempt*5))"; continue; } ;;
216
+ esac
217
+ printf '%s' "$body"; return 0
218
+ done
219
+ printf '%s' "$body"
220
+ }
221
+ rows() { python3 -c '
222
+ import sys,json
223
+ try:
224
+ d=json.load(sys.stdin)
225
+ a=d.get("answers") or d.get("data") or []
226
+ print(len(a) if isinstance(a,list) else 0)
227
+ except Exception: print(0)'; }
228
+
229
+ # extracts the numeric value from a `reduce $n = count(...)` answer row —
230
+ # TypeDB 3.x has no `return count($x)` (TQL03 syntax error); `reduce` is the
231
+ # only aggregate form, and it always answers ONE row holding the value, so
232
+ # rows() (which counts answer rows) reports 1-or-0 regardless of the count.
233
+ # val prints the reduce VALUE, or the literal string ERR when the response is not a
234
+ # readable answer envelope.
235
+ #
236
+ # It used to `except Exception: print(0)`. That made a 503 body, an HTML error page,
237
+ # a dropped connection and a genuine count of zero ALL indistinguishable — and every
238
+ # caller reads 0 as "empty", so an unreachable substrate reported RED. That is the
239
+ # red-vs-cannot-run confusion this promise exists to eliminate, living inside the
240
+ # checker that polices it. (Same trap the gaps board avoided by using
241
+ # typedbQueryDetail instead of typedbQuery, which answers [] on failure.)
242
+ #
243
+ # A `reduce` ALWAYS answers exactly one row holding the value, so a missing/short
244
+ # answers list is a failed query, never an empty result. Counting answer ROWS here
245
+ # would report 1-or-0 regardless of the count — a separate trap, documented above.
246
+ val() { python3 -c '
247
+ import sys,json
248
+ try:
249
+ d=json.load(sys.stdin)
250
+ except Exception:
251
+ print("ERR"); raise SystemExit
252
+ if not isinstance(d, dict) or d.get("err") or d.get("error"):
253
+ print("ERR"); raise SystemExit
254
+ a=d.get("answers")
255
+ if not isinstance(a, list) or not a:
256
+ print("ERR"); raise SystemExit
257
+ try:
258
+ v=next(iter(a[0]["data"].values()))
259
+ print(v.get("value", 0) if isinstance(v, dict) else v)
260
+ except Exception:
261
+ print("ERR")'; }
262
+
263
+ # Guard for every `q ... | val` caller: turn ERR into cannot-run (3), never red (1).
264
+ # Usage: n=$(q '...' | val); num "$n" "what was being counted" || return $?
265
+ num() {
266
+ case "$1" in
267
+ ''|ERR) cannot "substrate query failed while counting $2 — nothing was proven either way"; return 3 ;;
268
+ esac
269
+ return 0
270
+ }
271
+
272
+ # ── checks ────────────────────────────────────────────────────────────────
273
+
274
+ check_schema_live() {
275
+ local s; s=$(curl -s -m 30 "$TYPEDB_URL/v1/databases/$TYPEDB_DATABASE/schema" \
276
+ -H "Authorization: Bearer $TOK")
277
+ for v in objective deliverable attempt; do
278
+ echo "$s" | grep -q "\"$v\"" || return $(red "thing-type @values lacks \"$v\" — migration 0044 not deployed")
279
+ done
280
+ echo "$s" | grep -q "provenance" || return $(red "hypothesis lacks provenance — 0044 not deployed")
281
+ say "deployed schema carries the ladder"; echo "ok schema-live"
282
+ }
283
+
284
+ # Counts objectives THE RECEIVER WROTE, which is not the same as objectives.
285
+ #
286
+ # The discriminator is the `rung:<type>:<slug>:<ordinal>` tag. `factory:elaborate`
287
+ # stamps it (resolvers/factory.ts: `const tag = rungTag(type, slug, ordinal)`),
288
+ # and `cleanTags()` strips `rung:` from caller-supplied tags, so it cannot be
289
+ # forged through the API. A `.tql` migration insert carries none of it.
290
+ #
291
+ # It used to count bare `thing-type "objective"` and report the shortfall as
292
+ # "zero receiver-written objective things". Measured 2026-07-29:
293
+ # local rail 9 objectives, 0 carrying a rung: tag -> check was GREEN
294
+ # prod rail 10 objectives, 1 carrying a rung: tag
295
+ # The 9 are `schema/migrations/seed-factory-vision.tql` — raw inserts. So on the
296
+ # local rail this check passed while the receiver had written NOTHING, which is
297
+ # precisely the sentence its own red says is impossible.
298
+ #
299
+ # Same defect, same day, as the tracer seeder that raw-inserted instead of
300
+ # calling the receiver. Both were green on scaffolding. main's
301
+ # `not { $t has tag "tracer:hand-seeded"; }` exclusion did not help and is now
302
+ # redundant: the tracer is a DELIVERABLE, so it never matched this query at all.
303
+ # An exclusion list cannot answer "did a receiver write this" — only a tag the
304
+ # receiver alone can stamp can.
305
+ check_write_path() {
306
+ local n; n=$(q 'match $t isa thing, has thing-type "objective", has tag $g;
307
+ $g like "rung:objective:.*";
308
+ reduce $n = count($t);' | val)
309
+ num "$n" "receiver-written objective things" || return 3
310
+ [ "${n:-0}" -gt 0 ] || return $(red "zero objectives carry a rung: tag — every objective row was inserted by hand or by migration, so nothing proves factory:elaborate writes the ladder")
311
+ say "$n receiver-written objective rows"; echo "ok write-path"
312
+ }
313
+
314
+ check_seeded() {
315
+ local n; n=$(q 'match $t isa thing, has thing-type "objective", has tag "vision:row"; reduce $n = count($t);' | val)
316
+ num "$n" "vision:row objectives" || return 3
317
+ [ "${n:-0}" -ge 9 ] || return $(red "only ${n:-0} vision:row objectives — expected >= 9 (vision.md's map)")
318
+ say "$n vision rows seeded"; echo "ok seeded"
319
+ }
320
+
321
+ check_driver() {
322
+ local n; n=$(q 'match $p isa thing, has thing-type "do-plan", has tag "slug:factory";
323
+ let $t in ready-tasks($p); reduce $n = count($t);' | val)
324
+ num "$n" "ready tasks" || return 3
325
+ [ "${n:-0}" -gt 0 ] || return $(red "ready-tasks() empty — the driver has nothing to schedule")
326
+ say "$n ready tasks"; echo "ok driver"
327
+ }
328
+
329
+ check_gaps_visible() {
330
+ # The proof that downward completeness works: the dark wires are UNCOVERED
331
+ # deliverables — present in the graph, served by no task. Absence made visible.
332
+ local n; n=$(q 'match $p isa thing, has thing-type "do-plan", has tag "slug:factory";
333
+ let $d in uncovered($p); reduce $n = count($d);' | val)
334
+ num "$n" "uncovered deliverables" || return 3
335
+ [ "${n:-0}" -gt 0 ] || return $(red "uncovered() empty — either nothing is seeded, or absence is still invisible")
336
+ say "$n uncovered deliverables named"; echo "ok gaps-visible"
337
+ }
338
+
339
+ check_belief_guard() {
340
+ # Question 4 fails OPEN: with no signal->hypothesis edges, ungrounded() returns
341
+ # empty, which reads as "nothing ungrounded".
342
+ #
343
+ # REWRITTEN 2026-07-28 — the original form asserted DATA:
344
+ # match (source: $s, target: $h) isa path; $s isa signal; $h isa hypothesis;
345
+ # against the production `one` db, and required count > 0. That check could
346
+ # never go green by working, for a structural reason, not a timing one:
347
+ # * the runtime writes signals to D1, not TypeDB (root CLAUDE.md: "D1 for
348
+ # signals/messages"). `one` holds ZERO `signal` relations — measured.
349
+ # * writeEvidenceEdges() (resolvers/learning.ts) grounds a belief by matching
350
+ # `$sig isa signal` IN TYPEDB. With no signal there, it writes no edge —
351
+ # so the count stays 0 no matter how much real traffic flows.
352
+ # A kill-switch that cannot pass by working gates nothing. Same failure class
353
+ # as the `node --check` proof that false-failed forever (learnings 2026-07-04):
354
+ # the red was for the wrong reason, so the switch was decorative.
355
+ #
356
+ # What the deliverable actually promises is the WIRE — "ungrounded() returning
357
+ # empty means grounded rather than unwired". So assert the wire, against a REAL
358
+ # substrate (repo rule: never mock TypeDB), in a SCRATCH database, never `one`.
359
+ local t="$ROOT/one.ie/web/src/lib/resolvers/learning-evidence.test.ts"
360
+ [ -f "$t" ] || return $(red "no learning-evidence.test.ts — the evidence wire is unproven")
361
+ [ -n "${TYPEDB_TEST_DB:-}" ] \
362
+ || return $(cannot "no TYPEDB_TEST_DB scratch database — the evidence wire needs a real substrate to prove, and must never be proven against the production db")
363
+ local out
364
+ # Hand the test the SAME credentials this script already parsed out of .env.
365
+ # vitest does not read that file, so without this the test builds `${''}/v1/signin`
366
+ # and dies on "Failed to parse URL" — which the pass-grep below then reports as a
367
+ # RED ("ungrounded() does not discriminate"). That is an environment gap wearing
368
+ # a red's clothes, the exact 1-vs-3 conflation this script's header forbids.
369
+ # `:-` on every expansion: this script runs under `set -u`, and an assignment
370
+ # prefix that dereferences a possibly-unset name aborts the whole check with
371
+ # "unbound variable" — which surfaces as a RED for a reason that has nothing to
372
+ # do with the evidence wire.
373
+ out=$(cd "$ROOT/one.ie/web" && \
374
+ TYPEDB_URL="${TYPEDB_URL:-}" \
375
+ TYPEDB_USERNAME="${TYPEDB_USERNAME:-}" \
376
+ TYPEDB_PASSWORD="${TYPEDB_PASSWORD:-}" \
377
+ TYPEDB_TEST_DB="${TYPEDB_TEST_DB:-}" \
378
+ bunx vitest run src/lib/resolvers/learning-evidence.test.ts 2>&1)
379
+ # The scratch db needs the schema loaded; the test defines none of its own. An
380
+ # empty db answers "Type label 'hypothesis' not found", which is cannot-run.
381
+ printf '%s' "$out" | grep -qE "Type label '(hypothesis|group|path)' not found" \
382
+ && return $(cannot "scratch db $TYPEDB_TEST_DB has no schema — load it first (see text/typedb-production-problem-solutions.md)")
383
+ printf '%s' "$out" | grep -qE 'Failed to parse URL|ERR_INVALID_URL' \
384
+ && return $(cannot "scratch db reachable but TYPEDB_URL/credentials did not reach vitest")
385
+ # A SKIPPED test is not a proof. learning-evidence.test.ts is
386
+ # `describe.skipIf(!TYPEDB_TEST_DB)`, so without the guard above vitest exits 0
387
+ # having run nothing — the exact fail-open this check exists to forbid.
388
+ printf '%s' "$out" | grep -qE 'Tests[[:space:]]+.*skipped' \
389
+ && return $(red "evidence-wire tests SKIPPED — a skipped test is not a proof (fail-open)")
390
+ printf '%s' "$out" | grep -qE 'Tests[[:space:]]+[1-9][0-9]*[[:space:]]+passed' \
391
+ || return $(red "evidence-wire tests did not pass — ungrounded() does not discriminate an edged belief from an edgeless one")
392
+ say "evidence wire proven against scratch db $TYPEDB_TEST_DB"; echo "ok belief-guard"
393
+ }
394
+
395
+ # The SDK's generated zod enum is a SECOND gate in front of TypeDB: it rejects
396
+ # a thing-type it doesn't know before the write ever reaches the database.
397
+ # Deploying 0044 without regenerating leaves the ladder unwritable and the
398
+ # failure looks like a validation error, not a migration gap.
399
+ check_sdk_enum() {
400
+ local f="$ROOT/packages/sdk/src/generated/schemas.ts"
401
+ [ -f "$f" ] || return $(red "no generated/schemas.ts")
402
+ for v in objective deliverable attempt; do
403
+ grep -qE "\"thing-type\": z\.enum\(\[[^]]*\"$v\"" "$f" \
404
+ || return $(red "generated zod thing-type enum lacks \"$v\" — codegen not re-run after 0044")
405
+ done
406
+ say "sdk enum carries the ladder"; echo "ok sdk-enum"
407
+ }
408
+
409
+ # The factory's read side reaches every surface through ONE door: fn:run + its
410
+ # ALLOWLIST. Adding a fun to the allowlist lights it up on SDK, MCP, CLI, react,
411
+ # channels chat and workflow tool-steps at once — text/generate-from-tql-plan.md
412
+ # § Decision 3. A fun in FN_MAP but not on the allowlist is generated, typed, and
413
+ # unreachable.
414
+ #
415
+ # This used to grep each of the four consumer files for the fun name, because the
416
+ # allowlist WAS four literal copies. C8 consolidated them into one exported
417
+ # source, and check_fn_one_allowlist now FORBIDS a surface from carrying its own
418
+ # literal list — so the old grep could only be satisfied by re-adding quoted
419
+ # names to files its sibling check forbids from holding them. Two checks in the
420
+ # same proof: join cannot both be honoured that way, and a green bought with a
421
+ # comment naming a fun would be exactly the symbol-that-is-never-called this
422
+ # script's header rails against.
423
+ #
424
+ # So the name-grep moved to the ONE source, and the per-surface half became "does
425
+ # this surface read that source". That is strictly stronger than four independent
426
+ # copies: four copies could each pass while drifting apart, whereas one source +
427
+ # four readers cannot.
428
+ check_tracer() {
429
+ # PORTED FROM MAIN 2026-07-28 + FIXED. Main's version counted with
430
+ # return count($t) ... | rows
431
+ # which is broken twice over, and this script documents both traps itself:
432
+ # * TypeDB 3.x has NO `return count($x)` — it is a TQL03 syntax error;
433
+ # `reduce` is the only aggregate form. So the query never answered.
434
+ # * rows() counts ANSWER ROWS, and a reduce always answers exactly one,
435
+ # so it reported 1-or-0 regardless of the actual count.
436
+ # Net effect: check_tracer reported RED no matter what the graph held — a
437
+ # fifth check in this plan that could not pass by working. Now uses the same
438
+ # reduce/val/num path as every other counting check, so an unreachable
439
+ # substrate is cannot-run (3) and a genuine zero is red (1).
440
+ local n; n=$(q 'match $t isa thing, has thing-type "deliverable", has tag "tracer:hand-seeded"; reduce $c = count($t);' | val)
441
+ num "$n" "tracer:hand-seeded deliverables" || return 3
442
+ [ "${n:-0}" -gt 0 ] || return $(red "no tracer:hand-seeded deliverable — the chain has not been proven end to end")
443
+ local u; u=$(q 'match $p isa thing, has thing-type "do-plan", has tag "slug:factory";
444
+ let $d in uncovered($p); $d has tag "tracer:hand-seeded"; reduce $c = count($d);' | val)
445
+ num "$u" "tracer deliverables returned by uncovered()" || return 3
446
+ [ "${u:-0}" -gt 0 ] || return $(red "the tracer deliverable exists but uncovered() does not return it — the query half of the chain is broken")
447
+ say "tracer row seeded and returned by uncovered()"; echo "ok tracer"
448
+ }
449
+
450
+ # check_stream and check_walk_speed used to be defined HERE as well as below.
451
+ # Both sides of the C6 merge carried byte-identical bodies, so git auto-merged
452
+ # them in twice with no conflict — and bash takes the LAST definition silently.
453
+ # Two live copies of an assertion is how one of them drifts unnoticed. The
454
+ # surviving pair is main's, which carries the rationale comments this one lacked.
455
+
456
+ check_fn_exposed() {
457
+ local m="$ROOT/packages/sdk/src/generated/fn-map.ts"
458
+ local a="$ROOT/packages/sdk/src/fn-allowlist.ts"
459
+ [ -f "$a" ] || return $(red "no packages/sdk/src/fn-allowlist.ts — there is no allowlist to be on")
460
+ # Kept identical to check_fn_one_allowlist's list on purpose: two checks
461
+ # disagreeing about how many surfaces exist is how a fifth copy gets in.
462
+ local surfaces=(
463
+ "$ROOT/one.ie/web/src/lib/resolvers/fn.ts"
464
+ "$ROOT/packages/mcp/src/tools/fn.ts"
465
+ "$ROOT/packages/cli/src/fn.ts"
466
+ "$ROOT/channels/src/tools/fn.ts"
467
+ )
468
+ for fn in ready-tasks uncovered unrealised incomplete; do
469
+ # Anchored to the ENTRY, not to a mention. fn-map.ts ends with a `FnName`
470
+ # union listing every name, so a bare `grep "$fn"` stays green with the entry
471
+ # deleted — measured: the red-proof for this arm passed with `uncovered`
472
+ # removed from FN_MAP. A grep that cannot go red for the reason it claims is
473
+ # not a proof (this script's header).
474
+ grep -qE "^[[:space:]]*\"$fn\": \{" "$m" \
475
+ || return $(red "fn-map has no $fn entry — codegen not re-run after the factory .tql files landed")
476
+ # Quote-agnostic: the source is double-quoted today, but a re-format to
477
+ # single quotes must not fake a red on an allowlist that carries the fun.
478
+ grep -qE "['\"]$fn['\"]" "$a" \
479
+ || return $(red "the shared allowlist lacks $fn — generated and typed, unreachable from every surface")
480
+ done
481
+ for s in "${surfaces[@]}"; do
482
+ [ -f "$s" ] || return $(red "$s is missing — a declared allowlist consumer does not exist")
483
+ # An IMPORT statement, not a mention. Every one of the four surfaces names
484
+ # `@oneie/sdk/fn-allowlist` in a comment as well, so the unanchored form its
485
+ # sibling check uses stays green on a file that has stopped importing it.
486
+ grep -qE "^[[:space:]]*import .*(fn-allowlist|FN_ALLOWLIST)" "$s" \
487
+ || return $(red "$(basename "$(dirname "$s")")/$(basename "$s") does not import the shared allowlist — the factory funs are invisible from that surface")
488
+ done
489
+ say "factory funs in FN_MAP and on the one allowlist all four surfaces read"; echo "ok fn-exposed"
490
+ }
491
+
492
+ # The driver funs take an ENTITY param — ready-tasks($plan: thing). buildTypeQL
493
+ # in the web resolver binds params by their JS runtime type and emits
494
+ # `let $plan = "..."`, which cannot bind an entity; the `isEntity` flag the
495
+ # generator already writes into FN_MAP is read by nothing. So the door is built
496
+ # and will not open for any fun with an entity param — which includes three
497
+ # funs ALREADY on the allowlist (optimal_route, cheapest_provider,
498
+ # actor_classification). Fixing it is shared work, not factory-only.
499
+ check_fn_entity_args() {
500
+ # tests/unit/ is relative to the WEB package — that is where vitest.config.ts
501
+ # lives and where `tests/unit/**` is already on the include list. The repo root
502
+ # has no package.json and no vitest binary, so a root-relative path could never
503
+ # go green (`bun vitest` there answers "Script not found").
504
+ local t="$ROOT/one.ie/web/tests/unit/fn-entity-args.test.ts"
505
+ [ -f "$t" ] || return $(red "no one.ie/web/tests/unit/fn-entity-args.test.ts — entity-param binding unproven")
506
+ (cd "$ROOT/one.ie/web" && bunx vitest run tests/unit/fn-entity-args.test.ts >/dev/null 2>&1) \
507
+ || return $(red "buildTypeQL does not bind entity params by match — the driver funs cannot execute through fn:run")
508
+ say "fn:run binds entity params"; echo "ok fn-entity-args"
509
+ }
510
+
511
+ # ONE allowlist, not three. generate-from-tql-plan.md § Wave 4 promised "no
512
+ # sixth copy of the truth" and the implementation shipped three — so a fun
513
+ # reachable from chat can be invisible to the CLI. This asserts a single
514
+ # exported source AND that no surface still declares its own literal Set.
515
+ check_fn_one_allowlist() {
516
+ local src="$ROOT/packages/sdk/src/fn-allowlist.ts"
517
+ [ -f "$src" ] || return $(red "no packages/sdk/src/fn-allowlist.ts — the allowlists have no shared source")
518
+ # FOUR surfaces, not three. The plan, the promise and this check all said three;
519
+ # `channels/src/tools/fn.ts` was a pre-existing fourth copy nobody counted, and it
520
+ # was STALE — missing the four driver funs, so chat could not name them. A check
521
+ # that stops at three would have gone green with that copy alive, which is the
522
+ # exact failure this deliverable exists to prevent ("allowlisting a fun lights up
523
+ # EVERY surface"). Adding a fifth consumer? Add it here in the same commit.
524
+ local surfaces=(
525
+ "$ROOT/one.ie/web/src/lib/resolvers/fn.ts"
526
+ "$ROOT/packages/mcp/src/tools/fn.ts"
527
+ "$ROOT/packages/cli/src/fn.ts"
528
+ "$ROOT/channels/src/tools/fn.ts"
529
+ )
530
+ for s in "${surfaces[@]}"; do
531
+ [ -f "$s" ] || return $(red "$s is missing — a declared allowlist consumer does not exist")
532
+ grep -qE "fn-allowlist|FN_ALLOWLIST" "$s" \
533
+ || return $(red "$(basename "$(dirname "$s")")/$(basename "$s") does not import the shared allowlist")
534
+ # Unanchored + name-agnostic: the old form was `^const ALLOWLIST = new Set<string>\(\[`,
535
+ # which a rename or a re-indent walked straight past. Any local Set-of-strings
536
+ # literal assigned to an ALLOWLIST-ish name is copy N of the truth.
537
+ grep -qE "(const|let|var)[[:space:]]+[A-Za-z_]*ALLOW[A-Za-z_]*[[:space:]]*(:[^=]*)?=[[:space:]]*new Set" "$s" \
538
+ && return $(red "$(basename "$(dirname "$s")")/$(basename "$s") still declares its own literal allowlist — that is copy N of the truth")
539
+ done
540
+ say "one allowlist, four consumers"; echo "ok fn-one-allowlist"
541
+ }
542
+
543
+ # The gaps board must be HONEST WHEN EMPTY. A board that renders zero rows as a
544
+ # clean slate is the fail-open trap in visual form — the operator reads "no gaps"
545
+ # when the truth is "nothing seeded". So the component must distinguish the two
546
+ # and the check asserts the empty-state copy exists alongside the query wiring.
547
+ check_view_gaps() {
548
+ local c
549
+ c=$(ls "$ROOT"/one.ie/web/src/components/factory/*.tsx 2>/dev/null | head -1)
550
+ [ -n "$c" ] || return $(red "no one.ie/web/src/components/factory/ component — the gaps board does not exist")
551
+ grep -qE "uncovered|ready-tasks|readyTasks" "$c" \
552
+ || return $(red "gaps board does not query uncovered()/ready-tasks() — it renders nothing real")
553
+ grep -qiE "nothing seeded|no rows yet|not seeded|graph is empty" "$c" \
554
+ || return $(red "gaps board has no distinct EMPTY state — zero rows would read as 'no gaps', which is the fail-open trap in visual form")
555
+ say "gaps board queries the ladder and is honest when empty"; echo "ok view-gaps"
556
+ }
557
+
558
+ # The templates are where every FUTURE plan inherits or repeats this session's
559
+ # mistakes. Three concrete ones, all hit live while authoring this promise:
560
+ # 1. tier: — the template teaches "trivial|simple|complex" while /do resolves
561
+ # tier from this field as PATCH|FIX|FEATURE|SCHEMA (do.md:189 + :205).
562
+ # The corpus is split across both vocabularies, which is the tell.
563
+ # 2. deliverables: — the example shows UNQUOTED values, so any value carrying
564
+ # a backtick, colon or brace makes the frontmatter invalid YAML.
565
+ # 3. proof: — no mention that a check can exit "cannot run" (3) rather than
566
+ # red (1), so an outage reads as a broken promise.
567
+ #
568
+ # The RATCHET is the instrument, not a cleanup: 102 of 294 filled todos already
569
+ # fail to parse. Demanding all be fixed would stall this promise; demanding the
570
+ # number never RISES is enforceable per cycle and costs nothing (factory-plan
571
+ # §6.1). Baseline is recorded, and the check fails if it grows.
572
+ FACTORY_YAML_BASELINE=102
573
+ check_templates() {
574
+ local tt="$ROOT/text/template-todo.md" tf="$ROOT/text/template-feature.md"
575
+ grep -qE 'PATCH.*FIX.*FEATURE.*SCHEMA' "$tt" \
576
+ || return $(red "template-todo.md tier: does not name the vocabulary /do actually reads (PATCH|FIX|FEATURE|SCHEMA)")
577
+ grep -qE '^\s*#?\s*-\s+\w+:\s+"' "$tt" \
578
+ || return $(red "template-todo.md deliverables: example is unquoted — it teaches the shape that produces invalid YAML")
579
+ grep -qiE 'cannot run|exit 3' "$tf" \
580
+ || return $(red "template-feature.md does not distinguish a red proof from one that could not run")
581
+ local n
582
+ n=$(cd "$ROOT/text" && python3 -c "
583
+ import glob,yaml,sys
584
+ b=0
585
+ for f in glob.glob('*-todo.md'):
586
+ s=open(f, errors='replace').read()
587
+ if not s.startswith('---'): continue
588
+ try: yaml.safe_load(s.split('---')[1])
589
+ except Exception: b+=1
590
+ print(b)")
591
+ [ "${n:-999}" -le "$FACTORY_YAML_BASELINE" ] \
592
+ || return $(red "unparseable todo frontmatter rose to $n (baseline $FACTORY_YAML_BASELINE) — a new plan learned the broken shape")
593
+ say "templates corrected; unparseable todos $n <= $FACTORY_YAML_BASELINE"; echo "ok templates"
594
+ }
595
+
596
+ # THE HUMAN IS A MONITOR, NOT A GATE. The factory runs without anyone; the
597
+ # operator watches a stream and can stop it. Review as a GATE is serial and
598
+ # becomes the bottleneck — that is the failure mode of every "just add more
599
+ # review" factory. Streaming + stop is parallel: it never blocks a cycle, and
600
+ # it never lets one run unwatched either.
601
+ check_stream() {
602
+ local c
603
+ c=$(ls "$ROOT"/one.ie/web/src/components/factory/*.tsx 2>/dev/null | head -1)
604
+ [ -n "$c" ] || return $(red "no factory component — nothing streams")
605
+ grep -qiE "EventSource|text/event-stream|useSWR|setInterval|poll" "$c" \
606
+ || return $(red "/factory does not stream — a static page cannot show what is being built RIGHT NOW")
607
+ grep -qiE "stop|halt|abort" "$c" \
608
+ || return $(red "/factory has no stop control — the operator can watch but not intervene, which is lights-off with a window")
609
+ grep -rqE '"(do:halt|workflow:stop)"' "$ROOT/one.ie/web/src/lib/resolvers/" 2>/dev/null \
610
+ || return $(red "the stop control is not wired to a receiver that actually halts a run")
611
+ say "factory streams and can be stopped"; echo "ok stream"
612
+ }
613
+
614
+ # THE DETERMINISTIC LIFECYCLE WALK is what replaces human review. Not a model
615
+ # judging a diff — a script driving the real feature through its real lifecycle
616
+ # (signup -> purchase -> ...) against a running system, asserting it WORKS and
617
+ # is FAST. Zero LLM tokens, exits 0 iff every stop is green.
618
+ #
619
+ # Two things this check demands that do-walk.sh cannot do today:
620
+ # 1. max_ms: per stop — a lifecycle that works but takes 9s is a regression
621
+ # no pass/fail assert can see.
622
+ # 2. stops that DRIVE the lifecycle rather than grep for a symbol. A
623
+ # `grep -q ComponentName` stop passes on a component that is imported
624
+ # nowhere — presence is not proof, and most existing walks are greps.
625
+ check_walk_speed() {
626
+ grep -q "max_ms" "$ROOT/.claude/scripts/do-walk.sh" \
627
+ || return $(red "do-walk.sh has no max_ms — the walk can prove a lifecycle works but never that it is fast")
628
+ [ -f "$ROOT/text/factory-agents.md" ] \
629
+ || return $(red "no text/factory-agents.md — no deterministic lifecycle walk exists")
630
+ grep -q "max_ms" "$ROOT/text/factory-agents.md" \
631
+ || return $(red "the factory walk has no speed budget on any stop")
632
+ (cd "$ROOT" && bash .claude/scripts/do-walk.sh factory --agents >/dev/null 2>&1) \
633
+ || return $(red "do-walk.sh factory --agents did not exit 0 — the lifecycle does not pass its own walk")
634
+ say "lifecycle walk passes with speed budgets"; echo "ok walk-speed"
635
+ }
636
+
637
+ # The docs must stop saying the factory is unarmed once it is.
638
+ check_docs() {
639
+ [ -f "$ROOT/text/factory-docs.md" ] || return $(red "text/factory-docs.md missing")
640
+ grep -qiE 'nothing here is armed|no .?text/factory\.md.? promise' "$ROOT/text/factory-plan.md" \
641
+ && return $(red "factory-plan.md still says the factory is unarmed")
642
+ grep -qiE 'live +0%|LIVE +░{20}' "$ROOT/text/vision.md" \
643
+ && return $(red "vision.md still scores the factory at 0% LIVE")
644
+ # The public page was outside this gate, which is how it kept claiming "one of
645
+ # thirteen" while production ran fifteen of sixteen. This asserts the WIRE, not
646
+ # the prose: /do is the page about the factory, so it must carry a door to the
647
+ # board. Delete the link and this goes red.
648
+ local do_page="$ROOT/one.ie/web/src/pages/do.astro"
649
+ [ -f "$do_page" ] || return $(red "one.ie/web/src/pages/do.astro missing")
650
+ grep -q 'href="/factory"' "$do_page" \
651
+ || return $(red "/do describes the factory but has no door to /factory — the board is unreachable from the page that explains it")
652
+ grep -qiE 'one of thirteen|thirteen checks, and .*all thirteen were red' "$do_page" \
653
+ && return $(red "do.astro still reports the pre-ship tally — the board says otherwise")
654
+ # The promise's OWN canon was outside this gate — which is how § The honest
655
+ # status went on calling `stream` and `walk-speed` "genuinely red — not built"
656
+ # and `tracer` "defective" after all three had gone green. A doc that reports a
657
+ # stale verdict on its own proof is precisely the drift this promise exists to
658
+ # catch, sitting in the promise. So gate it against the CHECKER, not against a
659
+ # frozen phrase: any check the canon calls red must actually BE red.
660
+ #
661
+ # Only red-shaped claims are gated. A "cannot run" claim is NOT stale when the
662
+ # check passes here — cannot-run is a property of the machine you are standing
663
+ # on (TYPEDB_TEST_DB, a reachable cluster), so asserting it would make this go
664
+ # red on a laptop that merely has more provisioned than the writer's did.
665
+ local promise="$ROOT/text/factory.md"
666
+ [ -f "$promise" ] || return $(red "text/factory.md missing — the promise has no canon")
667
+ local claimed name crc
668
+ claimed=$(python3 - "$promise" <<'PY'
669
+ import re, sys
670
+ s = open(sys.argv[1], errors="replace").read()
671
+ m = re.search(r'^## The honest status$(.*?)(?=^## |\Z)', s, re.M | re.S)
672
+ names = set()
673
+ if m is None:
674
+ # The gate anchors on this heading. Rename the heading and every claim under
675
+ # it stops being read — the gate would pass while checking nothing, which is
676
+ # the `all`-runs-13-of-16 fail-open in a second costume. Missing anchor is a
677
+ # RED, not a skip.
678
+ print("__NO_HONEST_STATUS_SECTION__")
679
+ else:
680
+ for para in re.split(r'\n(?=- )', m.group(1)):
681
+ if not re.search(r'genuinely red|not built|defective|\bis red\b', para, re.I):
682
+ continue
683
+ # A paragraph that RECORDS a past red and its fix is reconciled history,
684
+ # not a stale verdict — "was defective, now passes by working" must stay
685
+ # writable. Only an unreconciled claim of CURRENT redness is drift.
686
+ if re.search(r'now (?:green|passes)|since fixed|fixed,|was red|no longer', para, re.I):
687
+ continue
688
+ names.update(re.findall(r'`([^`]+)`', para))
689
+ print(" ".join(sorted(names)))
690
+ PY
691
+ )
692
+ [ "$claimed" = "__NO_HONEST_STATUS_SECTION__" ] \
693
+ && return $(red "text/factory.md has no '## The honest status' section — the canon gate anchors on that heading and would pass while reading nothing")
694
+ for name in $claimed; do
695
+ # Only names that are real checks; `docs` itself never (infinite recursion).
696
+ case " $CHECK_ORDER " in *" $name "*) ;; *) continue ;; esac
697
+ [ "$name" = "docs" ] && continue
698
+ "check_${name//-/_}" >/dev/null 2>&1; crc=$?
699
+ [ $crc -eq 0 ] \
700
+ && return $(red "text/factory.md § The honest status still calls \`$name\` red or unbuilt, and it passes — the promise's own canon is stale")
701
+ done
702
+ say "docs reconciled; canon carries no stale red verdict"; echo "ok docs"
703
+ }
704
+
705
+ case "${1:-all}" in
706
+ schema-live) check_schema_live ;;
707
+ sdk-enum) check_sdk_enum ;;
708
+ docs) check_docs ;;
709
+ fn-exposed) check_fn_exposed ;;
710
+ fn-entity-args) check_fn_entity_args ;;
711
+ fn-one-allowlist) check_fn_one_allowlist ;;
712
+ view-gaps) check_view_gaps ;;
713
+ tracer) check_tracer ;;
714
+ stream) check_stream ;;
715
+ walk-speed) check_walk_speed ;;
716
+ templates) check_templates ;;
717
+ tracer) check_tracer ;;
718
+ stream) check_stream ;;
719
+ walk-speed) check_walk_speed ;;
720
+ write-path) check_write_path ;;
721
+ seeded) check_seeded ;;
722
+ driver) check_driver ;;
723
+ gaps-visible) check_gaps_visible ;;
724
+ belief-guard) check_belief_guard ;;
725
+ json) run_receipt ;;
726
+ # `all` iterates CHECK_ORDER — the SAME list json mode runs. It used to carry
727
+ # its own hand-maintained copy, and that copy was left at 13 while CHECK_ORDER
728
+ # grew to 16: tracer, stream and walk-speed were silently skipped, so `all`
729
+ # exited 0 while three legs of the promise's own proof were red or unrun. A
730
+ # fail-open in the aggregate runner of the script whose whole purpose is to
731
+ # stop fail-opens. There is now ONE list; a new check cannot be half-added.
732
+ #
733
+ # Exit preserves the 1-vs-3 distinction the header calls the point: any red
734
+ # wins (1), else any cannot-run (3), else green (0). Collapsing a cannot-run
735
+ # into a red here would send someone to fix the build when the substrate was
736
+ # simply unreachable.
737
+ all) rc=0
738
+ for c in $CHECK_ORDER; do
739
+ "check_${c//-/_}"; crc=$?
740
+ case $crc in
741
+ 0) ;;
742
+ 3) [ $rc -eq 0 ] && rc=3 ;;
743
+ *) rc=1 ;;
744
+ esac
745
+ done; exit $rc ;;
746
+ *) echo "unknown check: $1" >&2; exit 3 ;;
747
+ esac