@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,128 @@
1
+ #!/usr/bin/env bash
2
+ # typedb-cluster-status.sh — read the TypeDB Cloud CONTROL plane and answer the
3
+ # one question the data plane cannot: is the instance running and the process
4
+ # dying, or is the instance never coming up at all?
5
+ #
6
+ # This is step 1 of text/typedb-production-problem-solutions.md § Solutions.
7
+ # Everything else about the cluster (resize vs rebuild vs escalate) is guessing
8
+ # until this is read.
9
+ #
10
+ # servers present but unhealthy -> instance runs, TypeDB process fails
11
+ # => resource limits, resize (solution 2)
12
+ # servers empty / missing -> instance is not being provisioned
13
+ # => platform issue, TypeDB support (solution 4)
14
+ # servers appear/disappear -> crash loop, which is what the external
15
+ # between polls behaviour has looked like all along
16
+ #
17
+ # Usage:
18
+ # bash .claude/scripts/typedb-cluster-status.sh # one read
19
+ # bash .claude/scripts/typedb-cluster-status.sh --watch # poll 10x/5s — catches a crash loop
20
+ #
21
+ # Needs ONE secret this machine does not have. Mint it in the TypeDB Cloud
22
+ # console (Settings -> API keys) and put it in .claude/typedb/prod.env
23
+ # (gitignored, mode 600):
24
+ #
25
+ # TYPEDB_CLOUD_TOKEN=<the key>
26
+ #
27
+ # The team / space / cluster IDs are DISCOVERED from the token — don't hunt for
28
+ # them. Override only if discovery picks the wrong one:
29
+ # TYPEDB_CLOUD_TEAM_ID= / TYPEDB_CLOUD_SPACE_ID= / TYPEDB_CLOUD_CLUSTER_ID=
30
+ set -uo pipefail
31
+ ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
32
+ PROD_ENV="$ROOT/.claude/typedb/prod.env"
33
+ API="${TYPEDB_CLOUD_API:-https://cloud.typedb.com}"
34
+ WATCH=0; [ "${1:-}" = "--watch" ] && WATCH=1
35
+
36
+ [ -f "$PROD_ENV" ] && eval "$(python3 - "$PROD_ENV" <<'PY'
37
+ import re,sys,shlex
38
+ for line in open(sys.argv[1], errors="replace"):
39
+ m = re.match(r'^\s*(?:export\s+)?(TYPEDB_CLOUD_\w+)\s*=\s*(.*?)\s*$', line)
40
+ if m: print(f"{m.group(1)}={shlex.quote(m.group(2).strip(chr(34)+chr(39)))}")
41
+ PY
42
+ )"
43
+ : "${TYPEDB_CLOUD_TOKEN:=}" "${TYPEDB_CLOUD_TEAM_ID:=}" "${TYPEDB_CLOUD_SPACE_ID:=}" "${TYPEDB_CLOUD_CLUSTER_ID:=}"
44
+
45
+ if [ -z "$TYPEDB_CLOUD_TOKEN" ]; then
46
+ cat >&2 <<EOF
47
+ CANNOT RUN: no TYPEDB_CLOUD_TOKEN.
48
+
49
+ This is the control-plane credential, and it is NOT the same as the four
50
+ TYPEDB_* keys already in prod.env — those log in to the cluster to run queries
51
+ and carry no authority over the instance.
52
+
53
+ 1. TypeDB Cloud console -> Settings -> API keys -> create
54
+ 2. printf 'TYPEDB_CLOUD_TOKEN=%s\n' '<key>' >> $PROD_ENV
55
+ chmod 600 $PROD_ENV
56
+ 3. re-run this script
57
+
58
+ Verified reachable 2026-07-29: ${API}/api/v1/{team,teams,clusters,user} all
59
+ answer 401 UNAUTHENTICATED_REQUEST, so the API is live and only wants the key.
60
+ EOF
61
+ exit 3
62
+ fi
63
+
64
+ # The console has used more than one header scheme over time; try each and keep
65
+ # whichever authenticates, rather than guessing one and reporting a false 401.
66
+ AUTH=""
67
+ for scheme in "Authorization: Bearer $TYPEDB_CLOUD_TOKEN" "X-API-Key: $TYPEDB_CLOUD_TOKEN" "Authorization: $TYPEDB_CLOUD_TOKEN"; do
68
+ code=$(curl -s -o /dev/null -m 20 -w '%{http_code}' -H "$scheme" "$API/api/v1/teams")
69
+ [ "$code" = "200" ] && { AUTH="$scheme"; break; }
70
+ done
71
+ if [ -z "$AUTH" ]; then
72
+ echo "CANNOT RUN: token rejected by every auth scheme tried (Bearer / X-API-Key / raw)." >&2
73
+ echo " The key may be expired, scoped to another team, or the API may have moved." >&2
74
+ exit 3
75
+ fi
76
+
77
+ get() { curl -s -m 30 -H "$AUTH" "$API$1"; }
78
+
79
+ # ── discover the IDs so nobody has to hunt for them ───────────────────────
80
+ [ -z "$TYPEDB_CLOUD_TEAM_ID" ] && TYPEDB_CLOUD_TEAM_ID=$(get /api/v1/teams | python3 -c '
81
+ import sys,json
82
+ try:
83
+ d=json.load(sys.stdin)
84
+ ts=d.get("teams") or d.get("data") or (d if isinstance(d,list) else [])
85
+ print((ts[0].get("id") or ts[0].get("uuid") or ts[0].get("slug") or "") if ts else "")
86
+ except Exception: print("")')
87
+ [ -z "$TYPEDB_CLOUD_TEAM_ID" ] && { echo "CANNOT RUN: authenticated, but no team returned." >&2; exit 3; }
88
+
89
+ [ -z "$TYPEDB_CLOUD_SPACE_ID" ] && TYPEDB_CLOUD_SPACE_ID=$(get "/api/v1/team/$TYPEDB_CLOUD_TEAM_ID/spaces" | python3 -c '
90
+ import sys,json
91
+ try:
92
+ d=json.load(sys.stdin)
93
+ ss=d.get("spaces") or d.get("data") or (d if isinstance(d,list) else [])
94
+ print((ss[0].get("id") or ss[0].get("uuid") or ss[0].get("slug") or "") if ss else "")
95
+ except Exception: print("")')
96
+
97
+ BASE="/api/v1/team/$TYPEDB_CLOUD_TEAM_ID/spaces/$TYPEDB_CLOUD_SPACE_ID/clusters"
98
+ echo "team=$TYPEDB_CLOUD_TEAM_ID space=$TYPEDB_CLOUD_SPACE_ID"
99
+
100
+ report() {
101
+ get "$BASE${TYPEDB_CLOUD_CLUSTER_ID:+/$TYPEDB_CLOUD_CLUSTER_ID}" | python3 - "$@" <<'PY'
102
+ import sys, json
103
+ try:
104
+ d = json.load(sys.stdin)
105
+ except Exception:
106
+ print(" (unparseable response)"); sys.exit(0)
107
+ cs = d.get("clusters") or d.get("data") or (d if isinstance(d, list) else [d])
108
+ for c in cs:
109
+ if not isinstance(c, dict): continue
110
+ name = c.get("id") or c.get("name") or c.get("slug") or "?"
111
+ servers = c.get("servers") or []
112
+ line = f" cluster {name} status={c.get('status','?')} tier={c.get('machineType') or c.get('tier') or c.get('size','?')} servers={len(servers)}"
113
+ for s in servers:
114
+ line += f"\n server {s.get('id') or s.get('name','?')} status={s.get('status','?')}"
115
+ print(line)
116
+ if not servers:
117
+ print(" NO SERVERS -> the instance is not being provisioned. Platform issue: raise with TypeDB support.")
118
+ elif any(str(s.get("status","")).lower() not in ("running","ready","healthy") for s in servers):
119
+ print(" UNHEALTHY SERVER -> the instance runs and the TypeDB process fails. Resource limits: resize.")
120
+ PY
121
+ }
122
+
123
+ if [ $WATCH -eq 1 ]; then
124
+ echo "polling 10x every 5s — servers appearing and disappearing between polls IS the crash loop"
125
+ for i in $(seq 1 10); do printf '[%02d] ' "$i"; report; sleep 5; done
126
+ else
127
+ report
128
+ fi
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env bash
2
+ # typedb-env.sh — switch one.ie/web/.env between the local (dev) and cloud (prod)
3
+ # TypeDB substrate. Rewrites only the four TYPEDB_* lines; everything else in
4
+ # .env is untouched.
5
+ #
6
+ # .claude/scripts/typedb-env.sh # status
7
+ # .claude/scripts/typedb-env.sh dev # point at local OrbStack container
8
+ # .claude/scripts/typedb-env.sh prod # point back at TypeDB Cloud
9
+ # .claude/scripts/typedb-env.sh up # start the local container
10
+ # .claude/scripts/typedb-env.sh down # stop the local container
11
+ #
12
+ # Profiles live in .claude/typedb/{dev,prod}.env (gitignored — prod holds a
13
+ # password). Re-snapshot prod at any time with: cp-from-env prod
14
+ set -euo pipefail
15
+
16
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
17
+ ENVF="$ROOT/one.ie/web/.env"
18
+ PROFILES="$ROOT/.claude/typedb"
19
+ KEYS='TYPEDB_URL TYPEDB_DATABASE TYPEDB_USERNAME TYPEDB_PASSWORD'
20
+ CONTAINER=typedb
21
+ # Pinned to the version TypeDB Cloud runs (checked via GET $TYPEDB_URL/v1/version).
22
+ # Dev mirrors the substrate, it does not lead it — bump only when prod does.
23
+ # Prod moved to 3.12.1 on 2026-07-29 and the local container was migrated in place
24
+ # the same day; this pin was left at 3.8.3, so a container rebuild would have
25
+ # silently DOWNGRADED local back under the two 3.8.3 server-killing bugs
26
+ # (`\uXXXX` in a literal, and the same var in two roles → PANIC, not an error).
27
+ IMAGE=typedb/typedb:3.12.1
28
+ VOLUME=typedb-data
29
+ # The path the SERVER actually writes to, not the one the tarball layout suggests.
30
+ # `docker inspect typedb` -> `server --storage.data-directory=/var/lib/typedb/data`.
31
+ # The old value mounted the named volume at /opt/... where nothing was ever written,
32
+ # so every local database lived in an ANONYMOUS volume — one `docker rm` from being
33
+ # orphaned, and no local backup would have contained anything.
34
+ DATA_DIR=/var/lib/typedb/data
35
+
36
+ die() { echo "typedb-env: $*" >&2; exit 1; }
37
+ val() { grep -E "^$1=" "$ENVF" | tail -1 | cut -d= -f2-; }
38
+
39
+ # Which profile does the live .env currently match? (compare URL only)
40
+ current() {
41
+ local url; url=$(val TYPEDB_URL)
42
+ for p in dev prod; do
43
+ [ -f "$PROFILES/$p.env" ] || continue
44
+ if [ "$url" = "$(grep -E '^TYPEDB_URL=' "$PROFILES/$p.env" | cut -d= -f2-)" ]; then
45
+ echo "$p"; return
46
+ fi
47
+ done
48
+ echo "custom"
49
+ }
50
+
51
+ status() {
52
+ local cur; cur=$(current)
53
+ echo "profile : $cur"
54
+ echo "url : $(val TYPEDB_URL)"
55
+ echo "database: $(val TYPEDB_DATABASE)"
56
+ local st; st=$(docker ps -a --filter "name=^${CONTAINER}$" --format '{{.Status}}' 2>/dev/null || true)
57
+ echo "local : ${st:-not created}"
58
+ local url; url=$(val TYPEDB_URL)
59
+ if curl -sf -m 5 -X POST "$url/v1/signin" -H 'content-type: application/json' \
60
+ -d "{\"username\":\"$(val TYPEDB_USERNAME)\",\"password\":\"$(val TYPEDB_PASSWORD)\"}" \
61
+ >/dev/null 2>&1; then
62
+ echo "signin : ok"
63
+ else
64
+ echo "signin : FAILED"
65
+ fi
66
+ echo "scope : one.ie/web/.env only — api/wrangler.toml stays pinned to cloud,"
67
+ echo " as do tools that default to it when env is unset (pay/tools/"
68
+ echo " simulate-market.ts, text/seed-tasks-typedb.ts, text/backfill-task-context.ts)"
69
+ }
70
+
71
+ use() {
72
+ local p="$1" src="$PROFILES/$1.env"
73
+ [ -f "$src" ] || die "no profile $p (expected $src)"
74
+ [ -f "$ENVF" ] || die "no env file at $ENVF"
75
+ # Snapshot named for the profile being LEFT, so a rollback target is never
76
+ # clobbered by the next switch (a single .bak slot ends up holding the very
77
+ # profile you were trying to get away from).
78
+ cp "$ENVF" "$ENVF.bak.$(current)"
79
+ for k in $KEYS; do
80
+ local line; line=$(grep -E "^$k=" "$src" | tail -1) || die "$p profile missing $k"
81
+ # in-place replace; portable sed needs the value escaped for & and /
82
+ local esc; esc=$(printf '%s' "$line" | sed -e 's/[\/&]/\\&/g')
83
+ if grep -qE "^$k=" "$ENVF"; then
84
+ sed -i '' -E "s/^$k=.*/$esc/" "$ENVF"
85
+ else
86
+ printf '%s\n' "$line" >> "$ENVF"
87
+ fi
88
+ done
89
+ echo "switched to $p"
90
+ status
91
+ }
92
+
93
+ up() {
94
+ docker info >/dev/null 2>&1 || { open -a OrbStack; echo "starting OrbStack..."; }
95
+ for _ in $(seq 1 30); do docker info >/dev/null 2>&1 && break; sleep 2; done
96
+ docker info >/dev/null 2>&1 || die "docker daemon did not come up"
97
+ if docker ps -a --format '{{.Names}}' | grep -qx "$CONTAINER"; then
98
+ docker start "$CONTAINER" >/dev/null
99
+ else
100
+ docker volume create "$VOLUME" >/dev/null
101
+ docker run -d --name "$CONTAINER" --restart unless-stopped \
102
+ -p 1729:1729 -p 8000:8000 -v "$VOLUME:$DATA_DIR" "$IMAGE" >/dev/null
103
+ fi
104
+ for _ in $(seq 1 30); do
105
+ curl -sf -m 3 -X POST http://127.0.0.1:8000/v1/signin -H 'content-type: application/json' \
106
+ -d '{"username":"admin","password":"password"}' >/dev/null 2>&1 && { echo "local TypeDB ready"; return; }
107
+ sleep 2
108
+ done
109
+ die "local TypeDB did not become ready — docker logs $CONTAINER"
110
+ }
111
+
112
+ down() { docker stop "$CONTAINER" >/dev/null 2>&1 && echo "local TypeDB stopped" || echo "not running"; }
113
+
114
+ # Canonical load order — schema/CLAUDE.md § Files, narrowed to what production
115
+ # actually carries (probed via GET $TYPEDB_URL/v1/databases/one/schema, 2026-07-28).
116
+ # Deliberately excluded, because prod does NOT have them and dev must mirror the
117
+ # substrate rather than lead it:
118
+ # factory-*.tql — "DEFINES CLEAN, NOT DEPLOYED" per schema/CLAUDE.md
119
+ # marketing/contact/education-schema.tql
120
+ # — documented "LOAD-SAFE against prod 2026-07-08", but
121
+ # absent from the live schema AND they fail to commit
122
+ # on a clean 3.8.3 db (INF11: `incremental_roas` and
123
+ # `recommend_strategy` read attributes off variables
124
+ # whose inferred types can't own them). Loading them
125
+ # is a schema fix, not an env switch.
126
+ SCHEMA_STACK='one.tql roles.tql do.tql reason.tql router.tql channels.tql'
127
+
128
+ # seed — create the local `one` database and define the canonical stack.
129
+ # LOCAL ONLY: refuses to run unless the active profile is dev, so a stray
130
+ # invocation can never define schema against TypeDB Cloud.
131
+ seed() {
132
+ [ "$(current)" = dev ] || die "refusing: active profile is '$(current)', not dev. Run 'typedb-env.sh dev' first."
133
+ local url; url=$(val TYPEDB_URL)
134
+ local db; db=$(val TYPEDB_DATABASE)
135
+ local tok
136
+ tok=$(curl -s -m 15 -X POST "$url/v1/signin" -H 'Content-Type: application/json' \
137
+ -d "{\"username\":\"$(val TYPEDB_USERNAME)\",\"password\":\"$(val TYPEDB_PASSWORD)\"}" \
138
+ | python3 -c 'import sys,json;print(json.load(sys.stdin).get("token",""))' 2>/dev/null)
139
+ [ -n "$tok" ] || die "signin failed at $url"
140
+
141
+ if curl -s -m 10 "$url/v1/databases/$db" -H "Authorization: Bearer $tok" | grep -q '"name"'; then
142
+ echo "database '$db' exists"
143
+ else
144
+ curl -sf -m 30 -X POST "$url/v1/databases/$db" -H "Authorization: Bearer $tok" >/dev/null \
145
+ || die "could not create database '$db'"
146
+ echo "created database '$db'"
147
+ fi
148
+
149
+ for f in $SCHEMA_STACK; do
150
+ local path="$ROOT/schema/$f"
151
+ [ -f "$path" ] || die "missing $path"
152
+ python3 - "$path" "$db" > /tmp/typedb-seed.json <<'PY'
153
+ import json, sys
154
+ print(json.dumps({
155
+ "databaseName": sys.argv[2],
156
+ "transactionType": "schema",
157
+ "query": open(sys.argv[1]).read(),
158
+ }))
159
+ PY
160
+ local out
161
+ out=$(curl -s -m 120 -X POST "$url/v1/query" -H "Authorization: Bearer $tok" \
162
+ -H 'Content-Type: application/json' --data-binary @/tmp/typedb-seed.json)
163
+ if printf '%s' "$out" | grep -q '"code"'; then
164
+ echo " FAIL $f"
165
+ printf '%s\n' "$out" | head -c 600; echo
166
+ die "schema load aborted at $f"
167
+ fi
168
+ echo " ok $f"
169
+ done
170
+ rm -f /tmp/typedb-seed.json
171
+ echo "local schema loaded ($(printf '%s' "$SCHEMA_STACK" | wc -w | tr -d ' ') files)"
172
+ }
173
+
174
+ case "${1:-status}" in
175
+ dev) up; use dev ;;
176
+ prod) use prod ;;
177
+ up) up ;;
178
+ down) down ;;
179
+ seed) seed ;;
180
+ status) status ;;
181
+ *) die "usage: typedb-env.sh [status|dev|prod|up|down|seed]" ;;
182
+ esac
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env bash
2
+ # typedb-flap-recorder.sh — record the NEXT production flap in full, so it does
3
+ # not have to be reconstructed by hand the way 2026-07-28/29 was.
4
+ #
5
+ # The cluster is healthy between failures, which is exactly why this is hard:
6
+ # by the time anyone looks, the transition is gone. This samples the three
7
+ # layers that fail INDEPENDENTLY and timestamps every change of state:
8
+ #
9
+ # /v1/health 204 — readiness. Cheapest, and the first to go.
10
+ # /v1/signin 200 — auth path. Can pass while queries fail.
11
+ # /v1/query 200 — the data plane actually doing work, with latency.
12
+ #
13
+ # The doc's open question is whether the backend dies under no load. This answers
14
+ # it with a timeline instead of an inference: if health flips to 5xx while this
15
+ # is the only client, that is measured, not argued.
16
+ #
17
+ # Usage:
18
+ # bash .claude/scripts/typedb-flap-recorder.sh # foreground
19
+ # nohup bash .claude/scripts/typedb-flap-recorder.sh & # leave it running
20
+ # INTERVAL=5 bash .claude/scripts/typedb-flap-recorder.sh # tighter sampling
21
+ #
22
+ # Log: text/typedb-flap-log.tsv (append-only, gitignored-safe to commit if short)
23
+ # ts health signin query query_ms note
24
+ #
25
+ # Writes a line on EVERY sample to the log, but prints to the terminal only when
26
+ # the state CHANGES — so a week of green is quiet and the flap is loud.
27
+ set -uo pipefail
28
+ ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
29
+ LOG="${FLAP_LOG:-$ROOT/text/typedb-flap-log.tsv}"
30
+ INTERVAL="${INTERVAL:-15}"
31
+
32
+ eval "$(python3 - "$ROOT/.claude/typedb/prod.env" <<'PY'
33
+ import re,sys,shlex
34
+ try: f = open(sys.argv[1], errors="replace")
35
+ except OSError: sys.exit(0)
36
+ for line in f:
37
+ m = re.match(r'^\s*(?:export\s+)?(TYPEDB_(?:URL|DATABASE|USERNAME|PASSWORD))\s*=\s*(.*?)\s*$', line)
38
+ if m: print(f"{m.group(1)}={shlex.quote(m.group(2).strip(chr(34)+chr(39)))}")
39
+ PY
40
+ )"
41
+ : "${TYPEDB_URL:=}" "${TYPEDB_DATABASE:=one}" "${TYPEDB_USERNAME:=admin}" "${TYPEDB_PASSWORD:=}"
42
+ [ -z "$TYPEDB_URL" ] && { echo "CANNOT RUN: no TYPEDB_URL in .claude/typedb/prod.env" >&2; exit 3; }
43
+ case "$TYPEDB_URL" in
44
+ *127.0.0.1*|*localhost*) echo "refusing: prod.env points at a LOCAL substrate — nothing to record" >&2; exit 2 ;;
45
+ esac
46
+
47
+ [ -f "$LOG" ] || printf 'ts\thealth\tsignin\tquery\tquery_ms\tnote\n' > "$LOG"
48
+ echo "recording $TYPEDB_URL every ${INTERVAL}s -> $LOG (ctrl-c to stop)"
49
+
50
+ last=""
51
+ while true; do
52
+ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
53
+ health=$(curl -s -o /dev/null -m 10 -w '%{http_code}' "$TYPEDB_URL/v1/health")
54
+ tok=$(curl -s -m 10 -X POST "$TYPEDB_URL/v1/signin" -H 'Content-Type: application/json' \
55
+ -d "{\"username\":\"$TYPEDB_USERNAME\",\"password\":\"$TYPEDB_PASSWORD\"}" \
56
+ | python3 -c 'import sys,json
57
+ try: print(json.load(sys.stdin).get("token",""))
58
+ except Exception: print("")' 2>/dev/null)
59
+ if [ -n "$tok" ]; then
60
+ signin=200
61
+ t0=$(python3 -c 'import time;print(int(time.time()*1000))')
62
+ query=$(curl -s -o /dev/null -m 30 -w '%{http_code}' -X POST "$TYPEDB_URL/v1/query" \
63
+ -H "Authorization: Bearer $tok" -H 'Content-Type: application/json' \
64
+ -d "{\"databaseName\":\"$TYPEDB_DATABASE\",\"transactionType\":\"read\",\"query\":\"match \$x isa thing; reduce \$c = count;\"}")
65
+ t1=$(python3 -c 'import time;print(int(time.time()*1000))')
66
+ qms=$((t1 - t0))
67
+ else
68
+ # A signin that returns no token is NOT necessarily auth failure — on this
69
+ # cluster it is usually the backend being gone. Record the transport code.
70
+ signin=$(curl -s -o /dev/null -m 10 -w '%{http_code}' -X POST "$TYPEDB_URL/v1/signin" \
71
+ -H 'Content-Type: application/json' \
72
+ -d "{\"username\":\"$TYPEDB_USERNAME\",\"password\":\"$TYPEDB_PASSWORD\"}")
73
+ query="-"; qms="-"
74
+ fi
75
+
76
+ state="$health/$signin/$query"
77
+ note=""
78
+ [ "$state" != "$last" ] && [ -n "$last" ] && note="STATE CHANGE from $last"
79
+ printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$ts" "$health" "$signin" "$query" "$qms" "$note" >> "$LOG"
80
+ if [ "$state" != "$last" ]; then
81
+ printf '%s health=%-4s signin=%-4s query=%-4s %sms %s\n' "$ts" "$health" "$signin" "$query" "$qms" "$note"
82
+ last="$state"
83
+ fi
84
+ sleep "$INTERVAL"
85
+ done
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env python3
2
+ """Settles the doc's UNCONFIRMED claim:
3
+
4
+ "Do NOT run `match (container: $x, contained: $x) isa containment;` — the
5
+ same variable in two roles. It appeared to kill the cluster outright
6
+ rather than return empty."
7
+
8
+ Same oracle as the unicode panic probe: docker restart count before/after.
9
+ Runs against a scratch db with a containment relation defined and a couple of
10
+ real (non-self) edges, so an empty result is a meaningful empty.
11
+ """
12
+ import json, subprocess, sys, time, urllib.request, urllib.error
13
+
14
+ URL = "http://127.0.0.1:8000"
15
+ DB = "selfrole"
16
+
17
+
18
+ def restarts():
19
+ return subprocess.run(["docker", "inspect", "typedb", "--format", "{{.RestartCount}}"],
20
+ capture_output=True, text=True).stdout.strip()
21
+
22
+
23
+ def signin(tries=25):
24
+ for _ in range(tries):
25
+ try:
26
+ req = urllib.request.Request(URL + "/v1/signin", method="POST",
27
+ headers={"Content-Type": "application/json"})
28
+ with urllib.request.urlopen(req, json.dumps(
29
+ {"username": "admin", "password": "password"}).encode(), timeout=5) as r:
30
+ return json.load(r)["token"]
31
+ except Exception:
32
+ time.sleep(1)
33
+ sys.exit("could not sign in")
34
+
35
+
36
+ def send(tok, q, kind="read"):
37
+ req = urllib.request.Request(URL + "/v1/query", method="POST", headers={
38
+ "Content-Type": "application/json", "Authorization": "Bearer " + tok})
39
+ body = json.dumps({"databaseName": DB, "transactionType": kind, "query": q},
40
+ ensure_ascii=False).encode("utf-8")
41
+ t0 = time.time()
42
+ try:
43
+ with urllib.request.urlopen(req, body, timeout=60) as r:
44
+ d = json.load(r)
45
+ n = len(d.get("answers") or [])
46
+ return f"200 ({n} answers)", time.time() - t0
47
+ except urllib.error.HTTPError as e:
48
+ return f"{e.code} {e.read()[:60].decode('utf-8','replace')}", time.time() - t0
49
+ except Exception as e:
50
+ return f"DROPPED({type(e).__name__})", time.time() - t0
51
+
52
+
53
+ tok = signin()
54
+ try:
55
+ urllib.request.urlopen(urllib.request.Request(
56
+ URL + f"/v1/databases/{DB}", method="POST",
57
+ headers={"Authorization": "Bearer " + tok}), b"", timeout=15).read()
58
+ except Exception:
59
+ pass
60
+
61
+ send(tok, """define
62
+ attribute nid, value string;
63
+ entity node, owns nid @key, plays containment:container, plays containment:contained;
64
+ relation containment, relates container, relates contained;""", "schema")
65
+ send(tok, 'insert $a isa node, has nid "a"; $b isa node, has nid "b"; $c isa node, has nid "c";', "write")
66
+ send(tok, 'match $a isa node, has nid "a"; $b isa node, has nid "b"; '
67
+ 'insert $r links (container: $a, contained: $b), isa containment;', "write")
68
+ send(tok, 'match $b isa node, has nid "b"; $c isa node, has nid "c"; '
69
+ 'insert $r links (container: $b, contained: $c), isa containment;', "write")
70
+
71
+ PROBES = [
72
+ ("sanity: normal containment read", 'match $r links (container: $x, contained: $y), isa containment; select $x, $y;'),
73
+ ("THE SUSPECT: same var in two roles", 'match $r links (container: $x, contained: $x), isa containment; select $x;'),
74
+ ("legacy paren syntax, same var", 'match ($x, $x) isa containment; select $x;'),
75
+ ("recovery: normal read again", 'match $r links (container: $x, contained: $y), isa containment; select $x, $y;'),
76
+ ]
77
+
78
+ print(f" {'probe':40} {'result':30} {'secs':>6} restarts")
79
+ prev = restarts()
80
+ print(f" {'(baseline)':40} {'-':30} {'-':>6} {prev}")
81
+ for name, q in PROBES:
82
+ res, secs = send(tok, q)
83
+ time.sleep(4)
84
+ r = restarts()
85
+ flag = " <-- SERVER KILLED" if r != prev else ""
86
+ print(f" {name:40} {res:30} {secs:6.2f} {r}{flag}")
87
+ prev = r
88
+ tok = signin()
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env python3
2
+ """Run both confirmed server-killing query shapes against an arbitrary TypeDB.
3
+
4
+ Usage: panic-probe-version.py <url> <container-name>
5
+ Oracle is the container restart count, same as the 3.8.3 probes.
6
+ """
7
+ import json, subprocess, sys, time, urllib.request, urllib.error
8
+
9
+ URL, CONTAINER = sys.argv[1], sys.argv[2]
10
+ DB = "panicver"
11
+
12
+
13
+ def restarts():
14
+ return subprocess.run(["docker", "inspect", CONTAINER, "--format", "{{.RestartCount}}"],
15
+ capture_output=True, text=True).stdout.strip()
16
+
17
+
18
+ def alive():
19
+ try:
20
+ urllib.request.urlopen(urllib.request.Request(
21
+ URL + "/v1/signin", method="POST", headers={"Content-Type": "application/json"}),
22
+ json.dumps({"username": "admin", "password": "password"}).encode(), timeout=3)
23
+ return True
24
+ except urllib.error.HTTPError:
25
+ return True # answered => process alive
26
+ except Exception:
27
+ return False
28
+
29
+
30
+ def signin(tries=30):
31
+ for _ in range(tries):
32
+ try:
33
+ req = urllib.request.Request(URL + "/v1/signin", method="POST",
34
+ headers={"Content-Type": "application/json"})
35
+ with urllib.request.urlopen(req, json.dumps(
36
+ {"username": "admin", "password": "password"}).encode(), timeout=5) as r:
37
+ return json.load(r)["token"]
38
+ except Exception:
39
+ time.sleep(1)
40
+ return None
41
+
42
+
43
+ def send(tok, q, kind="write"):
44
+ req = urllib.request.Request(URL + "/v1/query", method="POST", headers={
45
+ "Content-Type": "application/json", "Authorization": "Bearer " + tok})
46
+ body = json.dumps({"databaseName": DB, "transactionType": kind, "query": q},
47
+ ensure_ascii=False).encode("utf-8")
48
+ try:
49
+ with urllib.request.urlopen(req, body, timeout=30) as r:
50
+ d = json.load(r)
51
+ return f"200 ({len(d.get('answers') or [])} answers)"
52
+ except urllib.error.HTTPError as e:
53
+ return f"{e.code} {json.loads(e.read()).get('code','')}"
54
+ except Exception as e:
55
+ return f"DROPPED({type(e).__name__})"
56
+
57
+
58
+ tok = signin()
59
+ try:
60
+ urllib.request.urlopen(urllib.request.Request(
61
+ URL + f"/v1/databases/{DB}", method="POST",
62
+ headers={"Authorization": "Bearer " + tok}), b"", timeout=15).read()
63
+ except Exception:
64
+ pass
65
+ send(tok, """define
66
+ attribute note, value string; attribute nid, value string;
67
+ entity node, owns nid @key, owns note,
68
+ plays containment:container, plays containment:contained;
69
+ relation containment, relates container, relates contained;""", "schema")
70
+ send(tok, 'insert $a isa node, has nid "a"; $b isa node, has nid "b";', "write")
71
+ send(tok, 'match $a isa node, has nid "a"; $b isa node, has nid "b"; '
72
+ 'insert $r links (container: $a, contained: $b), isa containment;', "write")
73
+
74
+ PROBES = [
75
+ ("control: ASCII insert", 'insert $x isa node, has nid "ctl1", has note "plain";', "write"),
76
+ ("control: raw UTF-8 insert", 'insert $x isa node, has nid "ctl2", has note "café — ok";', "write"),
77
+ ("KILLER 1: \\uXXXX in literal", 'insert $x isa node, has nid "u1", has note "caf\\u00e9";', "write"),
78
+ ("KILLER 2: same var, two roles", 'match $r links (container: $x, contained: $x), isa containment; select $x;', "read"),
79
+ ("recovery: ASCII read", 'match $x isa node, has nid $n; select $n;', "read"),
80
+ ]
81
+
82
+ print(f" {'probe':36} {'result':30} restarts alive")
83
+ prev = restarts()
84
+ print(f" {'(baseline)':36} {'-':30} {prev:8} {alive()}")
85
+ for name, q, kind in PROBES:
86
+ res = send(tok, q, kind)
87
+ time.sleep(4)
88
+ r, a = restarts(), alive()
89
+ flag = " <-- KILLED" if r != prev else ""
90
+ print(f" {name:36} {res:30} {r:8} {a}{flag}")
91
+ prev = r
92
+ tok = signin() or tok
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env python3
2
+ """Minimal reproducer: a \\uXXXX escape inside a TypeQL string literal PANICS
3
+ TypeDB 3.8.3 and kills the server process.
4
+
5
+ Each probe sends one write query and then reads the container's restart count.
6
+ A rising restart count means that query killed the server, not that it errored.
7
+
8
+ Usage: panic-probe.py [url]
9
+ """
10
+ import json, subprocess, sys, time, urllib.request, urllib.error
11
+
12
+ URL = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:8000"
13
+ DB = "paniclab"
14
+
15
+
16
+ def restarts():
17
+ return subprocess.run(["docker", "inspect", "typedb", "--format", "{{.RestartCount}}"],
18
+ capture_output=True, text=True).stdout.strip()
19
+
20
+
21
+ def signin(tries=20):
22
+ for _ in range(tries):
23
+ try:
24
+ req = urllib.request.Request(URL + "/v1/signin", method="POST")
25
+ req.add_header("Content-Type", "application/json")
26
+ body = json.dumps({"username": "admin", "password": "password"}).encode()
27
+ with urllib.request.urlopen(req, body, timeout=5) as r:
28
+ return json.load(r)["token"]
29
+ except Exception:
30
+ time.sleep(1)
31
+ sys.exit("could not sign in")
32
+
33
+
34
+ def send(tok, q, kind="write"):
35
+ req = urllib.request.Request(URL + "/v1/query", method="POST")
36
+ req.add_header("Content-Type", "application/json")
37
+ req.add_header("Authorization", "Bearer " + tok)
38
+ # ensure_ascii=False: the ESCAPE UNDER TEST must reach TypeQL as written,
39
+ # not be double-escaped by the JSON envelope.
40
+ body = json.dumps({"databaseName": DB, "transactionType": kind, "query": q},
41
+ ensure_ascii=False).encode("utf-8")
42
+ try:
43
+ with urllib.request.urlopen(req, body, timeout=15) as r:
44
+ return str(r.status)
45
+ except urllib.error.HTTPError as e:
46
+ return str(e.code)
47
+ except Exception as e:
48
+ return f"DROPPED({type(e).__name__})"
49
+
50
+
51
+ tok = signin()
52
+ # fresh scratch db
53
+ urllib.request.urlopen(urllib.request.Request(
54
+ URL + f"/v1/databases/{DB}", method="POST",
55
+ headers={"Authorization": "Bearer " + tok}), b"", timeout=15).read() if True else None
56
+ send(tok, "define attribute note, value string; entity probe, owns note;", "schema")
57
+
58
+ PROBES = [
59
+ ("ASCII literal", 'insert $x isa probe, has note "plain ascii";'),
60
+ ("raw UTF-8 (e-acute, em-dash)", 'insert $x isa probe, has note "café — ok";'),
61
+ ("escaped quote", 'insert $x isa probe, has note "she said \\"hi\\"";'),
62
+ ("\\uXXXX escape (e-acute)", 'insert $x isa probe, has note "caf\\u00e9";'),
63
+ ("\\uXXXX escape (control char)", 'insert $x isa probe, has note "bell\\u0007here";'),
64
+ ("ASCII again (did it recover?)", 'insert $x isa probe, has note "still alive";'),
65
+ ]
66
+
67
+ print(f" {'probe':38} {'http':16} restarts")
68
+ before = restarts()
69
+ print(f" {'(baseline)':38} {'-':16} {before}")
70
+ for name, q in PROBES:
71
+ code = send(tok, q)
72
+ time.sleep(4) # let docker restart-policy settle before reading the count
73
+ r = restarts()
74
+ killed = " <-- SERVER KILLED" if r != before else ""
75
+ print(f" {name:38} {code:16} {r}{killed}")
76
+ before = r
77
+ tok = signin() # token dies with the process