@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.
- package/agents/w1-recon.md +9 -4
- package/agents/w2-decide.md +51 -28
- package/agents/w3-edit.md +70 -78
- package/agents/w4-verify.md +164 -69
- package/commands/browser.md +98 -35
- package/commands/cc-connect.md +6 -0
- package/commands/chat.md +13 -1
- package/commands/close.md +45 -19
- package/commands/create.md +20 -10
- package/commands/db-sync.md +5 -1
- package/commands/deploy.md +174 -87
- package/commands/do-autonomous.md +31 -20
- package/commands/do-improve.md +4 -4
- package/commands/do-show.md +4 -3
- package/commands/do.md +53 -13
- package/commands/go.md +75 -0
- package/commands/improve.md +4 -2
- package/commands/kill.md +6 -8
- package/commands/notify.md +15 -2
- package/commands/oo-push.md +24 -9
- package/commands/rag.md +6 -0
- package/commands/release.md +46 -25
- package/commands/restart.md +5 -4
- package/commands/see.md +45 -35
- package/commands/skill-create.md +38 -5
- package/commands/sync.md +21 -11
- package/hooks/scripts/auto-continue.sh +70 -0
- package/hooks/scripts/branch-pin.sh +85 -0
- package/hooks/scripts/git-add-guard.sh +16 -5
- package/hooks/scripts/post-edit-check.sh +16 -0
- package/hooks/scripts/session-start.sh +13 -8
- package/hooks/scripts/sync-priority-todo.sh +5 -0
- package/package.json +1 -1
- package/rules/api.md +9 -0
- package/rules/design.md +13 -3
- package/rules/documentation.md +30 -71
- package/rules/engine.md +8 -1
- package/rules/ui.md +5 -1
- package/scripts/__pycache__/do-rank.cpython-313.pyc +0 -0
- package/scripts/browser-check.mjs +44 -103
- package/scripts/cc-events-proof.sh +13 -12
- package/scripts/chrome.mjs +381 -0
- package/scripts/do-analyze.sh +109 -4
- package/scripts/do-auto.sh +350 -43
- package/scripts/do-consumer-sweep.sh +72 -0
- package/scripts/do-derives-check.sh +88 -0
- package/scripts/do-fleet.sh +50 -1
- package/scripts/do-killswitch-audit.py +33 -14
- package/scripts/do-promise-lint.sh +150 -4
- package/scripts/do-promise-settle.sh +131 -9
- package/scripts/do-prove.sh +197 -17
- package/scripts/do-rank.py +606 -20
- package/scripts/do-rubric.py +21 -2
- package/scripts/do-signal.sh +157 -17
- package/scripts/do-smoke.sh +9 -0
- package/scripts/do-substrate-check.sh +49 -0
- package/scripts/do-tasks-wire-check.sh +81 -0
- package/scripts/do-tier.sh +90 -5
- package/scripts/do-ui-gate.sh +332 -0
- package/scripts/do-untracked-gate.sh +231 -0
- package/scripts/do-walk.sh +344 -0
- package/scripts/factory-check.sh +747 -0
- package/scripts/factory-repo.sh +1361 -0
- package/scripts/factory-tasks-check.sh +85 -0
- package/scripts/outcome-pull.ts +279 -0
- package/scripts/promise-manifest.mjs +167 -0
- package/scripts/reconcile-allow.txt +5 -0
- package/scripts/rubric-weights.json +64 -0
- package/scripts/spine-canary.sh +20 -0
- package/scripts/typedb-cluster-status.sh +128 -0
- package/scripts/typedb-env.sh +182 -0
- package/scripts/typedb-flap-recorder.sh +85 -0
- package/scripts/typedb-probes/containment-probe.py +88 -0
- package/scripts/typedb-probes/panic-probe-version.py +92 -0
- package/scripts/typedb-probes/panic-probe.py +77 -0
- package/scripts/typedb-scratch.sh +73 -0
- package/scripts/urls-lint.sh +58 -0
- package/scripts/w4-rubric.ts +96 -12
- package/scripts/wf-check.mjs +75 -0
- package/skills/ai-ui/SKILL.md +1 -1
- package/skills/astro/SKILL.md +177 -132
- package/skills/astro/add-content-collection.md +29 -13
- package/skills/astro/check-build.md +19 -13
- package/skills/astro/create-component.md +36 -9
- package/skills/astro/create-page.md +39 -10
- package/skills/astro/optimize-performance.md +36 -8
- package/skills/cli/SKILL.md +254 -220
- package/skills/dev/SKILL.md +1 -1
- package/skills/directory-autofill/SKILL.md +68 -0
- package/skills/docs/SKILL.md +4 -4
- package/skills/mcp/SKILL.md +186 -156
- package/skills/promise-make/SKILL.md +2 -2
- package/skills/puck/SKILL.md +465 -250
- package/skills/react19/SKILL.md +196 -296
- package/skills/reactflow/SKILL.md +387 -412
- package/skills/sdk/SKILL.md +167 -106
- package/skills/shadcn/SKILL.md +245 -201
- package/skills/signal/SKILL.md +5 -0
- package/skills/sui/SKILL.md +6 -1
- package/skills/tutorial/SKILL.md +15 -0
- package/skills/typedb/SKILL.md +427 -217
- package/skills/typedb/examples/python-patterns.py +11 -4
- package/skills/typedb/examples/query-patterns.tql +8 -3
- package/skills/typedb/examples/schema-patterns.tql +4 -2
- package/skills/typedb/reference/migration-2x-3x.md +19 -1
- package/skills/typedb/reference/python-driver.md +17 -1
- package/skills/voice/SKILL.md +117 -0
- package/skills/writer/SKILL.md +29 -12
- package/templates/template-feature.md +26 -1
- package/templates/template-tests.md +4 -0
- 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
|