@oneie/claude 0.8.0 → 0.10.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/.claude-plugin/plugin.json +1 -1
- package/agents/abm-strategist.md +67 -1
- package/agents/ads-meta.md +67 -1
- package/agents/analyst.md +67 -1
- package/agents/animator.md +108 -0
- package/agents/architect.md +269 -20
- package/agents/brand-guardian.md +67 -1
- package/agents/brand-strategist.md +67 -1
- package/agents/campaign-content.md +67 -1
- package/agents/campaign-email.md +67 -1
- package/agents/campaign-sms.md +67 -1
- package/agents/campaign-social.md +67 -1
- package/agents/cco.md +83 -2
- package/agents/ceo.md +108 -11
- package/agents/chairman.md +197 -0
- package/agents/cmo.md +82 -2
- package/agents/community-greeter.md +67 -1
- package/agents/community-moderator.md +67 -1
- package/agents/compliance.md +67 -1
- package/agents/copywriter.md +67 -1
- package/agents/creative-strategist.md +67 -1
- package/agents/cro.md +81 -1
- package/agents/cto.md +266 -28
- package/agents/customer-interviewer.md +67 -1
- package/agents/customer-researcher.md +67 -1
- package/agents/customer-success-manager.md +67 -1
- package/agents/customer-trainer.md +67 -1
- package/agents/cxo.md +82 -1
- package/agents/demand-creator.md +67 -1
- package/agents/demo-mover.md +67 -1
- package/agents/demo-specialist.md +67 -1
- package/agents/demo-thai-family-law.md +67 -1
- package/agents/designer.md +67 -1
- package/agents/discovery-caller.md +67 -1
- package/agents/doctor.md +269 -0
- package/agents/educate-coach.md +67 -1
- package/agents/elevate-tutor.md +67 -1
- package/agents/email-lifecycle-marketer.md +67 -1
- package/agents/engage-specialist.md +67 -1
- package/agents/events-coordinator.md +67 -1
- package/agents/foundation-builder.md +67 -1
- package/agents/funnel-architect.md +67 -1
- package/agents/gift-creator.md +67 -1
- package/agents/google-ads.md +67 -1
- package/agents/guide.md +67 -1
- package/agents/helpdesk-dispatcher.md +67 -1
- package/agents/hook-specialist.md +67 -1
- package/agents/identify-optimizer.md +67 -1
- package/agents/implementer.md +313 -45
- package/agents/incident-commander.md +67 -1
- package/agents/insights-lead.md +87 -1
- package/agents/journey-runner.md +67 -1
- package/agents/linkedin-ads.md +67 -1
- package/agents/live-sales-chat.md +67 -1
- package/agents/market-researcher.md +67 -1
- package/agents/media-buyer.md +67 -1
- package/agents/memory-keeper.md +195 -0
- package/agents/movers-customer-researcher.md +67 -1
- package/agents/movers-foundation-builder.md +67 -1
- package/agents/movers-market-researcher.md +67 -1
- package/agents/movers-pricing-strategist.md +67 -1
- package/agents/nurture-architect.md +67 -1
- package/agents/offer-architect.md +67 -1
- package/agents/onboarder.md +67 -1
- package/agents/onboarding-specialist.md +67 -1
- package/agents/operations-dashboard.md +87 -1
- package/agents/perf-engineer.md +333 -37
- package/agents/playbook-writer.md +67 -1
- package/agents/plg-strategist.md +67 -1
- package/agents/positioning-architect.md +67 -1
- package/agents/press-officer.md +67 -1
- package/agents/pricing-strategist.md +67 -1
- package/agents/privacy-officer.md +67 -1
- package/agents/referral-manager.md +67 -1
- package/agents/refine-analyst.md +67 -1
- package/agents/release-manager.md +446 -39
- package/agents/renewals-upsell-rep.md +67 -1
- package/agents/review-engineer.md +319 -45
- package/agents/rewards-steward.md +67 -1
- package/agents/sales-call-coach.md +67 -1
- package/agents/sales-closer.md +67 -1
- package/agents/security-auditor.md +343 -48
- package/agents/sell-closer.md +67 -1
- package/agents/share-amplifier.md +67 -1
- package/agents/social-media-manager.md +67 -1
- package/agents/storyteller.md +301 -0
- package/agents/strategist.md +67 -1
- package/agents/strategy-aligner.md +67 -1
- package/agents/support-agent.md +67 -1
- package/agents/tagger.md +327 -0
- package/agents/tech-writer.md +195 -22
- package/agents/test-engineer.md +398 -29
- package/agents/tiktok-ads.md +67 -1
- package/agents/tracking-engineer.md +67 -1
- package/agents/trailkeeper.md +181 -0
- package/agents/upsell-strategist.md +67 -1
- package/agents/voice.md +67 -1
- package/agents/w1-recon.md +1 -1
- package/agents/w2-decide.md +1 -1
- package/agents/w3-edit.md +8 -2
- package/agents/w4-verify.md +13 -0
- package/agents/workflow-optimiser.md +81 -1
- package/commands/close.md +916 -160
- package/commands/deploy.md +102 -724
- package/commands/do.md +58 -2
- package/commands/sweep.md +159 -0
- package/commands/tasks.md +222 -0
- package/hooks/scripts/dev-only.sh +135 -0
- package/hooks/scripts/git-add-guard.sh +37 -2
- package/hooks/scripts/session-start.sh +32 -4
- package/package.json +1 -1
- package/rules/scripts.md +85 -0
- package/scripts/CLAUDE.md +315 -0
- package/scripts/ad-copy-lint.sh +656 -0
- package/scripts/agent-actor-parity.sh +129 -0
- package/scripts/blocks-manifest-cached.sh +100 -0
- package/scripts/chat-context-check.sh +89 -0
- package/scripts/chrome.mjs +18 -0
- package/scripts/close-metrics.sh +587 -0
- package/scripts/close-owner.sh +326 -0
- package/scripts/db-sync-lock-check.sh +116 -0
- package/scripts/deploy-emit.sh +311 -0
- package/scripts/deploy-gate-check.sh +155 -0
- package/scripts/deploy-ready.sh +78 -0
- package/scripts/deploy-record.sh +605 -0
- package/scripts/deploy-schema-check.sh +58 -0
- package/scripts/deploy.sh +393 -243
- package/scripts/do-auto.sh +127 -26
- package/scripts/do-board.sh +429 -0
- package/scripts/do-close.sh +1184 -0
- package/scripts/do-consumer-sweep.sh +18 -1
- package/scripts/do-decide.sh +476 -0
- package/scripts/do-fleet.sh +8 -2
- package/scripts/do-plan-json.mjs +110 -12
- package/scripts/do-prove-selftest.sh +108 -0
- package/scripts/do-prove.sh +86 -10
- package/scripts/do-rank.py +200 -3
- package/scripts/do-reconcile.sh +73 -12
- package/scripts/do-signal.sh +101 -23
- package/scripts/do-smoke.sh +18 -1
- package/scripts/do-w4-gates.sh +11 -1
- package/scripts/do-world-check.sh +153 -0
- package/scripts/download-stats.sh +172 -0
- package/scripts/factory-brief-check.sh +330 -0
- package/scripts/factory-check.sh +18 -1
- 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 +71 -0
- package/scripts/factory-review-check.mjs +61 -0
- package/scripts/factory-tasks-check.sh +18 -1
- package/scripts/fixtures/factory-brief-real.md +44 -0
- package/scripts/flywheel-outcome.sh +63 -0
- package/scripts/gate-reaper-check.sh +98 -0
- package/scripts/gate-reaper.sh +9 -0
- package/scripts/gate-watchdog.sh +619 -0
- package/scripts/gc-content-check.sh +142 -0
- package/scripts/gh-traffic-capture.sh +153 -0
- package/scripts/govern-order-check.sh +202 -0
- package/scripts/governor-doors-check.sh +86 -5
- package/scripts/health.sh +448 -0
- package/scripts/id-inventory.mjs +418 -0
- package/scripts/incident.sh +212 -0
- package/scripts/land.sh +755 -45
- package/scripts/lib/gc-finished.sh +77 -0
- package/scripts/livekit-ratchet.sh +18 -1
- package/scripts/machine-check.sh +1 -1
- package/scripts/memory-index-budget.sh +79 -0
- package/scripts/npm-downloads.sh +109 -0
- package/scripts/one-agents.mjs +204 -8
- package/scripts/one-resume.sh +31 -3
- package/scripts/pr-body.sh +335 -0
- package/scripts/preview-fd-check.sh +289 -0
- package/scripts/redirect-lint.sh +169 -0
- package/scripts/release.sh +40 -6
- package/scripts/resume-lost-sessions.sh +68 -0
- package/scripts/shoot-pages.mjs +140 -0
- package/scripts/signal-meta-backfill.ts +451 -0
- package/scripts/signal-watch.sh +63 -6
- package/scripts/speed-cache-check.sh +12 -2
- package/scripts/sweep.sh +426 -0
- package/scripts/task-titles-dump.ts +101 -0
- package/scripts/test-cached.sh +47 -10
- package/scripts/test-lanes.sh +14 -0
- package/scripts/thread-name-backfill.ts +215 -0
- package/scripts/triage-shape-check.sh +149 -0
- package/scripts/tsc-cached.sh +155 -8
- package/scripts/typedb-flake-check.sh +3 -1
- package/scripts/urls-lint.sh +8 -0
- package/scripts/verify-board-doors.sh +80 -0
- package/scripts/verify-fast.sh +159 -6
- package/scripts/worktree-up.sh +21 -3
- package/skills/astro/SKILL.md +9 -3
- package/skills/astro/optimize-performance.md +3 -2
- package/skills/cloudflare/SKILL.md +3 -2
- package/skills/cloudflare-security-audit/AI-AND-LLM.md +83 -0
- package/skills/cloudflare-security-audit/ATTACK-CLASSES.md +130 -0
- package/skills/cloudflare-security-audit/CLIENT-SIDE.md +83 -0
- package/skills/cloudflare-security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
- package/skills/cloudflare-security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
- package/skills/cloudflare-security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
- package/skills/cloudflare-security-audit/HUNTING.md +251 -0
- package/skills/cloudflare-security-audit/LICENSE +21 -0
- package/skills/cloudflare-security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
- package/skills/cloudflare-security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
- package/skills/cloudflare-security-audit/PROVENANCE.md +78 -0
- package/skills/cloudflare-security-audit/RECONNAISSANCE.md +156 -0
- package/skills/cloudflare-security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
- package/skills/cloudflare-security-audit/SKILL.md +192 -0
- package/skills/cloudflare-security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
- package/skills/cloudflare-security-audit/VALIDATION-AND-REPORTING.md +186 -0
- package/skills/cloudflare-security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
- package/skills/cloudflare-security-audit/report-schema.json +461 -0
- package/skills/cloudflare-security-audit/validate-coverage-ledger.cjs +872 -0
- package/skills/cloudflare-security-audit/validate-coverage-ledger.test.cjs +740 -0
- package/skills/cloudflare-security-audit/validate-findings.cjs +773 -0
- package/skills/cloudflare-security-audit/validate-findings.test.cjs +652 -0
- package/skills/deploy/REFERENCE.md +713 -0
- package/skills/deploy/SKILL.md +140 -0
- package/skills/fleet-audit/SKILL.md +58 -0
- package/skills/meeting/SKILL.md +220 -0
- package/skills/planning/SKILL.md +256 -0
- package/skills/shadcn/SKILL.md +1 -1
- package/skills/typedb/SKILL.md +7 -0
- package/skills/voice/SKILL.md +94 -6
- package/skills/voice/corpus-check.sh +87 -0
- package/templates/template-agent.md +7 -1
- package/templates/template-feature.md +9 -0
- package/templates/template-todo.md +29 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# deploy-emit.sh — ONE deploy gate, onto the run/event stream every viewer reads.
|
|
3
|
+
#
|
|
4
|
+
# manifest: monorepo-only
|
|
5
|
+
# Same classification as deploy-record.sh, and for the same reason: deploy.sh
|
|
6
|
+
# is its only caller and deploy.sh does not ship. It DOES read a credential —
|
|
7
|
+
# GATEWAY_API_KEY through ONE_ENV_FILE (default one.ie/web/.dev.vars, plus the
|
|
8
|
+
# monorepo fallbacks do-signal.sh walks), honoured so that a future ship needs
|
|
9
|
+
# no change — and no credential is exit 3 BY NAME, never a quiet success and
|
|
10
|
+
# never a skipped gate that reads as a gate that ran.
|
|
11
|
+
#
|
|
12
|
+
# WHY IT EXISTS. `deploy.sh` emitted NOTHING — `grep -n signal deploy.sh` returns
|
|
13
|
+
# two comment lines and no call. A deploy's only trace was a committed JSON file,
|
|
14
|
+
# and `/deploy` says on its face what that costs: "nothing here animates a run
|
|
15
|
+
# that is happening now, because nothing here can know about one". The file stays
|
|
16
|
+
# (a page must render with no network); this makes the RUN a row as well.
|
|
17
|
+
#
|
|
18
|
+
# THE CALLER MUST NEVER LET THIS FAIL A DEPLOY. Every call site in deploy.sh ends
|
|
19
|
+
# in `|| true`, exactly as `deploy-record.sh` is called. This script is a
|
|
20
|
+
# reporter: a gate that passed while the network was down is still a gate that
|
|
21
|
+
# passed, and a deploy that goes red because its telemetry could not phone home
|
|
22
|
+
# is a worse failure than the one it was reporting. The other half of that rule
|
|
23
|
+
# lives in the mapper: a stage that never arrives is ABSENT, never a default
|
|
24
|
+
# frame, so a dropped emit degrades to a gap in the trace and never to a lie.
|
|
25
|
+
#
|
|
26
|
+
# USAGE
|
|
27
|
+
# deploy-emit.sh --run <id> --target <worker> [--stage <gate> --status <s>]
|
|
28
|
+
# [--verdict green|red] [--sha <sha>] [--door <text>]
|
|
29
|
+
# [--slug <ws>] [--env dev] [--reason <text>] [--wall-ms <n>]
|
|
30
|
+
# [--detail k=v]... [--dry-run]
|
|
31
|
+
# deploy-emit.sh --self-test # local red proofs, no network
|
|
32
|
+
#
|
|
33
|
+
# stage : tree credentials typecheck tests build smoke approval migrations
|
|
34
|
+
# ship health (DEPLOY_SPINE_STEPS)
|
|
35
|
+
# status : start | ok | fail (DeployStageStatus)
|
|
36
|
+
# verdict: green | red — the FINAL call, and the only thing that closes the
|
|
37
|
+
# run. Separate from the stages because `--gates-only` is a complete,
|
|
38
|
+
# successful run that never reaches ship or health; closing on the
|
|
39
|
+
# last stage would leave every one of those runs open forever.
|
|
40
|
+
#
|
|
41
|
+
# --run IS HALF THE RUN KEY, and the sha cannot serve. On 2026-09-06 the same sha
|
|
42
|
+
# (af3ba5919) ran gates-only, went red, went green, then shipped — four runs.
|
|
43
|
+
# Keyed on sha they would have interleaved into one. deploy.sh already mints a
|
|
44
|
+
# usable id for its own log name: deploy-20260906-225246.log.
|
|
45
|
+
#
|
|
46
|
+
# NEVER CALL THIS ON --dry-run. A run row for a deploy that did not happen is the
|
|
47
|
+
# same theater the page refuses to render. Enforced at the call site, because a
|
|
48
|
+
# script cannot know its caller's flags.
|
|
49
|
+
#
|
|
50
|
+
# EXITS 0 emitted (or --dry-run printed) · 2 bad arguments · 3 no credential
|
|
51
|
+
# 4 the receiver refused — its own words on stderr · 5 jq missing
|
|
52
|
+
#
|
|
53
|
+
# THE REFUSAL IS THE WHOLE POINT — measured against prod 2026-09-06:
|
|
54
|
+
# unknown receiver → HTTP 200 {"outcome":"dissolved","reason":"unknown_receiver"}
|
|
55
|
+
# forbidden call → HTTP 200 {"outcome":"result","result":{"ok":false,...}}
|
|
56
|
+
# A caller reading curl's exit code, the HTTP status, or `outcome` alone records
|
|
57
|
+
# a gate that never landed. Success is ONE shape: .outcome == "result" AND
|
|
58
|
+
# .result.ok == true.
|
|
59
|
+
#
|
|
60
|
+
# ENV DEPLOY_EMIT_URL default $ONE_API_URL, else https://one.ie
|
|
61
|
+
# DEPLOY_EVENT_RECEIVER default deploy:event
|
|
62
|
+
# ONE_ENV_FILE / DO_ENV_FILE the credential file. Defaults to .dev.vars
|
|
63
|
+
# and the ladder tries it FIRST, mirroring do-board.sh:54-60: BOTH
|
|
64
|
+
# files carry a GATEWAY_API_KEY and PROD REFUSES the one in .env. A
|
|
65
|
+
# refused key is exit 4 — but deploy.sh backgrounds every _emit with
|
|
66
|
+
# both streams discarded and `|| true`, so the whole run would have
|
|
67
|
+
# reported nothing while recording nothing. Reordering the ladder
|
|
68
|
+
# alone would not have fixed it: the default above lands on a file
|
|
69
|
+
# before the ladder is ever consulted.
|
|
70
|
+
# DEPLOY_EMIT_SKIP_FALLBACKS=1 ONE_ENV_FILE is the WHOLE ladder. Without it
|
|
71
|
+
# the no-credential state is unreachable from inside the monorepo, so
|
|
72
|
+
# exit 3 could not be driven red. Same reason and shape as
|
|
73
|
+
# FACTORY_EMIT_SKIP_FALLBACKS / DO_SIGNAL_SKIP_FALLBACKS.
|
|
74
|
+
set -uo pipefail
|
|
75
|
+
|
|
76
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
77
|
+
# .dev.vars, NOT .env — prod refuses the .env key (recorded trap; do-board.sh:56
|
|
78
|
+
# pins the same file for the same reason). Relative, as it has always been: from
|
|
79
|
+
# a subdirectory this misses and the ladder below — which now also leads with
|
|
80
|
+
# .dev.vars — picks it up by absolute path.
|
|
81
|
+
ONE_ENV_FILE="${ONE_ENV_FILE:-${DO_ENV_FILE:-one.ie/web/.dev.vars}}"
|
|
82
|
+
URL="${DEPLOY_EMIT_URL:-${ONE_API_URL:-https://one.ie}}"
|
|
83
|
+
RECEIVER="${DEPLOY_EVENT_RECEIVER:-deploy:event}"
|
|
84
|
+
TIMEOUT="${DEPLOY_EMIT_TIMEOUT:-8}"
|
|
85
|
+
|
|
86
|
+
die() { printf 'deploy-emit: %s\n' "$1" >&2; exit "$2"; }
|
|
87
|
+
|
|
88
|
+
STAGES='tree credentials typecheck tests build smoke approval migrations ship health'
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# --self-test — every LOCAL refusal, driven for real. No network, no credential.
|
|
92
|
+
# Each refusal is asserted by its EXIT CODE, never by its message: a gate this
|
|
93
|
+
# script would have posted anyway is the frame nobody meant.
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
self_test() {
|
|
96
|
+
local me="${BASH_SOURCE[0]}" fails=0 rc out pl
|
|
97
|
+
_case() {
|
|
98
|
+
local label="$1" want="$2"; shift 2
|
|
99
|
+
out="$(ONE_ENV_FILE=/dev/null DEPLOY_EMIT_SKIP_FALLBACKS=1 bash "$me" "$@" 2>&1)"; rc=$?
|
|
100
|
+
if [ "$rc" = "$want" ]; then printf ' ok %-52s exit %s\n' "$label" "$rc"
|
|
101
|
+
else printf ' FAIL %-52s exit %s, wanted %s\n %s\n' "$label" "$rc" "$want" "$(printf '%s' "$out" | head -1)" >&2
|
|
102
|
+
fails=$((fails + 1)); fi
|
|
103
|
+
}
|
|
104
|
+
printf '== deploy-emit --self-test\n'
|
|
105
|
+
# THE broken shape this exists for: deployEventToRunEvents returns [] for a
|
|
106
|
+
# gate outside the ten, and an empty mapping posted anyway is a lie on the stream.
|
|
107
|
+
_case 'unknown stage is REFUSED before the wire' 2 --run r1 --target one-prod --stage lint --status ok
|
|
108
|
+
_case 'unknown status is REFUSED' 2 --run r1 --target one-prod --stage tests --status green
|
|
109
|
+
_case 'unknown verdict is REFUSED' 2 --run r1 --target one-prod --verdict amber
|
|
110
|
+
_case 'missing --run is REFUSED' 2 --target one-prod --stage tests --status ok
|
|
111
|
+
_case 'missing --target is REFUSED' 2 --run r1 --stage tests --status ok
|
|
112
|
+
# Half a stage frame is not a stage frame — the mapper returns [] for either half.
|
|
113
|
+
_case 'stage without status is REFUSED' 2 --run r1 --target one-prod --stage tests
|
|
114
|
+
_case 'status without stage is REFUSED' 2 --run r1 --target one-prod --status ok
|
|
115
|
+
# A payload that names no stage AND no verdict would upsert a run and say nothing.
|
|
116
|
+
_case 'neither stage nor verdict is REFUSED' 2 --run r1 --target one-prod
|
|
117
|
+
_case 'non-numeric --wall-ms is REFUSED' 2 --run r1 --target one-prod --stage tests --status ok --wall-ms fast
|
|
118
|
+
_case 'malformed --detail is REFUSED' 2 --run r1 --target one-prod --stage tests --status ok --detail nokv
|
|
119
|
+
_case 'unknown argument is REFUSED' 2 --run r1 --target one-prod --stage tests --status ok --wat
|
|
120
|
+
_case 'no credential is exit 3, not a quiet pass' 3 --run r1 --target one-prod --stage tests --status ok
|
|
121
|
+
_case '--dry-run needs no credential' 0 --run r1 --target one-prod --stage build --status ok --dry-run
|
|
122
|
+
_case 'verdict alone is a valid final call' 0 --run r1 --target one-prod --verdict green --dry-run
|
|
123
|
+
|
|
124
|
+
pl="$(ONE_ENV_FILE=/dev/null DEPLOY_EMIT_SKIP_FALLBACKS=1 bash "$me" \
|
|
125
|
+
--run 20260906-225246 --target one-prod --stage tests --status ok --sha af3ba5919 \
|
|
126
|
+
--door './deploy' --slug one --reason none --wall-ms 168000 \
|
|
127
|
+
--detail reused=true --detail waived=false --dry-run 2>/dev/null)"
|
|
128
|
+
if printf '%s' "$pl" | jq -e '.data.run == "20260906-225246" and .data.target == "one-prod"
|
|
129
|
+
and .data.stage == "tests" and .data.status == "ok" and .data.sha == "af3ba5919"
|
|
130
|
+
and .data.slug == "one" and .data.wallMs == 168000
|
|
131
|
+
and .data.detail.reused == true and .data.detail.waived == false' >/dev/null 2>&1; then
|
|
132
|
+
printf ' ok %-52s\n' 'dry-run payload carries every named field'
|
|
133
|
+
else
|
|
134
|
+
printf ' FAIL %-52s\n %s\n' 'dry-run payload is incomplete' "$pl" >&2; fails=$((fails + 1))
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# wallMs must be a NUMBER and booleans must be BOOLEANS — zod rejects a string
|
|
138
|
+
# for z.number(), so a quoted 168000 is a receiver refusal at deploy time
|
|
139
|
+
# rather than a test failure here.
|
|
140
|
+
if printf '%s' "$pl" | jq -e '(.data.wallMs | type) == "number"
|
|
141
|
+
and (.data.detail.reused | type) == "boolean"' >/dev/null 2>&1; then
|
|
142
|
+
printf ' ok %-52s\n' 'wallMs is a number, booleans are booleans'
|
|
143
|
+
else
|
|
144
|
+
printf ' FAIL %-52s\n %s\n' 'metric types would be refused by zod' "$pl" >&2; fails=$((fails + 1))
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# Optional fields ABSENT rather than empty — zod strips undeclared keys
|
|
148
|
+
# silently, and an empty `slug` is not the same as no slug.
|
|
149
|
+
pl="$(ONE_ENV_FILE=/dev/null DEPLOY_EMIT_SKIP_FALLBACKS=1 bash "$me" \
|
|
150
|
+
--run r --target one-prod --stage tree --status start --dry-run 2>/dev/null)"
|
|
151
|
+
if printf '%s' "$pl" | jq -e '(.data | has("slug") | not) and (.data | has("detail") | not)
|
|
152
|
+
and (.data | has("wallMs") | not) and (.data | has("verdict") | not)' >/dev/null 2>&1; then
|
|
153
|
+
printf ' ok %-52s\n' 'unset optional fields are absent, not empty'
|
|
154
|
+
else
|
|
155
|
+
printf ' FAIL %-52s\n %s\n' 'unset optional fields leaked into the payload' "$pl" >&2; fails=$((fails + 1))
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# Every stage in STAGES must be accepted — a typo here silently narrows the
|
|
159
|
+
# pipeline to the gates somebody remembered.
|
|
160
|
+
local st
|
|
161
|
+
for st in $STAGES; do
|
|
162
|
+
ONE_ENV_FILE=/dev/null DEPLOY_EMIT_SKIP_FALLBACKS=1 bash "$me" \
|
|
163
|
+
--run r --target t --stage "$st" --status ok --dry-run >/dev/null 2>&1 \
|
|
164
|
+
|| { printf ' FAIL %-52s\n' "stage '$st' rejected by its own script" >&2; fails=$((fails + 1)); }
|
|
165
|
+
done
|
|
166
|
+
printf ' ok %-52s\n' "all 10 DEPLOY_SPINE_STEPS accepted"
|
|
167
|
+
|
|
168
|
+
if [ "$fails" -eq 0 ]; then printf 'deploy-emit --self-test: PASS (17 checks)\n'; return 0; fi
|
|
169
|
+
printf 'deploy-emit --self-test: RED — %s check(s) failed\n' "$fails" >&2; return 1
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
RUN=""; TARGET=""; STAGE=""; STATUS=""; VERDICT=""; SHA=""; DOOR=""
|
|
173
|
+
SLUG=""; ENVW=""; REASON=""; WALLMS=""; DETAILS=""; DRY=0
|
|
174
|
+
while [ $# -gt 0 ]; do
|
|
175
|
+
case "$1" in
|
|
176
|
+
--run) RUN="${2:-}"; shift 2 ;;
|
|
177
|
+
--target) TARGET="${2:-}"; shift 2 ;;
|
|
178
|
+
--stage) STAGE="${2:-}"; shift 2 ;;
|
|
179
|
+
--status) STATUS="${2:-}"; shift 2 ;;
|
|
180
|
+
--verdict) VERDICT="${2:-}"; shift 2 ;;
|
|
181
|
+
--sha) SHA="${2:-}"; shift 2 ;;
|
|
182
|
+
--door) DOOR="${2:-}"; shift 2 ;;
|
|
183
|
+
--slug) SLUG="${2:-}"; shift 2 ;;
|
|
184
|
+
--env) ENVW="${2:-}"; shift 2 ;;
|
|
185
|
+
--reason) REASON="${2:-}"; shift 2 ;;
|
|
186
|
+
--wall-ms) WALLMS="${2:-}"; shift 2 ;;
|
|
187
|
+
--detail) DETAILS="${DETAILS}${DETAILS:+$'\n'}${2:-}"; shift 2 ;;
|
|
188
|
+
--dry-run) DRY=1; shift ;;
|
|
189
|
+
--self-test) command -v jq >/dev/null 2>&1 || die "jq not found" 5; self_test; exit $? ;;
|
|
190
|
+
-h|--help) sed -n '2,48p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
|
191
|
+
*) die "unknown argument: $1" 2 ;;
|
|
192
|
+
esac
|
|
193
|
+
done
|
|
194
|
+
|
|
195
|
+
command -v jq >/dev/null 2>&1 || die "jq not found — this script builds and reads JSON with it, and a hand-rolled parser is how a refusal becomes a pass" 5
|
|
196
|
+
[ -n "$RUN" ] || die "--run <id> is required — it is half the run key (deploy:<target>:<run>, event.ts deployRunKey). The sha cannot serve: one sha ships repeatedly and the attempts would interleave" 2
|
|
197
|
+
[ -n "$TARGET" ] || die "--target <worker> is required — the other half of the run key, so 'did pay go out' does not need disambiguating by timestamp" 2
|
|
198
|
+
|
|
199
|
+
# Half a stage frame is not a stage frame. The mapper returns [] for either half
|
|
200
|
+
# alone, so refusing here keeps the wire and the mapper saying the same thing.
|
|
201
|
+
if [ -n "$STAGE" ] || [ -n "$STATUS" ]; then
|
|
202
|
+
[ -n "$STAGE" ] || die "--status without --stage — deployEventToRunEvents returns [] for half a frame, and guessing the other half records a frame nobody meant" 2
|
|
203
|
+
[ -n "$STATUS" ] || die "--stage without --status — same reason" 2
|
|
204
|
+
case " $STAGES " in
|
|
205
|
+
*" $STAGE "*) ;;
|
|
206
|
+
*) die "--stage must be one of: $STAGES (got '$STAGE') — deployEventToRunEvents returns [] for anything else, and an empty mapping posted anyway is a frame nobody meant" 2 ;;
|
|
207
|
+
esac
|
|
208
|
+
case "$STATUS" in
|
|
209
|
+
start|ok|fail) ;;
|
|
210
|
+
*) die "--status must be start|ok|fail (got '$STATUS'). There is deliberately no 'skip': a gate that did not run must be ABSENT, and an unrun gate is never a pass" 2 ;;
|
|
211
|
+
esac
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
if [ -n "$VERDICT" ]; then
|
|
215
|
+
case "$VERDICT" in
|
|
216
|
+
green|red) ;;
|
|
217
|
+
*) die "--verdict must be green|red (got '$VERDICT')" 2 ;;
|
|
218
|
+
esac
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
[ -n "$STAGE" ] || [ -n "$VERDICT" ] || \
|
|
222
|
+
die "nothing to say: pass --stage/--status, or --verdict, or both. A payload naming neither would upsert a run and record no frame" 2
|
|
223
|
+
|
|
224
|
+
if [ -n "$WALLMS" ]; then
|
|
225
|
+
case "$WALLMS" in
|
|
226
|
+
''|*[!0-9]*) die "--wall-ms wants an integer of milliseconds (got '$WALLMS') — the receiver types it z.number() and would refuse a string at deploy time" 2 ;;
|
|
227
|
+
esac
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# k=v, with `true`/`false`/integers kept as JSON scalars. `reused` and `waived`
|
|
231
|
+
# are the two metrics this whole feature exists to carry, and both are booleans:
|
|
232
|
+
# quoting them would make `waived:"false"` truthy in every reader.
|
|
233
|
+
detail_json='{}'
|
|
234
|
+
if [ -n "$DETAILS" ]; then
|
|
235
|
+
while IFS= read -r kv; do
|
|
236
|
+
[ -n "$kv" ] || continue
|
|
237
|
+
case "$kv" in *=*) ;; *) die "--detail wants k=v, got: $kv" 2 ;; esac
|
|
238
|
+
k="${kv%%=*}"; v="${kv#*=}"
|
|
239
|
+
case "$v" in
|
|
240
|
+
true|false) detail_json="$(jq -n --argjson d "$detail_json" --arg k "$k" --argjson v "$v" '$d + {($k): $v}')" ;;
|
|
241
|
+
''|*[!0-9]*) detail_json="$(jq -n --argjson d "$detail_json" --arg k "$k" --arg v "$v" '$d + {($k): $v}')" ;;
|
|
242
|
+
*) detail_json="$(jq -n --argjson d "$detail_json" --arg k "$k" --argjson v "$v" '$d + {($k): $v}')" ;;
|
|
243
|
+
esac
|
|
244
|
+
[ -n "$detail_json" ] || die "could not build --detail object" 2
|
|
245
|
+
done <<EOF
|
|
246
|
+
$DETAILS
|
|
247
|
+
EOF
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
payload="$(jq -n \
|
|
251
|
+
--arg run "$RUN" --arg target "$TARGET" --arg stage "$STAGE" --arg status "$STATUS" \
|
|
252
|
+
--arg verdict "$VERDICT" --arg sha "$SHA" --arg door "$DOOR" --arg slug "$SLUG" \
|
|
253
|
+
--arg env "$ENVW" --arg reason "$REASON" --arg wallms "$WALLMS" \
|
|
254
|
+
--argjson detail "$detail_json" '
|
|
255
|
+
{data: ({run: $run, target: $target}
|
|
256
|
+
+ (if $stage != "" then {stage: $stage} else {} end)
|
|
257
|
+
+ (if $status != "" then {status: $status} else {} end)
|
|
258
|
+
+ (if $verdict != "" then {verdict: $verdict} else {} end)
|
|
259
|
+
+ (if $sha != "" then {sha: $sha} else {} end)
|
|
260
|
+
+ (if $door != "" then {door: $door} else {} end)
|
|
261
|
+
+ (if $slug != "" then {slug: $slug} else {} end)
|
|
262
|
+
+ (if $env != "" then {env: $env} else {} end)
|
|
263
|
+
+ (if $reason != "" then {reason: $reason} else {} end)
|
|
264
|
+
+ (if $wallms != "" then {wallMs: ($wallms | tonumber)} else {} end)
|
|
265
|
+
+ (if ($detail | length) > 0 then {detail: $detail} else {} end))}')" \
|
|
266
|
+
|| die "could not build the payload" 2
|
|
267
|
+
|
|
268
|
+
if [ "$DRY" = 1 ]; then printf '%s\n' "$payload"; exit 0; fi
|
|
269
|
+
|
|
270
|
+
# The key never reaches argv (ps) and never reaches stdout. --config, chmod 600,
|
|
271
|
+
# removed on every path. Same shape as factory-emit.sh and do-signal.sh.
|
|
272
|
+
_gateway_key() {
|
|
273
|
+
local f v
|
|
274
|
+
# .dev.vars BEFORE .env, both here and in the default above. Two changes, not
|
|
275
|
+
# one: whichever is consulted first must be the file prod accepts.
|
|
276
|
+
for f in "$ONE_ENV_FILE" "$ROOT/one.ie/web/.dev.vars" "$ROOT/one.ie/web/.env" "$ROOT/.env"; do
|
|
277
|
+
[ -f "$f" ] || continue
|
|
278
|
+
if [ "${DEPLOY_EMIT_SKIP_FALLBACKS:-0}" = "1" ] && [ "$f" != "$ONE_ENV_FILE" ]; then continue; fi
|
|
279
|
+
v="$(grep -E '^GATEWAY_API_KEY=' "$f" 2>/dev/null | head -1 | sed 's/^[^=]*=//;s/^"//;s/"$//')"
|
|
280
|
+
[ -n "$v" ] && { printf '%s' "$v"; return 0; }
|
|
281
|
+
done
|
|
282
|
+
printf ''
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
KEYVAL="$(_gateway_key)"
|
|
286
|
+
[ -n "$KEYVAL" ] || die "no GATEWAY_API_KEY in $ONE_ENV_FILE (or the monorepo fallbacks) — an unauthenticated emit is refused at the door and would look exactly like a gate that never happened" 3
|
|
287
|
+
|
|
288
|
+
cfg="$(mktemp)"; chmod 600 "$cfg"
|
|
289
|
+
trap 'rm -f "$cfg"' EXIT INT TERM
|
|
290
|
+
printf 'header = "Authorization: Bearer %s"\n' "$KEYVAL" > "$cfg"
|
|
291
|
+
|
|
292
|
+
resp="$(curl -sS -X POST "$URL/api/ask/$RECEIVER" \
|
|
293
|
+
-H 'content-type: application/json' --config "$cfg" \
|
|
294
|
+
--max-time "$TIMEOUT" -d "$payload" -w $'\n%{http_code}' 2>&1)"; rc=$?
|
|
295
|
+
rm -f "$cfg"; trap - EXIT INT TERM
|
|
296
|
+
|
|
297
|
+
code="$(printf '%s' "$resp" | tail -1)"
|
|
298
|
+
body="$(printf '%s' "$resp" | sed '$d')"
|
|
299
|
+
ok="$(printf '%s' "$body" | jq -r 'if (.outcome == "result" and (.result.ok == true)) then "yes" else "no" end' 2>/dev/null)"
|
|
300
|
+
|
|
301
|
+
if [ "$rc" -ne 0 ] || [ "$ok" != "yes" ]; then
|
|
302
|
+
why="$(printf '%s' "$body" | jq -r '.reason // .result.error // .error // .hint // empty' 2>/dev/null)"
|
|
303
|
+
[ -n "$why" ] || why="curl rc=$rc http=${code:-none} body=$(printf '%s' "$body" | head -c 200)"
|
|
304
|
+
printf 'deploy-emit: RED run=%s target=%s stage=%s%s http=%s — %s\n' \
|
|
305
|
+
"$RUN" "$TARGET" "${STAGE:-none}" "${VERDICT:+ verdict=$VERDICT}" "${code:-none}" "$why" >&2
|
|
306
|
+
exit 4
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
printf 'deploy-emit: ok run=%s target=%s stage=%s%s receiver=%s\n' \
|
|
310
|
+
"$RUN" "$TARGET" "${STAGE:-none}" "${VERDICT:+ verdict=$VERDICT}" "$RECEIVER"
|
|
311
|
+
exit 0
|
|
@@ -83,6 +83,153 @@ else
|
|
|
83
83
|
fi
|
|
84
84
|
rm -rf "$_mc"
|
|
85
85
|
fi
|
|
86
|
+
# --- the memo probe: a hit must never queue for a slot ----------------------
|
|
87
|
+
# deploy's vitest gate runs under gate-run.sh, so it TOOK A SLOT and only then
|
|
88
|
+
# asked the memo whether there was anything to run. Measured 2026-09-13: 300s
|
|
89
|
+
# queued, then both lanes reported cache HIT.
|
|
90
|
+
#
|
|
91
|
+
# The block is EXTRACTED VERBATIM from deploy.sh and driven against a fake root
|
|
92
|
+
# and a SANDBOX cache -- a copy here would prove only that the copy works. Every
|
|
93
|
+
# axis that could touch the real thing is sandboxed: TEST_CACHE_DIR (a planted
|
|
94
|
+
# stamp replayed by a real deploy is the worst thing this file could do) and
|
|
95
|
+
# GOVERN_DIR (the only direct evidence that no slot was taken). The last two
|
|
96
|
+
# assertions are that neither real directory gained a file.
|
|
97
|
+
_probe_block="$(awk '/^# >>> vitest-memo-probe/{f=1} f{print} f&&/^# <<< vitest-memo-probe/{exit}' "$DEPLOY")"
|
|
98
|
+
_report_block="$(awk '/elif grep -q .SKIPPED by the memo probe./{f=1} f{print} f&&/^ *ok "\$TESTS_REPORT"$/{exit}' "$DEPLOY")"
|
|
99
|
+
if [ -z "$_probe_block" ] || [ -z "$_report_block" ]; then
|
|
100
|
+
echo "FAIL: could not extract the memo-probe block(s) from deploy.sh — this checker is blind"; fails=$((fails+1))
|
|
101
|
+
else
|
|
102
|
+
_sbcache="$(mktemp -d)"; _sbgov="$(mktemp -d)"; _froot="$(mktemp -d)"
|
|
103
|
+
_real_cache="${TMPDIR:-/tmp}/one-test-cache"
|
|
104
|
+
K1="$(printf 'lane-pool' | shasum -a 256 | cut -d' ' -f1)"
|
|
105
|
+
K2="$(printf 'lane-typedb' | shasum -a 256 | cut -d' ' -f1)"
|
|
106
|
+
mkdir -p "$_froot/.claude/scripts"
|
|
107
|
+
# a stand-in for test-full.sh under TEST_CACHE_KEY_ONLY=1: two lanes, two keys,
|
|
108
|
+
# and the chatter test-lanes.sh prints around them.
|
|
109
|
+
{ echo 'echo "test-lanes: pool lane (8 forks) + typedb lane (19 files, serial) — concurrent"'
|
|
110
|
+
echo "echo $K1"; echo "echo $K2"; } > "$_froot/.claude/scripts/test-full.sh"
|
|
111
|
+
|
|
112
|
+
_drive_probe() { # <SKIP_TESTS> <want_astro> <ROOT> — prints a transcript
|
|
113
|
+
local logd; logd="$(mktemp -d)"
|
|
114
|
+
SKIP_TESTS_IN="$1" WANT_IN="$2" ROOT_IN="$3" LOGD_IN="$logd" \
|
|
115
|
+
TEST_CACHE_DIR="$_sbcache" GOVERN_DIR="$_sbgov" \
|
|
116
|
+
bash -c '
|
|
117
|
+
set -uo pipefail
|
|
118
|
+
say(){ printf "SAY %s\n" "$*"; }
|
|
119
|
+
ok(){ printf "OK %s\n" "$*"; }
|
|
120
|
+
_spine_stage(){ [ "$1" = vitest ] && echo tests; }
|
|
121
|
+
_emit(){ printf "EMIT %s\n" "$*"; }
|
|
122
|
+
# the stub records a heavy registration the way the real one does, so
|
|
123
|
+
# HEAVY=0 on the skip path is a fact and not an artefact of the stub.
|
|
124
|
+
gate_start_heavy(){ printf "GATE_STARTED %s\n" "$1"; HEAVY_IDX+=(1); }
|
|
125
|
+
ROOT="$ROOT_IN"; LOG_DIR="$LOGD_IN"; STAMP="probe"; DRY=0
|
|
126
|
+
SKIP_TESTS="$SKIP_TESTS_IN"; want_astro="$WANT_IN"
|
|
127
|
+
GATE_NAMES=(); GATE_PIDS=(); GATE_LOGS=(); GATE_RC=(); GATE_T=(); GATE_DUR=(); HEAVY_IDX=()
|
|
128
|
+
'"$_probe_block"'
|
|
129
|
+
printf "GATES=%s RC=%s HEAVY=%s\n" "${#GATE_NAMES[@]}" "${GATE_RC[0]:-none}" "${#HEAVY_IDX[@]}"
|
|
130
|
+
if [ "${#GATE_LOGS[@]}" -gt 0 ]; then sed "s/^/LOG /" "${GATE_LOGS[0]}"; fi
|
|
131
|
+
exit 0'
|
|
132
|
+
rm -rf "$logd"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# (a) every lane stamped ⇒ the gate is SKIPPED, counted, and passed
|
|
136
|
+
date -u +%Y-%m-%dT%H:%M:%SZ > "$_sbcache/one.ie_web-$K1.pass"
|
|
137
|
+
date -u +%Y-%m-%dT%H:%M:%SZ > "$_sbcache/one.ie_web-$K2.pass"
|
|
138
|
+
_p_hit="$(_drive_probe 0 1 "$_froot")"
|
|
139
|
+
case "$_p_hit" in
|
|
140
|
+
*GATE_STARTED*) echo "FAIL: every lane stamped and the gate still took a slot"; fails=$((fails+1)) ;;
|
|
141
|
+
*) echo "ok: every lane stamped — the gate is skipped, no slot taken" ;;
|
|
142
|
+
esac
|
|
143
|
+
case "$_p_hit" in
|
|
144
|
+
*"GATES=1 RC=0"*) echo "ok: the skipped gate is still COUNTED as a passed gate" ;;
|
|
145
|
+
*) echo "FAIL: a memo-skipped gate was not recorded as a passed gate: $_p_hit"; fails=$((fails+1)) ;;
|
|
146
|
+
esac
|
|
147
|
+
# The hazard a stubbed gate_start_heavy would hide: a placeholder pid in
|
|
148
|
+
# HEAVY_IDX makes the NEXT heavy gate `wait 0` (exit 127 — verified) and
|
|
149
|
+
# overwrite this gate's rc with 1. A gate with no process is not a heavy gate.
|
|
150
|
+
case "$_p_hit" in
|
|
151
|
+
*HEAVY=0*) echo "ok: the skipped gate is not in HEAVY_IDX (no wait on a non-child)" ;;
|
|
152
|
+
*) echo "FAIL: a process-less gate entered HEAVY_IDX — the next heavy gate will mark it FAILED"; fails=$((fails+1)) ;;
|
|
153
|
+
esac
|
|
154
|
+
# …and the report it produces must say REUSED, never "all pass", and carry the
|
|
155
|
+
# stamp's timestamp. Driven through deploy.sh's OWN report branch, extracted.
|
|
156
|
+
_skiplog="$(mktemp)"
|
|
157
|
+
printf '%s\n' "$_p_hit" | sed -n 's/^LOG //p' > "$_skiplog"
|
|
158
|
+
_rep="$( glog="$_skiplog" bash -c '
|
|
159
|
+
ok(){ printf "OK %s\n" "$*"; }; TESTS_REPORT=""
|
|
160
|
+
if false; then :
|
|
161
|
+
'"$_report_block"'
|
|
162
|
+
fi
|
|
163
|
+
printf "REPORT=%s\n" "$TESTS_REPORT"' )"
|
|
164
|
+
case "$_rep" in
|
|
165
|
+
*"REUSED — identical inputs already passed"*"already PASSED 20"*)
|
|
166
|
+
echo "ok: the skip reports REUSED with the stamp's timestamp" ;;
|
|
167
|
+
*) echo "FAIL: the skip did not report REUSED with a timestamp: $_rep"; fails=$((fails+1)) ;;
|
|
168
|
+
esac
|
|
169
|
+
case "$_rep" in
|
|
170
|
+
*"all pass"*) echo "FAIL: a memo hit was reported as 'all pass'"; fails=$((fails+1)) ;;
|
|
171
|
+
*) echo "ok: a reused pass is never reported as 'all pass'" ;;
|
|
172
|
+
esac
|
|
173
|
+
# (b) RED HALF — ONE lane unstamped ⇒ the gate RUNS. This is the whole reason
|
|
174
|
+
# the probe does not read test-lanes.sh's exit code: under
|
|
175
|
+
# TYPEDB_LANE_NONBLOCKING=1 that code is the POOL lane's alone, so a pool-only
|
|
176
|
+
# hit would skip a typedb lane that had never run.
|
|
177
|
+
rm -f "$_sbcache/one.ie_web-$K2.pass"
|
|
178
|
+
_p_miss="$(_drive_probe 0 1 "$_froot")"
|
|
179
|
+
case "$_p_miss" in
|
|
180
|
+
*GATE_STARTED*) echo "ok: one lane unstamped — the gate RUNS (pool-only hit cannot skip it)" ;;
|
|
181
|
+
*) echo "FAIL: a pool-only hit skipped the gate — the typedb lane would never run"; fails=$((fails+1)) ;;
|
|
182
|
+
esac
|
|
183
|
+
case "$_p_miss" in *"memo probe: MISS"*) echo "ok: the miss is named in the report" ;;
|
|
184
|
+
*) echo "FAIL: a miss ran the gate without saying why"; fails=$((fails+1)) ;; esac
|
|
185
|
+
# (c) RED HALF — a probe that cannot answer runs the gate. An unrun check is
|
|
186
|
+
# not a pass, and neither is an unanswerable one.
|
|
187
|
+
echo 'exit 1' > "$_froot/.claude/scripts/test-full.sh"
|
|
188
|
+
_p_unk="$(_drive_probe 0 1 "$_froot")"
|
|
189
|
+
case "$_p_unk" in
|
|
190
|
+
*GATE_STARTED*) echo "ok: a probe that cannot answer runs the gate" ;;
|
|
191
|
+
*) echo "FAIL: an unanswerable probe skipped the gate"; fails=$((fails+1)) ;;
|
|
192
|
+
esac
|
|
193
|
+
# (d) --skip-tests keeps its own meaning: no gate at all, even with every lane
|
|
194
|
+
# stamped. A skipped suite is not a reused one.
|
|
195
|
+
{ echo "echo $K1"; } > "$_froot/.claude/scripts/test-full.sh"
|
|
196
|
+
date -u +%Y-%m-%dT%H:%M:%SZ > "$_sbcache/one.ie_web-$K1.pass"
|
|
197
|
+
_p_skip="$(_drive_probe 1 1 "$_froot")"
|
|
198
|
+
case "$_p_skip" in
|
|
199
|
+
*GATE_STARTED*|*GATES=1*) echo "FAIL: --skip-tests registered a gate"; fails=$((fails+1)) ;;
|
|
200
|
+
*) echo "ok: --skip-tests still skips distinctly (no gate, no memo report)" ;;
|
|
201
|
+
esac
|
|
202
|
+
# the real test-full.sh must actually print a key per lane, or the probe above
|
|
203
|
+
# answers `2` forever and the change buys nothing. KEY_ONLY runs no vitest.
|
|
204
|
+
if [ -d "$ROOT/one.ie/web" ]; then
|
|
205
|
+
_rkeys="$( cd "$ROOT" && TEST_CACHE_KEY_ONLY=1 TEST_CACHE_DIR="$_sbcache" \
|
|
206
|
+
bash "$ROOT/.claude/scripts/test-full.sh" 2>/dev/null | grep -Ec '^[0-9a-f]{64}$' )"
|
|
207
|
+
if [ "${_rkeys:-0}" -ge 2 ]; then echo "ok: the real full suite prints $_rkeys lane keys (probe can answer)"
|
|
208
|
+
else echo "FAIL: the real full suite printed ${_rkeys:-0} lane key(s) — the probe cannot answer"; fails=$((fails+1)); fi
|
|
209
|
+
else
|
|
210
|
+
echo "n/a: no one.ie/web here — the real lane-key probe cannot run (NOT a pass)"
|
|
211
|
+
fi
|
|
212
|
+
# NOTHING MAY LEAK — and the assertion is by CONTENT, not by count. release.sh's
|
|
213
|
+
# self-test compares file counts either side (release.sh:304-307); here that is a
|
|
214
|
+
# FALSE RED waiting to happen, because both directories are machine-wide and
|
|
215
|
+
# every other session on the box writes to them. Measured while writing this:
|
|
216
|
+
# the real governor dir went 161 -> 162 -> 163 inside one minute under two
|
|
217
|
+
# neighbouring `gate-run.sh verify-fast` processes, with nothing here running.
|
|
218
|
+
# So ask the precise question instead: did a PLANTED stamp reach the real cache?
|
|
219
|
+
_leaked=0
|
|
220
|
+
for _k in "$K1" "$K2"; do
|
|
221
|
+
for _f in "$_real_cache"/*"$_k".pass; do [ -f "$_f" ] && _leaked=$((_leaked+1)); done
|
|
222
|
+
done
|
|
223
|
+
if [ "$_leaked" -eq 0 ]; then echo "ok: no planted stamp leaked into the real cache"
|
|
224
|
+
else echo "FAIL: $_leaked planted stamp(s) reached the REAL cache — a future deploy could replay them"; fails=$((fails+1)); fi
|
|
225
|
+
# …and the slot. GOVERN_DIR is exported into the driven block, so a gate-run
|
|
226
|
+
# underneath it would leave its lock/slot HERE. An empty sandbox is the direct
|
|
227
|
+
# evidence that the hit path took no slot — the whole point of the change.
|
|
228
|
+
if [ -z "$(ls "$_sbgov" 2>/dev/null)" ]; then echo "ok: the probe took no governor slot at all (sandbox GOVERN_DIR empty)"
|
|
229
|
+
else echo "FAIL: the probe touched its governor dir — it is taking a slot"; fails=$((fails+1)); fi
|
|
230
|
+
rm -rf "$_sbcache" "$_sbgov" "$_froot" "$_skiplog"
|
|
231
|
+
fi
|
|
232
|
+
|
|
86
233
|
# --- the deferred-pin ledger: driven, not grepped ---------------------------
|
|
87
234
|
# Extract deploy.sh's debt block verbatim and run it with stubbed reporters. The
|
|
88
235
|
# block is the last chance a deferred pin has to come due: verify-fast.sh settles
|
|
@@ -118,6 +265,14 @@ else
|
|
|
118
265
|
if _drive_debt "pins deferred at cycle C3" 0 "Tests 10780 passed"; then
|
|
119
266
|
echo "ok: a green full suite settles the debt and the deploy continues"
|
|
120
267
|
else echo "FAIL: a green full suite did not settle the debt"; fails=$((fails+1)); fi
|
|
268
|
+
# …and a suite the MEMO PROBE reused pays the debt exactly as a cache HIT does.
|
|
269
|
+
# The probe skips a gate whose lanes are all green stamps, so the pins ran when
|
|
270
|
+
# those stamps were minted — but the debt block reads TESTS_REPORT, so the new
|
|
271
|
+
# wording has to keep settling it or a memo hit would refuse every deploy.
|
|
272
|
+
if _drive_debt "pins deferred at cycle C3" 0 \
|
|
273
|
+
"full suite REUSED — identical inputs already passed (memo probe, no slot taken: already PASSED 2026-09-13T00:00:00Z)"; then
|
|
274
|
+
echo "ok: a memo-probe REUSED suite settles the debt"
|
|
275
|
+
else echo "FAIL: a memo-probe REUSED suite did not settle the debt"; fails=$((fails+1)); fi
|
|
121
276
|
# and an EMPTY ledger must never block anything
|
|
122
277
|
if _drive_debt "" 1 "skipped"; then echo "ok: no debt, no refusal"
|
|
123
278
|
else echo "FAIL: an empty ledger blocked the deploy"; fails=$((fails+1)); fi
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# deploy-ready.sh — say which branch the next deploy will take, BEFORE it takes it.
|
|
3
|
+
#
|
|
4
|
+
# manifest: monorepo-only
|
|
5
|
+
#
|
|
6
|
+
# WHY THIS EXISTS. deploy.sh prices its heavy gates in memory, not cores:
|
|
7
|
+
# vitest (~6GB) and the astro build (--max-old-space-size=8192) overlap only
|
|
8
|
+
# when `gate_mem_avail_mb` reports >= DEPLOY_HEAVY_NEED_GB (default 14) at the
|
|
9
|
+
# moment the gates start. deploy.sh's own comment carries the measurement:
|
|
10
|
+
#
|
|
11
|
+
# overlapped 176s
|
|
12
|
+
# serialised 1145s
|
|
13
|
+
#
|
|
14
|
+
# That is a 6.5x swing decided by ONE sample taken once, at gate-start, and
|
|
15
|
+
# nothing in the pipeline records which way it went. Two runs on a byte-identical
|
|
16
|
+
# tree can differ by 6.5x and the ledger cannot say why.
|
|
17
|
+
#
|
|
18
|
+
# So this is not a new gate and it changes no behaviour. It reads the same
|
|
19
|
+
# probe deploy.sh reads, through the same function, and prints the branch the
|
|
20
|
+
# deploy WOULD take. It turns a coin-flip into a decision.
|
|
21
|
+
#
|
|
22
|
+
# THE PROBE IS THE HONEST ONE. `memory_pressure`'s "free percentage" counts file
|
|
23
|
+
# cache, purgeable and compressed pages: measured 2026-09-03 it read 74% on a box
|
|
24
|
+
# with 1.3% actually free and 9.4GB in swap. gate_mem_avail_mb is what
|
|
25
|
+
# lib/govern.sh and deploy.sh both use; this reads it rather than inventing a
|
|
26
|
+
# second definition that drifts.
|
|
27
|
+
#
|
|
28
|
+
# Exit codes, so a caller can branch on it:
|
|
29
|
+
# 0 the fast branch — heavy gates will overlap
|
|
30
|
+
# 1 the slow branch — they will serialise (deploy still WORKS, just ~6.5x)
|
|
31
|
+
# 2 the probe could not read memory
|
|
32
|
+
#
|
|
33
|
+
# It never refuses and never kills anything: what to do about a short box is the
|
|
34
|
+
# operator's call, and a pre-flight that took that call would be a gate.
|
|
35
|
+
set -uo pipefail
|
|
36
|
+
|
|
37
|
+
ROOT="${DEPLOY_READY_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
38
|
+
. "$ROOT/.claude/scripts/lib/govern.sh"
|
|
39
|
+
|
|
40
|
+
NEED_GB="${DEPLOY_HEAVY_NEED_GB:-14}"
|
|
41
|
+
|
|
42
|
+
avail_mb="$(gate_mem_avail_mb 2>/dev/null || echo "")"
|
|
43
|
+
if [ -z "$avail_mb" ]; then
|
|
44
|
+
echo " RED memory probe unreadable — deploy.sh will assume 999GB and OVERLAP"
|
|
45
|
+
echo " (a broken sensor must never silently serialise the pipeline, so"
|
|
46
|
+
echo " the fallback is the fast branch. On a short box that pages.)"
|
|
47
|
+
exit 2
|
|
48
|
+
fi
|
|
49
|
+
free_gb=$(( avail_mb / 1024 ))
|
|
50
|
+
|
|
51
|
+
swap="$(sysctl -n vm.swapusage 2>/dev/null || echo '')"
|
|
52
|
+
load="$(uptime | sed 's/.*load averages*: //')"
|
|
53
|
+
cores="$(sysctl -n hw.ncpu 2>/dev/null || echo '?')"
|
|
54
|
+
|
|
55
|
+
printf '\n deploy-ready — will the heavy gates overlap?\n\n'
|
|
56
|
+
printf ' free memory %sGB (need >= %sGB)\n' "$free_gb" "$NEED_GB"
|
|
57
|
+
printf ' swap %s\n' "$swap"
|
|
58
|
+
printf ' load %s on %s cores\n' "$load" "$cores"
|
|
59
|
+
|
|
60
|
+
# What is holding memory right now, so "unthrash" is actionable rather than advice.
|
|
61
|
+
printf '\n top memory holders:\n'
|
|
62
|
+
ps -Ao rss=,comm=,args= | sort -rn | head -6 | while read -r rss comm rest; do
|
|
63
|
+
printf ' %6s MB %s\n' "$((rss/1024))" "$(printf '%.58s' "$comm $rest")"
|
|
64
|
+
done
|
|
65
|
+
|
|
66
|
+
printf '\n'
|
|
67
|
+
if (( free_gb >= NEED_GB )); then
|
|
68
|
+
printf ' ok OVERLAP — vitest and build run together (measured ~176s)\n\n'
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
printf ' RED SERIALISE — vitest then build, one after the other\n'
|
|
72
|
+
printf ' measured 1145s vs 176s: this is the 6.5x branch\n'
|
|
73
|
+
printf ' short by %sGB. Free memory before shipping — browsers and\n' "$(( NEED_GB - free_gb ))"
|
|
74
|
+
printf ' sessions cost RAM; worktrees cost only disk, so sweeping\n'
|
|
75
|
+
printf ' trees is hygiene, not the fix.\n\n'
|
|
76
|
+
printf ' Do NOT set DEPLOY_HEAVY_PARALLEL=1 to force the fast branch:\n'
|
|
77
|
+
printf ' overlapping ~7GB gates on a short box is what measured 1145s.\n\n'
|
|
78
|
+
exit 1
|