@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,88 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# do-derives-check.sh — assert every artifact a promise's derives: block declares
|
|
3
|
+
# `true` actually EXISTS on disk. Zero LLM; the file's presence is the verdict.
|
|
4
|
+
#
|
|
5
|
+
# WHY: text/security-gates.md declared `docs: true` but text/security-gates-docs.md
|
|
6
|
+
# was never written — the promise settled green on a lie. This gate closes that
|
|
7
|
+
# class: a declared derives: artifact that is missing fails the check, so /close
|
|
8
|
+
# (via do-promise-settle.sh) cannot mark the promise KEPT until the file exists.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# do-derives-check.sh <slug> # exit 0 if every true file-key exists, else 1
|
|
12
|
+
# do-derives-check.sh --self-test # temp fixtures, zero network
|
|
13
|
+
# exit: 0 all present · 1 an artifact missing · 2 no promise file · 4 bad usage
|
|
14
|
+
#
|
|
15
|
+
# SCOPE — file-shaped keys ONLY. The derives: block mixes two kinds of key:
|
|
16
|
+
# (a) 1:1 DOC keys → a single deterministic text/<slug>-<key>.md path:
|
|
17
|
+
# plan · todo · docs · tutorial · how-to · reference · features · ui — CHECKED.
|
|
18
|
+
# (b) named-entity / multi-file keys with NO single deterministic path — SKIPPED:
|
|
19
|
+
# tests (per-surface files, not one text/<slug>.test.ts),
|
|
20
|
+
# agents (a named actor at one.ie/ai/agents/*, or the -agents-docs.md
|
|
21
|
+
# briefing — ambiguous; security-gates' agents:true is a compliance
|
|
22
|
+
# actor, NOT text/security-gates-agents.md — checking it false-fails),
|
|
23
|
+
# skills · tasks · tracking · routing · views (world: entries — presence-checked
|
|
24
|
+
# by the world: traversal + the proof's own test run, not a filename).
|
|
25
|
+
# v1: simple + deterministic, matched to the concrete bug (a missing DOC).
|
|
26
|
+
set -u
|
|
27
|
+
|
|
28
|
+
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
29
|
+
|
|
30
|
+
# derives keys that map 1:1 to text/<slug>-<key>.md (padded for whole-word match)
|
|
31
|
+
FILE_KEYS=" plan todo docs tutorial how-to reference features ui "
|
|
32
|
+
|
|
33
|
+
# print each derives sub-key whose value is exactly `true` (first frontmatter block).
|
|
34
|
+
# Ends the block on the first column-0 non-space, non-comment line (e.g. `rubric:` /
|
|
35
|
+
# `world:`); blank + col-0 comment lines inside the block are tolerated. world.agents
|
|
36
|
+
# is never counted — only `^derives:` opens the block.
|
|
37
|
+
true_derives_keys() { # $1 = promise file
|
|
38
|
+
awk '
|
|
39
|
+
/^---[[:space:]]*$/ { fm++; if (fm>=2) exit; next }
|
|
40
|
+
fm!=1 { next }
|
|
41
|
+
/^derives:[[:space:]]*$/ { ind=1; next }
|
|
42
|
+
ind==1 && /^[^[:space:]#]/ { ind=0 }
|
|
43
|
+
ind==1 && /^[[:space:]]+[A-Za-z][A-Za-z-]*:[[:space:]]+true([[:space:]]|#|$)/ {
|
|
44
|
+
k=$0; sub(/^[[:space:]]+/,"",k); sub(/:.*/,"",k); print k
|
|
45
|
+
}
|
|
46
|
+
' "$1"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
check() { # $1 = slug
|
|
50
|
+
local slug="$1"
|
|
51
|
+
local file="$ROOT/text/$slug.md" missing=0 checked=0 key
|
|
52
|
+
if [ ! -f "$file" ]; then
|
|
53
|
+
echo "[derives-check] $slug — no promise file at text/$slug.md"; return 2
|
|
54
|
+
fi
|
|
55
|
+
while IFS= read -r key; do
|
|
56
|
+
[ -z "$key" ] && continue
|
|
57
|
+
case "$FILE_KEYS" in *" $key "*) ;; *) continue ;; esac # skip named-entity keys
|
|
58
|
+
checked=$((checked+1))
|
|
59
|
+
if [ ! -f "$ROOT/text/$slug-$key.md" ]; then
|
|
60
|
+
echo "[derives-check] MISSING: derives.$key=true but text/$slug-$key.md does not exist"
|
|
61
|
+
missing=$((missing+1))
|
|
62
|
+
fi
|
|
63
|
+
done < <(true_derives_keys "$file")
|
|
64
|
+
if [ "$missing" -gt 0 ]; then
|
|
65
|
+
echo "[derives-check] $slug — $missing/$checked declared artifact(s) missing"; return 1
|
|
66
|
+
fi
|
|
67
|
+
echo "[derives-check] $slug — ok ($checked file-shaped derives artifact(s) present)"; return 0
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if [ "${1:-}" = "--self-test" ]; then
|
|
71
|
+
dir="$(mktemp -d)"; trap 'rm -rf "$dir"' EXIT
|
|
72
|
+
mkdir -p "$dir/text"
|
|
73
|
+
fails=0
|
|
74
|
+
# green: docs:true + doc present; agents:true present but MUST be skipped (no -agents.md)
|
|
75
|
+
printf -- '---\nslug: green\nderives:\n plan: true\n docs: true\n agents: true\n---\n' >"$dir/text/green.md"
|
|
76
|
+
: >"$dir/text/green-plan.md"; : >"$dir/text/green-docs.md"
|
|
77
|
+
# red: docs:true but the doc is missing
|
|
78
|
+
printf -- '---\nslug: red\nderives:\n plan: true\n docs: true\n---\n' >"$dir/text/red.md"
|
|
79
|
+
: >"$dir/text/red-plan.md"
|
|
80
|
+
ROOT="$dir" check green >/dev/null 2>&1 || { echo "FAIL green should pass (agents skipped)"; fails=$((fails+1)); }
|
|
81
|
+
ROOT="$dir" check red >/dev/null 2>&1 && { echo "FAIL red should fail (missing docs)"; fails=$((fails+1)); }
|
|
82
|
+
echo "[derives-check] self-test: 2 fixtures, $fails failed"
|
|
83
|
+
exit "$fails"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
SLUG="${1:-}"
|
|
87
|
+
[ -z "$SLUG" ] && { echo "usage: do-derives-check.sh <slug> | --self-test" >&2; exit 4; }
|
|
88
|
+
check "$SLUG"
|
package/scripts/do-fleet.sh
CHANGED
|
@@ -31,6 +31,11 @@ RANK="$ROOT/.claude/scripts/do-rank.py"
|
|
|
31
31
|
AUTO="$ROOT/.claude/scripts/do-auto.sh"
|
|
32
32
|
WT_BASE="$ROOT/.do-worktrees"
|
|
33
33
|
|
|
34
|
+
# Worktree branches cut from trunk, never HEAD — a shared tree parked on a
|
|
35
|
+
# feature branch must not seed fleet plans with its commits. DO_BASE_REF overrides.
|
|
36
|
+
BASE_REF="${DO_BASE_REF:-main}"
|
|
37
|
+
git -C "$ROOT" show-ref --verify --quiet "refs/heads/$BASE_REF" || BASE_REF=HEAD
|
|
38
|
+
|
|
34
39
|
GO=false
|
|
35
40
|
SLOTS=3
|
|
36
41
|
TOP=40
|
|
@@ -131,11 +136,50 @@ has_conflict() { # has_conflict <slug> → exit 0 if conflict
|
|
|
131
136
|
return 1
|
|
132
137
|
}
|
|
133
138
|
|
|
139
|
+
# claim_task — fires tasks:claim before a worktree is cut (claim = lease, tasks-do C5).
|
|
140
|
+
# Best-effort like do-signal.sh's world:do-event stance: no configured secret or an
|
|
141
|
+
# unparseable response degrades to "ok to launch" rather than blocking the fleet. An
|
|
142
|
+
# explicit already-claimed/blocked response returns 1 so fill_slots skips this slug —
|
|
143
|
+
# two fleet runs (or a fleet run racing a human claim) never double-build the same task.
|
|
144
|
+
claim_task() { # claim_task <slug> → 0 ok to launch, 1 already claimed elsewhere
|
|
145
|
+
# GATEWAY_API_KEY, not SERVER_SECRET — /api/ask/<receiver>'s isServiceCaller checks
|
|
146
|
+
# env.GATEWAY_API_KEY (one.ie/web/src/pages/api/ask/[...receiver].ts). SERVER_SECRET
|
|
147
|
+
# is what /api/signal/ checks; the two routes don't share a secret. Also pin
|
|
148
|
+
# workspace so the claim's ctx.ownerSlug lands in /u/one/tasks like every other
|
|
149
|
+
# Claude Code task (do-signal.sh's CC_TASKS_WORKSPACE convention).
|
|
150
|
+
# Address the lease by the plan SLUG, never a guessed tid: real tids are minted
|
|
151
|
+
# task:<traceId> (resolvers/tasks.ts insertTaskRow), so "task:<slug>" resolved
|
|
152
|
+
# not_found for every plan and the lease could never gate. tasks:claim resolves
|
|
153
|
+
# data.slug via resolveTaskBySlug when present (resolvers/tasks.ts claim).
|
|
154
|
+
local slug="$1" secret="" f resp
|
|
155
|
+
# ONE_ENV_FILE first: the loop must be able to read credentials with no
|
|
156
|
+
# one.ie/web/ on disk. The monorepo paths stay as fallbacks and expand to
|
|
157
|
+
# nothing outside it.
|
|
158
|
+
for f in "${ONE_ENV_FILE:-${DO_ENV_FILE:-}}" "$ROOT/.env" "$ROOT/one.ie/web/.env" "$ROOT/one.ie/web/.secrets.generated.local"; do
|
|
159
|
+
[ -n "$f" ] || continue
|
|
160
|
+
[ -f "$f" ] || continue
|
|
161
|
+
secret=$(grep -E '^GATEWAY_API_KEY=' "$f" 2>/dev/null | head -1 | sed 's/^[^=]*=//;s/^"//;s/"$//' || true)
|
|
162
|
+
[ -n "$secret" ] && break
|
|
163
|
+
done
|
|
164
|
+
[ -z "$secret" ] && return 0
|
|
165
|
+
resp=$(curl -sf -X POST "${DO_SIGNAL_URL:-https://one.ie}/api/ask/tasks:claim" \
|
|
166
|
+
-H "Content-Type: application/json" -H "Authorization: Bearer $secret" \
|
|
167
|
+
-d "{\"data\":{\"slug\":\"$slug\",\"workspace\":\"${CC_TASKS_WORKSPACE:-one}\"}}" 2>/dev/null || true)
|
|
168
|
+
case "$resp" in
|
|
169
|
+
*'"ok":true'*) return 0 ;; # lease held by us — launch
|
|
170
|
+
*'"error":"already-claimed"'*) echo " ~ $slug — claimed by another actor, skipping" >&2; return 1 ;;
|
|
171
|
+
*'"error":"blocked"'*) echo " ~ $slug — blocked by an unresolved dependency, skipping" >&2; return 1 ;;
|
|
172
|
+
*'"error":"not_found"'*) return 0 ;; # no substrate task yet (claim-before-create) — launch
|
|
173
|
+
*'"error"'*) echo " ! $slug — claim degraded: $resp" >&2; return 0 ;;
|
|
174
|
+
*) return 0 ;; # unparseable/empty — best-effort, launch
|
|
175
|
+
esac
|
|
176
|
+
}
|
|
177
|
+
|
|
134
178
|
launch_plan() { # launch_plan <slug> → echoes PID
|
|
135
179
|
local slug="$1"
|
|
136
180
|
local wt="$WT_BASE/$slug"
|
|
137
181
|
if [ ! -d "$wt" ]; then
|
|
138
|
-
git -C "$ROOT" worktree add -b "do/$slug" "$wt"
|
|
182
|
+
git -C "$ROOT" worktree add -b "do/$slug" "$wt" "$BASE_REF" >/dev/null 2>&1 \
|
|
139
183
|
|| git -C "$ROOT" worktree add "$wt" "do/$slug" >/dev/null 2>&1 || true
|
|
140
184
|
fi
|
|
141
185
|
link_deps "$wt"
|
|
@@ -260,6 +304,11 @@ fill_slots() {
|
|
|
260
304
|
while [ "${#SLOT_PIDS[@]}" -lt "$SLOTS" ]; do
|
|
261
305
|
pop_next
|
|
262
306
|
[ -z "$NEXT_SLUG" ] && break
|
|
307
|
+
if ! claim_task "$NEXT_SLUG"; then
|
|
308
|
+
echo " ~ $NEXT_SLUG — already claimed elsewhere, skipping"
|
|
309
|
+
DONE_SLUGS+=("$NEXT_SLUG")
|
|
310
|
+
continue
|
|
311
|
+
fi
|
|
263
312
|
local pid; pid=$(launch_plan "$NEXT_SLUG")
|
|
264
313
|
SLOT_PIDS+=("$pid")
|
|
265
314
|
SLOT_SLUGS+=("$NEXT_SLUG")
|
|
@@ -54,27 +54,46 @@ def parse_outcome(text):
|
|
|
54
54
|
lines = [re.sub(r"\\$", "", l.strip()) for l in bm.group(1).splitlines() if l.strip()]
|
|
55
55
|
return " ".join(lines).strip()
|
|
56
56
|
# inline form: outcome: "..." or outcome: ...
|
|
57
|
-
|
|
57
|
+
# A double-quoted YAML scalar must be unescaped (\" → ", \\ → \) and may
|
|
58
|
+
# carry a trailing comment after the closing quote — match the quoted
|
|
59
|
+
# string properly first, else the escapes leak into the shell verbatim
|
|
60
|
+
# and grep hunts for quote characters that were never in the file.
|
|
61
|
+
qm = re.search(r'^outcome:\s*"((?:[^"\\]|\\.)*)"', body, re.MULTILINE)
|
|
62
|
+
if qm:
|
|
63
|
+
return re.sub(r'\\(.)', r'\1', qm.group(1)).strip()
|
|
64
|
+
# single-quoted YAML scalar: '' is the only escape (a literal ')
|
|
65
|
+
sm = re.search(r"^outcome:\s*'((?:[^']|'')*)'", body, re.MULTILINE)
|
|
66
|
+
if sm:
|
|
67
|
+
return sm.group(1).replace("''", "'").strip()
|
|
68
|
+
im = re.search(r'^outcome:\s*(.+?)\s*$', body, re.MULTILINE)
|
|
58
69
|
return im.group(1).strip() if im else ""
|
|
59
70
|
|
|
60
71
|
|
|
61
72
|
def stale_cd(cmd):
|
|
62
73
|
"""Walk the &&/; chain tracking cwd — `cd a && cd ../b` makes ../b relative
|
|
63
|
-
to a, not to root.
|
|
74
|
+
to a, not to root. A `(cd x && …)` subshell inherits the outer cwd but its
|
|
75
|
+
cd does NOT leak past the closing paren (outcomes are commonly chains of
|
|
76
|
+
subshells: `(cd web && test) && (cd channels && test)`). Parens inside
|
|
77
|
+
quotes (grep patterns) can unbalance the stack; that degrades to the outer
|
|
78
|
+
cwd, never a crash. Return the first cd target that doesn't exist, else None."""
|
|
64
79
|
cwd = ROOT
|
|
80
|
+
stack = []
|
|
65
81
|
for seg in re.split(r"&&|;|\|", cmd):
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
for _ in range(seg.count("(")):
|
|
83
|
+
stack.append(cwd)
|
|
84
|
+
m = re.search(r"\bcd\s+([^\s&|;)]+)", seg)
|
|
85
|
+
if m:
|
|
86
|
+
t = m.group(1)
|
|
87
|
+
nxt = Path(t) if t.startswith("/") else (cwd / t)
|
|
88
|
+
try:
|
|
89
|
+
nxt = nxt.resolve()
|
|
90
|
+
except Exception:
|
|
91
|
+
return t
|
|
92
|
+
if not nxt.exists():
|
|
93
|
+
return t
|
|
94
|
+
cwd = nxt
|
|
95
|
+
for _ in range(seg.count(")")):
|
|
96
|
+
cwd = stack.pop() if stack else ROOT
|
|
78
97
|
return None
|
|
79
98
|
|
|
80
99
|
|
|
@@ -57,6 +57,31 @@ assumes_entries() { # $1 = file
|
|
|
57
57
|
| while IFS= read -r l; do unquote "$l"; echo; done
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
# exec_dry — IC6: catch a path-shape-fragile proof at PROMISE, before it dies
|
|
61
|
+
# at settle. Runs proof: for real (env -i, from $base) and inspects stderr for
|
|
62
|
+
# the classic bug: two deliverables' accept: joined by `&&`, each independently
|
|
63
|
+
# written assuming CWD=repo-root, e.g. "cd A && test 1 && cd A && test 2" — the
|
|
64
|
+
# second bare `cd A` runs from inside A (after the first leg), tries A/A, dies.
|
|
65
|
+
# Subshell-wrapped legs ("(cd A && test 1) && (cd A && test 2)") reset CWD
|
|
66
|
+
# between legs and never hit this. A non-zero exit alone is NOT a failure here —
|
|
67
|
+
# red-before-green proofs are legitimately red; only the path-shape death is.
|
|
68
|
+
exec_dry() { # $1 = file, $2 = base dir (default $ROOT)
|
|
69
|
+
local file="$1" base="${2:-$ROOT}" proof err
|
|
70
|
+
proof="$(extract_proof "$file")"
|
|
71
|
+
if [ -z "$proof" ]; then
|
|
72
|
+
echo "[promise-lint:exec-dry] FAIL — no proof: to test"
|
|
73
|
+
return 1
|
|
74
|
+
fi
|
|
75
|
+
err="$(cd "$base" && env -i HOME="$HOME" PATH="$PATH" bash -c "$proof" 2>&1 1>/dev/null)"
|
|
76
|
+
if printf '%s' "$err" | grep -qE 'cd: .*No such file or directory'; then
|
|
77
|
+
echo "[promise-lint:exec-dry] FAIL — path-shape death (a repeated bare 'cd X &&' leg dies once a prior leg already moved CWD into X):"
|
|
78
|
+
printf '%s\n' "$err" | grep -E 'cd: .*No such file or directory' | sed 's/^/ /'
|
|
79
|
+
return 1
|
|
80
|
+
fi
|
|
81
|
+
echo "[promise-lint:exec-dry] OK — proof is path-shape safe (a non-zero exit here is fine; red-before-green is legitimate)"
|
|
82
|
+
return 0
|
|
83
|
+
}
|
|
84
|
+
|
|
60
85
|
lint() { # $1 = slug, $2 = file, $3 = no_run (true/false), $4 = red (true/false)
|
|
61
86
|
local slug="$1" file="$2" no_run="$3" red="$4" fails=0
|
|
62
87
|
echo "[promise-lint] slug=$slug file=${file#"$ROOT"/}"
|
|
@@ -106,6 +131,28 @@ lint() { # $1 = slug, $2 = file, $3 = no_run (true/false), $4 = red (true/false)
|
|
|
106
131
|
$(printf '%s\n' "$block" | grep '^[[:space:]]*accept:')
|
|
107
132
|
EOF
|
|
108
133
|
|
|
134
|
+
# user-visible ⇒ page: a deliverable that explicitly names a page/route/view
|
|
135
|
+
# (kind prefix `route:`/`page:`/`view:`/`component:`, an .astro / src/pages/
|
|
136
|
+
# path, or the phrase "a page") must be proven by at least one accept: that
|
|
137
|
+
# actually checks the page — curl / browser-check / do-prove / an http(s) URL /
|
|
138
|
+
# a `--route /x` flag / a src/pages/ or .astro file check.
|
|
139
|
+
# Heuristic is deliberately conservative (deliverables text only, NOT the
|
|
140
|
+
# world: block) so settled promises whose UI is proven via vitest/grep
|
|
141
|
+
# (brain, chat) don't retro-fail; it catches new promises that schedule a
|
|
142
|
+
# page but never load it.
|
|
143
|
+
local vis_re='(route|page|view|component):|src/pages/|\.astro| a page '
|
|
144
|
+
local page_proof_re='curl|browser-check|do-prove|https?://|--route[[:space:]]+/|src/pages/|\.astro'
|
|
145
|
+
local vis_item
|
|
146
|
+
vis_item="$(printf '%s\n' "$block" | grep -E '^[[:space:]]*-[[:space:]]*item:' | grep -iE "$vis_re" | head -1)"
|
|
147
|
+
if [ -n "$vis_item" ]; then
|
|
148
|
+
if ! printf '%s\n' "$block" | grep '^[[:space:]]*accept:' | grep -qiE "$page_proof_re"; then
|
|
149
|
+
echo "[promise-lint] FAIL — user-visible deliverable but no page-load proof:"
|
|
150
|
+
echo "[promise-lint] $(printf '%s' "$vis_item" | sed -E 's/^[[:space:]]*//')"
|
|
151
|
+
echo "[promise-lint] add a route/component deliverable whose accept: loads the page (curl / browser-check / do-prove.sh --route /x / src/pages file check)"
|
|
152
|
+
fails=$((fails+1))
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
|
|
109
156
|
# assumes: — client-side dependencies must be green at the making
|
|
110
157
|
if [ "$no_run" != true ]; then
|
|
111
158
|
local a_n=0
|
|
@@ -139,6 +186,60 @@ EOF
|
|
|
139
186
|
return 1
|
|
140
187
|
}
|
|
141
188
|
|
|
189
|
+
# check_ui_coupling — IC9 lint coupling: when a promise's derives: block has
|
|
190
|
+
# `ui: true`, its proof: must invoke `do-ui-gate.sh <slug>` (that promise's own
|
|
191
|
+
# filename stem) — a UI-bearing promise can't settle without its screens
|
|
192
|
+
# actually being gate-checked.
|
|
193
|
+
check_ui_coupling() { # $1 = file
|
|
194
|
+
local file="$1" slug ui_val proof
|
|
195
|
+
slug="$(basename "$file" .md)"
|
|
196
|
+
ui_val="$(fm "$file" | awk '/^derives:/{on=1;next} on && /^[a-zA-Z_-]+:/{exit} on && /^[[:space:]]*ui:/{sub(/^[[:space:]]*ui:[[:space:]]*/,"");print;exit}')"
|
|
197
|
+
ui_val="$(unquote "$ui_val" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')"
|
|
198
|
+
if [ "$ui_val" != "true" ]; then
|
|
199
|
+
echo "[promise-lint:ui-coupling] skip — derives.ui != true (slug=$slug)"
|
|
200
|
+
return 0
|
|
201
|
+
fi
|
|
202
|
+
proof="$(extract_proof "$file")"
|
|
203
|
+
if printf '%s' "$proof" | grep -qF "do-ui-gate.sh $slug"; then
|
|
204
|
+
echo "[promise-lint:ui-coupling] OK — proof invokes do-ui-gate.sh $slug"
|
|
205
|
+
return 0
|
|
206
|
+
fi
|
|
207
|
+
echo "[promise-lint:ui-coupling] FAIL — derives.ui: true but proof: does not invoke 'do-ui-gate.sh $slug'"
|
|
208
|
+
return 1
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# ── self-test: ui-coupling fixtures, zero network ────────────────────────────
|
|
212
|
+
if [ "${1:-}" = "--self-test-ui-coupling" ]; then
|
|
213
|
+
dir="$(mktemp -d)"; trap 'rm -rf "$dir"' EXIT
|
|
214
|
+
cat >"$dir/uinocheck.md" <<'F'
|
|
215
|
+
---
|
|
216
|
+
derives:
|
|
217
|
+
ui: true
|
|
218
|
+
proof: "true"
|
|
219
|
+
---
|
|
220
|
+
F
|
|
221
|
+
cat >"$dir/uigood.md" <<'F'
|
|
222
|
+
---
|
|
223
|
+
derives:
|
|
224
|
+
ui: true
|
|
225
|
+
proof: "bash .claude/scripts/do-ui-gate.sh uigood"
|
|
226
|
+
---
|
|
227
|
+
F
|
|
228
|
+
cat >"$dir/nouify.md" <<'F'
|
|
229
|
+
---
|
|
230
|
+
derives:
|
|
231
|
+
ui: false
|
|
232
|
+
proof: "true"
|
|
233
|
+
---
|
|
234
|
+
F
|
|
235
|
+
fails=0
|
|
236
|
+
check_ui_coupling "$dir/uinocheck.md" >/dev/null && { echo "FAIL: expected ui-coupling FAIL when ui:true but proof lacks do-ui-gate.sh"; fails=$((fails+1)); }
|
|
237
|
+
check_ui_coupling "$dir/uigood.md" >/dev/null || { echo "FAIL: expected ui-coupling PASS when proof invokes do-ui-gate.sh <slug>"; fails=$((fails+1)); }
|
|
238
|
+
check_ui_coupling "$dir/nouify.md" >/dev/null || { echo "FAIL: expected ui-coupling PASS (skip) when ui:false"; fails=$((fails+1)); }
|
|
239
|
+
echo "[promise-lint] self-test-ui-coupling: 3 fixtures, $fails failed"
|
|
240
|
+
exit "$fails"
|
|
241
|
+
fi
|
|
242
|
+
|
|
142
243
|
# ── self-test: fixtures, zero network ────────────────────────────────────────
|
|
143
244
|
if [ "${1:-}" = "--self-test" ]; then
|
|
144
245
|
dir="$(mktemp -d)"; trap 'rm -rf "$dir"' EXIT
|
|
@@ -194,6 +295,22 @@ assumes:
|
|
|
194
295
|
- "false"
|
|
195
296
|
proof: "test -f a"
|
|
196
297
|
---
|
|
298
|
+
F
|
|
299
|
+
cat >"$dir/pagegap.md" <<'F'
|
|
300
|
+
---
|
|
301
|
+
deliverables:
|
|
302
|
+
- item: "route: /foo — user sees the page"
|
|
303
|
+
accept: "grep -q foo a.ts"
|
|
304
|
+
proof: "grep -q foo a.ts"
|
|
305
|
+
---
|
|
306
|
+
F
|
|
307
|
+
cat >"$dir/pagegood.md" <<'F'
|
|
308
|
+
---
|
|
309
|
+
deliverables:
|
|
310
|
+
- item: "route: /foo — user sees the page"
|
|
311
|
+
accept: "curl -sf https://one.ie/foo"
|
|
312
|
+
proof: "curl -sf https://one.ie/foo"
|
|
313
|
+
---
|
|
197
314
|
F
|
|
198
315
|
fails=0
|
|
199
316
|
lint good "$dir/good.md" true false >/dev/null || { echo "FAIL good → 0"; fails=$((fails+1)); }
|
|
@@ -203,19 +320,48 @@ F
|
|
|
203
320
|
lint emptyproof "$dir/emptyproof.md" true false >/dev/null && { echo "FAIL emptyproof → 1"; fails=$((fails+1)); }
|
|
204
321
|
lint badassume "$dir/badassume.md" false false >/dev/null && { echo "FAIL badassume → 1"; fails=$((fails+1)); }
|
|
205
322
|
lint redcheck "$dir/gap.md" true true >/dev/null && { echo "FAIL red-on-gap → 1"; fails=$((fails+1)); }
|
|
206
|
-
echo "
|
|
323
|
+
lint pagegap "$dir/pagegap.md" true false >/dev/null && { echo "FAIL pagegap → 1 (user-visible, no page proof)"; fails=$((fails+1)); }
|
|
324
|
+
lint pagegood "$dir/pagegood.md" true false >/dev/null || { echo "FAIL pagegood → 0"; fails=$((fails+1)); }
|
|
325
|
+
echo "[promise-lint] self-test: 9 fixtures, $fails failed"
|
|
326
|
+
exit "$fails"
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
# ── self-test: exec-dry fixtures, zero network ───────────────────────────────
|
|
330
|
+
if [ "${1:-}" = "--self-test-exec-dry" ]; then
|
|
331
|
+
dir="$(mktemp -d)"; trap 'rm -rf "$dir"' EXIT
|
|
332
|
+
mkdir -p "$dir/leg"
|
|
333
|
+
touch "$dir/leg/marker"
|
|
334
|
+
cat >"$dir/failcase.md" <<'F'
|
|
335
|
+
---
|
|
336
|
+
proof: "cd leg && test -f marker && cd leg && test -f marker"
|
|
337
|
+
---
|
|
338
|
+
F
|
|
339
|
+
cat >"$dir/passcase.md" <<'F'
|
|
340
|
+
---
|
|
341
|
+
proof: "(cd leg && test -f marker) && (cd leg && test -f marker)"
|
|
342
|
+
---
|
|
343
|
+
F
|
|
344
|
+
fails=0
|
|
345
|
+
exec_dry "$dir/failcase.md" "$dir" >/dev/null && { echo "FAIL: expected exec-dry FAIL on repeated bare 'cd leg &&' legs"; fails=$((fails+1)); }
|
|
346
|
+
exec_dry "$dir/passcase.md" "$dir" >/dev/null || { echo "FAIL: expected exec-dry PASS on subshell-wrapped legs"; fails=$((fails+1)); }
|
|
347
|
+
echo "[promise-lint] self-test-exec-dry: 2 fixtures, $fails failed"
|
|
207
348
|
exit "$fails"
|
|
208
349
|
fi
|
|
209
350
|
|
|
210
351
|
# ── main ─────────────────────────────────────────────────────────────────────
|
|
211
352
|
SLUG="${1:-}"; [ -z "$SLUG" ] && { grep '^# ' "$0" | sed 's/^# //' | sed -n '3,24p'; exit 4; }
|
|
212
353
|
shift
|
|
213
|
-
NO_RUN=false; RED=false
|
|
354
|
+
NO_RUN=false; RED=false; EXEC_DRY=false
|
|
214
355
|
while [ $# -gt 0 ]; do
|
|
215
356
|
case "$1" in
|
|
216
|
-
--no-run)
|
|
217
|
-
--red)
|
|
357
|
+
--no-run) NO_RUN=true; shift ;;
|
|
358
|
+
--red) RED=true; shift ;;
|
|
359
|
+
--exec-dry) EXEC_DRY=true; shift ;;
|
|
218
360
|
*) echo "unknown flag $1"; exit 4 ;;
|
|
219
361
|
esac
|
|
220
362
|
done
|
|
363
|
+
if $EXEC_DRY; then
|
|
364
|
+
exec_dry "${PROMISE_FILE:-$ROOT/text/$SLUG.md}"
|
|
365
|
+
exit $?
|
|
366
|
+
fi
|
|
221
367
|
lint "$SLUG" "${PROMISE_FILE:-$ROOT/text/$SLUG.md}" "$NO_RUN" "$RED"
|
|
@@ -24,6 +24,35 @@ set -u
|
|
|
24
24
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
25
25
|
BASE="${ONE_API_URL:-http://localhost:4321}"
|
|
26
26
|
|
|
27
|
+
# Credentials come from ONE_ENV_FILE (DO_ENV_FILE is the accepted alias), the same
|
|
28
|
+
# indirection do-signal.sh and do-fleet.sh honour and that factory-repo.sh's
|
|
29
|
+
# --check-env-indirection enforces for every shipped credential-reading script.
|
|
30
|
+
#
|
|
31
|
+
# This script read $GATEWAY_API_KEY from the PROCESS environment only, and nothing
|
|
32
|
+
# exports it — so it never sent an Authorization header and every mark/warn 401'd.
|
|
33
|
+
# The failure was invisible because a 401 printed "wire unreachable, broadcast-only":
|
|
34
|
+
# the settle blaming the network for its own missing credential. Measured 2026-08-02
|
|
35
|
+
# settling pages-fill-pack and tauri — both proofs exit 0, both marks 401, against a
|
|
36
|
+
# localhost:4321 answering 200. tauri had already settled KEPT this way once before.
|
|
37
|
+
ONE_ENV_FILE="${ONE_ENV_FILE:-${DO_ENV_FILE:-one.ie/web/.env}}"
|
|
38
|
+
DO_ENV_FILE="$ONE_ENV_FILE"
|
|
39
|
+
|
|
40
|
+
# Unquoted on purpose: expands to nothing outside the monorepo, so the loop
|
|
41
|
+
# degrades to ONE_ENV_FILE alone rather than erroring.
|
|
42
|
+
_MONOREPO_ENV_CANDIDATES="$ROOT/one.ie/web/.env $ROOT/one.ie/web/.dev.vars"
|
|
43
|
+
|
|
44
|
+
# Reads a key out of the first env file that carries it. Never echoes the value
|
|
45
|
+
# anywhere but its own stdout, which the caller assigns straight to a local.
|
|
46
|
+
_env_key() { # $1 = key name
|
|
47
|
+
local _f _v
|
|
48
|
+
for _f in "$ONE_ENV_FILE" "$ROOT/.env" $_MONOREPO_ENV_CANDIDATES; do
|
|
49
|
+
[ -f "$_f" ] || continue
|
|
50
|
+
_v=$(grep -E "^$1=" "$_f" 2>/dev/null | head -1 | sed 's/^[^=]*=//;s/^"//;s/"$//' || true)
|
|
51
|
+
[ -n "$_v" ] && { printf '%s' "$_v"; return 0; }
|
|
52
|
+
done
|
|
53
|
+
printf ''
|
|
54
|
+
}
|
|
55
|
+
|
|
27
56
|
usage() { grep '^# ' "$0" | sed 's/^# //' | sed -n '3,14p'; exit 4; }
|
|
28
57
|
|
|
29
58
|
# ── frontmatter proof: extractor (first frontmatter block only) ──────────────
|
|
@@ -82,6 +111,55 @@ settle() { # $1 = slug, $2 = promise file, $3 = composite ("" = none), $4 = dry-
|
|
|
82
111
|
local rc=0
|
|
83
112
|
( cd "$ROOT" && bash -c "$proof" ) >/dev/null 2>&1 || rc=$?
|
|
84
113
|
|
|
114
|
+
# EXIT 3 IS "COULD NOT RUN", NOT "BROKEN". A proof: is an `&&`-join, so a leg
|
|
115
|
+
# that cannot reach its substrate stops the chain and the join returns 3 — and
|
|
116
|
+
# this script used to fold that into `else → warn`. A TypeDB blip would then
|
|
117
|
+
# deposit resistance on a proven path, permanently, for infrastructure noise:
|
|
118
|
+
# the settle would record a promise as BROKEN for a reason the maker cannot
|
|
119
|
+
# fix and did not cause. text/factory.md § "A note on red vs cannot run" states
|
|
120
|
+
# the rule — "exit 3 anywhere in the chain means re-run later, never warn" —
|
|
121
|
+
# and until now the tool that enforces promises did not implement the sharpest
|
|
122
|
+
# idea in them.
|
|
123
|
+
#
|
|
124
|
+
# An unverifiable promise closes NOTHING: no mark, no warn, no on-chain settle,
|
|
125
|
+
# and the loop stays open by design. It is not `dissolved` either — dissolved
|
|
126
|
+
# means there is no observable to run (rule 1's third exit); here the observable
|
|
127
|
+
# exists and was simply unreachable. The caller re-runs later. /close must treat
|
|
128
|
+
# exit 3 as "not settled yet", never as a close.
|
|
129
|
+
if [ "$rc" -eq 3 ]; then
|
|
130
|
+
echo "[promise-settle] proof exit=3 → UNVERIFIABLE (substrate unreachable) — no mark, no warn, promise stays open"
|
|
131
|
+
echo "[promise-settle] re-run when the substrate answers: $0 $slug"
|
|
132
|
+
if [ "$dry" = true ]; then
|
|
133
|
+
echo "[promise-settle] DRY would write no path and broadcast unverifiable"
|
|
134
|
+
return 3
|
|
135
|
+
fi
|
|
136
|
+
# Announce it anyway: an unverifiable settle is news the world can act on
|
|
137
|
+
# (someone can fix the cluster), and the broadcast writes no path.
|
|
138
|
+
# shellcheck source=/dev/null
|
|
139
|
+
source "$ROOT/.claude/hooks/lib/signal.sh" 2>/dev/null && \
|
|
140
|
+
emit_world "promise" "fyi" "promise,do,unverifiable,$slug" \
|
|
141
|
+
"promise $slug UNVERIFIABLE — proof could not run; no path written" "proof_exit=3"
|
|
142
|
+
echo "[promise-settle] broadcast world:announce queued (fire-and-forget, unconfirmed) tags=[promise,do,unverifiable,$slug]"
|
|
143
|
+
return 3
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
# derives gate — a promise cannot settle KEPT while an artifact its derives: block
|
|
147
|
+
# declares true is missing on disk (the class that let security-gates ship a missing
|
|
148
|
+
# -docs.md). Zero LLM; folds into the verdict so the close still ends in mark/warn.
|
|
149
|
+
# Only exit 1 ("artifact missing") overrides kept→broken — exit 2 ("no promise file
|
|
150
|
+
# at the conventional text/<slug>.md path") is a different failure class (e.g. a
|
|
151
|
+
# settle() call against a custom PROMISE_FILE, or a self-test fixture slug with no
|
|
152
|
+
# real promise on disk) and must not be conflated with a genuinely broken promise.
|
|
153
|
+
if [ "$rc" -eq 0 ]; then
|
|
154
|
+
local derives_out derives_rc=0
|
|
155
|
+
derives_out="$(bash "$ROOT/.claude/scripts/do-derives-check.sh" "$slug" 2>&1)" || derives_rc=$?
|
|
156
|
+
if [ "$derives_rc" -eq 1 ]; then
|
|
157
|
+
echo "[promise-settle] derives-check FAILED — declared artifact missing; overriding kept→broken:"
|
|
158
|
+
echo "$derives_out" | sed 's/^/[promise-settle] /'
|
|
159
|
+
rc=1
|
|
160
|
+
fi
|
|
161
|
+
fi
|
|
162
|
+
|
|
85
163
|
local verb strength verdict
|
|
86
164
|
if [ "$rc" -eq 0 ]; then
|
|
87
165
|
verdict=kept; verb=mark
|
|
@@ -94,7 +172,7 @@ settle() { # $1 = slug, $2 = promise file, $3 = composite ("" = none), $4 = dry-
|
|
|
94
172
|
local edge="promise:${slug}→proof"
|
|
95
173
|
if [ "$dry" = true ]; then
|
|
96
174
|
echo "[promise-settle] DRY $verb $edge strength=$strength"
|
|
97
|
-
echo "[promise-settle] DRY broadcast world:announce tags=[promise,do,$verdict,$slug]"
|
|
175
|
+
echo "[promise-settle] DRY broadcast world:announce queued (fire-and-forget, unconfirmed) tags=[promise,do,$verdict,$slug]"
|
|
98
176
|
if [ -n "${SUI_PRIVATE_KEY:-}" ]; then
|
|
99
177
|
echo "[promise-settle] DRY on-chain settle_promise kept=$([ "$verdict" = kept ] && echo true || echo false) signer=env-override"
|
|
100
178
|
else
|
|
@@ -112,20 +190,46 @@ settle() { # $1 = slug, $2 = promise file, $3 = composite ("" = none), $4 = dry-
|
|
|
112
190
|
local -a hdr=(-H 'Content-Type: application/json')
|
|
113
191
|
# Secrets off argv (ps aux would leak a bare -H value) — fold both into a
|
|
114
192
|
# private 0600 curl --config file instead.
|
|
193
|
+
# Environment wins over the env file, so an explicit export still overrides.
|
|
194
|
+
local gsecret gkey
|
|
195
|
+
gsecret="${GATEWAY_SERVICE_SECRET:-$(_env_key GATEWAY_SERVICE_SECRET)}"
|
|
196
|
+
gkey="${GATEWAY_API_KEY:-$(_env_key GATEWAY_API_KEY)}"
|
|
115
197
|
local authcfg=""
|
|
116
|
-
if [ -n "$
|
|
198
|
+
if [ -n "$gsecret" ] || [ -n "$gkey" ]; then
|
|
117
199
|
authcfg="$(mktemp)"; chmod 600 "$authcfg"
|
|
118
|
-
[ -n "$
|
|
119
|
-
[ -n "$
|
|
200
|
+
[ -n "$gsecret" ] && printf 'header = "X-Gateway-Key: %s"\n' "$gsecret" >>"$authcfg"
|
|
201
|
+
[ -n "$gkey" ] && printf 'header = "Authorization: Bearer %s"\n' "$gkey" >>"$authcfg"
|
|
120
202
|
hdr+=(--config "$authcfg")
|
|
121
203
|
fi
|
|
122
|
-
|
|
123
|
-
|
|
204
|
+
# --max-time 20, not 5. This POST is a SUBSTRATE WRITE: the route authenticates,
|
|
205
|
+
# then opens a TypeDB transaction against the cloud cluster, where a cold sign-in
|
|
206
|
+
# alone measured 4.1s (factory-check.sh header, 2026-07-29). A 5s ceiling made a
|
|
207
|
+
# healthy system report "wire unreachable" — the settle blaming the network for
|
|
208
|
+
# its own stopwatch, which is exactly what /factory's gaps board did before it
|
|
209
|
+
# was fixed. Measured after the change: the same call answers well inside 20s.
|
|
210
|
+
#
|
|
211
|
+
# curl's own failure is captured separately. `|| echo 000` appended a second
|
|
212
|
+
# "000" to the -w output, printing http=000000 — a status code that does not
|
|
213
|
+
# exist, in the line an operator reads to decide whether the path moved.
|
|
214
|
+
local curl_rc=0
|
|
215
|
+
http="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 -X POST "$BASE/api/$verb/$enc" \
|
|
216
|
+
"${hdr[@]}" -d "{\"strength\":$strength,\"tags\":[\"promise\",\"do\",\"$slug\"]}" 2>/dev/null)" || curl_rc=$?
|
|
217
|
+
[ "$curl_rc" -ne 0 ] && http="000 (curl rc=$curl_rc)"
|
|
124
218
|
[ -n "$authcfg" ] && rm -f "$authcfg"
|
|
219
|
+
# Name the failure accurately. Calling a 401 "wire unreachable" is what hid the
|
|
220
|
+
# missing-credential bug above: an operator reading that line goes looking at the
|
|
221
|
+
# network for a fault that is entirely local.
|
|
125
222
|
if [ "$http" = 200 ]; then
|
|
126
223
|
echo "[promise-settle] $verb $edge strength=$strength http=$http"
|
|
127
224
|
else
|
|
128
|
-
|
|
225
|
+
local why
|
|
226
|
+
case "$http" in
|
|
227
|
+
401|403) why="AUTH REJECTED — no/!bad GATEWAY_API_KEY; checked ONE_ENV_FILE=$ONE_ENV_FILE. Substrate NOT written" ;;
|
|
228
|
+
000*) why="wire unreachable ($BASE)" ;;
|
|
229
|
+
404) why="no such route at $BASE/api/$verb — substrate NOT written" ;;
|
|
230
|
+
*) why="substrate NOT written" ;;
|
|
231
|
+
esac
|
|
232
|
+
echo "[promise-settle] $verb $edge strength=$strength http=$http — $why, broadcast-only"
|
|
129
233
|
fi
|
|
130
234
|
|
|
131
235
|
# public broadcast — the world hears the settlement regardless of the substrate wire
|
|
@@ -133,7 +237,14 @@ settle() { # $1 = slug, $2 = promise file, $3 = composite ("" = none), $4 = dry-
|
|
|
133
237
|
source "$ROOT/.claude/hooks/lib/signal.sh" 2>/dev/null && \
|
|
134
238
|
emit_world "promise" "fyi" "promise,do,$verdict,$slug" \
|
|
135
239
|
"promise $slug $verdict — $verb $edge strength=$strength" "proof_exit=$rc"
|
|
136
|
-
|
|
240
|
+
# NOT a confirmation. emit_world curls in a background subshell, discards the
|
|
241
|
+
# response and returns 0 unconditionally — by design, since it also runs from
|
|
242
|
+
# hooks that must never block. So this line reports what was QUEUED, not what
|
|
243
|
+
# the world heard. Printing it as a delivery would be a settle announcing its
|
|
244
|
+
# own success on no evidence, which is the failure class this whole promise
|
|
245
|
+
# family exists to name. Confirming it needs a synchronous emitter; until then
|
|
246
|
+
# the wording carries the uncertainty.
|
|
247
|
+
echo "[promise-settle] broadcast world:announce queued (fire-and-forget, unconfirmed) tags=[promise,do,$verdict,$slug]"
|
|
137
248
|
|
|
138
249
|
# on-chain twin — settle_promise on the generated Move object (schema/sui.tql →
|
|
139
250
|
# substrate.move). Fires only when armed (signer present + object minted); the
|
|
@@ -183,6 +294,10 @@ if [ "${1:-}" = "--self-test" ]; then
|
|
|
183
294
|
printf -- '---\ntitle: no proof here\n---\n' >"$dir/noproof.md"
|
|
184
295
|
printf -- '---\nproof: "[ \\"a\\" = \\"a\\" ]"\n---\n' >"$dir/escaped-quotes.md"
|
|
185
296
|
printf -- '---\nproof: "true"\ncontract:\n oracle: "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"\n---\n' >"$dir/unmatchable-oracle.md"
|
|
297
|
+
# cannot-run: the `&&`-join stops at a leg that exits 3, so the join returns 3.
|
|
298
|
+
# The second leg must NEVER run — if it did, this fixture would return 0 and the
|
|
299
|
+
# test would pass for the wrong reason.
|
|
300
|
+
printf -- '---\nproof: "exit 3 && true"\n---\n' >"$dir/cannotrun.md"
|
|
186
301
|
fails=0
|
|
187
302
|
out="$(settle kept-fixture "$dir/kept.md" 0.8 true)"
|
|
188
303
|
echo "$out" | grep -q 'DRY mark .*strength=4' || { echo "FAIL kept → mark strength=4"; fails=$((fails+1)); }
|
|
@@ -195,7 +310,14 @@ if [ "${1:-}" = "--self-test" ]; then
|
|
|
195
310
|
rc=$?
|
|
196
311
|
echo "$out" | grep -q 'DRY on-chain: unmatchable oracle=0xffff.*would skip' || { echo "FAIL unmatchable-oracle → clean skip, no wrong-key attempt"; fails=$((fails+1)); }
|
|
197
312
|
[ "$rc" -eq 0 ] || { echo "FAIL unmatchable-oracle → exit 0"; fails=$((fails+1)); }
|
|
198
|
-
|
|
313
|
+
# cannot-run must be its OWN verdict: exit 3, and NEITHER verb written. The bug
|
|
314
|
+
# this fixture pins is a cluster blip settling a promise BROKEN and warning its
|
|
315
|
+
# path — so asserting "not mark" is not enough; assert "not warn" explicitly.
|
|
316
|
+
out="$(settle cannotrun-fixture "$dir/cannotrun.md" "" true)"; rc=$?
|
|
317
|
+
[ "$rc" -eq 3 ] || { echo "FAIL cannot-run → exit 3 (got $rc)"; fails=$((fails+1)); }
|
|
318
|
+
echo "$out" | grep -q 'UNVERIFIABLE' || { echo "FAIL cannot-run → says UNVERIFIABLE"; fails=$((fails+1)); }
|
|
319
|
+
echo "$out" | grep -qE 'DRY (warn|mark) ' && { echo "FAIL cannot-run → wrote a path verb (must write neither)"; fails=$((fails+1)); }
|
|
320
|
+
echo "[promise-settle] self-test: 6 fixtures, $fails failed"
|
|
199
321
|
exit "$fails"
|
|
200
322
|
fi
|
|
201
323
|
|