@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,298 @@
|
|
|
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/.env, 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
|
|
63
|
+
# DEPLOY_EMIT_SKIP_FALLBACKS=1 ONE_ENV_FILE is the WHOLE ladder. Without it
|
|
64
|
+
# the no-credential state is unreachable from inside the monorepo, so
|
|
65
|
+
# exit 3 could not be driven red. Same reason and shape as
|
|
66
|
+
# FACTORY_EMIT_SKIP_FALLBACKS / DO_SIGNAL_SKIP_FALLBACKS.
|
|
67
|
+
set -uo pipefail
|
|
68
|
+
|
|
69
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
70
|
+
ONE_ENV_FILE="${ONE_ENV_FILE:-${DO_ENV_FILE:-one.ie/web/.env}}"
|
|
71
|
+
URL="${DEPLOY_EMIT_URL:-${ONE_API_URL:-https://one.ie}}"
|
|
72
|
+
RECEIVER="${DEPLOY_EVENT_RECEIVER:-deploy:event}"
|
|
73
|
+
TIMEOUT="${DEPLOY_EMIT_TIMEOUT:-8}"
|
|
74
|
+
|
|
75
|
+
die() { printf 'deploy-emit: %s\n' "$1" >&2; exit "$2"; }
|
|
76
|
+
|
|
77
|
+
STAGES='tree credentials typecheck tests build smoke approval migrations ship health'
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# --self-test — every LOCAL refusal, driven for real. No network, no credential.
|
|
81
|
+
# Each refusal is asserted by its EXIT CODE, never by its message: a gate this
|
|
82
|
+
# script would have posted anyway is the frame nobody meant.
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
self_test() {
|
|
85
|
+
local me="${BASH_SOURCE[0]}" fails=0 rc out pl
|
|
86
|
+
_case() {
|
|
87
|
+
local label="$1" want="$2"; shift 2
|
|
88
|
+
out="$(ONE_ENV_FILE=/dev/null DEPLOY_EMIT_SKIP_FALLBACKS=1 bash "$me" "$@" 2>&1)"; rc=$?
|
|
89
|
+
if [ "$rc" = "$want" ]; then printf ' ok %-52s exit %s\n' "$label" "$rc"
|
|
90
|
+
else printf ' FAIL %-52s exit %s, wanted %s\n %s\n' "$label" "$rc" "$want" "$(printf '%s' "$out" | head -1)" >&2
|
|
91
|
+
fails=$((fails + 1)); fi
|
|
92
|
+
}
|
|
93
|
+
printf '== deploy-emit --self-test\n'
|
|
94
|
+
# THE broken shape this exists for: deployEventToRunEvents returns [] for a
|
|
95
|
+
# gate outside the ten, and an empty mapping posted anyway is a lie on the stream.
|
|
96
|
+
_case 'unknown stage is REFUSED before the wire' 2 --run r1 --target one-prod --stage lint --status ok
|
|
97
|
+
_case 'unknown status is REFUSED' 2 --run r1 --target one-prod --stage tests --status green
|
|
98
|
+
_case 'unknown verdict is REFUSED' 2 --run r1 --target one-prod --verdict amber
|
|
99
|
+
_case 'missing --run is REFUSED' 2 --target one-prod --stage tests --status ok
|
|
100
|
+
_case 'missing --target is REFUSED' 2 --run r1 --stage tests --status ok
|
|
101
|
+
# Half a stage frame is not a stage frame — the mapper returns [] for either half.
|
|
102
|
+
_case 'stage without status is REFUSED' 2 --run r1 --target one-prod --stage tests
|
|
103
|
+
_case 'status without stage is REFUSED' 2 --run r1 --target one-prod --status ok
|
|
104
|
+
# A payload that names no stage AND no verdict would upsert a run and say nothing.
|
|
105
|
+
_case 'neither stage nor verdict is REFUSED' 2 --run r1 --target one-prod
|
|
106
|
+
_case 'non-numeric --wall-ms is REFUSED' 2 --run r1 --target one-prod --stage tests --status ok --wall-ms fast
|
|
107
|
+
_case 'malformed --detail is REFUSED' 2 --run r1 --target one-prod --stage tests --status ok --detail nokv
|
|
108
|
+
_case 'unknown argument is REFUSED' 2 --run r1 --target one-prod --stage tests --status ok --wat
|
|
109
|
+
_case 'no credential is exit 3, not a quiet pass' 3 --run r1 --target one-prod --stage tests --status ok
|
|
110
|
+
_case '--dry-run needs no credential' 0 --run r1 --target one-prod --stage build --status ok --dry-run
|
|
111
|
+
_case 'verdict alone is a valid final call' 0 --run r1 --target one-prod --verdict green --dry-run
|
|
112
|
+
|
|
113
|
+
pl="$(ONE_ENV_FILE=/dev/null DEPLOY_EMIT_SKIP_FALLBACKS=1 bash "$me" \
|
|
114
|
+
--run 20260906-225246 --target one-prod --stage tests --status ok --sha af3ba5919 \
|
|
115
|
+
--door './deploy' --slug one --reason none --wall-ms 168000 \
|
|
116
|
+
--detail reused=true --detail waived=false --dry-run 2>/dev/null)"
|
|
117
|
+
if printf '%s' "$pl" | jq -e '.data.run == "20260906-225246" and .data.target == "one-prod"
|
|
118
|
+
and .data.stage == "tests" and .data.status == "ok" and .data.sha == "af3ba5919"
|
|
119
|
+
and .data.slug == "one" and .data.wallMs == 168000
|
|
120
|
+
and .data.detail.reused == true and .data.detail.waived == false' >/dev/null 2>&1; then
|
|
121
|
+
printf ' ok %-52s\n' 'dry-run payload carries every named field'
|
|
122
|
+
else
|
|
123
|
+
printf ' FAIL %-52s\n %s\n' 'dry-run payload is incomplete' "$pl" >&2; fails=$((fails + 1))
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# wallMs must be a NUMBER and booleans must be BOOLEANS — zod rejects a string
|
|
127
|
+
# for z.number(), so a quoted 168000 is a receiver refusal at deploy time
|
|
128
|
+
# rather than a test failure here.
|
|
129
|
+
if printf '%s' "$pl" | jq -e '(.data.wallMs | type) == "number"
|
|
130
|
+
and (.data.detail.reused | type) == "boolean"' >/dev/null 2>&1; then
|
|
131
|
+
printf ' ok %-52s\n' 'wallMs is a number, booleans are booleans'
|
|
132
|
+
else
|
|
133
|
+
printf ' FAIL %-52s\n %s\n' 'metric types would be refused by zod' "$pl" >&2; fails=$((fails + 1))
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# Optional fields ABSENT rather than empty — zod strips undeclared keys
|
|
137
|
+
# silently, and an empty `slug` is not the same as no slug.
|
|
138
|
+
pl="$(ONE_ENV_FILE=/dev/null DEPLOY_EMIT_SKIP_FALLBACKS=1 bash "$me" \
|
|
139
|
+
--run r --target one-prod --stage tree --status start --dry-run 2>/dev/null)"
|
|
140
|
+
if printf '%s' "$pl" | jq -e '(.data | has("slug") | not) and (.data | has("detail") | not)
|
|
141
|
+
and (.data | has("wallMs") | not) and (.data | has("verdict") | not)' >/dev/null 2>&1; then
|
|
142
|
+
printf ' ok %-52s\n' 'unset optional fields are absent, not empty'
|
|
143
|
+
else
|
|
144
|
+
printf ' FAIL %-52s\n %s\n' 'unset optional fields leaked into the payload' "$pl" >&2; fails=$((fails + 1))
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# Every stage in STAGES must be accepted — a typo here silently narrows the
|
|
148
|
+
# pipeline to the gates somebody remembered.
|
|
149
|
+
local st
|
|
150
|
+
for st in $STAGES; do
|
|
151
|
+
ONE_ENV_FILE=/dev/null DEPLOY_EMIT_SKIP_FALLBACKS=1 bash "$me" \
|
|
152
|
+
--run r --target t --stage "$st" --status ok --dry-run >/dev/null 2>&1 \
|
|
153
|
+
|| { printf ' FAIL %-52s\n' "stage '$st' rejected by its own script" >&2; fails=$((fails + 1)); }
|
|
154
|
+
done
|
|
155
|
+
printf ' ok %-52s\n' "all 10 DEPLOY_SPINE_STEPS accepted"
|
|
156
|
+
|
|
157
|
+
if [ "$fails" -eq 0 ]; then printf 'deploy-emit --self-test: PASS (17 checks)\n'; return 0; fi
|
|
158
|
+
printf 'deploy-emit --self-test: RED — %s check(s) failed\n' "$fails" >&2; return 1
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
RUN=""; TARGET=""; STAGE=""; STATUS=""; VERDICT=""; SHA=""; DOOR=""
|
|
162
|
+
SLUG=""; ENVW=""; REASON=""; WALLMS=""; DETAILS=""; DRY=0
|
|
163
|
+
while [ $# -gt 0 ]; do
|
|
164
|
+
case "$1" in
|
|
165
|
+
--run) RUN="${2:-}"; shift 2 ;;
|
|
166
|
+
--target) TARGET="${2:-}"; shift 2 ;;
|
|
167
|
+
--stage) STAGE="${2:-}"; shift 2 ;;
|
|
168
|
+
--status) STATUS="${2:-}"; shift 2 ;;
|
|
169
|
+
--verdict) VERDICT="${2:-}"; shift 2 ;;
|
|
170
|
+
--sha) SHA="${2:-}"; shift 2 ;;
|
|
171
|
+
--door) DOOR="${2:-}"; shift 2 ;;
|
|
172
|
+
--slug) SLUG="${2:-}"; shift 2 ;;
|
|
173
|
+
--env) ENVW="${2:-}"; shift 2 ;;
|
|
174
|
+
--reason) REASON="${2:-}"; shift 2 ;;
|
|
175
|
+
--wall-ms) WALLMS="${2:-}"; shift 2 ;;
|
|
176
|
+
--detail) DETAILS="${DETAILS}${DETAILS:+$'\n'}${2:-}"; shift 2 ;;
|
|
177
|
+
--dry-run) DRY=1; shift ;;
|
|
178
|
+
--self-test) command -v jq >/dev/null 2>&1 || die "jq not found" 5; self_test; exit $? ;;
|
|
179
|
+
-h|--help) sed -n '2,48p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
|
180
|
+
*) die "unknown argument: $1" 2 ;;
|
|
181
|
+
esac
|
|
182
|
+
done
|
|
183
|
+
|
|
184
|
+
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
|
|
185
|
+
[ -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
|
|
186
|
+
[ -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
|
|
187
|
+
|
|
188
|
+
# Half a stage frame is not a stage frame. The mapper returns [] for either half
|
|
189
|
+
# alone, so refusing here keeps the wire and the mapper saying the same thing.
|
|
190
|
+
if [ -n "$STAGE" ] || [ -n "$STATUS" ]; then
|
|
191
|
+
[ -n "$STAGE" ] || die "--status without --stage — deployEventToRunEvents returns [] for half a frame, and guessing the other half records a frame nobody meant" 2
|
|
192
|
+
[ -n "$STATUS" ] || die "--stage without --status — same reason" 2
|
|
193
|
+
case " $STAGES " in
|
|
194
|
+
*" $STAGE "*) ;;
|
|
195
|
+
*) 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 ;;
|
|
196
|
+
esac
|
|
197
|
+
case "$STATUS" in
|
|
198
|
+
start|ok|fail) ;;
|
|
199
|
+
*) 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 ;;
|
|
200
|
+
esac
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
if [ -n "$VERDICT" ]; then
|
|
204
|
+
case "$VERDICT" in
|
|
205
|
+
green|red) ;;
|
|
206
|
+
*) die "--verdict must be green|red (got '$VERDICT')" 2 ;;
|
|
207
|
+
esac
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
[ -n "$STAGE" ] || [ -n "$VERDICT" ] || \
|
|
211
|
+
die "nothing to say: pass --stage/--status, or --verdict, or both. A payload naming neither would upsert a run and record no frame" 2
|
|
212
|
+
|
|
213
|
+
if [ -n "$WALLMS" ]; then
|
|
214
|
+
case "$WALLMS" in
|
|
215
|
+
''|*[!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 ;;
|
|
216
|
+
esac
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
# k=v, with `true`/`false`/integers kept as JSON scalars. `reused` and `waived`
|
|
220
|
+
# are the two metrics this whole feature exists to carry, and both are booleans:
|
|
221
|
+
# quoting them would make `waived:"false"` truthy in every reader.
|
|
222
|
+
detail_json='{}'
|
|
223
|
+
if [ -n "$DETAILS" ]; then
|
|
224
|
+
while IFS= read -r kv; do
|
|
225
|
+
[ -n "$kv" ] || continue
|
|
226
|
+
case "$kv" in *=*) ;; *) die "--detail wants k=v, got: $kv" 2 ;; esac
|
|
227
|
+
k="${kv%%=*}"; v="${kv#*=}"
|
|
228
|
+
case "$v" in
|
|
229
|
+
true|false) detail_json="$(jq -n --argjson d "$detail_json" --arg k "$k" --argjson v "$v" '$d + {($k): $v}')" ;;
|
|
230
|
+
''|*[!0-9]*) detail_json="$(jq -n --argjson d "$detail_json" --arg k "$k" --arg v "$v" '$d + {($k): $v}')" ;;
|
|
231
|
+
*) detail_json="$(jq -n --argjson d "$detail_json" --arg k "$k" --argjson v "$v" '$d + {($k): $v}')" ;;
|
|
232
|
+
esac
|
|
233
|
+
[ -n "$detail_json" ] || die "could not build --detail object" 2
|
|
234
|
+
done <<EOF
|
|
235
|
+
$DETAILS
|
|
236
|
+
EOF
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
payload="$(jq -n \
|
|
240
|
+
--arg run "$RUN" --arg target "$TARGET" --arg stage "$STAGE" --arg status "$STATUS" \
|
|
241
|
+
--arg verdict "$VERDICT" --arg sha "$SHA" --arg door "$DOOR" --arg slug "$SLUG" \
|
|
242
|
+
--arg env "$ENVW" --arg reason "$REASON" --arg wallms "$WALLMS" \
|
|
243
|
+
--argjson detail "$detail_json" '
|
|
244
|
+
{data: ({run: $run, target: $target}
|
|
245
|
+
+ (if $stage != "" then {stage: $stage} else {} end)
|
|
246
|
+
+ (if $status != "" then {status: $status} else {} end)
|
|
247
|
+
+ (if $verdict != "" then {verdict: $verdict} else {} end)
|
|
248
|
+
+ (if $sha != "" then {sha: $sha} else {} end)
|
|
249
|
+
+ (if $door != "" then {door: $door} else {} end)
|
|
250
|
+
+ (if $slug != "" then {slug: $slug} else {} end)
|
|
251
|
+
+ (if $env != "" then {env: $env} else {} end)
|
|
252
|
+
+ (if $reason != "" then {reason: $reason} else {} end)
|
|
253
|
+
+ (if $wallms != "" then {wallMs: ($wallms | tonumber)} else {} end)
|
|
254
|
+
+ (if ($detail | length) > 0 then {detail: $detail} else {} end))}')" \
|
|
255
|
+
|| die "could not build the payload" 2
|
|
256
|
+
|
|
257
|
+
if [ "$DRY" = 1 ]; then printf '%s\n' "$payload"; exit 0; fi
|
|
258
|
+
|
|
259
|
+
# The key never reaches argv (ps) and never reaches stdout. --config, chmod 600,
|
|
260
|
+
# removed on every path. Same shape as factory-emit.sh and do-signal.sh.
|
|
261
|
+
_gateway_key() {
|
|
262
|
+
local f v
|
|
263
|
+
for f in "$ONE_ENV_FILE" "$ROOT/one.ie/web/.env" "$ROOT/one.ie/web/.dev.vars" "$ROOT/.env"; do
|
|
264
|
+
[ -f "$f" ] || continue
|
|
265
|
+
if [ "${DEPLOY_EMIT_SKIP_FALLBACKS:-0}" = "1" ] && [ "$f" != "$ONE_ENV_FILE" ]; then continue; fi
|
|
266
|
+
v="$(grep -E '^GATEWAY_API_KEY=' "$f" 2>/dev/null | head -1 | sed 's/^[^=]*=//;s/^"//;s/"$//')"
|
|
267
|
+
[ -n "$v" ] && { printf '%s' "$v"; return 0; }
|
|
268
|
+
done
|
|
269
|
+
printf ''
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
KEYVAL="$(_gateway_key)"
|
|
273
|
+
[ -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
|
|
274
|
+
|
|
275
|
+
cfg="$(mktemp)"; chmod 600 "$cfg"
|
|
276
|
+
trap 'rm -f "$cfg"' EXIT INT TERM
|
|
277
|
+
printf 'header = "Authorization: Bearer %s"\n' "$KEYVAL" > "$cfg"
|
|
278
|
+
|
|
279
|
+
resp="$(curl -sS -X POST "$URL/api/ask/$RECEIVER" \
|
|
280
|
+
-H 'content-type: application/json' --config "$cfg" \
|
|
281
|
+
--max-time "$TIMEOUT" -d "$payload" -w $'\n%{http_code}' 2>&1)"; rc=$?
|
|
282
|
+
rm -f "$cfg"; trap - EXIT INT TERM
|
|
283
|
+
|
|
284
|
+
code="$(printf '%s' "$resp" | tail -1)"
|
|
285
|
+
body="$(printf '%s' "$resp" | sed '$d')"
|
|
286
|
+
ok="$(printf '%s' "$body" | jq -r 'if (.outcome == "result" and (.result.ok == true)) then "yes" else "no" end' 2>/dev/null)"
|
|
287
|
+
|
|
288
|
+
if [ "$rc" -ne 0 ] || [ "$ok" != "yes" ]; then
|
|
289
|
+
why="$(printf '%s' "$body" | jq -r '.reason // .result.error // .error // .hint // empty' 2>/dev/null)"
|
|
290
|
+
[ -n "$why" ] || why="curl rc=$rc http=${code:-none} body=$(printf '%s' "$body" | head -c 200)"
|
|
291
|
+
printf 'deploy-emit: RED run=%s target=%s stage=%s%s http=%s — %s\n' \
|
|
292
|
+
"$RUN" "$TARGET" "${STAGE:-none}" "${VERDICT:+ verdict=$VERDICT}" "${code:-none}" "$why" >&2
|
|
293
|
+
exit 4
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
printf 'deploy-emit: ok run=%s target=%s stage=%s%s receiver=%s\n' \
|
|
297
|
+
"$RUN" "$TARGET" "${STAGE:-none}" "${VERDICT:+ verdict=$VERDICT}" "$RECEIVER"
|
|
298
|
+
exit 0
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# deploy-gate-check.sh — red proofs for the deploy gate wrappers. Ships nothing,
|
|
3
|
+
# deploys nothing, touches no service. Exits non-zero on failure.
|
|
4
|
+
#
|
|
5
|
+
# manifest: monorepo-only
|
|
6
|
+
#
|
|
7
|
+
# WHY. deploy.sh grew a memoised test gate, a cached build gate, and a rule that
|
|
8
|
+
# an unpaid deferred-pin debt refuses a deploy. Each is a place a gate could
|
|
9
|
+
# quietly stop biting, and a wiring check that only greps for a string proves
|
|
10
|
+
# nothing about behaviour. So the deferred-pin block is EXTRACTED VERBATIM from
|
|
11
|
+
# deploy.sh and executed against stubs -- a copy of it here would prove only that
|
|
12
|
+
# the copy works.
|
|
13
|
+
set -uo pipefail
|
|
14
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
15
|
+
DEPLOY="$ROOT/.claude/scripts/deploy.sh"
|
|
16
|
+
fails=0
|
|
17
|
+
|
|
18
|
+
# --- deploy must NOT have re-wired tsc through the shared-lock cache --------
|
|
19
|
+
# It was wired that way for one commit and reverted on measurement: the five
|
|
20
|
+
# typechecks are 8s serial on a warm box and finish inside vitest's shadow, while
|
|
21
|
+
# tsc-cached.sh takes the same gate_lock do-reconcile.sh takes -- so a concurrent
|
|
22
|
+
# /do cycle could make the ship gate queue 900s and then go RED over nothing.
|
|
23
|
+
if grep -qE 'gate_start(_governed)? "tsc-\$\(echo "\$svc" \| tr / -\)" "\$svc" bunx tsc --noEmit' "$DEPLOY"; then
|
|
24
|
+
echo "ok: typechecks run bare (no shared lock on the ship path)"
|
|
25
|
+
else
|
|
26
|
+
echo "FAIL: the typecheck gate no longer runs bare — check the lock-contention note in deploy.sh"; fails=$((fails+1))
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# --- deploy must still call the cached build, not a bare `bun run build` -----
|
|
30
|
+
if grep -q 'gate_start_heavy build one.ie/web bash "\$ROOT/.claude/scripts/astro-build-cached.sh"' "$DEPLOY"; then
|
|
31
|
+
echo "ok: the build gate routes through astro-build-cached.sh"
|
|
32
|
+
else
|
|
33
|
+
echo "FAIL: the build gate no longer routes through astro-build-cached.sh"; fails=$((fails+1))
|
|
34
|
+
fi
|
|
35
|
+
# --- and the vitest gate must keep its pty, or it hangs on a log file --------
|
|
36
|
+
# The assertion names test-full.sh, NOT test-cached.sh, and that is the whole
|
|
37
|
+
# point of the edit that put it here. deploy.sh used to spell the vitest argv out
|
|
38
|
+
# itself and call test-cached.sh directly; test-full.sh was then created as the
|
|
39
|
+
# ONE definition of the full suite so deploy and /close could not present
|
|
40
|
+
# different argv to the memo and miss each other's stamps. This grep kept naming
|
|
41
|
+
# the old call, so it emitted "the vitest gate lost TEST_CACHE_PTY" over wiring
|
|
42
|
+
# that was correct -- a FALSE RED on a ship gate, which is the failure mode that
|
|
43
|
+
# gets a gate ignored. The pty is still set, twice: here, and per-lane at
|
|
44
|
+
# test-lanes.sh:102/107.
|
|
45
|
+
if grep -q 'TEST_CACHE_PTY=1 bash "\$ROOT/.claude/scripts/test-full.sh"' "$DEPLOY"; then
|
|
46
|
+
echo "ok: the vitest gate runs through test-full.sh with a pty"
|
|
47
|
+
else
|
|
48
|
+
echo "FAIL: the vitest gate lost TEST_CACHE_PTY — it will hang on a non-TTY stdout"; fails=$((fails+1))
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# --- and the memo behind that gate must actually MISS when the tree moves ----
|
|
52
|
+
# The line above is a wiring grep. This drives the real thing on one small
|
|
53
|
+
# selection: a cold MISS, then a HIT on identical inputs, then a MISS again after
|
|
54
|
+
# a source file the selection reads is edited. Without the third step a memo that
|
|
55
|
+
# always hit would look perfect -- and would hand deploy a green suite over a
|
|
56
|
+
# changed tree, which is the only way this optimisation can ship bad code.
|
|
57
|
+
_SEL="tests/unit/agents/subscribe-tags-parity.test.ts"
|
|
58
|
+
_SRC="one.ie/web/src/lib/in/spaces.ts"
|
|
59
|
+
if [ ! -d "$ROOT/one.ie/web/node_modules" ] || [ ! -f "$ROOT/$_SRC" ]; then
|
|
60
|
+
# An unrunnable check is n/a. Never ok: a worktree without node_modules cannot
|
|
61
|
+
# start vitest, and a startup crash exits non-zero for reasons that have
|
|
62
|
+
# nothing to do with the property under test.
|
|
63
|
+
echo "n/a: no one.ie/web/node_modules here — the memo drive cannot run (NOT a pass)"
|
|
64
|
+
else
|
|
65
|
+
_mc="$(mktemp -d)"; _memo() { TEST_CACHE_DIR="$_mc" bash "$ROOT/.claude/scripts/test-cached.sh" \
|
|
66
|
+
one.ie/web -- --reporter=dot "$_SEL" 2>&1; }
|
|
67
|
+
m1="$(_memo)"; r1=$?
|
|
68
|
+
m2="$(_memo)"; r2=$?
|
|
69
|
+
if [ "$r1" -ne 0 ] || [ "$r2" -ne 0 ]; then
|
|
70
|
+
echo "n/a: the probe selection did not pass, cannot measure the memo (NOT a pass)"
|
|
71
|
+
else
|
|
72
|
+
case "$m1" in *"cache HIT"*) echo "FAIL: a cold cache reported a HIT"; fails=$((fails+1));;
|
|
73
|
+
*) echo "ok: a cold memo MISSES and runs the tests";; esac
|
|
74
|
+
case "$m2" in *"cache HIT"*) echo "ok: identical inputs HIT the memo";;
|
|
75
|
+
*) echo "FAIL: identical inputs did not hit — the memo buys nothing"; fails=$((fails+1));; esac
|
|
76
|
+
# RED PROOF — move a source file the selection reads; the memo MUST miss.
|
|
77
|
+
cp "$ROOT/$_SRC" "$_mc/src.bak"
|
|
78
|
+
echo "// __deploy_gate_check_probe" >> "$ROOT/$_SRC"
|
|
79
|
+
m3="$(_memo)"
|
|
80
|
+
cp -f "$_mc/src.bak" "$ROOT/$_SRC"
|
|
81
|
+
case "$m3" in *"cache HIT"*) echo "FAIL: a CHANGED tree replayed a cached PASS"; fails=$((fails+1));;
|
|
82
|
+
*) echo "ok: a source edit invalidates the memo";; esac
|
|
83
|
+
fi
|
|
84
|
+
rm -rf "$_mc"
|
|
85
|
+
fi
|
|
86
|
+
# --- the deferred-pin ledger: driven, not grepped ---------------------------
|
|
87
|
+
# Extract deploy.sh's debt block verbatim and run it with stubbed reporters. The
|
|
88
|
+
# block is the last chance a deferred pin has to come due: verify-fast.sh settles
|
|
89
|
+
# the ledger only in its own FULL_VERIFY branch, and deploy sets FULL_VERIFY=1 but
|
|
90
|
+
# invokes the suite directly -- so before this block existed, a production deploy
|
|
91
|
+
# shipped straight over pins that no run had ever executed.
|
|
92
|
+
_debt_block="$(awk '/^DEBT_FILE=/{f=1} f{print} f&&/^fi$/{exit}' "$DEPLOY")"
|
|
93
|
+
if [ -z "$_debt_block" ]; then
|
|
94
|
+
echo "FAIL: could not extract the debt block from deploy.sh — this checker is blind"; fails=$((fails+1))
|
|
95
|
+
else
|
|
96
|
+
_drive_debt() { # <ledger-contents|""> <SKIP_TESTS> <TESTS_REPORT>
|
|
97
|
+
local led; led="$(mktemp)"
|
|
98
|
+
[ -n "$1" ] && printf '%s\n' "$1" > "$led" || : > "$led"
|
|
99
|
+
VERIFY_FAST_DEBT_FILE="$led" SKIP_TESTS="$2" TESTS_REPORT="$3" LOG=/dev/null \
|
|
100
|
+
bash -c '
|
|
101
|
+
say(){ printf "%s\n" "$*"; }; step(){ :; }; ok(){ printf "ok %s\n" "$*"; }
|
|
102
|
+
die(){ printf "DIE %s\n" "$*"; exit 1; }
|
|
103
|
+
'"$_debt_block"'
|
|
104
|
+
exit 0' >/dev/null 2>&1
|
|
105
|
+
local rc=$?; rm -f "$led"; return $rc
|
|
106
|
+
}
|
|
107
|
+
# RED PROOF — an outstanding debt with a SKIPPED suite must refuse the deploy.
|
|
108
|
+
if _drive_debt "pins deferred at cycle C3" 1 "skipped"; then
|
|
109
|
+
echo "FAIL: an unpaid pin debt did NOT refuse a --skip-tests deploy"; fails=$((fails+1))
|
|
110
|
+
else echo "ok: an unpaid pin debt refuses a test-skipping deploy"; fi
|
|
111
|
+
# RED PROOF — the same debt with no suite REPORT must refuse too (an unrun gate
|
|
112
|
+
# is not a passing gate, whatever the flags said).
|
|
113
|
+
if _drive_debt "pins deferred at cycle C3" 0 "skipped"; then
|
|
114
|
+
echo "FAIL: an unpaid debt passed with no suite result"; fails=$((fails+1))
|
|
115
|
+
else echo "ok: an unpaid debt with no suite result refuses"; fi
|
|
116
|
+
# GREEN half — a debt paid by a green full suite must let the deploy through,
|
|
117
|
+
# or the two proofs above are just a gate that always says no.
|
|
118
|
+
if _drive_debt "pins deferred at cycle C3" 0 "Tests 10780 passed"; then
|
|
119
|
+
echo "ok: a green full suite settles the debt and the deploy continues"
|
|
120
|
+
else echo "FAIL: a green full suite did not settle the debt"; fails=$((fails+1)); fi
|
|
121
|
+
# and an EMPTY ledger must never block anything
|
|
122
|
+
if _drive_debt "" 1 "skipped"; then echo "ok: no debt, no refusal"
|
|
123
|
+
else echo "FAIL: an empty ledger blocked the deploy"; fails=$((fails+1)); fi
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
echo ""
|
|
127
|
+
[ "$fails" -eq 0 ] && { echo "deploy-gate-check: PASS"; exit 0; }
|
|
128
|
+
echo "deploy-gate-check: FAILED ($fails)"; exit 1
|
|
@@ -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
|