@oneie/claude 0.7.0 → 0.9.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/abm-strategist.md +89 -0
- package/agents/ads-meta.md +97 -0
- package/agents/analyst.md +173 -0
- package/agents/architect.md +280 -0
- package/agents/brand-guardian.md +88 -0
- package/agents/brand-strategist.md +92 -0
- package/agents/campaign-content.md +90 -0
- package/agents/campaign-email.md +88 -0
- package/agents/campaign-sms.md +87 -0
- package/agents/campaign-social.md +87 -0
- package/agents/cco.md +99 -0
- package/agents/ceo.md +106 -0
- package/agents/chairman.md +120 -0
- package/agents/cmo.md +101 -0
- package/agents/community-greeter.md +89 -0
- package/agents/community-moderator.md +92 -0
- package/agents/compliance.md +92 -0
- package/agents/copywriter.md +100 -0
- package/agents/creative-strategist.md +95 -0
- package/agents/cro.md +98 -0
- package/agents/cto.md +267 -0
- package/agents/customer-interviewer.md +93 -0
- package/agents/customer-researcher.md +89 -0
- package/agents/customer-success-manager.md +90 -0
- package/agents/customer-trainer.md +90 -0
- package/agents/cxo.md +100 -0
- package/agents/demand-creator.md +89 -0
- package/agents/demo-mover.md +83 -0
- package/agents/demo-specialist.md +89 -0
- package/agents/demo-thai-family-law.md +87 -0
- package/agents/designer.md +112 -0
- package/agents/discovery-caller.md +98 -0
- package/agents/doctor.md +139 -0
- package/agents/educate-coach.md +100 -0
- package/agents/elevate-tutor.md +83 -0
- package/agents/email-lifecycle-marketer.md +91 -0
- package/agents/engage-specialist.md +99 -0
- package/agents/events-coordinator.md +90 -0
- package/agents/foundation-builder.md +94 -0
- package/agents/funnel-architect.md +89 -0
- package/agents/gift-creator.md +100 -0
- package/agents/google-ads.md +103 -0
- package/agents/guide.md +292 -0
- package/agents/helpdesk-dispatcher.md +88 -0
- package/agents/hook-specialist.md +91 -0
- package/agents/identify-optimizer.md +101 -0
- package/agents/implementer.md +305 -0
- package/agents/incident-commander.md +120 -0
- package/agents/insights-lead.md +93 -0
- package/agents/journey-runner.md +113 -0
- package/agents/linkedin-ads.md +123 -0
- package/agents/live-sales-chat.md +90 -0
- package/agents/market-researcher.md +88 -0
- package/agents/media-buyer.md +96 -0
- package/agents/movers-customer-researcher.md +95 -0
- package/agents/movers-foundation-builder.md +96 -0
- package/agents/movers-market-researcher.md +97 -0
- package/agents/movers-pricing-strategist.md +171 -0
- package/agents/nurture-architect.md +99 -0
- package/agents/offer-architect.md +92 -0
- package/agents/onboarder.md +222 -0
- package/agents/onboarding-specialist.md +92 -0
- package/agents/operations-dashboard.md +98 -0
- package/agents/perf-engineer.md +348 -0
- package/agents/playbook-writer.md +71 -0
- package/agents/plg-strategist.md +91 -0
- package/agents/positioning-architect.md +88 -0
- package/agents/press-officer.md +89 -0
- package/agents/pricing-strategist.md +94 -0
- package/agents/privacy-officer.md +92 -0
- package/agents/referral-manager.md +91 -0
- package/agents/refine-analyst.md +102 -0
- package/agents/release-manager.md +261 -0
- package/agents/renewals-upsell-rep.md +90 -0
- package/agents/review-engineer.md +327 -0
- package/agents/rewards-steward.md +82 -0
- package/agents/sales-call-coach.md +94 -0
- package/agents/sales-closer.md +98 -0
- package/agents/security-auditor.md +313 -0
- package/agents/sell-closer.md +101 -0
- package/agents/share-amplifier.md +99 -0
- package/agents/social-media-manager.md +92 -0
- package/agents/storyteller.md +250 -0
- package/agents/strategist.md +83 -0
- package/agents/strategy-aligner.md +94 -0
- package/agents/support-agent.md +90 -0
- package/agents/tagger.md +245 -0
- package/agents/tech-writer.md +216 -0
- package/agents/test-engineer.md +243 -0
- package/agents/tiktok-ads.md +105 -0
- package/agents/tracking-engineer.md +92 -0
- package/agents/upsell-strategist.md +101 -0
- package/agents/voice.md +85 -0
- package/agents/w4-tools.md +153 -0
- package/agents/w4-verify.md +20 -138
- package/agents/workflow-optimiser.md +84 -0
- package/commands/close.md +814 -160
- package/commands/create.md +2 -2
- package/commands/deploy.md +554 -175
- package/commands/do.md +119 -109
- package/commands/fast.md +67 -0
- package/commands/improve.md +2 -2
- package/commands/one.md +418 -0
- package/commands/see.md +1 -1
- package/commands/sync.md +1 -1
- package/commands/tasks.md +222 -0
- package/commands/vespio.md +110 -0
- package/commands/vespio.remote.md +227 -0
- package/hooks/hooks.json +25 -79
- package/hooks/lib/governor-escape-match.sh +88 -0
- package/hooks/lib/hook.sh +4 -13
- package/hooks/lib/signal.sh +9 -2
- package/hooks/scripts/branch-pin.sh +6 -0
- package/hooks/scripts/config-protect.sh +6 -0
- package/hooks/scripts/do-outcome-gate.sh +19 -2
- package/hooks/scripts/git-add-guard.sh +43 -2
- package/hooks/scripts/governor-escape.sh +96 -0
- package/hooks/scripts/load-guard.sh +127 -0
- package/hooks/scripts/post-edit-check.sh +36 -29
- package/hooks/scripts/session-start.sh +34 -80
- package/hooks/scripts/task-complete-verify.sh +46 -40
- package/package.json +1 -1
- package/rules/documentation.md +9 -0
- package/scripts/ad-copy-lint.sh +656 -0
- package/scripts/agentverse-audit.sh +256 -0
- package/scripts/asi-walk.sh +435 -0
- package/scripts/astro-build-cached.sh +143 -0
- package/scripts/blocks-manifest-cached.sh +100 -0
- package/scripts/blocks-manifest.mjs +497 -0
- package/scripts/blocks-ratchet.sh +116 -0
- package/scripts/blocks-render-probe.mjs +529 -0
- package/scripts/blocks-usage.mjs +334 -0
- package/scripts/chat-context-check.sh +89 -0
- package/scripts/close-metrics.sh +558 -0
- package/scripts/close-owner.sh +268 -0
- package/scripts/db-sync-lock-check.sh +116 -0
- package/scripts/deploy-dev.sh +35 -0
- package/scripts/deploy-emit.sh +298 -0
- package/scripts/deploy-gate-check.sh +128 -0
- package/scripts/deploy-ready.sh +78 -0
- package/scripts/deploy-record.sh +605 -0
- package/scripts/deploy.sh +1273 -0
- package/scripts/do-auto.sh +598 -48
- package/scripts/do-brief.sh +113 -0
- package/scripts/do-close.sh +1137 -0
- package/scripts/do-commit.sh +75 -0
- package/scripts/do-consumer-sweep.sh +18 -1
- package/scripts/do-cycle-shape-check.sh +160 -0
- package/scripts/do-decide.sh +476 -0
- package/scripts/do-derive-check.sh +436 -0
- package/scripts/do-fleet.sh +106 -28
- package/scripts/do-folder.sh +10 -1
- package/scripts/do-next.sh +106 -0
- package/scripts/do-orchestrate.sh +17 -5
- package/scripts/do-plan-json.mjs +201 -0
- package/scripts/do-plan-json.sh +8 -0
- package/scripts/do-preflight.sh +117 -0
- package/scripts/do-project.sh +157 -0
- package/scripts/do-prove-selftest.sh +108 -0
- package/scripts/do-prove.sh +295 -23
- package/scripts/do-rank.py +31 -1
- package/scripts/do-recon-cache.sh +7 -1
- package/scripts/do-recon-pack.sh +196 -0
- package/scripts/do-reconcile.sh +121 -4
- package/scripts/do-signal.sh +280 -23
- package/scripts/do-smoke.sh +18 -1
- package/scripts/do-test-gate.sh +80 -0
- package/scripts/do-tick.sh +102 -0
- package/scripts/do-tier.sh +6 -0
- package/scripts/do-triage.sh +182 -0
- package/scripts/do-ui-gate.sh +1 -1
- package/scripts/do-w4-gates.sh +451 -0
- package/scripts/do-walk.sh +12 -1
- package/scripts/env-sync.sh +173 -0
- package/scripts/factory-brief-check.sh +330 -0
- package/scripts/factory-check.sh +68 -14
- package/scripts/factory-close-check.sh +257 -0
- package/scripts/factory-emit.sh +211 -0
- package/scripts/factory-executor-check.mjs +353 -0
- package/scripts/factory-peak.sh +301 -0
- package/scripts/factory-repo.sh +118 -3
- package/scripts/factory-review-check.mjs +61 -0
- package/scripts/factory-ship.sh +61 -0
- package/scripts/factory-tasks-check.sh +18 -1
- package/scripts/factory-turn.sh +326 -0
- package/scripts/factory-walk.sh +396 -0
- package/scripts/factory-width.sh +57 -0
- package/scripts/fade-toxic.sh +4 -3
- package/scripts/fixtures/factory-brief-real.md +44 -0
- package/scripts/fixtures/triage-dupe.md +5 -0
- package/scripts/fleet-manifest.mjs +108 -0
- package/scripts/fleet-status.sh +110 -0
- package/scripts/full-suite-paths-check.sh +144 -0
- package/scripts/gate-reaper-check.sh +98 -0
- package/scripts/gate-reaper.sh +125 -0
- package/scripts/gate-run.sh +73 -0
- package/scripts/gc-content-check.sh +140 -0
- package/scripts/gen-dev-config.py +20 -0
- package/scripts/govern-bound-check.sh +60 -0
- package/scripts/govern-claims-check.sh +233 -0
- package/scripts/govern-mem-check.sh +290 -0
- package/scripts/governor-doors-check.sh +362 -0
- package/scripts/governor-escape-check.sh +171 -0
- package/scripts/health.sh +413 -0
- package/scripts/id-inventory.mjs +418 -0
- package/scripts/land.sh +551 -0
- package/scripts/lib/gc-finished.sh +77 -0
- package/scripts/lib/govern.sh +361 -0
- package/scripts/lib/govern.ts +756 -0
- package/scripts/lighthouse-run.sh +187 -0
- package/scripts/livekit-live-check.sh +61 -0
- package/scripts/livekit-ratchet.sh +46 -0
- package/scripts/load-guard-check.sh +49 -0
- package/scripts/machine-check.sh +102 -0
- package/scripts/machine-watch.sh +177 -0
- package/scripts/one-agents.mjs +415 -0
- package/scripts/one-resume.sh +78 -0
- package/scripts/orphan-baseline.json +182 -0
- package/scripts/orphan-modules.mjs +179 -0
- package/scripts/pr-body.sh +335 -0
- package/scripts/preview-fd-check.sh +289 -0
- package/scripts/promise-manifest.mjs +24 -1
- package/scripts/release.sh +322 -0
- package/scripts/roles-check.sh +946 -0
- package/scripts/sdk-build-cached.sh +64 -0
- package/scripts/signal-watch.sh +241 -0
- package/scripts/skills-publish.sh +94 -0
- package/scripts/speed-cache-check.sh +173 -0
- package/scripts/speed-check.mjs +907 -0
- package/scripts/speed-parity-check.sh +648 -0
- package/scripts/speed-waterfall-check.sh +355 -0
- package/scripts/substrate-env-parity.mjs +156 -0
- package/scripts/tasks-claim-race.mjs +108 -0
- package/scripts/tasks-loop.sh +185 -0
- package/scripts/test-cached.sh +255 -0
- package/scripts/test-full.sh +87 -0
- package/scripts/test-honesty.mjs +137 -0
- package/scripts/test-lanes.sh +166 -0
- package/scripts/test-speed.sh +94 -0
- package/scripts/triage-shape-check.sh +149 -0
- package/scripts/tsc-cached.sh +179 -0
- package/scripts/typedb-flake-check.sh +197 -0
- package/scripts/urls-lint.sh +15 -0
- package/scripts/verify-fast.sh +445 -0
- package/scripts/vespio-sync.sh +149 -0
- package/scripts/wf-check.mjs +104 -1
- package/scripts/worktree-preview.sh +879 -0
- package/scripts/worktree-up.sh +208 -0
- package/skills/livekit-agents/SKILL.md +285 -0
- package/skills/livekit-agents/references/freshness-rules.md +168 -0
- package/skills/shadcn/SKILL.md +1 -1
- package/skills/signal/SKILL.md +0 -1
- package/skills/voice/SKILL.md +94 -6
- package/skills/voice/corpus-check.sh +87 -0
- package/hooks/scripts/compact-hint.sh +0 -35
- package/hooks/scripts/gate-guard.sh +0 -83
- package/hooks/scripts/read-tracker.sh +0 -26
- package/hooks/scripts/session-end-verify.sh +0 -51
- package/hooks/scripts/stop-reflect.sh +0 -140
- package/hooks/scripts/sync-priority-todo.sh +0 -57
- package/hooks/scripts/sync-todo-docs.sh +0 -46
- package/hooks/scripts/tool-signal.sh +0 -48
- package/scripts/do-tasks-bridge.py +0 -366
|
@@ -0,0 +1,946 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# roles-check.sh — the nine behavioural checks for roles-vantage-collapse.
|
|
3
|
+
#
|
|
4
|
+
# Interface Contract row 1: `roles-check.sh --check-<name>` · exit 0 = pass ·
|
|
5
|
+
# every subcommand must demonstrate its own red path (never merely assert that
|
|
6
|
+
# a file or a line is present).
|
|
7
|
+
#
|
|
8
|
+
# --check-gate C1 IMPLEMENTED
|
|
9
|
+
# --check-decide C3 IMPLEMENTED
|
|
10
|
+
# --check-503 C3 IMPLEMENTED
|
|
11
|
+
# --check-floors C4 IMPLEMENTED
|
|
12
|
+
# --check-no-vantage C5 IMPLEMENTED
|
|
13
|
+
# --check-worldkey C6 IMPLEMENTED
|
|
14
|
+
# --check-staff C7 stub
|
|
15
|
+
# --check-suite C8 stub
|
|
16
|
+
# --check-docs C9 stub
|
|
17
|
+
#
|
|
18
|
+
# --check-gate proves the gate that catches the bug this plan exists to kill: a
|
|
19
|
+
# vantage word compared against a rung word (`viewer === 'owner'`) — a ts(2367)
|
|
20
|
+
# only `astro check` can see, because `tsc --noEmit` never reads .astro
|
|
21
|
+
# frontmatter. It runs one.ie/web's astro-check ratchet twice: clean (green —
|
|
22
|
+
# the committed baseline is accurate) then with a planted comparison (red — and
|
|
23
|
+
# the diagnostic must name the planted file, ts(2367), and "have no overlap").
|
|
24
|
+
# Presence of the script line in package.json is NOT the check.
|
|
25
|
+
#
|
|
26
|
+
# Env:
|
|
27
|
+
# ROLES_CHECK_SCRATCH path of the planted file. Default
|
|
28
|
+
# one.ie/web/src/__roles_check_scratch.astro — outside
|
|
29
|
+
# src/pages (so it can never become a served route) and
|
|
30
|
+
# outside src/components (no directory contract). If
|
|
31
|
+
# astro check does not glob that path the check FAILS
|
|
32
|
+
# loudly and names the src/pages/ retry.
|
|
33
|
+
#
|
|
34
|
+
# Exit: 0 pass · 1 fail · 2 inconclusive (astro check OOMed or emitted no
|
|
35
|
+
# parseable summary — an unrun check is never a green one).
|
|
36
|
+
set -euo pipefail
|
|
37
|
+
|
|
38
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
39
|
+
WEB="$ROOT/one.ie/web"
|
|
40
|
+
SCRATCH="${ROLES_CHECK_SCRATCH:-$WEB/src/__roles_check_scratch.astro}"
|
|
41
|
+
RATCHET="$WEB/scripts/astro-check-ratchet.mjs"
|
|
42
|
+
|
|
43
|
+
_stub() { echo "ROLES-CHECK: $1 is not implemented yet — lands in $2" >&2; exit 1; }
|
|
44
|
+
_fail() { echo "ROLES-CHECK: FAIL — $*" >&2; exit 1; }
|
|
45
|
+
|
|
46
|
+
# astro check's output is read from a FILE, never a $( ) capture — see check_gate.
|
|
47
|
+
RATCHET_LOG=""
|
|
48
|
+
_run_ratchet() { ( cd "$WEB" && node scripts/astro-check-ratchet.mjs ) >"$RATCHET_LOG" 2>&1; }
|
|
49
|
+
|
|
50
|
+
check_gate() {
|
|
51
|
+
[ -f "$RATCHET" ] || _fail "no ratchet at one.ie/web/scripts/astro-check-ratchet.mjs"
|
|
52
|
+
[ -f "$WEB/.astro-check-baseline.json" ] \
|
|
53
|
+
|| _fail "no committed baseline at one.ie/web/.astro-check-baseline.json (generate: cd one.ie/web && node scripts/astro-check-ratchet.mjs --update)"
|
|
54
|
+
grep -q '"verify":.*check:ratchet' "$WEB/package.json" \
|
|
55
|
+
|| _fail "bun run verify does not call the ratchet — the gate is not wired"
|
|
56
|
+
|
|
57
|
+
# `bun run verify` builds the SDK before typechecking for a reason: an unbuilt
|
|
58
|
+
# packages/sdk/dist emits a wall of phantom TS2307s, which would raise ts(2307)
|
|
59
|
+
# above baseline and report a stale baseline that has nothing to do with the
|
|
60
|
+
# plant. This check runs standalone (it is first in the plan's kill-switch, in a
|
|
61
|
+
# bare shell), so it warms the same build itself rather than assuming one.
|
|
62
|
+
( cd "$ROOT/packages/sdk" && bun run build ) >/dev/null 2>&1 \
|
|
63
|
+
|| _fail "packages/sdk build failed — astro check cannot be measured honestly without it"
|
|
64
|
+
|
|
65
|
+
rm -f "$SCRATCH"
|
|
66
|
+
trap 'rm -f "$SCRATCH"' EXIT
|
|
67
|
+
|
|
68
|
+
local out rc t0 green_secs red_secs base
|
|
69
|
+
base="$(basename "$SCRATCH")"
|
|
70
|
+
|
|
71
|
+
# astro check emits megabytes (2436 files, minified inline scripts among them),
|
|
72
|
+
# and capturing that through $( ) drops lines: measured on the merged tree, the
|
|
73
|
+
# substitution kept 65 KB and lost the very diagnostic this check asserts on,
|
|
74
|
+
# while the same run redirected to a FILE kept it at line 967. That produced a
|
|
75
|
+
# false RED — "the gate went red but no diagnostic names the plant" — on a run
|
|
76
|
+
# where the ratchet had correctly reported ts(2367) 1 -> 2. Same family as the
|
|
77
|
+
# pipefail/SIGPIPE trap in .claude/CLAUDE.md: never read a large producer
|
|
78
|
+
# through a pipe or a substitution. Redirect, then read the file.
|
|
79
|
+
RATCHET_LOG="$(mktemp -t roles-check-ratchet)"
|
|
80
|
+
trap 'rm -f "$SCRATCH" "${RATCHET_LOG:-}"' EXIT
|
|
81
|
+
|
|
82
|
+
# --- green half: no plant, the committed baseline is accurate ---
|
|
83
|
+
t0=$(date +%s)
|
|
84
|
+
set +e; _run_ratchet; rc=$?; out="$(cat "$RATCHET_LOG")"; set -e
|
|
85
|
+
green_secs=$(( $(date +%s) - t0 ))
|
|
86
|
+
case "$rc" in
|
|
87
|
+
2) printf '%s\n' "$out" | tail -30
|
|
88
|
+
echo "ROLES-CHECK: INCONCLUSIVE — astro check gave no parseable summary (heap OOM reports 134)" >&2
|
|
89
|
+
exit 2 ;;
|
|
90
|
+
0) echo "ROLES-CHECK: green half ok (${green_secs}s) — baseline accurate with no plant" ;;
|
|
91
|
+
*) printf '%s\n' "$out" | tail -30
|
|
92
|
+
_fail "the ratchet is red BEFORE the plant — the committed baseline is stale (${green_secs}s)" ;;
|
|
93
|
+
esac
|
|
94
|
+
|
|
95
|
+
# --- red half: plant the cross-vocabulary comparison ---
|
|
96
|
+
cat > "$SCRATCH" <<'ASTRO'
|
|
97
|
+
---
|
|
98
|
+
// SCRATCH — planted by .claude/scripts/roles-check.sh --check-gate, removed on exit.
|
|
99
|
+
// The bug class this plan exists to kill: a vantage word compared to a rung word.
|
|
100
|
+
type Vantage = 'platform' | 'agency' | 'client' | 'end_user';
|
|
101
|
+
const viewer: Vantage = 'agency';
|
|
102
|
+
const planted = viewer === 'owner';
|
|
103
|
+
---
|
|
104
|
+
<p>{String(planted)}</p>
|
|
105
|
+
ASTRO
|
|
106
|
+
|
|
107
|
+
t0=$(date +%s)
|
|
108
|
+
set +e; _run_ratchet; rc=$?; out="$(cat "$RATCHET_LOG")"; set -e
|
|
109
|
+
red_secs=$(( $(date +%s) - t0 ))
|
|
110
|
+
rm -f "$SCRATCH"
|
|
111
|
+
|
|
112
|
+
if [ "$rc" -eq 2 ]; then
|
|
113
|
+
printf '%s\n' "$out" | tail -30
|
|
114
|
+
echo "ROLES-CHECK: INCONCLUSIVE — astro check gave no parseable summary on the red half" >&2
|
|
115
|
+
exit 2
|
|
116
|
+
fi
|
|
117
|
+
[ "$rc" -ne 0 ] \
|
|
118
|
+
|| _fail "the planted comparison did NOT turn the gate red (${red_secs}s) — if astro check never globbed $base, retry with ROLES_CHECK_SCRATCH=$WEB/src/pages/__roles_check_scratch.astro"
|
|
119
|
+
grep -qF "$base" <<<"$out" \
|
|
120
|
+
|| _fail "the gate went red but no diagnostic names $base — that red is pre-existing, not the plant"
|
|
121
|
+
grep -qF 'ts(2367)' <<<"$out" \
|
|
122
|
+
|| _fail "the diagnostic for $base is not a ts(2367) cross-vocabulary comparison"
|
|
123
|
+
grep -qF 'have no overlap' <<<"$out" \
|
|
124
|
+
|| _fail "no \"have no overlap\" diagnostic — the planted comparison was not the cause"
|
|
125
|
+
|
|
126
|
+
echo "ROLES-CHECK: red half ok (${red_secs}s) — ts(2367) \"have no overlap\" reported at $base"
|
|
127
|
+
echo "ROLES-CHECK: --check-gate pass · $((green_secs + red_secs))s wall (green ${green_secs}s + red ${red_secs}s)"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# =============================================================================
|
|
131
|
+
# C3 — the authority matrix (--check-decide) and the 503 state (--check-503).
|
|
132
|
+
#
|
|
133
|
+
# Both run one.ie/web/tests/authority/decide.test.ts, which drives the REAL
|
|
134
|
+
# decide() over the fixture tree in tests/authority/fixture-tree.ts. Neither
|
|
135
|
+
# check trusts vitest's exit code alone: vitest exits 0 when its include
|
|
136
|
+
# allowlist matches nothing, so the suite must also EMIT its receipt line
|
|
137
|
+
# ("AUTHORITY-MATRIX: N cells, D deny, U undetermined") and clear the floors.
|
|
138
|
+
#
|
|
139
|
+
# Each check then demonstrates its own red path by MUTATING THE FIXTURE
|
|
140
|
+
# (ROLES_FIXTURE_MUTATE) — never by a hook inside decide.ts, which would be a
|
|
141
|
+
# test backdoor shipping in the authority path.
|
|
142
|
+
#
|
|
143
|
+
# --check-decide controls-all-true / rung-owner-always must each break the
|
|
144
|
+
# suite, proving the deny cells are actually asserted.
|
|
145
|
+
# --check-503 swallow-membership-throw / swallow-controls-throw reproduce
|
|
146
|
+
# the historical `.catch(() => [])` and must each break the
|
|
147
|
+
# suite, proving a lookup failure cannot silently become 403.
|
|
148
|
+
#
|
|
149
|
+
# Floors (a matrix may grow, never shrink):
|
|
150
|
+
MATRIX_MIN_CELLS=24
|
|
151
|
+
MATRIX_MIN_DENY=12
|
|
152
|
+
MATRIX_MIN_UNDETERMINED=3
|
|
153
|
+
|
|
154
|
+
VITEST="$WEB/node_modules/.bin/vitest"
|
|
155
|
+
SUITE="tests/authority/decide.test.ts"
|
|
156
|
+
FIXTURE="$WEB/tests/authority/fixture-tree.ts"
|
|
157
|
+
RUN_OUT=""
|
|
158
|
+
RUN_RC=0
|
|
159
|
+
|
|
160
|
+
_matrix_preflight() {
|
|
161
|
+
[ -f "$FIXTURE" ] || _fail "no fixture tree at one.ie/web/tests/authority/fixture-tree.ts"
|
|
162
|
+
[ -f "$WEB/$SUITE" ] || _fail "no matrix suite at one.ie/web/$SUITE"
|
|
163
|
+
[ -x "$VITEST" ] || _fail "vitest is not installed in one.ie/web/node_modules (cd one.ie/web && bun install)"
|
|
164
|
+
# The include array is an allowlist; without an entry the suite is never
|
|
165
|
+
# collected AND vitest still exits 0. That would be a fail-open check.
|
|
166
|
+
grep -q "tests/authority/" "$WEB/vitest.config.ts" \
|
|
167
|
+
|| _fail "vitest.config.ts include allowlist has no tests/authority entry — the suite would never be collected"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# run_matrix <mutation> -> sets RUN_OUT / RUN_RC
|
|
171
|
+
run_matrix() {
|
|
172
|
+
set +e
|
|
173
|
+
# `--reporter=basic` was removed in vitest 3 and is a hard STARTUP error on
|
|
174
|
+
# the installed 4.1.7 ("Failed to load custom Reporter from basic") —
|
|
175
|
+
# measured, so never pass that. `--reporter=default` is required explicitly
|
|
176
|
+
# instead: vitest's implicit reporter (no flag at all) swallows per-test
|
|
177
|
+
# stdout/console.log even with --silent=false, but naming "default" prints
|
|
178
|
+
# it — also measured, on 4.1.7.
|
|
179
|
+
RUN_OUT="$(cd "$WEB" && ROLES_FIXTURE_MUTATE="$1" "$VITEST" run "$SUITE" --silent=false --reporter=default 2>&1)"
|
|
180
|
+
RUN_RC=$?
|
|
181
|
+
set -e
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# _receipt -> echoes "<cells> <deny> <undetermined>", or nothing when absent.
|
|
185
|
+
# Here-string, never a pipe: under pipefail `<producer> | grep -q` returns 141
|
|
186
|
+
# on a MATCH when the producer is still writing (recorded 2026-08-04).
|
|
187
|
+
_receipt() {
|
|
188
|
+
local line
|
|
189
|
+
line="$(grep -F 'AUTHORITY-MATRIX:' <<<"$RUN_OUT" | tail -1)"
|
|
190
|
+
[ -n "$line" ] || return 0
|
|
191
|
+
sed -E 's/.*AUTHORITY-MATRIX: ([0-9]+) cells, ([0-9]+) deny, ([0-9]+) undetermined.*/\1 \2 \3/' <<<"$line"
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# _green_half <label> — run unmutated, assert exit 0, receipt present, floors met.
|
|
195
|
+
_green_half() {
|
|
196
|
+
local cells deny undet receipt
|
|
197
|
+
run_matrix ""
|
|
198
|
+
if [ "$RUN_RC" -ne 0 ]; then
|
|
199
|
+
printf '%s\n' "$RUN_OUT" | tail -40
|
|
200
|
+
_fail "the authority matrix is RED with no mutation — fix decide.ts or the fixture before trusting $1"
|
|
201
|
+
fi
|
|
202
|
+
receipt="$(_receipt)"
|
|
203
|
+
[ -n "$receipt" ] \
|
|
204
|
+
|| _fail "no AUTHORITY-MATRIX receipt in the output — the suite did not execute (vitest exits 0 when the include allowlist matches nothing)"
|
|
205
|
+
read -r cells deny undet <<<"$receipt"
|
|
206
|
+
# sed echoes its input unchanged when the pattern does not match, so a drifted
|
|
207
|
+
# receipt format would sail past the -n test and die on `integer expression
|
|
208
|
+
# expected` instead of naming the cause.
|
|
209
|
+
[[ "$cells" =~ ^[0-9]+$ && "$deny" =~ ^[0-9]+$ && "$undet" =~ ^[0-9]+$ ]] \
|
|
210
|
+
|| _fail "malformed AUTHORITY-MATRIX receipt: $receipt"
|
|
211
|
+
[ "$cells" -ge "$MATRIX_MIN_CELLS" ] \
|
|
212
|
+
|| _fail "the matrix has $cells cells, floor is $MATRIX_MIN_CELLS"
|
|
213
|
+
[ "$deny" -ge "$MATRIX_MIN_DENY" ] \
|
|
214
|
+
|| _fail "the matrix asserts $deny deny cells, floor is $MATRIX_MIN_DENY — an allow-only matrix proves nothing"
|
|
215
|
+
[ "$undet" -ge "$MATRIX_MIN_UNDETERMINED" ] \
|
|
216
|
+
|| _fail "the matrix asserts $undet undetermined cells, floor is $MATRIX_MIN_UNDETERMINED"
|
|
217
|
+
echo "ROLES-CHECK: green half ok — $cells cells, $deny deny, $undet undetermined"
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
check_decide() {
|
|
221
|
+
_matrix_preflight
|
|
222
|
+
_green_half --check-decide
|
|
223
|
+
|
|
224
|
+
# Red half: if the tree said yes to everything, or every membership came back
|
|
225
|
+
# owner, the deny cells MUST notice. If they don't, they aren't assertions.
|
|
226
|
+
local mut
|
|
227
|
+
for mut in controls-all-true rung-owner-always; do
|
|
228
|
+
run_matrix "$mut"
|
|
229
|
+
if [ "$RUN_RC" -eq 0 ]; then
|
|
230
|
+
printf '%s\n' "$RUN_OUT" | tail -20
|
|
231
|
+
_fail "mutation '$mut' did NOT turn the matrix red — the deny cells are not actually asserted"
|
|
232
|
+
fi
|
|
233
|
+
echo "ROLES-CHECK: red half ok — '$mut' breaks the matrix"
|
|
234
|
+
done
|
|
235
|
+
echo "ROLES-CHECK: --check-decide pass"
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
check_503() {
|
|
239
|
+
_matrix_preflight
|
|
240
|
+
_green_half --check-503
|
|
241
|
+
|
|
242
|
+
# Red half: restore the historical `.catch(() => [])` — a failed lookup
|
|
243
|
+
# answering "not a member" instead of "couldn't determine". The 503 cells must
|
|
244
|
+
# go red, and the failure must name 503 (otherwise the red is unrelated).
|
|
245
|
+
local mut
|
|
246
|
+
for mut in swallow-membership-throw swallow-controls-throw; do
|
|
247
|
+
run_matrix "$mut"
|
|
248
|
+
if [ "$RUN_RC" -eq 0 ]; then
|
|
249
|
+
printf '%s\n' "$RUN_OUT" | tail -20
|
|
250
|
+
_fail "mutation '$mut' did NOT turn the matrix red — a swallowed lookup error still reads as a denial and this check cannot see it"
|
|
251
|
+
fi
|
|
252
|
+
# Must match the ASSERTION, not a test NAME: three fixture cells are literally
|
|
253
|
+
# titled "503 ...", so a bare grep for 503 would pass on the labels alone —
|
|
254
|
+
# a presence grep in the one check whose whole identity is 403-vs-503.
|
|
255
|
+
# vitest prints `expected 403 to be 503` for the collapsed status.
|
|
256
|
+
grep -qF 'to be 503' <<<"$RUN_OUT" \
|
|
257
|
+
|| _fail "mutation '$mut' went red but no assertion collapsed a 503 into a 403 — that red is unrelated to this check"
|
|
258
|
+
echo "ROLES-CHECK: red half ok — '$mut' collapses 503 into 403 and the matrix catches it"
|
|
259
|
+
done
|
|
260
|
+
echo "ROLES-CHECK: --check-503 pass"
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# =============================================================================
|
|
264
|
+
# C4 — page action floors (--check-floors).
|
|
265
|
+
#
|
|
266
|
+
# Three legs, each with its own red path:
|
|
267
|
+
#
|
|
268
|
+
# 1. BEHAVIOUR tests/authority/page-floors.test.ts drives the REAL guardPage()
|
|
269
|
+
# over the fixture tree: anonymous / stranger / below-floor
|
|
270
|
+
# member / at-floor member / ancestor owner, plus a 503 cell per
|
|
271
|
+
# page. Red path: ROLES_FIXTURE_MUTATE=controls-all-true and
|
|
272
|
+
# =rung-owner-always must each break it.
|
|
273
|
+
# 2. SOURCE the same suite reads each .astro and asserts it passes that
|
|
274
|
+
# exact RoleAction to guardPage. Red path:
|
|
275
|
+
# ROLES_FLOORS_MUTATE=floor-drift, which must break with a
|
|
276
|
+
# `page-floor:` message — otherwise this leg is a presence grep
|
|
277
|
+
# and this script's header forbids those.
|
|
278
|
+
# 3. LANDING do-prove's landing rule, red half only: a TRULY anonymous
|
|
279
|
+
# request to a private page must fail to land on it. The GREEN
|
|
280
|
+
# half is the demo command's own do-prove call — running it twice
|
|
281
|
+
# would double a 120s budget.
|
|
282
|
+
#
|
|
283
|
+
# Leg 3 needs `signed_out=1`, not merely PROVE_NO_SESSION=1. Measured in
|
|
284
|
+
# middleware.ts: DEV_SLUG (line 258) assigns locals.slug on any signed-out
|
|
285
|
+
# localhost request, and principalFromLocals accepts locals.slug — so a
|
|
286
|
+
# session-less run of /u/one/billing RENDERS, and asserting it fails would fail
|
|
287
|
+
# for the opposite reason. The `signed_out` cookie (line 234) is the one lever
|
|
288
|
+
# that skips every auto-auth path including DEV_SLUG, which makes the caller
|
|
289
|
+
# genuinely anonymous and forces the /signin bounce this leg is about.
|
|
290
|
+
#
|
|
291
|
+
# Same reason `one` is not a negative fixture and `jcoffey`/`vxbmxdeg` are not
|
|
292
|
+
# used: those are PRODUCTION accounts (one.ie/web/scripts/fix-jcoffey.ts posts to
|
|
293
|
+
# api.one.ie). The rung deny cells live in the fixture tree, where fx-stranger /
|
|
294
|
+
# fx-teammate / fx-writer have exactly the null-parent shape the plan wanted.
|
|
295
|
+
FLOORS_SUITE="tests/authority/page-floors.test.ts"
|
|
296
|
+
FLOOR_MIN_CELLS=72
|
|
297
|
+
FLOOR_MIN_DENY=34
|
|
298
|
+
FLOOR_MIN_UNDETERMINED=12
|
|
299
|
+
FLOOR_MIN_PAGES=14
|
|
300
|
+
PROVE_BASE="${PROVE_BASE:-http://localhost:4321}"
|
|
301
|
+
FLOOR_PROVE_ROUTE="${FLOOR_PROVE_ROUTE:-/u/one/billing}"
|
|
302
|
+
|
|
303
|
+
# run_floors <fixture-mutation> <floor-mutation> -> sets RUN_OUT / RUN_RC
|
|
304
|
+
run_floors() {
|
|
305
|
+
set +e
|
|
306
|
+
RUN_OUT="$(cd "$WEB" && ROLES_FIXTURE_MUTATE="$1" ROLES_FLOORS_MUTATE="$2" \
|
|
307
|
+
"$VITEST" run "$FLOORS_SUITE" --silent=false --reporter=default 2>&1)"
|
|
308
|
+
RUN_RC=$?
|
|
309
|
+
set -e
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
check_floors() {
|
|
313
|
+
[ -f "$FIXTURE" ] || _fail "no fixture tree at one.ie/web/tests/authority/fixture-tree.ts"
|
|
314
|
+
[ -f "$WEB/$FLOORS_SUITE" ] || _fail "no page-floor matrix at one.ie/web/$FLOORS_SUITE"
|
|
315
|
+
[ -x "$VITEST" ] || _fail "vitest is not installed in one.ie/web/node_modules (cd one.ie/web && bun install)"
|
|
316
|
+
grep -q "tests/authority/" "$WEB/vitest.config.ts" \
|
|
317
|
+
|| _fail "vitest.config.ts include allowlist has no tests/authority entry — the suite would never be collected"
|
|
318
|
+
|
|
319
|
+
# --- green: unmutated, receipt present, floors met ---
|
|
320
|
+
local line cells deny undet pages
|
|
321
|
+
run_floors "" ""
|
|
322
|
+
if [ "$RUN_RC" -ne 0 ]; then
|
|
323
|
+
printf '%s\n' "$RUN_OUT" | tail -40
|
|
324
|
+
_fail "the page-floor matrix is RED with no mutation — fix the page gates before trusting --check-floors"
|
|
325
|
+
fi
|
|
326
|
+
line="$(grep -F 'PAGE-FLOORS:' <<<"$RUN_OUT" | tail -1)"
|
|
327
|
+
[ -n "$line" ] \
|
|
328
|
+
|| _fail "no PAGE-FLOORS receipt in the output — the suite did not execute (vitest exits 0 when the include allowlist matches nothing)"
|
|
329
|
+
read -r cells deny undet pages <<<"$(sed -E 's/.*PAGE-FLOORS: ([0-9]+) cells, ([0-9]+) deny, ([0-9]+) undetermined, ([0-9]+) pages.*/\1 \2 \3 \4/' <<<"$line")"
|
|
330
|
+
[[ "$cells" =~ ^[0-9]+$ && "$deny" =~ ^[0-9]+$ && "$undet" =~ ^[0-9]+$ && "$pages" =~ ^[0-9]+$ ]] \
|
|
331
|
+
|| _fail "malformed PAGE-FLOORS receipt: $line"
|
|
332
|
+
[ "$pages" -ge "$FLOOR_MIN_PAGES" ] || _fail "$pages pages carry a floor, floor is $FLOOR_MIN_PAGES"
|
|
333
|
+
[ "$cells" -ge "$FLOOR_MIN_CELLS" ] || _fail "the matrix has $cells cells, floor is $FLOOR_MIN_CELLS"
|
|
334
|
+
[ "$deny" -ge "$FLOOR_MIN_DENY" ] || _fail "the matrix asserts $deny deny cells, floor is $FLOOR_MIN_DENY — an allow-only matrix proves nothing"
|
|
335
|
+
[ "$undet" -ge "$FLOOR_MIN_UNDETERMINED" ] \
|
|
336
|
+
|| _fail "the matrix asserts $undet undetermined cells, floor is $FLOOR_MIN_UNDETERMINED — without them 503-not-403 ships unproven at the page"
|
|
337
|
+
echo "ROLES-CHECK: green half ok — $cells cells over $pages pages, $deny deny, $undet undetermined"
|
|
338
|
+
|
|
339
|
+
# --- red 1: the tree said yes to everything / every membership came back owner ---
|
|
340
|
+
local mut
|
|
341
|
+
for mut in controls-all-true rung-owner-always; do
|
|
342
|
+
run_floors "$mut" ""
|
|
343
|
+
if [ "$RUN_RC" -eq 0 ]; then
|
|
344
|
+
printf '%s\n' "$RUN_OUT" | tail -20
|
|
345
|
+
_fail "mutation '$mut' did NOT turn the page-floor matrix red — the bounce cells are not actually asserted"
|
|
346
|
+
fi
|
|
347
|
+
echo "ROLES-CHECK: red half ok — '$mut' breaks the page-floor matrix"
|
|
348
|
+
done
|
|
349
|
+
|
|
350
|
+
# --- red 2: the table drifts from what the page names ---
|
|
351
|
+
run_floors "" "floor-drift"
|
|
352
|
+
if [ "$RUN_RC" -eq 0 ]; then
|
|
353
|
+
printf '%s\n' "$RUN_OUT" | tail -20
|
|
354
|
+
_fail "'floor-drift' did NOT turn the matrix red — the source check does not read the page, so the floors are unwired"
|
|
355
|
+
fi
|
|
356
|
+
grep -qF 'page-floor:' <<<"$RUN_OUT" \
|
|
357
|
+
|| _fail "'floor-drift' went red but no 'page-floor:' assertion fired — that red is unrelated to the source check"
|
|
358
|
+
echo "ROLES-CHECK: red half ok — 'floor-drift' is caught by the source check"
|
|
359
|
+
|
|
360
|
+
# --- red 3: a genuinely anonymous caller cannot land on a private page ---
|
|
361
|
+
local out rc
|
|
362
|
+
set +e
|
|
363
|
+
curl -fsS -o /dev/null --max-time 5 "$PROVE_BASE/" 2>/dev/null
|
|
364
|
+
rc=$?
|
|
365
|
+
set -e
|
|
366
|
+
if [ "$rc" -ne 0 ]; then
|
|
367
|
+
echo "ROLES-CHECK: INCONCLUSIVE — no dev server at $PROVE_BASE, so the landing leg could not run (cd one.ie/web && bun run dev)" >&2
|
|
368
|
+
exit 2
|
|
369
|
+
fi
|
|
370
|
+
set +e
|
|
371
|
+
out="$(PROVE_NO_SESSION=1 PROVE_SESSION_COOKIE='signed_out=1' PROVE_BASE_URL="$PROVE_BASE" \
|
|
372
|
+
bash "$ROOT/.claude/scripts/do-prove.sh" --route "$FLOOR_PROVE_ROUTE" 2>&1)"
|
|
373
|
+
rc=$?
|
|
374
|
+
set -e
|
|
375
|
+
if [ "$rc" -eq 0 ]; then
|
|
376
|
+
printf '%s\n' "$out" | tail -20
|
|
377
|
+
_fail "an ANONYMOUS $FLOOR_PROVE_ROUTE scored green — either the gate is not wired or the landing rule is not applying, and every authed page proof is then worthless"
|
|
378
|
+
fi
|
|
379
|
+
grep -qF '/signin' <<<"$out" \
|
|
380
|
+
|| _fail "the anonymous run failed but never mentions /signin — that red is not the login-wall landing this leg is about"
|
|
381
|
+
echo "ROLES-CHECK: red half ok — anonymous $FLOOR_PROVE_ROUTE lands on /signin and fails the landing rule"
|
|
382
|
+
|
|
383
|
+
echo "ROLES-CHECK: --check-floors pass"
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
# =============================================================================
|
|
387
|
+
# C5 — the vantage vocabulary is gone (--check-no-vantage).
|
|
388
|
+
#
|
|
389
|
+
# Presence greps are not proof (header law), so this check demonstrates its own
|
|
390
|
+
# red path the simplest way available for a pure grep with no environment
|
|
391
|
+
# dependency: it plants the exact token it's hunting for, asserts the grep
|
|
392
|
+
# actually notices, then removes the plant and asserts clean again. No process
|
|
393
|
+
# to start, no fixture to mutate — the grep IS the check, so the grep is what
|
|
394
|
+
# gets proven.
|
|
395
|
+
#
|
|
396
|
+
# Tokens: deriveViewer|end_user|workspaceContext\.viewer|lib/viewer. 'agency',
|
|
397
|
+
# 'client', 'platform' are excluded — they collide with the billing tier and
|
|
398
|
+
# MCP vocabulary; end_user is unique to the vantage enum and unambiguous.
|
|
399
|
+
# src/data/promises.json is excluded by extension (generated history —
|
|
400
|
+
# legitimately contains the old strings; not a source-code carrier).
|
|
401
|
+
VANTAGE_PATTERN='deriveViewer|end_user|workspaceContext\.viewer|lib/viewer'
|
|
402
|
+
VANTAGE_PROBE="$WEB/src/lib/.vantage-probe.ts"
|
|
403
|
+
|
|
404
|
+
_vantage_grep() {
|
|
405
|
+
# tests/authority/ is the C3/C4/C6 regression net: it must document the deleted
|
|
406
|
+
# vantage words (e.g. worldkey.test.ts asserts deny messages never contain them)
|
|
407
|
+
# in order to test their absence elsewhere — self-reference, not resurrection.
|
|
408
|
+
grep -rnE "$VANTAGE_PATTERN" "$WEB/src" "$WEB/tests" \
|
|
409
|
+
--include='*.ts' --include='*.tsx' --include='*.astro' 2>/dev/null \
|
|
410
|
+
| grep -v "$WEB/tests/authority/" || true
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
check_no_vantage() {
|
|
414
|
+
# --- leg 1: the vantage module itself is gone ---
|
|
415
|
+
[ ! -f "$WEB/src/lib/viewer.ts" ] \
|
|
416
|
+
|| _fail "one.ie/web/src/lib/viewer.ts still exists — the vantage module was not deleted"
|
|
417
|
+
echo "ROLES-CHECK: leg 1 ok — one.ie/web/src/lib/viewer.ts does not exist"
|
|
418
|
+
|
|
419
|
+
rm -f "$VANTAGE_PROBE"
|
|
420
|
+
trap 'rm -f "$VANTAGE_PROBE"' EXIT
|
|
421
|
+
|
|
422
|
+
# --- leg 2 (green): no vantage vocabulary left under src or tests ---
|
|
423
|
+
local hits
|
|
424
|
+
hits="$(_vantage_grep)"
|
|
425
|
+
if [ -n "$hits" ]; then
|
|
426
|
+
printf '%s\n' "$hits"
|
|
427
|
+
_fail "vantage vocabulary still present — see hits above"
|
|
428
|
+
fi
|
|
429
|
+
echo "ROLES-CHECK: leg 2 ok — no deriveViewer/end_user/workspaceContext.viewer/lib/viewer hits under src or tests"
|
|
430
|
+
|
|
431
|
+
# --- red path self-test: plant the token, prove the grep can go red ---
|
|
432
|
+
printf "export const v = 'end_user'\n" > "$VANTAGE_PROBE"
|
|
433
|
+
hits="$(_vantage_grep)"
|
|
434
|
+
rm -f "$VANTAGE_PROBE"
|
|
435
|
+
grep -qF '.vantage-probe.ts' <<<"$hits" \
|
|
436
|
+
|| _fail "the red-path self-test did NOT detect a planted end_user token — this check cannot go red, it is a presence claim"
|
|
437
|
+
echo "ROLES-CHECK: red half ok — planted end_user token in .vantage-probe.ts was detected"
|
|
438
|
+
|
|
439
|
+
# --- cleanup verification: probe removed, grep clean again ---
|
|
440
|
+
hits="$(_vantage_grep)"
|
|
441
|
+
[ -z "$hits" ] \
|
|
442
|
+
|| _fail "the probe was removed but the grep is still non-empty — cleanup failed or a real hit remains"
|
|
443
|
+
echo "ROLES-CHECK: cleanup ok — probe removed, grep clean again"
|
|
444
|
+
|
|
445
|
+
echo "ROLES-CHECK: --check-no-vantage pass"
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
# =============================================================================
|
|
449
|
+
# C6 — the world key is a principal, the tree is the judge (--check-worldkey).
|
|
450
|
+
#
|
|
451
|
+
# Replays the original bug report: an agent world-key acting in a workspace it
|
|
452
|
+
# legitimately reached was refused with a VANTAGE word compared to a RUNG word.
|
|
453
|
+
# The suite drives the REAL decide() with a world-key Principal over the same
|
|
454
|
+
# fixture tree C3 built, and asserts the three clauses of the accept:
|
|
455
|
+
#
|
|
456
|
+
# 1. a key whose actor controls the node succeeds (via controls, or via rung
|
|
457
|
+
# for the membership-only key)
|
|
458
|
+
# 2. the same key elsewhere is 403 whose reason NAMES THE NODE and speaks
|
|
459
|
+
# neither vocabulary
|
|
460
|
+
# 3. a lookup that could not be read is 503, never 403
|
|
461
|
+
#
|
|
462
|
+
# It deliberately does NOT build packages/sdk — that build is what makes
|
|
463
|
+
# --check-gate cost minutes, and this check has a 60s budget. Nothing on the
|
|
464
|
+
# suite's import path reaches the receiver registry: decide.ts -> role-check.ts
|
|
465
|
+
# imports the action union TYPE-ONLY, and a type import is erased.
|
|
466
|
+
#
|
|
467
|
+
# Floors (a matrix may grow, never shrink):
|
|
468
|
+
WORLDKEY_MIN_CELLS=10
|
|
469
|
+
WORLDKEY_MIN_DENY=5
|
|
470
|
+
WORLDKEY_MIN_UNDETERMINED=2
|
|
471
|
+
|
|
472
|
+
WORLDKEY_SUITE="tests/authority/worldkey.test.ts"
|
|
473
|
+
|
|
474
|
+
# run_worldkey <mutation> -> sets RUN_OUT / RUN_RC. Same reporter reasoning as
|
|
475
|
+
# run_matrix: --reporter=basic is a hard startup error on vitest 4.1.7, and the
|
|
476
|
+
# implicit reporter swallows console.log even with --silent=false.
|
|
477
|
+
run_worldkey() {
|
|
478
|
+
set +e
|
|
479
|
+
RUN_OUT="$(cd "$WEB" && ROLES_FIXTURE_MUTATE="$1" "$VITEST" run "$WORLDKEY_SUITE" --silent=false --reporter=default 2>&1)"
|
|
480
|
+
RUN_RC=$?
|
|
481
|
+
set -e
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
check_worldkey() {
|
|
485
|
+
[ -f "$FIXTURE" ] || _fail "no fixture tree at one.ie/web/tests/authority/fixture-tree.ts"
|
|
486
|
+
[ -f "$WEB/$WORLDKEY_SUITE" ] || _fail "no world-key suite at one.ie/web/$WORLDKEY_SUITE"
|
|
487
|
+
[ -x "$VITEST" ] || _fail "vitest is not installed in one.ie/web/node_modules (cd one.ie/web && bun install)"
|
|
488
|
+
grep -q "tests/authority/" "$WEB/vitest.config.ts" \
|
|
489
|
+
|| _fail "vitest.config.ts include allowlist has no tests/authority entry — the suite would never be collected"
|
|
490
|
+
|
|
491
|
+
local cells deny undet line t0 green_secs
|
|
492
|
+
|
|
493
|
+
# --- green half ---
|
|
494
|
+
t0=$(date +%s)
|
|
495
|
+
run_worldkey ""
|
|
496
|
+
green_secs=$(( $(date +%s) - t0 ))
|
|
497
|
+
if [ "$RUN_RC" -ne 0 ]; then
|
|
498
|
+
printf '%s\n' "$RUN_OUT" | tail -40
|
|
499
|
+
_fail "the world-key matrix is RED with no mutation (${green_secs}s) — fix api-auth.ts, decide.ts or the fixture first"
|
|
500
|
+
fi
|
|
501
|
+
# Here-string, never a pipe: under pipefail `<producer> | grep -q` returns 141
|
|
502
|
+
# on a MATCH when the producer is still writing (recorded 2026-08-04).
|
|
503
|
+
line="$(grep -F 'WORLDKEY-MATRIX:' <<<"$RUN_OUT" | tail -1)"
|
|
504
|
+
[ -n "$line" ] \
|
|
505
|
+
|| _fail "no WORLDKEY-MATRIX receipt in the output — the suite did not execute (vitest exits 0 when the include allowlist matches nothing)"
|
|
506
|
+
read -r cells deny undet <<<"$(sed -E 's/.*WORLDKEY-MATRIX: ([0-9]+) cells, ([0-9]+) deny, ([0-9]+) undetermined.*/\1 \2 \3/' <<<"$line")"
|
|
507
|
+
[[ "$cells" =~ ^[0-9]+$ && "$deny" =~ ^[0-9]+$ && "$undet" =~ ^[0-9]+$ ]] \
|
|
508
|
+
|| _fail "malformed WORLDKEY-MATRIX receipt: $line"
|
|
509
|
+
[ "$cells" -ge "$WORLDKEY_MIN_CELLS" ] || _fail "the world-key matrix has $cells cells, floor is $WORLDKEY_MIN_CELLS"
|
|
510
|
+
[ "$deny" -ge "$WORLDKEY_MIN_DENY" ] \
|
|
511
|
+
|| _fail "the world-key matrix asserts $deny deny cells, floor is $WORLDKEY_MIN_DENY — an allow-only matrix proves nothing"
|
|
512
|
+
[ "$undet" -ge "$WORLDKEY_MIN_UNDETERMINED" ] \
|
|
513
|
+
|| _fail "the world-key matrix asserts $undet undetermined cells, floor is $WORLDKEY_MIN_UNDETERMINED"
|
|
514
|
+
echo "ROLES-CHECK: green half ok (${green_secs}s) — $cells cells, $deny deny, $undet undetermined"
|
|
515
|
+
|
|
516
|
+
# --- red half: mutate the FIXTURE, never the authority path ---
|
|
517
|
+
# controls-all-true: if the tree said yes to every key, the "same key
|
|
518
|
+
# elsewhere" cells MUST notice, or they are not assertions.
|
|
519
|
+
run_worldkey "controls-all-true"
|
|
520
|
+
if [ "$RUN_RC" -eq 0 ]; then
|
|
521
|
+
printf '%s\n' "$RUN_OUT" | tail -20
|
|
522
|
+
_fail "mutation 'controls-all-true' did NOT turn the world-key matrix red — the 'same key elsewhere' cells are not actually asserted"
|
|
523
|
+
fi
|
|
524
|
+
echo "ROLES-CHECK: red half ok — 'controls-all-true' breaks the world-key matrix"
|
|
525
|
+
|
|
526
|
+
# swallow-controls-throw: the historical `.catch(() => [])` — an unreadable
|
|
527
|
+
# tree answering "no" instead of "couldn't determine". Match the ASSERTION,
|
|
528
|
+
# not a test NAME: cells are titled "503 ...", so a bare grep for 503 would
|
|
529
|
+
# pass on the labels alone.
|
|
530
|
+
run_worldkey "swallow-controls-throw"
|
|
531
|
+
if [ "$RUN_RC" -eq 0 ]; then
|
|
532
|
+
printf '%s\n' "$RUN_OUT" | tail -20
|
|
533
|
+
_fail "mutation 'swallow-controls-throw' did NOT turn the world-key matrix red — a swallowed lookup error still reads as a denial"
|
|
534
|
+
fi
|
|
535
|
+
grep -qF 'to be 503' <<<"$RUN_OUT" \
|
|
536
|
+
|| _fail "mutation 'swallow-controls-throw' went red but no assertion collapsed a 503 into a 403 — that red is unrelated to this check"
|
|
537
|
+
echo "ROLES-CHECK: red half ok — 'swallow-controls-throw' collapses 503 into 403 and the matrix catches it"
|
|
538
|
+
|
|
539
|
+
echo "ROLES-CHECK: --check-worldkey pass"
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
# ── C7 · staff = owner of the root ────────────────────────────────────────────
|
|
543
|
+
# Four legs. The point is that "platform staff" stops being a bypass consulted
|
|
544
|
+
# BEFORE the tree is walked and becomes an ownership row the walk reads — so the
|
|
545
|
+
# check must prove (a) the row is what grants, (b) removing it takes the grant
|
|
546
|
+
# away, and (c) the flag and the row have not drifted apart.
|
|
547
|
+
STAFF_SUITE="tests/authority/staff-root.test.ts"
|
|
548
|
+
STAFF_MIGRATION="migrations/0220_staff_root_membership.sql"
|
|
549
|
+
|
|
550
|
+
run_staff() {
|
|
551
|
+
set +e
|
|
552
|
+
RUN_OUT="$(cd "$WEB" && ROLES_STAFF_MUTATE="$1" \
|
|
553
|
+
"$VITEST" run "$STAFF_SUITE" --silent=false --reporter=default 2>&1)"
|
|
554
|
+
RUN_RC=$?
|
|
555
|
+
set -e
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
check_staff() {
|
|
559
|
+
[ -f "$WEB/$STAFF_MIGRATION" ] || _fail "no root-owners migration at one.ie/web/$STAFF_MIGRATION"
|
|
560
|
+
[ -f "$WEB/$STAFF_SUITE" ] || _fail "no staff-root suite at one.ie/web/$STAFF_SUITE"
|
|
561
|
+
[ -x "$VITEST" ] || _fail "vitest is not installed in one.ie/web/node_modules"
|
|
562
|
+
|
|
563
|
+
# Leg 1 (source): root ownership is READ BY THE WALK, not by a branch beside it.
|
|
564
|
+
grep -q 'export async function isRootOwner' "$WEB/src/lib/analytics/authz.ts" \
|
|
565
|
+
|| _fail "no isRootOwner in analytics/authz.ts — staff authority is not expressed as ownership"
|
|
566
|
+
grep -q 'isRootOwner(callerSlug, db)' "$WEB/src/lib/analytics/authz.ts" \
|
|
567
|
+
|| _fail "callerControlsSlug does not consult isRootOwner — the walk cannot answer for staff"
|
|
568
|
+
grep -q 'root_owners' "$WEB/$STAFF_MIGRATION" \
|
|
569
|
+
|| _fail "the migration does not create root_owners"
|
|
570
|
+
|
|
571
|
+
# Leg 2 (green): the suite passes unmutated.
|
|
572
|
+
run_staff ""
|
|
573
|
+
[ "$RUN_RC" -eq 0 ] || { printf '%s\n' "$RUN_OUT" | tail -20 >&2
|
|
574
|
+
_fail "the staff-root suite is RED with no mutation — fix authz.ts or the suite"; }
|
|
575
|
+
local receipt
|
|
576
|
+
receipt="$(printf '%s\n' "$RUN_OUT" | grep -oE 'STAFF-ROOT cells=[0-9]+ grant-via-root=[0-9]+ deny=[0-9]+' | head -1)"
|
|
577
|
+
[ -n "$receipt" ] || _fail "no STAFF-ROOT receipt — the suite did not execute (vitest exits 0 when the include allowlist matches nothing)"
|
|
578
|
+
local deny; deny="$(printf '%s' "$receipt" | sed -E 's/.*deny=([0-9]+).*/\1/')"
|
|
579
|
+
[ "${deny:-0}" -ge 3 ] || _fail "the staff matrix has only ${deny} deny cells — a staff check that can only say yes is not a check"
|
|
580
|
+
echo "ROLES-CHECK: green half ok — $receipt"
|
|
581
|
+
|
|
582
|
+
# Leg 3 (red): take the root row out of the walk and the grant must vanish.
|
|
583
|
+
run_staff "ignore-root"
|
|
584
|
+
[ "$RUN_RC" -ne 0 ] \
|
|
585
|
+
|| _fail "'ignore-root' did NOT break the staff matrix — root ownership is not what grants, so this check proves nothing"
|
|
586
|
+
echo "ROLES-CHECK: red half ok — 'ignore-root' removes the grant and the matrix catches it"
|
|
587
|
+
|
|
588
|
+
# Leg 4 (drift): the flag is a CACHE now. Report divergence rather than trust it.
|
|
589
|
+
local d1 drift
|
|
590
|
+
d1="$(find "$WEB/.wrangler/state" -name '*.sqlite' 2>/dev/null | head -1)"
|
|
591
|
+
if [ -z "$d1" ] || ! command -v sqlite3 >/dev/null 2>&1; then
|
|
592
|
+
echo "ROLES-CHECK: drift leg INCONCLUSIVE — no local D1 (or no sqlite3); run 'bun run db:migrate:local' to enable it"
|
|
593
|
+
else
|
|
594
|
+
drift="$(sqlite3 "$d1" "SELECT COUNT(*) FROM user WHERE staff_role = 1 AND slug IS NOT NULL AND slug NOT IN (SELECT slug FROM root_owners);" 2>/dev/null || echo unknown)"
|
|
595
|
+
case "$drift" in
|
|
596
|
+
0) echo "ROLES-CHECK: drift leg ok — every staff_role=1 slug has a root_owners row" ;;
|
|
597
|
+
unknown) echo "ROLES-CHECK: drift leg INCONCLUSIVE — root_owners not migrated locally (bun run db:migrate:local)" ;;
|
|
598
|
+
*) _fail "cache drift: ${drift} staff_role=1 slug(s) have no root_owners row — the flag and the tree disagree" ;;
|
|
599
|
+
esac
|
|
600
|
+
fi
|
|
601
|
+
|
|
602
|
+
echo "ROLES-CHECK: --check-staff pass"
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
# ── C8 · the authority suite is a real gate ───────────────────────────────────
|
|
606
|
+
# Two things must hold. (1) WIRING: the suite actually runs inside bun run verify
|
|
607
|
+
# — a suite nobody runs is documentation. (2) TEETH: mutating the authority code
|
|
608
|
+
# in named, plausible ways breaks it. A suite that survives every mutation is
|
|
609
|
+
# asserting nothing, which is the failure this whole plan exists to make
|
|
610
|
+
# impossible. Mutations are applied to SOURCE and always restored by a trap, so
|
|
611
|
+
# an interrupted run cannot leave the tree patched.
|
|
612
|
+
AUTH_SUITES="tests/authority"
|
|
613
|
+
DECIDE_SRC="src/lib/decide.ts"
|
|
614
|
+
AUTHZ_SRC="src/lib/analytics/authz.ts"
|
|
615
|
+
|
|
616
|
+
_restore_sources() {
|
|
617
|
+
[ -f "$WEB/$DECIDE_SRC.rolesbak" ] && mv -f "$WEB/$DECIDE_SRC.rolesbak" "$WEB/$DECIDE_SRC"
|
|
618
|
+
[ -f "$WEB/$AUTHZ_SRC.rolesbak" ] && mv -f "$WEB/$AUTHZ_SRC.rolesbak" "$WEB/$AUTHZ_SRC"
|
|
619
|
+
return 0
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
run_auth_suites() {
|
|
623
|
+
set +e
|
|
624
|
+
RUN_OUT="$(cd "$WEB" && "$VITEST" run "$AUTH_SUITES" --silent=false --reporter=default 2>&1)"
|
|
625
|
+
RUN_RC=$?
|
|
626
|
+
set -e
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
# mutate <name> — patches source in place; returns 1 if the pattern is absent
|
|
630
|
+
# (an unapplied mutation must FAIL the check, never silently score as red).
|
|
631
|
+
_mutate() {
|
|
632
|
+
case "$1" in
|
|
633
|
+
swap-403-503)
|
|
634
|
+
grep -q 'status: 403' "$WEB/$DECIDE_SRC" || return 1
|
|
635
|
+
cp "$WEB/$DECIDE_SRC" "$WEB/$DECIDE_SRC.rolesbak"
|
|
636
|
+
sed -i.tmp 's/status: 403,/status: 503,/; s/status: 503,$/status: 403,/' "$WEB/$DECIDE_SRC"
|
|
637
|
+
rm -f "$WEB/$DECIDE_SRC.tmp" ;;
|
|
638
|
+
invert-bind-order)
|
|
639
|
+
grep -q '.bind(targetSlug, callerSlug)' "$WEB/$AUTHZ_SRC" || return 1
|
|
640
|
+
cp "$WEB/$AUTHZ_SRC" "$WEB/$AUTHZ_SRC.rolesbak"
|
|
641
|
+
sed -i.tmp 's/\.bind(targetSlug, callerSlug)/.bind(callerSlug, targetSlug)/' "$WEB/$AUTHZ_SRC"
|
|
642
|
+
rm -f "$WEB/$AUTHZ_SRC.tmp" ;;
|
|
643
|
+
drop-depth-cap)
|
|
644
|
+
grep -q 'a.depth < 16' "$WEB/$AUTHZ_SRC" || return 1
|
|
645
|
+
cp "$WEB/$AUTHZ_SRC" "$WEB/$AUTHZ_SRC.rolesbak"
|
|
646
|
+
sed -i.tmp 's/a\.depth < 16/a.depth < 0/g' "$WEB/$AUTHZ_SRC"
|
|
647
|
+
rm -f "$WEB/$AUTHZ_SRC.tmp" ;;
|
|
648
|
+
*) return 1 ;;
|
|
649
|
+
esac
|
|
650
|
+
return 0
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
check_suite() {
|
|
654
|
+
[ -d "$WEB/$AUTH_SUITES" ] || _fail "no authority suite directory at one.ie/web/$AUTH_SUITES"
|
|
655
|
+
[ -x "$VITEST" ] || _fail "vitest is not installed in one.ie/web/node_modules"
|
|
656
|
+
|
|
657
|
+
# Leg 1 — WIRING: verify must reach the suite.
|
|
658
|
+
grep -q '"verify"' "$WEB/package.json" || _fail "no verify script in one.ie/web/package.json"
|
|
659
|
+
# Read the SCRIPT VALUE with a JSON parser, never a regex over the raw file.
|
|
660
|
+
# `"verify": "[^"]*bun run test` cannot cross an escaped quote, so wrapping
|
|
661
|
+
# verify in gate-run.sh (`-- bash -c \"…\"`, 2026-08-18) turned a live wiring
|
|
662
|
+
# into a false red while `bun run test` sat intact inside the string. Assert
|
|
663
|
+
# what verify DOES, not the shape it is written in.
|
|
664
|
+
node -e '
|
|
665
|
+
const s = require(process.argv[1]).scripts || {}
|
|
666
|
+
const seen = new Set()
|
|
667
|
+
const reaches = (name) => {
|
|
668
|
+
if (seen.has(name)) return false
|
|
669
|
+
seen.add(name)
|
|
670
|
+
const body = s[name]
|
|
671
|
+
if (!body) return false
|
|
672
|
+
if (/\bvitest\b/.test(body)) return true
|
|
673
|
+
// follow `bun run <script>` / `npm run <script>` one hop deeper
|
|
674
|
+
return [...body.matchAll(/\b(?:bun|npm|pnpm|yarn) run ([\w:.-]+)/g)]
|
|
675
|
+
.some((m) => reaches(m[1]))
|
|
676
|
+
}
|
|
677
|
+
process.exit(reaches("verify") ? 0 : 1)
|
|
678
|
+
' "$WEB/package.json" \
|
|
679
|
+
|| _fail "bun run verify does not reach vitest — the authority matrix would never gate anything"
|
|
680
|
+
grep -q "tests/authority" "$WEB/vitest.config.ts" \
|
|
681
|
+
|| _fail "vitest.config.ts include allowlist has no tests/authority entry — verify would collect zero authority tests"
|
|
682
|
+
|
|
683
|
+
# Leg 2 — GREEN: the suites pass unmutated, and there are enough of them.
|
|
684
|
+
local n; n=$(find "$WEB/$AUTH_SUITES" -name '*.test.ts' | wc -l | tr -d ' ')
|
|
685
|
+
[ "${n:-0}" -ge 3 ] || _fail "only ${n} authority suite(s) — decide, page-floors, world-key and staff-root are all expected"
|
|
686
|
+
run_auth_suites
|
|
687
|
+
[ "$RUN_RC" -eq 0 ] || { printf '%s\n' "$RUN_OUT" | tail -20 >&2
|
|
688
|
+
_fail "the authority suites are RED unmutated — fix them before claiming a gate"; }
|
|
689
|
+
echo "ROLES-CHECK: green half ok — ${n} authority suites pass unmutated"
|
|
690
|
+
|
|
691
|
+
# Leg 3 — TEETH: each named mutation must break at least one assertion.
|
|
692
|
+
trap _restore_sources EXIT INT TERM
|
|
693
|
+
local m
|
|
694
|
+
for m in swap-403-503 invert-bind-order drop-depth-cap; do
|
|
695
|
+
_mutate "$m" || { _restore_sources; _fail "mutation '$m' could not be applied — its anchor moved, so this leg proves nothing"; }
|
|
696
|
+
run_auth_suites
|
|
697
|
+
if [ "$RUN_RC" -eq 0 ]; then
|
|
698
|
+
_restore_sources
|
|
699
|
+
_fail "mutation '$m' did NOT break the authority suites — they are not asserting what they claim"
|
|
700
|
+
fi
|
|
701
|
+
_restore_sources
|
|
702
|
+
echo "ROLES-CHECK: red half ok — '$m' breaks the authority suites"
|
|
703
|
+
done
|
|
704
|
+
trap - EXIT INT TERM
|
|
705
|
+
|
|
706
|
+
# Leg 4 — the tree is exactly as it was. A mutation check that leaves the
|
|
707
|
+
# source patched would be worse than no check at all.
|
|
708
|
+
run_auth_suites
|
|
709
|
+
[ "$RUN_RC" -eq 0 ] || _fail "sources were not restored cleanly after mutation — the tree is dirty"
|
|
710
|
+
echo "ROLES-CHECK: restore ok — suites green again, tree unpatched"
|
|
711
|
+
|
|
712
|
+
echo "ROLES-CHECK: --check-suite pass"
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
# ── C9 · the docs no longer teach a vocabulary that does not exist ────────────
|
|
716
|
+
# A doc that describes a deleted type is worse than a missing doc: the next
|
|
717
|
+
# reader writes code against it. This check reads the DOCS for live claims, and
|
|
718
|
+
# proves itself by planting one.
|
|
719
|
+
DOCS_ORACLE="text/roles-vantage-collapse-docs.md"
|
|
720
|
+
# Files that legitimately keep the dead words as HISTORY (struck through, or in
|
|
721
|
+
# a "retired/was" sentence). Everything else must be clean.
|
|
722
|
+
# Two kinds of file legitimately keep the dead words:
|
|
723
|
+
# (a) this plan's own family + the design doc that marks the model retired;
|
|
724
|
+
# (b) APPEND-ONLY RECORDS of what was true at the time — a learnings line, an
|
|
725
|
+
# improvements entry, a closed cycle's todo, a generated concatenation.
|
|
726
|
+
# Rewriting those would be falsifying a log, not reconciling a doc.
|
|
727
|
+
# Everything else must be reconciled. Adding a row here is a deliberate act:
|
|
728
|
+
# it says "this file is a record", and a file that actually TEACHES belongs
|
|
729
|
+
# outside the list.
|
|
730
|
+
DOC_HISTORY="text/roles-vantage-collapse.md text/roles-vantage-collapse-plan.md text/roles-vantage-collapse-todo.md text/roles-vantage-collapse-docs.md text/roles-authority-plan.md text/learnings.md text/improvements.md text/roles-todo.md text/improve/02-agency.md text/all.md text/routing-plan.md text/security-surfaces.md"
|
|
731
|
+
|
|
732
|
+
# What counts as a LIVE CLAIM — deliberately narrow, and the narrowness is the
|
|
733
|
+
# point. Three earlier drafts of this scan were too blunt and would have forced
|
|
734
|
+
# 11 unrelated rewrites:
|
|
735
|
+
# `viewer` is an English word AND a rung — never searched.
|
|
736
|
+
# `end_user` is ALSO a persona word (`creator | developer | end_user`,
|
|
737
|
+
# text/ui.md · text/workspaces-plan.md) — searching it bare flags
|
|
738
|
+
# docs that never described the vantage at all.
|
|
739
|
+
# So match only what genuinely no longer exists: the two identifiers, and the
|
|
740
|
+
# 4-value union written out as a type. A checker that cries wolf gets disabled,
|
|
741
|
+
# and a disabled checker is the failure this whole plan is about.
|
|
742
|
+
# Kept deliberately simple. An earlier draft alternated on quoted forms of the
|
|
743
|
+
# union ("platform" | "agency" and its single-quoted twin) and the shell quoting
|
|
744
|
+
# produced an INVALID ERE — grep then errored, `2>/dev/null` swallowed the
|
|
745
|
+
# error, and the scan reported zero hits. A silent-empty scan is a green light
|
|
746
|
+
# that means nothing, which is precisely the defect class this plan exists to
|
|
747
|
+
# remove. Three unambiguous needles beat one clever pattern.
|
|
748
|
+
DEAD_IDENTIFIERS='deriveViewer|workspaceContext\.viewer|platform/agency/client/end_user'
|
|
749
|
+
|
|
750
|
+
_docs_live_claims() {
|
|
751
|
+
local f rel
|
|
752
|
+
# Line-delimited, NOT `-Z`. Two traps here, both hit:
|
|
753
|
+
# 1. `for f in $(grep -rl …)` word-splits `text/navigation-plan 1.md` into
|
|
754
|
+
# two nonexistent paths, so the check reports phantom files.
|
|
755
|
+
# 2. The fix for (1) is normally `grep -Z` + `read -d ''` — but the grep on
|
|
756
|
+
# PATH here is **ugrep**, where `-Z` means FUZZY MATCH, not `--null`.
|
|
757
|
+
# Output stayed newline-separated, `read -d ''` hit EOF with no delimiter,
|
|
758
|
+
# the loop body never ran once, and the scan reported a clean tree.
|
|
759
|
+
# Another silently-empty green.
|
|
760
|
+
# `IFS= read -r` per line handles the space correctly; no doc here has a
|
|
761
|
+
# newline in its name.
|
|
762
|
+
while IFS= read -r f; do
|
|
763
|
+
[ -n "$f" ] || continue
|
|
764
|
+
rel="${f#"$ROOT/"}"
|
|
765
|
+
case " $DOC_HISTORY " in *" $rel "*) continue ;; esac
|
|
766
|
+
# STRUCTURAL history — a class of file, not a name on a list, so the rule
|
|
767
|
+
# cannot rot as files are added. A closed cycle's todo, an archived doc and
|
|
768
|
+
# a generated concatenation are all append-only RECORDS of what was true at
|
|
769
|
+
# the time; editing them would falsify a log rather than reconcile a doc.
|
|
770
|
+
case "$rel" in
|
|
771
|
+
*-todo.md|text/archive/*|text/improve/*|text/all.md) continue ;;
|
|
772
|
+
esac
|
|
773
|
+
# A doc that carries the retirement banner has been reconciled — it names
|
|
774
|
+
# the deleted thing precisely so the next reader is not misled by it.
|
|
775
|
+
# `grep … && continue` is NOT set -e safe here: when grep finds nothing the
|
|
776
|
+
# && list evaluates non-zero and the shell exits mid-scan, silently
|
|
777
|
+
# truncating the result to whatever it had. An if-block keeps the loop alive.
|
|
778
|
+
if grep -qE 'RETIRED|DELETED 2026-08-04|STALE on the viewer model' "$f"; then
|
|
779
|
+
continue
|
|
780
|
+
fi
|
|
781
|
+
echo "$rel"
|
|
782
|
+
done < <(grep -rlE "$DEAD_IDENTIFIERS" "$ROOT/text" --include="*.md" 2>/dev/null)
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
check_docs() {
|
|
786
|
+
[ -f "$ROOT/$DOCS_ORACLE" ] || _fail "no acceptance oracle at $DOCS_ORACLE"
|
|
787
|
+
|
|
788
|
+
# Leg 1 — the oracle states the contract this plan is about.
|
|
789
|
+
grep -qiE "couldn.t determine|503" "$ROOT/$DOCS_ORACLE" \
|
|
790
|
+
|| _fail "$DOCS_ORACLE does not state the three-state decision contract"
|
|
791
|
+
grep -qE 'controls\(' "$ROOT/$DOCS_ORACLE" \
|
|
792
|
+
|| _fail "$DOCS_ORACLE does not name controls() — the question the whole model reduces to"
|
|
793
|
+
|
|
794
|
+
# Leg 2 — the design doc marks the vantage RETIRED with a migration target,
|
|
795
|
+
# rather than still presenting it as the current model.
|
|
796
|
+
grep -qiE 'RETIRED|deleted' "$ROOT/text/roles-authority-plan.md" \
|
|
797
|
+
|| _fail "text/roles-authority-plan.md does not mark the vantage model retired"
|
|
798
|
+
grep -qE 'decide\.ts|roles-vantage-collapse-docs' "$ROOT/text/roles-authority-plan.md" \
|
|
799
|
+
|| _fail "text/roles-authority-plan.md marks the model retired but names no migration target"
|
|
800
|
+
|
|
801
|
+
# Leg 3 — green: no OTHER doc still teaches the deleted identifiers.
|
|
802
|
+
local live; live="$(_docs_live_claims)"
|
|
803
|
+
[ -z "$live" ] || { printf '%s\n' "$live" >&2
|
|
804
|
+
_fail "doc(s) still teach the deleted vocabulary as current (above) — reconcile or add to DOC_HISTORY"; }
|
|
805
|
+
echo "ROLES-CHECK: green half ok — no doc outside the history set teaches the deleted vocabulary"
|
|
806
|
+
|
|
807
|
+
# Leg 4 — red: plant a live claim and prove the scan catches it.
|
|
808
|
+
local plant="$ROOT/text/__roles_check_docs_probe.md"
|
|
809
|
+
trap 'rm -f "$plant"' EXIT INT TERM
|
|
810
|
+
printf '# probe\n\nCall `deriveViewer(session)` and compare `workspaceContext.viewer`.\n' > "$plant"
|
|
811
|
+
live="$(_docs_live_claims)"
|
|
812
|
+
rm -f "$plant"; trap - EXIT INT TERM
|
|
813
|
+
grep -q '__roles_check_docs_probe' <<<"$live" \
|
|
814
|
+
|| _fail "the planted stale doc was NOT detected — this scan proves nothing"
|
|
815
|
+
echo "ROLES-CHECK: red half ok — a planted stale doc claim was detected"
|
|
816
|
+
|
|
817
|
+
# Leg 5 — the probe is gone and the tree is clean again.
|
|
818
|
+
[ ! -f "$plant" ] || _fail "the doc probe was not removed"
|
|
819
|
+
[ -z "$(_docs_live_claims)" ] || _fail "scan is not clean after the probe — the tree is dirty"
|
|
820
|
+
echo "ROLES-CHECK: cleanup ok — probe removed, scan clean again"
|
|
821
|
+
|
|
822
|
+
echo "ROLES-CHECK: --check-docs pass"
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
# =============================================================================
|
|
827
|
+
# ONE LADDER — every authority decision goes through the same walk
|
|
828
|
+
# (--check-one-ladder).
|
|
829
|
+
#
|
|
830
|
+
# The bug this exists to prevent, reported live 2026-08-05: an operator asked
|
|
831
|
+
# their own chat "what's open on my task board?" and got
|
|
832
|
+
#
|
|
833
|
+
# "You're agency here, not an owner/admin — only those can read tasks."
|
|
834
|
+
#
|
|
835
|
+
# Two failures, both structural, both invisible to every existing check:
|
|
836
|
+
#
|
|
837
|
+
# 1. A SECOND LADDER. `defineWorkspaceTool` gated 137 tools on `canWalk()`
|
|
838
|
+
# alone — the LAST rung. Where the TypeDB group tree is unseeded that is
|
|
839
|
+
# false for the workspace's own owner, while D1 held the ownership all
|
|
840
|
+
# along. The walk existed in three private copies at the time; an operator
|
|
841
|
+
# was an owner to one surface and a stranger to another.
|
|
842
|
+
# 2. A SECOND VOCABULARY. `agency` is the deleted 4-word vantage ladder and
|
|
843
|
+
# `owner/admin` are rungs. --check-no-vantage greps src+tests for vantage
|
|
844
|
+
# TOKENS, but this sentence was assembled at runtime from `viewer.role`, so
|
|
845
|
+
# it passed every check while shipping the exact comparison the
|
|
846
|
+
# roles-vantage-collapse cycle existed to end.
|
|
847
|
+
#
|
|
848
|
+
# Both legs are red-proofed: a planted violation must be detected, or the check
|
|
849
|
+
# is a presence claim rather than a guard.
|
|
850
|
+
# =============================================================================
|
|
851
|
+
|
|
852
|
+
LADDER_PROBE="$ROOT/channels/src/tools/.ladder-probe.ts"
|
|
853
|
+
|
|
854
|
+
# Any authority gate in channels OUTSIDE the one ladder. substrate.ts DEFINES
|
|
855
|
+
# canWalk; authority.ts is the only legitimate caller (rung 4). A `walk(` default
|
|
856
|
+
# parameter is the injected-for-tests seam and is matched by name, not by call.
|
|
857
|
+
_second_ladder_grep() {
|
|
858
|
+
grep -rn --include=*.ts 'canWalk(' "$ROOT/channels/src" 2>/dev/null \
|
|
859
|
+
| grep -v '^[^:]*/src/authority\.ts:' \
|
|
860
|
+
| grep -v '^[^:]*/src/substrate\.ts:' \
|
|
861
|
+
| grep -v '= canWalk,$' \
|
|
862
|
+
| grep -v '^\s*[^:]*:[0-9]*: *\*' \
|
|
863
|
+
| grep -v '//' \
|
|
864
|
+
|| true
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
# A refusal built out of viewer.role — the runtime path --check-no-vantage cannot
|
|
868
|
+
# see, because the dead word never appears as a literal.
|
|
869
|
+
_vantage_message_grep() {
|
|
870
|
+
grep -rn --include=*.ts -E "viewer[?]?\.role|\\\$\{viewer\.role\}" "$ROOT/channels/src/tools/_contract.ts" "$ROOT/channels/src/authority.ts" 2>/dev/null \
|
|
871
|
+
| grep -vE '^\s*[^:]*:[0-9]*: *(\*|//)' \
|
|
872
|
+
|| true
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
check_one_ladder() {
|
|
876
|
+
rm -f "$LADDER_PROBE"
|
|
877
|
+
trap 'rm -f "$LADDER_PROBE"' EXIT
|
|
878
|
+
|
|
879
|
+
# --- leg 1: the ladder exists and carries all four rungs ---
|
|
880
|
+
local ladder="$ROOT/channels/src/authority.ts"
|
|
881
|
+
[ -f "$ladder" ] || _fail "channels/src/authority.ts is missing — there is no one ladder to enforce"
|
|
882
|
+
local rung
|
|
883
|
+
for rung in "via: 'self'" "via: 'owner-tree'" "via: 'membership'" "via: 'graph'"; do
|
|
884
|
+
grep -qF "$rung" "$ladder" \
|
|
885
|
+
|| _fail "the ladder lost a rung ($rung) — a missing rung is how an owner becomes a stranger"
|
|
886
|
+
done
|
|
887
|
+
grep -q 'minTier' "$ladder" \
|
|
888
|
+
|| _fail "the ladder no longer compares against minTier — the policy table is not being consulted"
|
|
889
|
+
echo "ROLES-CHECK: leg 1 ok — authority.ts carries self/owner-tree/membership/graph + the tier comparison"
|
|
890
|
+
|
|
891
|
+
# --- leg 2 (green): no second ladder anywhere in channels ---
|
|
892
|
+
local hits
|
|
893
|
+
hits="$(_second_ladder_grep)"
|
|
894
|
+
if [ -n "$hits" ]; then
|
|
895
|
+
printf '%s\n' "$hits"
|
|
896
|
+
_fail "canWalk() is gated outside src/authority.ts — that is a SECOND ladder, and it will deny someone the one ladder allows"
|
|
897
|
+
fi
|
|
898
|
+
echo "ROLES-CHECK: leg 2 ok — canWalk is called only by the ladder"
|
|
899
|
+
|
|
900
|
+
# --- leg 3 (green): no refusal assembled from the vantage word ---
|
|
901
|
+
hits="$(_vantage_message_grep)"
|
|
902
|
+
if [ -n "$hits" ]; then
|
|
903
|
+
printf '%s\n' "$hits"
|
|
904
|
+
_fail "a deny path still reads viewer.role — that is how 'You're agency here, not an owner/admin' shipped"
|
|
905
|
+
fi
|
|
906
|
+
echo "ROLES-CHECK: leg 3 ok — refusals name the workspace and the rung, never the vantage"
|
|
907
|
+
|
|
908
|
+
# --- red half A: plant a second ladder, prove leg 2 detects it ---
|
|
909
|
+
printf "import { canWalk } from '../substrate'\nexport const g = (e: never) => canWalk(e, 'a', 'b', 'read_tasks')\n" > "$LADDER_PROBE"
|
|
910
|
+
hits="$(_second_ladder_grep)"
|
|
911
|
+
rm -f "$LADDER_PROBE"
|
|
912
|
+
grep -qF '.ladder-probe.ts' <<<"$hits" \
|
|
913
|
+
|| _fail "the red-path self-test did NOT detect a planted second ladder — this check cannot go red"
|
|
914
|
+
echo "ROLES-CHECK: red half A ok — a planted canWalk gate outside the ladder was detected"
|
|
915
|
+
|
|
916
|
+
# --- red half B: plant the vantage-shaped refusal, prove leg 3 detects it ---
|
|
917
|
+
local contract="$ROOT/channels/src/tools/_contract.ts"
|
|
918
|
+
cp "$contract" "$contract.bak"
|
|
919
|
+
printf "\nconst _probe = (viewer: { role: string }) => \`You're \${viewer.role} here\`\nvoid _probe\n" >> "$contract"
|
|
920
|
+
hits="$(_vantage_message_grep)"
|
|
921
|
+
mv "$contract.bak" "$contract"
|
|
922
|
+
[ -n "$hits" ] \
|
|
923
|
+
|| _fail "the red-path self-test did NOT detect a planted viewer.role refusal — leg 3 is a presence claim"
|
|
924
|
+
echo "ROLES-CHECK: red half B ok — a planted viewer.role refusal was detected"
|
|
925
|
+
|
|
926
|
+
# --- cleanup verification ---
|
|
927
|
+
[ -z "$(_second_ladder_grep)" ] && [ -z "$(_vantage_message_grep)" ] \
|
|
928
|
+
|| _fail "probes were removed but a grep is still non-empty — cleanup failed or a real hit remains"
|
|
929
|
+
echo "ROLES-CHECK: cleanup ok — both probes removed, both greps clean again"
|
|
930
|
+
|
|
931
|
+
echo "ROLES-CHECK: --check-one-ladder pass"
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
case "${1:-}" in
|
|
935
|
+
--check-gate) check_gate ;;
|
|
936
|
+
--check-decide) check_decide ;;
|
|
937
|
+
--check-503) check_503 ;;
|
|
938
|
+
--check-floors) check_floors ;;
|
|
939
|
+
--check-no-vantage) check_no_vantage ;;
|
|
940
|
+
--check-worldkey) check_worldkey ;;
|
|
941
|
+
--check-staff) check_staff ;;
|
|
942
|
+
--check-suite) check_suite ;;
|
|
943
|
+
--check-docs) check_docs ;;
|
|
944
|
+
--check-one-ladder) check_one_ladder ;;
|
|
945
|
+
*) echo "usage: roles-check.sh --check-{gate,decide,503,floors,no-vantage,worldkey,staff,suite,docs,one-ladder}" >&2; exit 1 ;;
|
|
946
|
+
esac
|