@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,648 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# speed-parity-check.sh — C10's checker. Three flags:
|
|
3
|
+
#
|
|
4
|
+
# --parity static, exit 0/1 — P1-P6, the six structural facts the
|
|
5
|
+
# Interface Contract (text/speed-todo.md, C10) pins: the
|
|
6
|
+
# altitude of the read probe (P1), the sequence order of the
|
|
7
|
+
# store middleware (P2), the frozen export surface of
|
|
8
|
+
# lib/page-cache.ts (P3), pageCacheVerdict as the single
|
|
9
|
+
# decision point in both tenant routes (P4), hasLiveBindings
|
|
10
|
+
# staying uncapped (P5), and no per-visitor value reaching
|
|
11
|
+
# LeadCaptureScript un-gated (P6).
|
|
12
|
+
# --freshness static, exit 0/1 — F1/F2. touchPage is the ONE choke point
|
|
13
|
+
# for "this page changed" (IC row 8): every UPDATE pages SET /
|
|
14
|
+
# INSERT INTO pages write site across the four writer files
|
|
15
|
+
# must be followed by a touchPage( call within 6 lines (F1),
|
|
16
|
+
# and the old name (broadcastPageUpdated) must have zero
|
|
17
|
+
# occurrences repo-wide once the rename lands (F2) — a
|
|
18
|
+
# survivor is a second, silent choke point.
|
|
19
|
+
# --check-gate exit 0 iff EVERY one of six planted violations goes RED
|
|
20
|
+
# against the real checks above. G1/G2/G4/G5 exercise the pure
|
|
21
|
+
# pageCacheVerdict() function directly with a bad input — no
|
|
22
|
+
# file mutation needed. G3 and G6 mutate a COPY of the source
|
|
23
|
+
# under $TMP (never a tracked file) and re-run the matching
|
|
24
|
+
# structural check against that copy.
|
|
25
|
+
#
|
|
26
|
+
# Exit codes: 0 pass · 1 fail · 2 missing prerequisite (a source file or `bun`
|
|
27
|
+
# is absent — never reported as pass).
|
|
28
|
+
#
|
|
29
|
+
# THE TWO TRAPS THIS SCRIPT IS WRITTEN AROUND (both already paid for in this
|
|
30
|
+
# repo — see .claude/CLAUDE.md and text/learnings.md):
|
|
31
|
+
# 1. Plant against a FIXTURE COPY in $TMP, never a tracked file. An
|
|
32
|
+
# interrupted run must never leave middleware.ts / inject-rows.ts /
|
|
33
|
+
# page-cache.ts corrupted, and mutate-then-restore is unsafe under a live
|
|
34
|
+
# parallel batch (roles-check.sh --check-decide is the shape copied here).
|
|
35
|
+
# 2. NO `producer | grep -q` under `set -o pipefail` anywhere — a MATCH
|
|
36
|
+
# SIGPIPEs the producer and the pipeline reports 141 (false RED, or false
|
|
37
|
+
# GREEN under a `!`). Every check below captures a command's full output
|
|
38
|
+
# to a variable or a scratch file FIRST (`out=$(...)`), then matches it
|
|
39
|
+
# with `grep -c` (which reads to EOF, never exits early) or an `awk`
|
|
40
|
+
# pass with no `head`/`grep -q` downstream of a live process.
|
|
41
|
+
#
|
|
42
|
+
# Usage:
|
|
43
|
+
# speed-parity-check.sh --parity
|
|
44
|
+
# speed-parity-check.sh --freshness
|
|
45
|
+
# speed-parity-check.sh --check-gate
|
|
46
|
+
|
|
47
|
+
set -euo pipefail
|
|
48
|
+
|
|
49
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
50
|
+
cd "$ROOT"
|
|
51
|
+
|
|
52
|
+
PAGE_CACHE_TS="one.ie/web/src/lib/page-cache.ts"
|
|
53
|
+
PAGE_CACHE_MW="one.ie/web/src/lib/page-cache-middleware.ts"
|
|
54
|
+
MIDDLEWARE_TS="one.ie/web/src/middleware.ts"
|
|
55
|
+
INJECT_ROWS="one.ie/web/src/lib/puck/inject-rows.ts"
|
|
56
|
+
PAGESLUG_ASTRO="one.ie/web/src/pages/u/[slug]/p/[pageSlug].astro"
|
|
57
|
+
INDEX_ASTRO="one.ie/web/src/pages/u/[slug]/index.astro"
|
|
58
|
+
PAGES_TS="one.ie/web/src/lib/resolvers/pages.ts"
|
|
59
|
+
PAGES_CHAT_TS="one.ie/web/src/lib/resolvers/pages-chat.ts"
|
|
60
|
+
API_SLUG_TS="one.ie/web/src/pages/api/pages/[slug].ts"
|
|
61
|
+
API_INDEX_TS="one.ie/web/src/pages/api/pages/index.ts"
|
|
62
|
+
|
|
63
|
+
WRITER_FILES=("$PAGES_TS" "$PAGES_CHAT_TS" "$API_SLUG_TS" "$API_INDEX_TS")
|
|
64
|
+
ROUTE_FILES=("$PAGESLUG_ASTRO" "$INDEX_ASTRO")
|
|
65
|
+
|
|
66
|
+
TMP="$(mktemp -d)"
|
|
67
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
68
|
+
|
|
69
|
+
need_file() {
|
|
70
|
+
if [ ! -f "$1" ]; then
|
|
71
|
+
echo "[speed-parity-check] missing $1" >&2
|
|
72
|
+
return 2
|
|
73
|
+
fi
|
|
74
|
+
return 0
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
need_bun() {
|
|
78
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
79
|
+
echo "[speed-parity-check] bun not on PATH — cannot run pure-function checks" >&2
|
|
80
|
+
return 2
|
|
81
|
+
fi
|
|
82
|
+
return 0
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# first_field_of_first_line PATTERN VAR_CONTAINING_GREP_N_OUTPUT
|
|
86
|
+
# Never `grep -n ... | head -1` — that pipes a live grep into an early-exiting
|
|
87
|
+
# consumer. Every caller here first captures grep's FULL output to a shell
|
|
88
|
+
# variable (`matches=$(grep -n ... file || true)`), which is a command
|
|
89
|
+
# substitution (reads to EOF, no early-exit risk), then this just does string
|
|
90
|
+
# processing on the already-captured text.
|
|
91
|
+
first_line_number() {
|
|
92
|
+
awk -F: 'NR==1 && $1 != "" {n=$1} END{print n+0}' <<<"$1"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# --parity : P1-P6
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
p1_altitude() {
|
|
100
|
+
local file="${1:-$MIDDLEWARE_TS}"
|
|
101
|
+
need_file "$file" || return 2
|
|
102
|
+
local isprimary_return domainmemo_matches pagecache_matches
|
|
103
|
+
isprimary_return=$(awk '/const isPrimary =/{f=1} f && /return next\(\);/{print NR; exit}' "$file")
|
|
104
|
+
domainmemo_matches=$(grep -n 'readDomainRowMemo(' "$file" || true)
|
|
105
|
+
pagecache_matches=$(grep -n 'readPageCache(' "$file" || true)
|
|
106
|
+
local domainmemo_line pagecache_line
|
|
107
|
+
domainmemo_line=$(first_line_number "$domainmemo_matches")
|
|
108
|
+
pagecache_line=$(first_line_number "$pagecache_matches")
|
|
109
|
+
if [ -z "${isprimary_return:-}" ] || [ "$domainmemo_line" -eq 0 ] || [ "$pagecache_line" -eq 0 ]; then
|
|
110
|
+
echo " P1 FAIL — could not locate anchors (isPrimary-return='${isprimary_return:-}' readDomainRowMemo=$domainmemo_line readPageCache=$pagecache_line) in $file" >&2
|
|
111
|
+
return 1
|
|
112
|
+
fi
|
|
113
|
+
if [ "$pagecache_line" -gt "$isprimary_return" ] && [ "$pagecache_line" -lt "$domainmemo_line" ]; then
|
|
114
|
+
return 0
|
|
115
|
+
fi
|
|
116
|
+
echo " P1 FAIL — altitude violated in $file: isPrimary-return=$isprimary_return readPageCache=$pagecache_line readDomainRowMemo=$domainmemo_line (want isPrimary-return < readPageCache < readDomainRowMemo)" >&2
|
|
117
|
+
return 1
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
p2_sequence() {
|
|
121
|
+
local file="${1:-$MIDDLEWARE_TS}"
|
|
122
|
+
need_file "$file" || return 2
|
|
123
|
+
local block
|
|
124
|
+
block=$(sed -n '/^export const onRequest = sequence(/,/^);/p' "$file")
|
|
125
|
+
if [ -z "$block" ]; then
|
|
126
|
+
echo " P2 FAIL — sequence(...) block not found in $file" >&2
|
|
127
|
+
return 1
|
|
128
|
+
fi
|
|
129
|
+
local perfstart_matches store_matches mainmw_matches
|
|
130
|
+
perfstart_matches=$(grep -n 'perfStart' <<<"$block" || true)
|
|
131
|
+
store_matches=$(grep -n 'pageCacheStore' <<<"$block" || true)
|
|
132
|
+
mainmw_matches=$(grep -nE '^\s*mainMiddleware,\s*$' <<<"$block" || true)
|
|
133
|
+
local perfstart_line store_line mainmw_line
|
|
134
|
+
perfstart_line=$(first_line_number "$perfstart_matches")
|
|
135
|
+
store_line=$(first_line_number "$store_matches")
|
|
136
|
+
mainmw_line=$(first_line_number "$mainmw_matches")
|
|
137
|
+
if [ "$perfstart_line" -eq 0 ] || [ "$store_line" -eq 0 ] || [ "$mainmw_line" -eq 0 ]; then
|
|
138
|
+
echo " P2 FAIL — sequence missing an anchor (perfStart=$perfstart_line pageCacheStore=$store_line mainMiddleware=$mainmw_line)" >&2
|
|
139
|
+
return 1
|
|
140
|
+
fi
|
|
141
|
+
if [ "$store_line" -gt "$perfstart_line" ] && [ "$store_line" -lt "$mainmw_line" ]; then
|
|
142
|
+
return 0
|
|
143
|
+
fi
|
|
144
|
+
echo " P2 FAIL — sequence order wrong: perfStart=$perfstart_line pageCacheStore=$store_line mainMiddleware=$mainmw_line (want perfStart < pageCacheStore < mainMiddleware)" >&2
|
|
145
|
+
return 1
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
p3_exports() {
|
|
149
|
+
local file="${1:-$PAGE_CACHE_TS}"
|
|
150
|
+
need_file "$file" || return 2
|
|
151
|
+
local pinned=(PAGE_CACHE_TTL_SECONDS PAGE_CACHE_HEADER PAGE_CACHE_STATUS_HEADER pageCacheKey PageCacheInput pageCacheVerdict readPageCache storePageCache purgePageCache)
|
|
152
|
+
# PageCacheVerdict is the return-type companion to PageCacheInput and is
|
|
153
|
+
# the one allowed export beyond the nine pinned names.
|
|
154
|
+
local allowed_extra="PageCacheVerdict"
|
|
155
|
+
local exports
|
|
156
|
+
exports=$(grep -nE '^export ' "$file" || true)
|
|
157
|
+
printf '%s\n' "$exports" >"$TMP/p3-exports.txt"
|
|
158
|
+
local missing=0 name
|
|
159
|
+
for name in "${pinned[@]}"; do
|
|
160
|
+
local hit
|
|
161
|
+
hit=$(grep -c "[[:space:]]$name\b" "$TMP/p3-exports.txt" || true)
|
|
162
|
+
if [ "$hit" -eq 0 ]; then
|
|
163
|
+
echo " P3 FAIL — $file does not export $name" >&2
|
|
164
|
+
missing=1
|
|
165
|
+
fi
|
|
166
|
+
done
|
|
167
|
+
# Any export line not naming a pinned or allowed-extra identifier is drift.
|
|
168
|
+
local extra=0
|
|
169
|
+
while IFS= read -r line; do
|
|
170
|
+
[ -z "$line" ] && continue
|
|
171
|
+
local ok=0 n hit
|
|
172
|
+
for n in "${pinned[@]}" "$allowed_extra"; do
|
|
173
|
+
hit=$(grep -cE "[[:space:]]$n\b" <<<"$line" || true)
|
|
174
|
+
if [ "$hit" -ge 1 ]; then
|
|
175
|
+
ok=1
|
|
176
|
+
fi
|
|
177
|
+
done
|
|
178
|
+
if [ "$ok" -eq 0 ]; then
|
|
179
|
+
echo " P3 FAIL — unexpected export: $line" >&2
|
|
180
|
+
extra=1
|
|
181
|
+
fi
|
|
182
|
+
done <"$TMP/p3-exports.txt"
|
|
183
|
+
if [ "$missing" -eq 0 ] && [ "$extra" -eq 0 ]; then
|
|
184
|
+
return 0
|
|
185
|
+
fi
|
|
186
|
+
return 1
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
p4_decision_point() {
|
|
190
|
+
local files=("${@:-${ROUTE_FILES[@]}}")
|
|
191
|
+
local bad=0
|
|
192
|
+
local f
|
|
193
|
+
for f in "${files[@]}"; do
|
|
194
|
+
need_file "$f" || return 2
|
|
195
|
+
done
|
|
196
|
+
# Exactly the two tenant routes call pageCacheVerdict( in the astro tree.
|
|
197
|
+
local call_files
|
|
198
|
+
call_files=$(grep -rl 'pageCacheVerdict(' --include='*.astro' one.ie/web/src/pages 2>/dev/null || true)
|
|
199
|
+
local n
|
|
200
|
+
n=$(grep -c '.' <<<"$call_files" || true)
|
|
201
|
+
if [ "$n" -ne "${#files[@]}" ]; then
|
|
202
|
+
echo " P4 FAIL — pageCacheVerdict( is called in $n .astro file(s), want ${#files[@]}: $call_files" >&2
|
|
203
|
+
bad=1
|
|
204
|
+
fi
|
|
205
|
+
for f in "${files[@]}"; do
|
|
206
|
+
local vmatches hmatches vline hline gated
|
|
207
|
+
vmatches=$(grep -n 'pageCacheVerdict(' "$f" || true)
|
|
208
|
+
hmatches=$(grep -n 'Astro.response.headers.set(PAGE_CACHE_HEADER' "$f" || true)
|
|
209
|
+
gated=$(grep -c 'if (cacheVerdict.cacheable) Astro.response.headers.set(PAGE_CACHE_HEADER' "$f" || true)
|
|
210
|
+
vline=$(first_line_number "$vmatches")
|
|
211
|
+
hline=$(first_line_number "$hmatches")
|
|
212
|
+
if [ "$vline" -eq 0 ] || [ "$hline" -eq 0 ] || [ "$gated" -lt 1 ]; then
|
|
213
|
+
echo " P4 FAIL — $f: verdict-call=$vline header-set=$hline gated-form=$gated (want both present and the header set inside \`if (cacheVerdict.cacheable)\`)" >&2
|
|
214
|
+
bad=1
|
|
215
|
+
continue
|
|
216
|
+
fi
|
|
217
|
+
if [ "$hline" -le "$vline" ]; then
|
|
218
|
+
echo " P4 FAIL — $f: PAGE_CACHE_HEADER set at line $hline is not after the verdict call at line $vline" >&2
|
|
219
|
+
bad=1
|
|
220
|
+
fi
|
|
221
|
+
done
|
|
222
|
+
[ "$bad" -eq 0 ]
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
p5_uncapped() {
|
|
226
|
+
local file="${1:-$INJECT_ROWS}"
|
|
227
|
+
need_file "$file" || return 2
|
|
228
|
+
local body
|
|
229
|
+
body=$(awk '/^export function hasLiveBindings/{f=1} f{print} f && /^}/{exit}' "$file")
|
|
230
|
+
if [ -z "$body" ]; then
|
|
231
|
+
echo " P5 FAIL — hasLiveBindings not found in $file" >&2
|
|
232
|
+
return 1
|
|
233
|
+
fi
|
|
234
|
+
printf '%s\n' "$body" >"$TMP/p5-body.txt"
|
|
235
|
+
local hits
|
|
236
|
+
hits=$(grep -cE '\bcap\b' "$TMP/p5-body.txt" || true)
|
|
237
|
+
if [ "$hits" -eq 0 ]; then
|
|
238
|
+
return 0
|
|
239
|
+
fi
|
|
240
|
+
echo " P5 FAIL — hasLiveBindings in $file references \`cap\` — it must stay uncapped (existence, not job-limited collection)" >&2
|
|
241
|
+
return 1
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
p6_visitor_value() {
|
|
245
|
+
local files=("${@:-${ROUTE_FILES[@]}}")
|
|
246
|
+
local bad=0 f
|
|
247
|
+
for f in "${files[@]}"; do
|
|
248
|
+
need_file "$f" || return 2
|
|
249
|
+
local raw leadv
|
|
250
|
+
raw=$(grep -c 'visitorHash={Astro.locals.visitorHash' "$f" || true)
|
|
251
|
+
leadv=$(grep -c 'visitorHash={leadVisitorHash}' "$f" || true)
|
|
252
|
+
# A route with no LeadCaptureScript at all (leadv==0 AND no
|
|
253
|
+
# LeadCaptureScript tag) is not a violation — only a route that both
|
|
254
|
+
# renders the script AND still passes the raw session value is.
|
|
255
|
+
local has_script
|
|
256
|
+
has_script=$(grep -c '<LeadCaptureScript' "$f" || true)
|
|
257
|
+
if [ "$raw" -gt 0 ]; then
|
|
258
|
+
echo " P6 FAIL — $f passes Astro.locals.visitorHash directly into a component; must go through the verdict-gated variable" >&2
|
|
259
|
+
bad=1
|
|
260
|
+
elif [ "$has_script" -gt 0 ] && [ "$leadv" -eq 0 ]; then
|
|
261
|
+
echo " P6 FAIL — $f renders LeadCaptureScript without the gated leadVisitorHash variable" >&2
|
|
262
|
+
bad=1
|
|
263
|
+
fi
|
|
264
|
+
done
|
|
265
|
+
[ "$bad" -eq 0 ]
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
run_parity() {
|
|
269
|
+
local bad=0
|
|
270
|
+
echo "speed-parity-check --parity"
|
|
271
|
+
if p1_altitude; then echo " ok P1 altitude — readPageCache sits strictly between the isPrimary exit and readDomainRowMemo"; else bad=1; fi
|
|
272
|
+
if p2_sequence; then echo " ok P2 sequence — pageCacheStore wired after perfStart, before mainMiddleware"; else bad=1; fi
|
|
273
|
+
if p3_exports; then echo " ok P3 exports — lib/page-cache.ts exports exactly the frozen surface"; else bad=1; fi
|
|
274
|
+
if p4_decision_point; then echo " ok P4 single decision point — pageCacheVerdict is the one gate on both tenant routes"; else bad=1; fi
|
|
275
|
+
if p5_uncapped; then echo " ok P5 uncapped — hasLiveBindings carries no job cap"; else bad=1; fi
|
|
276
|
+
if p6_visitor_value; then echo " ok P6 visitor value — no route hands a raw per-visitor hash to a cacheable component"; else bad=1; fi
|
|
277
|
+
if [ "$bad" -eq 0 ]; then
|
|
278
|
+
echo "[speed-parity-check --parity] ok — all six structural facts hold"
|
|
279
|
+
return 0
|
|
280
|
+
fi
|
|
281
|
+
echo "[speed-parity-check --parity] FAIL" >&2
|
|
282
|
+
return 1
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
# --freshness : F1/F2
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
# A write SITE is one DB.prepare(...).bind(...).run() call (or an if/else
|
|
290
|
+
# group of them, per spec 12's "ONE call after the if/else, never one per
|
|
291
|
+
# branch") — never a raw string-literal count. A ternary of two SQL literals
|
|
292
|
+
# for the same statement, or a 3-way if/elseif/else each with its own
|
|
293
|
+
# UPDATE/.run(), is ONE site: matches within 5 lines of each other cluster
|
|
294
|
+
# together, anchored at the cluster's LAST `.run()`. From there the rule is
|
|
295
|
+
# 6 SIGNIFICANT lines (non-blank, non-comment) — not 6 raw file lines, or a
|
|
296
|
+
# W2-authored explanatory comment between the `.run()` and the `touchPage(`
|
|
297
|
+
# call (exactly the shape at api/pages/[slug].ts:130-138) would itself burn
|
|
298
|
+
# the budget and make the pinned rule self-defeating. Comment-only lines
|
|
299
|
+
# (`//`, `*`, `/*`) never count as a write-site match either — pages.ts:32's
|
|
300
|
+
# own docblock quotes `UPDATE pages SET` / `INSERT INTO pages` in backticks
|
|
301
|
+
# and must not be read as a 19th writer.
|
|
302
|
+
f1_scan_file() {
|
|
303
|
+
local f="$1"
|
|
304
|
+
awk '
|
|
305
|
+
function is_comment(s, t) {
|
|
306
|
+
t = s
|
|
307
|
+
sub(/^[ \t]*/, "", t)
|
|
308
|
+
return (t ~ /^\/\// || t ~ /^\*/ || t ~ /^\/\*/)
|
|
309
|
+
}
|
|
310
|
+
{
|
|
311
|
+
lines[NR] = $0
|
|
312
|
+
if (!is_comment($0) && ($0 ~ /UPDATE pages SET/ || $0 ~ /INSERT INTO pages/)) {
|
|
313
|
+
if (NR - last_match > 10) {
|
|
314
|
+
nclusters++
|
|
315
|
+
cstart[nclusters] = NR
|
|
316
|
+
}
|
|
317
|
+
cend[nclusters] = NR
|
|
318
|
+
last_match = NR
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
END {
|
|
322
|
+
total_lines = NR
|
|
323
|
+
for (c = 1; c <= nclusters; c++) {
|
|
324
|
+
anchor = 0
|
|
325
|
+
for (i = cend[c]; i <= cend[c] + 10 && i <= total_lines; i++) {
|
|
326
|
+
if (lines[i] ~ /\.run\(\)/) { anchor = i; break }
|
|
327
|
+
}
|
|
328
|
+
if (anchor == 0) anchor = cend[c]
|
|
329
|
+
found = 0
|
|
330
|
+
sig = 0
|
|
331
|
+
for (i = anchor + 1; i <= total_lines && sig < 6; i++) {
|
|
332
|
+
t2 = lines[i]
|
|
333
|
+
trimmed = t2
|
|
334
|
+
sub(/^[ \t]*/, "", trimmed)
|
|
335
|
+
if (trimmed == "") continue
|
|
336
|
+
if (is_comment(t2)) continue
|
|
337
|
+
sig++
|
|
338
|
+
if (t2 ~ /touchPage\(/) { found = 1; break }
|
|
339
|
+
}
|
|
340
|
+
total_sites++
|
|
341
|
+
if (!found) {
|
|
342
|
+
print "ORPHAN\t" anchor
|
|
343
|
+
orphan_count++
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
print "TOTAL\t" total_sites + 0
|
|
347
|
+
print "ORPHANS\t" orphan_count + 0
|
|
348
|
+
}
|
|
349
|
+
' "$f"
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
f1_write_sites() {
|
|
353
|
+
local files=("${@:-${WRITER_FILES[@]}}")
|
|
354
|
+
local total=0 orphans=0 f
|
|
355
|
+
for f in "${files[@]}"; do
|
|
356
|
+
need_file "$f" || return 2
|
|
357
|
+
done
|
|
358
|
+
for f in "${files[@]}"; do
|
|
359
|
+
local out
|
|
360
|
+
out=$(f1_scan_file "$f")
|
|
361
|
+
printf '%s\n' "$out" >"$TMP/f1-scan.txt"
|
|
362
|
+
local file_total file_orphans
|
|
363
|
+
file_total=$(awk -F'\t' '$1=="TOTAL"{print $2}' "$TMP/f1-scan.txt")
|
|
364
|
+
file_orphans=$(awk -F'\t' '$1=="ORPHANS"{print $2}' "$TMP/f1-scan.txt")
|
|
365
|
+
file_total=${file_total:-0}
|
|
366
|
+
file_orphans=${file_orphans:-0}
|
|
367
|
+
total=$((total + file_total))
|
|
368
|
+
orphans=$((orphans + file_orphans))
|
|
369
|
+
while IFS=$'\t' read -r tag lineno; do
|
|
370
|
+
[ "$tag" = "ORPHAN" ] || continue
|
|
371
|
+
echo " F1 orphan — $f: no touchPage( within 6 significant lines after the .run() at line $lineno" >&2
|
|
372
|
+
done <"$TMP/f1-scan.txt"
|
|
373
|
+
done
|
|
374
|
+
echo " F1 write-sites=$total touchPage-covered=$((total - orphans)) orphans=$orphans"
|
|
375
|
+
[ "$orphans" -eq 0 ]
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
f2_no_broadcast() {
|
|
379
|
+
local hits
|
|
380
|
+
hits=$(grep -rn --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist --exclude-dir=.astro --exclude-dir=.wrangler --include='*.ts' --include='*.astro' 'broadcastPageUpdated' one.ie 2>/dev/null || true)
|
|
381
|
+
printf '%s\n' "$hits" >"$TMP/f2-hits.txt"
|
|
382
|
+
local n
|
|
383
|
+
n=$(grep -c '.' "$TMP/f2-hits.txt" || true)
|
|
384
|
+
if [ "$n" -eq 0 ]; then
|
|
385
|
+
return 0
|
|
386
|
+
fi
|
|
387
|
+
echo " F2 FAIL — broadcastPageUpdated survives at $n site(s):" >&2
|
|
388
|
+
cat "$TMP/f2-hits.txt" >&2
|
|
389
|
+
return 1
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
run_freshness() {
|
|
393
|
+
local bad=0
|
|
394
|
+
echo "speed-parity-check --freshness"
|
|
395
|
+
if f1_write_sites; then echo " ok F1 — every UPDATE pages SET / INSERT INTO pages site is followed by touchPage( within 6 lines"; else bad=1; fi
|
|
396
|
+
if f2_no_broadcast; then echo " ok F2 — broadcastPageUpdated has zero occurrences repo-wide (touchPage is the one choke point)"; else bad=1; fi
|
|
397
|
+
if [ "$bad" -eq 0 ]; then
|
|
398
|
+
echo "[speed-parity-check --freshness] ok"
|
|
399
|
+
return 0
|
|
400
|
+
fi
|
|
401
|
+
echo "[speed-parity-check --freshness] FAIL" >&2
|
|
402
|
+
return 1
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
# ---------------------------------------------------------------------------
|
|
406
|
+
# --check-gate : G1-G6
|
|
407
|
+
# ---------------------------------------------------------------------------
|
|
408
|
+
|
|
409
|
+
PAGE_CACHE_ABS="$ROOT/$PAGE_CACHE_TS"
|
|
410
|
+
|
|
411
|
+
write_probe() {
|
|
412
|
+
cat >"$TMP/probe.mjs" <<PROBEEOF
|
|
413
|
+
import { pageCacheVerdict } from '${PAGE_CACHE_ABS}'
|
|
414
|
+
const input = JSON.parse(process.argv[2])
|
|
415
|
+
process.stdout.write(JSON.stringify(pageCacheVerdict(input)))
|
|
416
|
+
PROBEEOF
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
assert_reason() {
|
|
420
|
+
local name="$1" json="$2" want="$3"
|
|
421
|
+
local out
|
|
422
|
+
if ! out=$(bun "$TMP/probe.mjs" "$json" 2>&1); then
|
|
423
|
+
echo " FAIL $name — probe threw: $out" >&2
|
|
424
|
+
return 1
|
|
425
|
+
fi
|
|
426
|
+
printf '%s' "$out" >"$TMP/last-probe.json"
|
|
427
|
+
local got
|
|
428
|
+
got=$(grep -c "\"reason\":\"$want\"" "$TMP/last-probe.json" || true)
|
|
429
|
+
if [ "$got" -ge 1 ]; then
|
|
430
|
+
echo " ok $name -> reason=$want"
|
|
431
|
+
return 0
|
|
432
|
+
fi
|
|
433
|
+
echo " FAIL $name — want reason=$want, got: $out" >&2
|
|
434
|
+
return 1
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
write_key_probe() {
|
|
438
|
+
local module="$1" out="$2"
|
|
439
|
+
cat >"$out" <<KEYEOF
|
|
440
|
+
import { pageCacheKey } from '${module}'
|
|
441
|
+
console.log(pageCacheKey(process.argv[2], process.argv[3]))
|
|
442
|
+
KEYEOF
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
plant_key_confusion() {
|
|
446
|
+
# G3 — a $TMP key builder that drops the host. Never touches the tracked
|
|
447
|
+
# file; writes only into $TMP.
|
|
448
|
+
cat >"$TMP/plant-key.mjs" <<'JSEOF'
|
|
449
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
450
|
+
const src = readFileSync(process.argv[2], 'utf8')
|
|
451
|
+
const needle = 'return `${PAGE_CACHE_ORIGIN}/${encodeURIComponent(host)}${pathname}`;'
|
|
452
|
+
const replacement = 'return `${PAGE_CACHE_ORIGIN}${pathname}`;'
|
|
453
|
+
if (!src.includes(needle)) {
|
|
454
|
+
console.error('PLANT_FAILED_NO_MATCH')
|
|
455
|
+
process.exit(1)
|
|
456
|
+
}
|
|
457
|
+
writeFileSync(process.argv[3], src.replace(needle, replacement))
|
|
458
|
+
JSEOF
|
|
459
|
+
bun "$TMP/plant-key.mjs" "$PAGE_CACHE_ABS" "$TMP/page-cache-red.ts"
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
plant_capped_bindings() {
|
|
463
|
+
# G4b — a $TMP copy of inject-rows.ts whose hasLiveBindings caps at 5.
|
|
464
|
+
cat >"$TMP/plant-cap.mjs" <<'JSEOF'
|
|
465
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
466
|
+
const src = readFileSync(process.argv[2], 'utf8')
|
|
467
|
+
const needle = 'export function hasLiveBindings(data: Data): boolean {'
|
|
468
|
+
if (!src.includes(needle)) {
|
|
469
|
+
console.error('PLANT_FAILED_NO_MATCH')
|
|
470
|
+
process.exit(1)
|
|
471
|
+
}
|
|
472
|
+
const replacement = needle + '\n\tconst cap = 5 // PLANTED — proves P5 catches a job cap creeping in\n'
|
|
473
|
+
writeFileSync(process.argv[3], src.replace(needle, replacement))
|
|
474
|
+
JSEOF
|
|
475
|
+
bun "$TMP/plant-cap.mjs" "$ROOT/$INJECT_ROWS" "$TMP/inject-rows-red.ts"
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
plant_altitude() {
|
|
479
|
+
# G6 — a $TMP copy of middleware.ts with the read probe moved BELOW
|
|
480
|
+
# readDomainRowMemo. Text-only mutation; the plant never needs to compile,
|
|
481
|
+
# it only needs to reproduce the byte order p1_altitude scans for.
|
|
482
|
+
cat >"$TMP/plant-altitude.mjs" <<'JSEOF'
|
|
483
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
484
|
+
const src = readFileSync(process.argv[2], 'utf8')
|
|
485
|
+
const lines = src.split('\n')
|
|
486
|
+
const startIdx = lines.findIndex((l) => l.includes('C10 PARITY PROBE'))
|
|
487
|
+
if (startIdx === -1) {
|
|
488
|
+
console.error('PLANT_FAILED_NO_START')
|
|
489
|
+
process.exit(1)
|
|
490
|
+
}
|
|
491
|
+
const anchorIdx = lines.findIndex((l) => l.includes('An explicit verified'))
|
|
492
|
+
if (anchorIdx === -1) {
|
|
493
|
+
console.error('PLANT_FAILED_NO_ANCHOR')
|
|
494
|
+
process.exit(1)
|
|
495
|
+
}
|
|
496
|
+
let cut = anchorIdx - 1
|
|
497
|
+
while (cut > startIdx && lines[cut].trim() === '') cut--
|
|
498
|
+
const endIdx = cut
|
|
499
|
+
const block = lines.slice(startIdx, endIdx + 1)
|
|
500
|
+
const rest = lines.slice(0, startIdx).concat(lines.slice(endIdx + 1))
|
|
501
|
+
const domainIdx = rest.findIndex((l) => l.includes('readDomainRowMemo('))
|
|
502
|
+
if (domainIdx === -1) {
|
|
503
|
+
console.error('PLANT_FAILED_NO_DOMAIN')
|
|
504
|
+
process.exit(1)
|
|
505
|
+
}
|
|
506
|
+
const out = rest.slice(0, domainIdx + 1).concat(['']).concat(block).concat(rest.slice(domainIdx + 1))
|
|
507
|
+
writeFileSync(process.argv[3], out.join('\n'))
|
|
508
|
+
JSEOF
|
|
509
|
+
bun "$TMP/plant-altitude.mjs" "$ROOT/$MIDDLEWARE_TS" "$TMP/middleware-red.ts"
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
run_check_gate() {
|
|
513
|
+
need_bun || return 2
|
|
514
|
+
need_file "$PAGE_CACHE_TS" || return 2
|
|
515
|
+
need_file "$INJECT_ROWS" || return 2
|
|
516
|
+
need_file "$MIDDLEWARE_TS" || return 2
|
|
517
|
+
|
|
518
|
+
echo "speed-parity-check --check-gate — proving each of the six safety-rule plants goes RED"
|
|
519
|
+
echo
|
|
520
|
+
local bad=0
|
|
521
|
+
|
|
522
|
+
write_probe
|
|
523
|
+
|
|
524
|
+
# G1 draft
|
|
525
|
+
if assert_reason "G1 draft" '{"status":"draft","hasSession":false,"hasLiveBindings":false,"hasExperiment":false,"canEdit":false,"visitorHash":""}' "draft"; then :; else bad=1; fi
|
|
526
|
+
|
|
527
|
+
# G2 session
|
|
528
|
+
if assert_reason "G2 session" '{"status":"published","hasSession":true,"hasLiveBindings":false,"hasExperiment":false,"canEdit":false,"visitorHash":""}' "session"; then :; else bad=1; fi
|
|
529
|
+
|
|
530
|
+
# G4a live bindings
|
|
531
|
+
if assert_reason "G4a bindings" '{"status":"published","hasSession":false,"hasLiveBindings":true,"hasExperiment":false,"canEdit":false,"visitorHash":""}' "bindings"; then :; else bad=1; fi
|
|
532
|
+
|
|
533
|
+
# G5 visitor value
|
|
534
|
+
if assert_reason "G5 visitor-value" '{"status":"published","hasSession":false,"hasLiveBindings":false,"hasExperiment":false,"canEdit":false,"visitorHash":"abc123"}' "visitor-value"; then :; else bad=1; fi
|
|
535
|
+
|
|
536
|
+
# G — experiment (sixth named reason; IC row 3 lists all six in order)
|
|
537
|
+
if assert_reason "G experiment" '{"status":"published","hasSession":false,"hasLiveBindings":false,"hasExperiment":true,"canEdit":false,"visitorHash":""}' "experiment"; then :; else bad=1; fi
|
|
538
|
+
|
|
539
|
+
# G — can-edit
|
|
540
|
+
if assert_reason "G can-edit" '{"status":"published","hasSession":false,"hasLiveBindings":false,"hasExperiment":false,"canEdit":true,"visitorHash":""}' "can-edit"; then :; else bad=1; fi
|
|
541
|
+
|
|
542
|
+
# Sanity: the all-clear input is cacheable — proves the five guards above
|
|
543
|
+
# are not vacuously true (e.g. always returning cacheable:false).
|
|
544
|
+
if assert_reason "sanity all-clear" '{"status":"published","hasSession":false,"hasLiveBindings":false,"hasExperiment":false,"canEdit":false,"visitorHash":""}' "ok"; then :; else bad=1; fi
|
|
545
|
+
|
|
546
|
+
# G3 — host confusion. Real module: two hosts, same pathname, different keys.
|
|
547
|
+
write_key_probe "$PAGE_CACHE_ABS" "$TMP/key-real.mjs"
|
|
548
|
+
local ka kb
|
|
549
|
+
ka=$(bun "$TMP/key-real.mjs" 'a.com' '/p/x' 2>&1) || { echo " FAIL G3 real probe threw: $ka" >&2; bad=1; }
|
|
550
|
+
kb=$(bun "$TMP/key-real.mjs" 'b.com' '/p/x' 2>&1) || { echo " FAIL G3 real probe threw: $kb" >&2; bad=1; }
|
|
551
|
+
if [ -n "$ka" ] && [ -n "$kb" ] && [ "$ka" != "$kb" ]; then
|
|
552
|
+
echo " ok G3a real pageCacheKey — a.com and b.com produce different keys"
|
|
553
|
+
else
|
|
554
|
+
echo " FAIL G3a real pageCacheKey did not distinguish hosts: '$ka' vs '$kb'" >&2
|
|
555
|
+
bad=1
|
|
556
|
+
fi
|
|
557
|
+
# ...then plant a key builder that drops the host and prove the SAME
|
|
558
|
+
# assertion goes red against the plant.
|
|
559
|
+
if plant_key_confusion; then
|
|
560
|
+
write_key_probe "$TMP/page-cache-red.ts" "$TMP/key-red.mjs"
|
|
561
|
+
local rka rkb
|
|
562
|
+
rka=$(bun "$TMP/key-red.mjs" 'a.com' '/p/x' 2>&1) || rka=""
|
|
563
|
+
rkb=$(bun "$TMP/key-red.mjs" 'b.com' '/p/x' 2>&1) || rkb=""
|
|
564
|
+
if [ -n "$rka" ] && [ "$rka" = "$rkb" ]; then
|
|
565
|
+
echo " ok G3b planted host-dropping key builder is flagged RED (a.com and b.com collide)"
|
|
566
|
+
else
|
|
567
|
+
echo " FAIL G3b planted key builder was NOT flagged red — got '$rka' vs '$rkb' (want them equal, proving the bug is caught)" >&2
|
|
568
|
+
bad=1
|
|
569
|
+
fi
|
|
570
|
+
else
|
|
571
|
+
echo " FAIL G3b could not plant the key-confusion fixture" >&2
|
|
572
|
+
bad=1
|
|
573
|
+
fi
|
|
574
|
+
|
|
575
|
+
# G4b — capped hasLiveBindings must flag P5 red.
|
|
576
|
+
if p5_uncapped "$INJECT_ROWS" >/dev/null 2>&1; then
|
|
577
|
+
echo " ok G4b(real) working tree's hasLiveBindings is GREEN (uncapped)"
|
|
578
|
+
else
|
|
579
|
+
echo " FAIL G4b(real) working tree's hasLiveBindings did NOT pass P5" >&2
|
|
580
|
+
bad=1
|
|
581
|
+
fi
|
|
582
|
+
if plant_capped_bindings; then
|
|
583
|
+
if p5_uncapped "$TMP/inject-rows-red.ts" >/dev/null 2>&1; then
|
|
584
|
+
echo " FAIL G4b planted capped hasLiveBindings was NOT flagged (got pass, want fail)" >&2
|
|
585
|
+
bad=1
|
|
586
|
+
else
|
|
587
|
+
echo " ok G4b planted capped hasLiveBindings is flagged RED by P5"
|
|
588
|
+
fi
|
|
589
|
+
else
|
|
590
|
+
echo " FAIL G4b could not plant the capped-bindings fixture" >&2
|
|
591
|
+
bad=1
|
|
592
|
+
fi
|
|
593
|
+
|
|
594
|
+
# G6 — altitude. Working tree must be GREEN; the plant (probe moved below
|
|
595
|
+
# readDomainRowMemo) must be RED.
|
|
596
|
+
if p1_altitude "$MIDDLEWARE_TS" >/dev/null 2>&1; then
|
|
597
|
+
echo " ok G6(real) working tree's middleware.ts passes P1 altitude"
|
|
598
|
+
else
|
|
599
|
+
echo " FAIL G6(real) working tree's middleware.ts did NOT pass P1 altitude" >&2
|
|
600
|
+
bad=1
|
|
601
|
+
fi
|
|
602
|
+
if plant_altitude; then
|
|
603
|
+
if p1_altitude "$TMP/middleware-red.ts" >/dev/null 2>&1; then
|
|
604
|
+
echo " FAIL G6 planted altitude violation was NOT flagged (got pass, want fail)" >&2
|
|
605
|
+
bad=1
|
|
606
|
+
else
|
|
607
|
+
echo " ok G6 planted altitude violation (probe moved below readDomainRowMemo) is flagged RED by P1 — this is the plant that guards the deliverable itself"
|
|
608
|
+
fi
|
|
609
|
+
else
|
|
610
|
+
echo " FAIL G6 could not plant the altitude fixture" >&2
|
|
611
|
+
bad=1
|
|
612
|
+
fi
|
|
613
|
+
|
|
614
|
+
echo
|
|
615
|
+
if [ "$bad" -eq 0 ]; then
|
|
616
|
+
echo "check-gate proven: all six planted violations are flagged RED, and the working tree stays GREEN where it should"
|
|
617
|
+
return 0
|
|
618
|
+
fi
|
|
619
|
+
echo "$bad self-test group(s) FAILED — the parity gate is not trustworthy" >&2
|
|
620
|
+
return 1
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
# ---------------------------------------------------------------------------
|
|
624
|
+
# dispatch
|
|
625
|
+
# ---------------------------------------------------------------------------
|
|
626
|
+
|
|
627
|
+
MODE=""
|
|
628
|
+
for arg in "$@"; do
|
|
629
|
+
case "$arg" in
|
|
630
|
+
--parity) MODE="parity" ;;
|
|
631
|
+
--freshness) MODE="freshness" ;;
|
|
632
|
+
--check-gate) MODE="check-gate" ;;
|
|
633
|
+
*)
|
|
634
|
+
echo "[speed-parity-check] unknown flag: $arg" >&2
|
|
635
|
+
exit 2
|
|
636
|
+
;;
|
|
637
|
+
esac
|
|
638
|
+
done
|
|
639
|
+
|
|
640
|
+
case "$MODE" in
|
|
641
|
+
parity) run_parity; exit $? ;;
|
|
642
|
+
freshness) run_freshness; exit $? ;;
|
|
643
|
+
check-gate) run_check_gate; exit $? ;;
|
|
644
|
+
*)
|
|
645
|
+
echo "usage: speed-parity-check.sh --parity|--freshness|--check-gate" >&2
|
|
646
|
+
exit 2
|
|
647
|
+
;;
|
|
648
|
+
esac
|