@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,257 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# factory-close-check.sh — did the Close actually land on the board?
|
|
3
|
+
#
|
|
4
|
+
# manifest: needs-env
|
|
5
|
+
# Reads GATEWAY_API_KEY through ONE_ENV_FILE (default one.ie/web/.env, plus the
|
|
6
|
+
# monorepo fallbacks). A read it cannot make is exit 3 by name; it never
|
|
7
|
+
# answers MISSING on evidence it did not get.
|
|
8
|
+
#
|
|
9
|
+
# WHY IT EXISTS. On the first factory run the Close agent RETURNED
|
|
10
|
+
# {"status":"done"} while the board row stayed `picked`
|
|
11
|
+
# (task:01a07591e5c350a306c93933, workspace one, measured 2026-09-06). The
|
|
12
|
+
# executor's result JSON is a claim about an agent, not evidence about the
|
|
13
|
+
# substrate. This reads the row back and prints what the BOARD says, one line.
|
|
14
|
+
#
|
|
15
|
+
# WHY NOT /api/factory/board. Its `?slug=` is a PLAN selector from a closed list
|
|
16
|
+
# (board.ts PLAN_SLUGS = ['factory-do','factory']; planSlug coerces everything
|
|
17
|
+
# else to the default), so `?slug=one` answers for factory-do — measured. And the
|
|
18
|
+
# executor's rows are on neither plan board. readWork is
|
|
19
|
+
# `tasks:everywhere {workspace, limit:200}` anyway, and it deliberately never
|
|
20
|
+
# reads the CLOSED queue, which is the half a close-check needs. So: the same
|
|
21
|
+
# source, one hop earlier, both queues.
|
|
22
|
+
#
|
|
23
|
+
# USAGE factory-close-check.sh <tid> <workspace>
|
|
24
|
+
# factory-close-check.sh --self-test [workspace]
|
|
25
|
+
# PRINTS CLOSE tid=<tid> lane=<lane> verdict=<verdict> status=<status>
|
|
26
|
+
# EXITS 0 the row was read
|
|
27
|
+
# 2 both queues answered cleanly and completely, and no such row: MISSING
|
|
28
|
+
# 3 UNREADABLE — a refusal, a truncated answer, an unparseable count, or
|
|
29
|
+
# zero rows in both queues (subscriptions.ts answers `{ok:true,tasks:[]}`
|
|
30
|
+
# when env.DB is absent, so an outage and an empty workspace are the same
|
|
31
|
+
# bytes; that must never become "your task is not there")
|
|
32
|
+
# 4 bad arguments / jq missing
|
|
33
|
+
set -uo pipefail
|
|
34
|
+
|
|
35
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
36
|
+
ONE_ENV_FILE="${ONE_ENV_FILE:-${DO_ENV_FILE:-one.ie/web/.env}}"
|
|
37
|
+
URL="${FACTORY_EMIT_URL:-${ONE_API_URL:-https://one.ie}}"
|
|
38
|
+
TIMEOUT="${FACTORY_CLOSE_TIMEOUT:-15}"
|
|
39
|
+
# The server's own ceiling: subscriptions.ts is Math.min(data.limit ?? 50, 200),
|
|
40
|
+
# so 200 is the most any caller can ask for. `returned == 200` is therefore the
|
|
41
|
+
# only honest truncation signal — measured 2026-09-06, workspace one answered
|
|
42
|
+
# 100 open rows to a limit of 200 and 100 again to a limit of 500, which is the
|
|
43
|
+
# whole visible set, not a cap.
|
|
44
|
+
LIMIT=200
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
# The row reader. Mirrors toWorkRow (board.ts): the labelled NOTES lines ride
|
|
48
|
+
# first (WORK_LABELS), the `lane:`/`verdict:` TAGS are the fallback for rows a
|
|
49
|
+
# pre-fix close tagged. The label regex and the normalisation are copied from
|
|
50
|
+
# spec-gate.ts LABEL_RE / normaliseLabel — case- and space-insensitive, markdown
|
|
51
|
+
# furniture tolerated, smart quotes folded.
|
|
52
|
+
#
|
|
53
|
+
# ONE deviation, stated out loud: parseLabelledLines appends CONTINUATION lines
|
|
54
|
+
# to the open field; this takes the label line's own value only. For lane and
|
|
55
|
+
# verdict the executor writes single-line values (closePrompt), and a multi-line
|
|
56
|
+
# lane would print here as its first line rather than as a mangled one.
|
|
57
|
+
#
|
|
58
|
+
# `lane=unrecorded` / `verdict=unrun` are deliberate. board.ts says a NULL
|
|
59
|
+
# verdict "renders as unrun, which is not a pass"; an absent lane is not
|
|
60
|
+
# `lane=none`, which is a value the executor actually writes.
|
|
61
|
+
#
|
|
62
|
+
# It lives in a heredoc file, not inline, so the apostrophe inside the label
|
|
63
|
+
# character class never has to survive shell quoting.
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
_write_jq() {
|
|
66
|
+
cat > "$1" <<'JQ'
|
|
67
|
+
def lbls:
|
|
68
|
+
(.notes // "")
|
|
69
|
+
| split("\n")
|
|
70
|
+
| map(capture("^[ \t>*#-]*(?<k>[A-Za-z][A-Za-z'‘’ ]{2,24}?)[ \t]*:[ \t]*(?<v>.*)$")?)
|
|
71
|
+
| map(select(. != null))
|
|
72
|
+
| map({key: (.k | gsub("[‘’]"; "'") | ascii_downcase | gsub("\\s+"; " ") | gsub("^\\s+|\\s+$"; "")),
|
|
73
|
+
value: (.v | gsub("^\\s+|\\s+$"; ""))})
|
|
74
|
+
| from_entries;
|
|
75
|
+
def tagv($p): ([.tags[]? | select(startswith($p))] | .[0] // "") | if . == "" then "" else .[($p|length):] end;
|
|
76
|
+
def pick($a; $b; $d): if ($a // "") != "" then $a elif ($b // "") != "" then $b else $d end;
|
|
77
|
+
. as $t | (lbls) as $l
|
|
78
|
+
| "CLOSE tid=" + $t.id
|
|
79
|
+
+ " lane=" + ((pick($l["lane"]; ($t | tagv("lane:")); "unrecorded")) | gsub("\\s+"; "_"))
|
|
80
|
+
+ " verdict=" + ((pick($l["verdict"]; ($t | tagv("verdict:")); "unrun")) | gsub("\\s+"; "_"))
|
|
81
|
+
+ " status=" + (($t.status // "") | if . == "" then "unknown" else . end)
|
|
82
|
+
JQ
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_gateway_key() {
|
|
86
|
+
local f v
|
|
87
|
+
for f in "$ONE_ENV_FILE" "$ROOT/one.ie/web/.env" "$ROOT/one.ie/web/.dev.vars" "$ROOT/.env"; do
|
|
88
|
+
[ -f "$f" ] || continue
|
|
89
|
+
if [ "${FACTORY_EMIT_SKIP_FALLBACKS:-0}" = "1" ] && [ "$f" != "$ONE_ENV_FILE" ]; then continue; fi
|
|
90
|
+
v="$(grep -E '^GATEWAY_API_KEY=' "$f" 2>/dev/null | head -1 | sed 's/^[^=]*=//;s/^"//;s/"$//')"
|
|
91
|
+
[ -n "$v" ] && { printf '%s' "$v"; return 0; }
|
|
92
|
+
done
|
|
93
|
+
printf ''
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# --self-test — the local guards, the parser against the REAL close notes, and
|
|
98
|
+
# one network assertion: an impossible tid must be MISSING (exit 2), which is
|
|
99
|
+
# only reachable after a clean two-queue read.
|
|
100
|
+
#
|
|
101
|
+
# THE DISCIPLINE: an UNREADABLE (exit 3) here is a FAILED self-test, never a
|
|
102
|
+
# skip. A checker that goes green because it could not reach the substrate is
|
|
103
|
+
# the exact defect this script was written against.
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
self_test() {
|
|
106
|
+
local me="${BASH_SOURCE[0]}" ws="${1:-${FACTORY_CLOSE_SELFTEST_WS:-one}}" fails=0 rc out
|
|
107
|
+
printf '== factory-close-check --self-test (workspace %s)\n' "$ws"
|
|
108
|
+
|
|
109
|
+
_case() { # label, want-rc, argv...
|
|
110
|
+
local label="$1" want="$2"; shift 2
|
|
111
|
+
out="$(bash "$me" "$@" 2>&1)"; rc=$?
|
|
112
|
+
if [ "$rc" = "$want" ]; then printf ' ok %-52s exit %s\n' "$label" "$rc"
|
|
113
|
+
else printf ' FAIL %-52s exit %s, wanted %s\n %s\n' "$label" "$rc" "$want" "$(printf '%s' "$out" | head -1)" >&2; fails=$((fails+1)); fi
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_case 'no arguments is REFUSED' 4
|
|
117
|
+
_case 'uppercase workspace is REFUSED, not read' 4 task:x One
|
|
118
|
+
_case 'implausible tid is REFUSED' 4 'task:$(rm -rf /)' one
|
|
119
|
+
# Reachable only because SKIP_FALLBACKS exists; without it the monorepo's own
|
|
120
|
+
# .env answers and the no-credential branch is unreachable.
|
|
121
|
+
out="$(ONE_ENV_FILE=/dev/null FACTORY_EMIT_SKIP_FALLBACKS=1 bash "$me" task:x "$ws" 2>&1)"; rc=$?
|
|
122
|
+
if [ "$rc" = 3 ] && printf '%s' "$out" | grep -q 'reason=no-credential'; then
|
|
123
|
+
printf ' ok %-52s exit 3\n' 'no credential is UNREADABLE, not MISSING'
|
|
124
|
+
else
|
|
125
|
+
printf ' FAIL %-52s exit %s\n %s\n' 'no credential must be UNREADABLE' "$rc" "$out" >&2; fails=$((fails+1))
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# The parser, against the FIRST REAL CLOSE, byte-identical to the fixture in
|
|
129
|
+
# one.ie/web/src/lib/factory/close-parse.test.ts. Two parsers drift; this is
|
|
130
|
+
# the line that would go red when WORK_LABELS or LABEL_RE moves.
|
|
131
|
+
local jqf row got
|
|
132
|
+
jqf="$(mktemp)"; _write_jq "$jqf"
|
|
133
|
+
row='{"id":"task:01a07591e5c350a306c93933","status":"picked","tags":[],"notes":"Lane: none\nVerdict: red\nPreview: http://localhost:4325\nCommit: https://github.com/one-ie/one/commit/10a821f97\nHeld: red — see stage lines above\nwalk red (lane none, tier ?) · http://localhost:4325/components · commit 10a821f97 · 2026-09-06T07:18:36Z"}'
|
|
134
|
+
got="$(printf '%s' "$row" | jq -r -f "$jqf" 2>&1)"
|
|
135
|
+
rm -f "$jqf"
|
|
136
|
+
if [ "$got" = "CLOSE tid=task:01a07591e5c350a306c93933 lane=none verdict=red status=picked" ]; then
|
|
137
|
+
printf ' ok %-52s\n' 'the real close notes parse as the TS reader does'
|
|
138
|
+
else
|
|
139
|
+
printf ' FAIL %-52s\n got: %s\n' 'parser disagrees with close-parse.test.ts' "$got" >&2; fails=$((fails+1))
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# An absent verdict must read `unrun`, never a pass — board.ts's own rule.
|
|
143
|
+
jqf="$(mktemp)"; _write_jq "$jqf"
|
|
144
|
+
got="$(printf '%s' '{"id":"t","status":"picked","tags":[],"notes":"Preview: x"}' | jq -r -f "$jqf" 2>&1)"; rm -f "$jqf"
|
|
145
|
+
if [ "$got" = "CLOSE tid=t lane=unrecorded verdict=unrun status=picked" ]; then
|
|
146
|
+
printf ' ok %-52s\n' 'an absent verdict reads unrun, never a pass'
|
|
147
|
+
else
|
|
148
|
+
printf ' FAIL %-52s\n got: %s\n' 'absent verdict did not read unrun' "$got" >&2; fails=$((fails+1))
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
# THE NETWORK HALF. exit 2 is only reachable through a clean read of BOTH
|
|
152
|
+
# queues, so this asserts the read as much as the verdict.
|
|
153
|
+
#
|
|
154
|
+
# A LINKED WORKTREE HAS NO one.ie/web/.env — it is gitignored and never checked
|
|
155
|
+
# out. Say that by name and STILL FAIL: an unreachable substrate is a failed
|
|
156
|
+
# self-test, never a skip, exactly as the exit-3 branch above insists.
|
|
157
|
+
if [ -z "$(_gateway_key)" ]; then
|
|
158
|
+
printf ' FAIL %-52s\n' 'no credential — the read half could not be asserted' >&2
|
|
159
|
+
printf ' SKIP-IMPOSSIBLE: no GATEWAY_API_KEY via %s. This is not a broken script;\n' "$ONE_ENV_FILE" >&2
|
|
160
|
+
printf ' run --self-test from the MAIN tree, or export ONE_ENV_FILE=<main>/one.ie/web/.env\n' >&2
|
|
161
|
+
fails=$((fails + 1))
|
|
162
|
+
printf 'factory-close-check --self-test: RED — %s check(s) failed\n' "$fails" >&2; return 1
|
|
163
|
+
fi
|
|
164
|
+
out="$(bash "$me" 'task:0000000000000000000000000' "$ws" 2>&1)"; rc=$?
|
|
165
|
+
if [ "$rc" = 2 ] && printf '%s' "$out" | grep -q 'MISSING'; then
|
|
166
|
+
printf ' ok %-52s exit 2\n' 'an impossible tid is MISSING after a clean read'
|
|
167
|
+
elif [ "$rc" = 3 ]; then
|
|
168
|
+
printf ' FAIL %-52s exit 3 — the substrate was NOT read, so this test proved nothing\n %s\n' \
|
|
169
|
+
'impossible tid must be MISSING' "$out" >&2; fails=$((fails+1))
|
|
170
|
+
else
|
|
171
|
+
printf ' FAIL %-52s exit %s\n %s\n' 'impossible tid must be MISSING' "$rc" "$out" >&2; fails=$((fails+1))
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
if [ "$fails" -eq 0 ]; then printf 'factory-close-check --self-test: PASS (7 checks)\n'; return 0; fi
|
|
175
|
+
printf 'factory-close-check --self-test: RED — %s check(s) failed\n' "$fails" >&2; return 1
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
case "${1:-}" in
|
|
179
|
+
--self-test) command -v jq >/dev/null 2>&1 || { echo "factory-close-check: jq not found" >&2; exit 4; }
|
|
180
|
+
self_test "${2:-}"; exit $? ;;
|
|
181
|
+
-h|--help) sed -n '2,32p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
|
182
|
+
esac
|
|
183
|
+
|
|
184
|
+
TID="${1:-}"; WS="${2:-}"
|
|
185
|
+
[ -n "$TID" ] && [ -n "$WS" ] || { echo "usage: factory-close-check.sh <tid> <workspace>" >&2; exit 4; }
|
|
186
|
+
command -v jq >/dev/null 2>&1 || { echo "factory-close-check: jq not found" >&2; exit 4; }
|
|
187
|
+
# Validated, not escaped — the same shape readWork asserts. The workspace is
|
|
188
|
+
# interpolated into the JSON body below, so this is the injection guard too.
|
|
189
|
+
#
|
|
190
|
+
# ENUMERATED, never a RANGE. Measured on this box 2026-09-06 (bash 3.2,
|
|
191
|
+
# en_US.UTF-8): `case One in *[!a-z0-9-]*)` does NOT match — a glob range uses
|
|
192
|
+
# COLLATION order, which interleaves case, so `One` sailed past this very guard
|
|
193
|
+
# and reached the wire. Caught by --self-test's uppercase case. `-` is LAST in
|
|
194
|
+
# both sets on purpose; anywhere else it forms a range of its own.
|
|
195
|
+
WS_OK='abcdefghijklmnopqrstuvwxyz0123456789-'
|
|
196
|
+
TID_OK='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:_-'
|
|
197
|
+
case "$WS" in *[!$WS_OK]*|'') echo "factory-close-check: workspace must match ^[a-z0-9-]+$ (got '$WS')" >&2; exit 4 ;; esac
|
|
198
|
+
case "$TID" in *[!$TID_OK]*|'') echo "factory-close-check: implausible tid '$TID'" >&2; exit 4 ;; esac
|
|
199
|
+
|
|
200
|
+
KEYVAL="$(_gateway_key)"
|
|
201
|
+
[ -n "$KEYVAL" ] || { printf 'CLOSE tid=%s UNREADABLE reason=no-credential file=%s\n' "$TID" "$ONE_ENV_FILE"; exit 3; }
|
|
202
|
+
|
|
203
|
+
cfg="$(mktemp)"; chmod 600 "$cfg"
|
|
204
|
+
JQF="$(mktemp)"; _write_jq "$JQF"
|
|
205
|
+
trap 'rm -f "$cfg" "$JQF"' EXIT INT TERM
|
|
206
|
+
printf 'header = "Authorization: Bearer %s"\n' "$KEYVAL" > "$cfg"
|
|
207
|
+
|
|
208
|
+
read_queue() { # $1 = false | true (the CLOSED flag)
|
|
209
|
+
curl -sS -X POST "$URL/api/ask/tasks:everywhere" \
|
|
210
|
+
-H 'content-type: application/json' --config "$cfg" --max-time "$TIMEOUT" \
|
|
211
|
+
-d "{\"data\":{\"workspace\":\"$WS\",\"limit\":$LIMIT,\"closed\":$1}}" 2>&1
|
|
212
|
+
}
|
|
213
|
+
open_body="$(read_queue false)"; rc_open=$?
|
|
214
|
+
done_body="$(read_queue true)"; rc_done=$?
|
|
215
|
+
rm -f "$cfg"
|
|
216
|
+
|
|
217
|
+
# One shape is a read; everything else is UNREADABLE. Measured: a forbidden call
|
|
218
|
+
# is HTTP 200 {"outcome":"result","result":{"ok":false,"error":"forbidden: ..."}}.
|
|
219
|
+
check_read() { # $1 = body, $2 = rc, $3 = label
|
|
220
|
+
local why
|
|
221
|
+
if [ "$2" -ne 0 ]; then printf 'CLOSE tid=%s UNREADABLE reason=curl-rc-%s queue=%s\n' "$TID" "$2" "$3"; exit 3; fi
|
|
222
|
+
if [ "$(printf '%s' "$1" | jq -r 'if (.outcome == "result" and (.result.ok == true)) then "yes" else "no" end' 2>/dev/null)" != "yes" ]; then
|
|
223
|
+
why="$(printf '%s' "$1" | jq -r '.reason // .result.error // .result.reason // .error // .hint // empty' 2>/dev/null)"
|
|
224
|
+
[ -n "$why" ] || why="unparseable body: $(printf '%s' "$1" | head -c 160)"
|
|
225
|
+
printf 'CLOSE tid=%s UNREADABLE reason=%s queue=%s\n' "$TID" "$why" "$3"; exit 3
|
|
226
|
+
fi
|
|
227
|
+
}
|
|
228
|
+
check_read "$open_body" "$rc_open" open
|
|
229
|
+
check_read "$done_body" "$rc_done" closed
|
|
230
|
+
|
|
231
|
+
n_open="$(printf '%s' "$open_body" | jq '.result.tasks | length' 2>/dev/null)"
|
|
232
|
+
n_done="$(printf '%s' "$done_body" | jq '.result.tasks | length' 2>/dev/null)"
|
|
233
|
+
# A count that is not a number would make BOTH arithmetic tests below evaluate
|
|
234
|
+
# false and drop this straight through to MISSING — the house bug in my own
|
|
235
|
+
# arithmetic. It is UNREADABLE by name instead.
|
|
236
|
+
case "${n_open}|${n_done}" in
|
|
237
|
+
''|*[!0-9\|]*|'|'*|*'|') printf 'CLOSE tid=%s UNREADABLE reason=unparseable-count open=%s closed=%s\n' "$TID" "${n_open:-none}" "${n_done:-none}"; exit 3 ;;
|
|
238
|
+
esac
|
|
239
|
+
|
|
240
|
+
row="$(jq -s -c --arg tid "$TID" '[.[] | .result.tasks[]?] | map(select(.id == $tid)) | .[0] // empty' <<EOF
|
|
241
|
+
$open_body
|
|
242
|
+
$done_body
|
|
243
|
+
EOF
|
|
244
|
+
)"
|
|
245
|
+
|
|
246
|
+
if [ -z "$row" ]; then
|
|
247
|
+
if [ "$n_open" -ge "$LIMIT" ] || [ "$n_done" -ge "$LIMIT" ]; then
|
|
248
|
+
printf 'CLOSE tid=%s UNREADABLE reason=answer-truncated open=%s closed=%s limit=%s\n' "$TID" "$n_open" "$n_done" "$LIMIT"; exit 3
|
|
249
|
+
fi
|
|
250
|
+
if [ "$n_open" -eq 0 ] && [ "$n_done" -eq 0 ]; then
|
|
251
|
+
printf 'CLOSE tid=%s UNREADABLE reason=zero-rows-in-both-queues (subscriptions.ts answers ok:true with no rows when env.DB is absent — an outage wears this exact shape)\n' "$TID"; exit 3
|
|
252
|
+
fi
|
|
253
|
+
printf 'CLOSE tid=%s MISSING open=%s closed=%s\n' "$TID" "$n_open" "$n_done"; exit 2
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
printf '%s' "$row" | jq -r -f "$JQF"
|
|
257
|
+
exit 0
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# factory-emit.sh — ONE factory stage, onto the run/event stream every viewer reads.
|
|
3
|
+
#
|
|
4
|
+
# manifest: needs-env
|
|
5
|
+
# Reads GATEWAY_API_KEY through ONE_ENV_FILE (default one.ie/web/.env, plus the
|
|
6
|
+
# monorepo fallbacks do-signal.sh walks). No credential is exit 3 by name —
|
|
7
|
+
# never a quiet success, never a skipped stage that reads as a stage that ran.
|
|
8
|
+
#
|
|
9
|
+
# WHY IT EXISTS. .claude/workflows/factory-executor.js writes to the substrate
|
|
10
|
+
# exactly TWICE per job: tasks:claim at Claim and tasks:notes+tasks:status at
|
|
11
|
+
# Close. Ready · Build · Review · Prove emit nothing, so no surface can draw a
|
|
12
|
+
# job DURING. This is the call that closes that gap: one FactoryEventPayload
|
|
13
|
+
# (one.ie/web/src/lib/factory/event.ts) per stage boundary, posted to the
|
|
14
|
+
# factory:event receiver, which maps it onto the same run/event stream RunTrack
|
|
15
|
+
# already reads.
|
|
16
|
+
#
|
|
17
|
+
# USAGE
|
|
18
|
+
# factory-emit.sh --job <tid> --stage <stage> --status <start|ok|fail>
|
|
19
|
+
# [--slug <workspace>] [--model <rung>] [--reason <text>]
|
|
20
|
+
# [--detail k=v]... [--env dev] [--dry-run]
|
|
21
|
+
# factory-emit.sh --self-test # local red proofs, no network
|
|
22
|
+
#
|
|
23
|
+
# stage : ready | claim | build | review | prove | close (FACTORY_SPINE_STEPS)
|
|
24
|
+
# status: start | ok | fail (FactoryStageStatus)
|
|
25
|
+
#
|
|
26
|
+
# EXITS 0 emitted (or --dry-run printed) · 2 bad arguments · 3 no credential
|
|
27
|
+
# 4 the receiver refused — its own words on stderr · 5 jq missing
|
|
28
|
+
#
|
|
29
|
+
# THE REFUSAL IS THE WHOLE POINT — both measured against prod 2026-09-06:
|
|
30
|
+
# unknown receiver → HTTP 200 {"outcome":"dissolved","reason":"unknown_receiver",...}
|
|
31
|
+
# forbidden call → HTTP 200 {"outcome":"result","result":{"ok":false,"error":"forbidden: ..."}}
|
|
32
|
+
# A caller that reads curl's exit code, the HTTP status, or `outcome` alone
|
|
33
|
+
# records a stage that never landed. Success is ONE shape and nothing else:
|
|
34
|
+
# .outcome == "result" AND .result.ok == true
|
|
35
|
+
#
|
|
36
|
+
# ENV FACTORY_EMIT_URL default $ONE_API_URL, else https://one.ie
|
|
37
|
+
# FACTORY_EVENT_RECEIVER default factory:event
|
|
38
|
+
# ONE_ENV_FILE / DO_ENV_FILE the credential file
|
|
39
|
+
# FACTORY_EMIT_SKIP_FALLBACKS=1 ONE_ENV_FILE is the WHOLE ladder. Without
|
|
40
|
+
# it the no-credential state is unreachable from inside the monorepo
|
|
41
|
+
# (the fallbacks resolve to a tree that carries a real .env), so exit 3
|
|
42
|
+
# could not be driven red. Same reason, same shape, as
|
|
43
|
+
# DO_SIGNAL_SKIP_FALLBACKS in do-signal.sh.
|
|
44
|
+
set -uo pipefail
|
|
45
|
+
|
|
46
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
47
|
+
ONE_ENV_FILE="${ONE_ENV_FILE:-${DO_ENV_FILE:-one.ie/web/.env}}"
|
|
48
|
+
URL="${FACTORY_EMIT_URL:-${ONE_API_URL:-https://one.ie}}"
|
|
49
|
+
RECEIVER="${FACTORY_EVENT_RECEIVER:-factory:event}"
|
|
50
|
+
TIMEOUT="${FACTORY_EMIT_TIMEOUT:-8}"
|
|
51
|
+
|
|
52
|
+
die() { printf 'factory-emit: %s\n' "$1" >&2; exit "$2"; }
|
|
53
|
+
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# --self-test — every LOCAL refusal, driven for real. No network, no credential.
|
|
56
|
+
# A stage this script would have posted anyway is the frame nobody meant, so
|
|
57
|
+
# each refusal is asserted by its EXIT CODE, never by its message.
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
self_test() {
|
|
60
|
+
local me="${BASH_SOURCE[0]}" fails=0 rc out
|
|
61
|
+
_case() { # $1 label, $2 expected rc, rest: argv (run with no credential ladder)
|
|
62
|
+
local label="$1" want="$2"; shift 2
|
|
63
|
+
out="$(ONE_ENV_FILE=/dev/null FACTORY_EMIT_SKIP_FALLBACKS=1 bash "$me" "$@" 2>&1)"; rc=$?
|
|
64
|
+
if [ "$rc" = "$want" ]; then
|
|
65
|
+
printf ' ok %-46s exit %s\n' "$label" "$rc"
|
|
66
|
+
else
|
|
67
|
+
printf ' FAIL %-46s exit %s, wanted %s\n %s\n' "$label" "$rc" "$want" "$(printf '%s' "$out" | head -1)" >&2
|
|
68
|
+
fails=$((fails + 1))
|
|
69
|
+
fi
|
|
70
|
+
}
|
|
71
|
+
printf '== factory-emit --self-test\n'
|
|
72
|
+
# THE broken shape this test exists for: factoryEventToRunEvent returns null
|
|
73
|
+
# for a stage outside the six, and a null posted anyway is a lie on the stream.
|
|
74
|
+
_case 'unknown stage is REFUSED before the wire' 2 --job task:selftest --stage warm --status ok
|
|
75
|
+
_case 'unknown status is REFUSED' 2 --job task:selftest --stage build --status green
|
|
76
|
+
_case 'missing --job is REFUSED' 2 --stage build --status ok
|
|
77
|
+
_case 'missing --stage is REFUSED' 2 --job task:selftest --status ok
|
|
78
|
+
_case 'malformed --detail is REFUSED' 2 --job t --stage build --status ok --detail nokv
|
|
79
|
+
_case 'unknown argument is REFUSED' 2 --job t --stage build --status ok --wat
|
|
80
|
+
# No credential must be a NAMED exit 3, never a quiet skip that reads as a
|
|
81
|
+
# stage that ran. Reachable only because SKIP_FALLBACKS exists.
|
|
82
|
+
_case 'no credential is exit 3, not a quiet pass' 3 --job task:selftest --stage build --status start
|
|
83
|
+
# --dry-run precedes the credential read, so it stays 0 with no key at all.
|
|
84
|
+
_case '--dry-run needs no credential' 0 --job task:selftest --stage prove --status ok --dry-run
|
|
85
|
+
|
|
86
|
+
# And the dry-run payload must actually carry the three required fields plus
|
|
87
|
+
# an optional one, or a green emit could be an empty frame.
|
|
88
|
+
local pl
|
|
89
|
+
pl="$(ONE_ENV_FILE=/dev/null FACTORY_EMIT_SKIP_FALLBACKS=1 bash "$me" \
|
|
90
|
+
--job task:selftest --stage review --status fail --slug one --model opus \
|
|
91
|
+
--reason 'refuted' --detail lens=urls --dry-run 2>/dev/null)"
|
|
92
|
+
if printf '%s' "$pl" | jq -e '.data.job == "task:selftest" and .data.stage == "review"
|
|
93
|
+
and .data.status == "fail" and .data.slug == "one" and .data.model == "opus"
|
|
94
|
+
and .data.reason == "refuted" and .data.detail.lens == "urls"' >/dev/null 2>&1; then
|
|
95
|
+
printf ' ok %-46s\n' 'dry-run payload carries every named field'
|
|
96
|
+
else
|
|
97
|
+
printf ' FAIL %-46s\n %s\n' 'dry-run payload is incomplete' "$pl" >&2
|
|
98
|
+
fails=$((fails + 1))
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# The optional fields must be ABSENT rather than empty strings — zod strips
|
|
102
|
+
# undeclared keys silently, and an empty `slug` is not the same as no slug.
|
|
103
|
+
pl="$(ONE_ENV_FILE=/dev/null FACTORY_EMIT_SKIP_FALLBACKS=1 bash "$me" \
|
|
104
|
+
--job t --stage ready --status start --dry-run 2>/dev/null)"
|
|
105
|
+
if printf '%s' "$pl" | jq -e '(.data | has("slug") | not) and (.data | has("detail") | not)' >/dev/null 2>&1; then
|
|
106
|
+
printf ' ok %-46s\n' 'unset optional fields are absent, not empty'
|
|
107
|
+
else
|
|
108
|
+
printf ' FAIL %-46s\n %s\n' 'unset optional fields leaked into the payload' "$pl" >&2
|
|
109
|
+
fails=$((fails + 1))
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
if [ "$fails" -eq 0 ]; then
|
|
113
|
+
printf 'factory-emit --self-test: PASS (10 checks)\n'; return 0
|
|
114
|
+
fi
|
|
115
|
+
printf 'factory-emit --self-test: RED — %s check(s) failed\n' "$fails" >&2; return 1
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
JOB=""; STAGE=""; STATUS=""; SLUG=""; MODEL=""; REASON=""; ENVW=""; DETAILS=""; DRY=0
|
|
119
|
+
while [ $# -gt 0 ]; do
|
|
120
|
+
case "$1" in
|
|
121
|
+
--job) JOB="${2:-}"; shift 2 ;;
|
|
122
|
+
--stage) STAGE="${2:-}"; shift 2 ;;
|
|
123
|
+
--status) STATUS="${2:-}"; shift 2 ;;
|
|
124
|
+
--slug) SLUG="${2:-}"; shift 2 ;;
|
|
125
|
+
--model) MODEL="${2:-}"; shift 2 ;;
|
|
126
|
+
--reason) REASON="${2:-}"; shift 2 ;;
|
|
127
|
+
--env) ENVW="${2:-}"; shift 2 ;;
|
|
128
|
+
--detail) DETAILS="${DETAILS}${DETAILS:+$'\n'}${2:-}"; shift 2 ;;
|
|
129
|
+
--dry-run) DRY=1; shift ;;
|
|
130
|
+
--self-test) command -v jq >/dev/null 2>&1 || die "jq not found" 5; self_test; exit $? ;;
|
|
131
|
+
-h|--help) sed -n '2,42p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
|
132
|
+
*) die "unknown argument: $1" 2 ;;
|
|
133
|
+
esac
|
|
134
|
+
done
|
|
135
|
+
|
|
136
|
+
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
|
|
137
|
+
[ -n "$JOB" ] || die "--job <tid> is required — it is the run key (factory:<tid>, event.ts factoryRunKey)" 2
|
|
138
|
+
case "$STAGE" in
|
|
139
|
+
ready|claim|build|review|prove|close) ;;
|
|
140
|
+
*) die "--stage must be one of ready claim build review prove close (got '$STAGE') — factoryEventToRunEvent returns NULL for anything else, and a null that is posted anyway is a frame nobody meant" 2 ;;
|
|
141
|
+
esac
|
|
142
|
+
case "$STATUS" in
|
|
143
|
+
start|ok|fail) ;;
|
|
144
|
+
*) die "--status must be start|ok|fail (got '$STATUS')" 2 ;;
|
|
145
|
+
esac
|
|
146
|
+
|
|
147
|
+
detail_json='{}'
|
|
148
|
+
if [ -n "$DETAILS" ]; then
|
|
149
|
+
while IFS= read -r kv; do
|
|
150
|
+
[ -n "$kv" ] || continue
|
|
151
|
+
case "$kv" in *=*) ;; *) die "--detail wants k=v, got: $kv" 2 ;; esac
|
|
152
|
+
detail_json="$(jq -n --argjson d "$detail_json" --arg k "${kv%%=*}" --arg v "${kv#*=}" '$d + {($k): $v}')" \
|
|
153
|
+
|| die "could not build --detail object" 2
|
|
154
|
+
done <<EOF
|
|
155
|
+
$DETAILS
|
|
156
|
+
EOF
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
payload="$(jq -n \
|
|
160
|
+
--arg job "$JOB" --arg stage "$STAGE" --arg status "$STATUS" \
|
|
161
|
+
--arg slug "$SLUG" --arg model "$MODEL" --arg reason "$REASON" --arg env "$ENVW" \
|
|
162
|
+
--argjson detail "$detail_json" '
|
|
163
|
+
{data: ({job: $job, stage: $stage, status: $status}
|
|
164
|
+
+ (if $slug != "" then {slug: $slug} else {} end)
|
|
165
|
+
+ (if $model != "" then {model: $model} else {} end)
|
|
166
|
+
+ (if $reason != "" then {reason: $reason} else {} end)
|
|
167
|
+
+ (if $env != "" then {env: $env} else {} end)
|
|
168
|
+
+ (if ($detail | length) > 0 then {detail: $detail} else {} end))}')" \
|
|
169
|
+
|| die "could not build the payload" 2
|
|
170
|
+
|
|
171
|
+
if [ "$DRY" = 1 ]; then printf '%s\n' "$payload"; exit 0; fi
|
|
172
|
+
|
|
173
|
+
# The key never reaches argv (ps) and never reaches stdout. --config, chmod 600,
|
|
174
|
+
# removed on every path. Same shape as do-signal.sh.
|
|
175
|
+
_gateway_key() {
|
|
176
|
+
local f v
|
|
177
|
+
for f in "$ONE_ENV_FILE" "$ROOT/one.ie/web/.env" "$ROOT/one.ie/web/.dev.vars" "$ROOT/.env"; do
|
|
178
|
+
[ -f "$f" ] || continue
|
|
179
|
+
if [ "${FACTORY_EMIT_SKIP_FALLBACKS:-0}" = "1" ] && [ "$f" != "$ONE_ENV_FILE" ]; then continue; fi
|
|
180
|
+
v="$(grep -E '^GATEWAY_API_KEY=' "$f" 2>/dev/null | head -1 | sed 's/^[^=]*=//;s/^"//;s/"$//')"
|
|
181
|
+
[ -n "$v" ] && { printf '%s' "$v"; return 0; }
|
|
182
|
+
done
|
|
183
|
+
printf ''
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
KEYVAL="$(_gateway_key)"
|
|
187
|
+
[ -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 stage that never happened" 3
|
|
188
|
+
|
|
189
|
+
cfg="$(mktemp)"; chmod 600 "$cfg"
|
|
190
|
+
trap 'rm -f "$cfg"' EXIT INT TERM
|
|
191
|
+
printf 'header = "Authorization: Bearer %s"\n' "$KEYVAL" > "$cfg"
|
|
192
|
+
|
|
193
|
+
resp="$(curl -sS -X POST "$URL/api/ask/$RECEIVER" \
|
|
194
|
+
-H 'content-type: application/json' --config "$cfg" \
|
|
195
|
+
--max-time "$TIMEOUT" -d "$payload" -w $'\n%{http_code}' 2>&1)"; rc=$?
|
|
196
|
+
rm -f "$cfg"; trap - EXIT INT TERM
|
|
197
|
+
|
|
198
|
+
code="$(printf '%s' "$resp" | tail -1)"
|
|
199
|
+
body="$(printf '%s' "$resp" | sed '$d')"
|
|
200
|
+
ok="$(printf '%s' "$body" | jq -r 'if (.outcome == "result" and (.result.ok == true)) then "yes" else "no" end' 2>/dev/null)"
|
|
201
|
+
|
|
202
|
+
if [ "$rc" -ne 0 ] || [ "$ok" != "yes" ]; then
|
|
203
|
+
why="$(printf '%s' "$body" | jq -r '.reason // .result.error // .error // .hint // empty' 2>/dev/null)"
|
|
204
|
+
[ -n "$why" ] || why="curl rc=$rc http=${code:-none} body=$(printf '%s' "$body" | head -c 200)"
|
|
205
|
+
printf 'factory-emit: RED job=%s stage=%s status=%s http=%s — %s\n' \
|
|
206
|
+
"$JOB" "$STAGE" "$STATUS" "${code:-none}" "$why" >&2
|
|
207
|
+
exit 4
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
printf 'factory-emit: ok job=%s stage=%s status=%s receiver=%s\n' "$JOB" "$STAGE" "$STATUS" "$RECEIVER"
|
|
211
|
+
exit 0
|