muse-crew 0.14.0 → 0.14.1
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/lib/AGENTS.md +4 -1
- package/lib/advance-publish-base.js +74 -28
- package/lib/crew-api.js +8 -0
- package/lib/scaffold-crew.js +129 -0
- package/package.json +1 -1
- package/workflows/bugfix.js +7 -13
- package/workflows/chore.js +6 -12
- package/workflows/crew-dispatch.js +43 -8
- package/workflows/crew-init.js +333 -78
- package/workflows/crew-uninstall.js +57 -10
- package/workflows/standard.js +5 -7
- package/workflows/upgrade.js +27 -13
|
@@ -17,6 +17,13 @@ const force = inputs.force;
|
|
|
17
17
|
|
|
18
18
|
if (!crewHome) throw new Error("crewHome is required — e.g. ~/workspace/.crew");
|
|
19
19
|
|
|
20
|
+
// sq(p) — single-quote a path for safe interpolation into shell commands the
|
|
21
|
+
// agents run. A bare double-quoted path breaks on spaces, $, backticks, or
|
|
22
|
+
// quotes; single-quote wrapping is always safe.
|
|
23
|
+
function sq(p) {
|
|
24
|
+
return "'" + String(p).split("'").join("'\\''") + "'";
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
// ── Pure decision helpers ──────────────────────────────────────────────
|
|
21
28
|
// The agent is a sensor, not a judge: it reports raw facts (HOME, expanded
|
|
22
29
|
// paths, existence, in-progress counts) and the verdicts below are computed
|
|
@@ -110,8 +117,8 @@ if (gateFacts.homeExists) {
|
|
|
110
117
|
"Report whether this crew has live work. Do not judge — just report.\n\n" +
|
|
111
118
|
"crewHome: " + crewHomeExpanded + "\n\n" +
|
|
112
119
|
"Steps (run in shell):\n" +
|
|
113
|
-
"1. If
|
|
114
|
-
" sqlite3
|
|
120
|
+
"1. If " + sq(crewHomeExpanded + "/crew-state.db") + " exists, run:\n" +
|
|
121
|
+
" sqlite3 " + sq(crewHomeExpanded + "/crew-state.db") + " \"SELECT COUNT(*) FROM tasks WHERE state='in_progress';\"\n" +
|
|
115
122
|
" Report the number. When the database is missing, the count is 0.\n" +
|
|
116
123
|
"2. Return JSON { homeExists: true, inProgress: <number> }.",
|
|
117
124
|
{
|
|
@@ -149,7 +156,7 @@ try {
|
|
|
149
156
|
"Remove this crew instance's scheduler cron jobs — and nothing else.\n\n" +
|
|
150
157
|
"crewHome: " + crewHomeExpanded + "\n\n" +
|
|
151
158
|
"Steps:\n" +
|
|
152
|
-
"1. Registry: if
|
|
159
|
+
"1. Registry: if " + sq(crewHomeExpanded + "/.cron-registry.json") + " exists, read it\n" +
|
|
153
160
|
" and take its crons[].id list as registry candidates.\n" +
|
|
154
161
|
"2. Discovery: call cron_list. For every job whose id starts with 'crew-poll-',\n" +
|
|
155
162
|
" call cron_view and keep it as a discovery candidate when the job body\n" +
|
|
@@ -203,8 +210,29 @@ try {
|
|
|
203
210
|
if (!cronsResult.passed || !cronsResult.summary || !Array.isArray(cronsResult.summary.removed)) {
|
|
204
211
|
return { __hatchWorkflowControl: "blocked", result: { blocked_reason: "Cron removal failed", message: "The cron-removal agent did not pass. No further teardown was attempted; the crew home is untouched." } };
|
|
205
212
|
}
|
|
206
|
-
|
|
207
|
-
|
|
213
|
+
// ── Razor witness: the removal list above is the agent's report, not a
|
|
214
|
+
// fact. Re-read the cron registry independently and only assert removal for
|
|
215
|
+
// ids the re-read confirms absent. Agent returns are testimony, never
|
|
216
|
+
// evidence. Non-blocking: a surviving id is logged loudly, not asserted away.
|
|
217
|
+
var removedEntries = cronsResult.summary.removed;
|
|
218
|
+
var cronsGone = [], cronsPresent = [], cronsUnverified = [];
|
|
219
|
+
try {
|
|
220
|
+
var cronListOut = String(await agent(
|
|
221
|
+
"Call cron_list and return its full output verbatim. Do not summarize, interpret, or filter it.",
|
|
222
|
+
{ key: "uninstall-crons-verify", label: "Re-reading cron registry after removal" }
|
|
223
|
+
) || "");
|
|
224
|
+
removedEntries.forEach(function (r) {
|
|
225
|
+
if (cronListOut.indexOf(r.id) === -1) { cronsGone.push(r.id); }
|
|
226
|
+
else { cronsPresent.push(r.id); }
|
|
227
|
+
});
|
|
228
|
+
} catch (e) {
|
|
229
|
+
cronsUnverified = removedEntries.map(function (r) { return r.id; });
|
|
230
|
+
}
|
|
231
|
+
var cronBits = [];
|
|
232
|
+
if (cronsGone.length) { cronBits.push("removed (registry re-read confirms gone): " + cronsGone.join(", ")); }
|
|
233
|
+
if (cronsPresent.length) { cronBits.push("agent reported removed but STILL PRESENT in registry: " + cronsPresent.join(", ")); }
|
|
234
|
+
if (cronsUnverified.length) { cronBits.push("removal reported but not re-verified (re-read failed): " + cronsUnverified.join(", ")); }
|
|
235
|
+
log("Crons: " + (cronBits.join("; ") || "(none belonged to this crew)"));
|
|
208
236
|
|
|
209
237
|
// ── Delete the crew home directory ─────────────────────────────────────
|
|
210
238
|
phase("home");
|
|
@@ -217,10 +245,10 @@ if (gateFacts.homeExists) {
|
|
|
217
245
|
"crewHome: " + crewHomeExpanded + "\n\n" +
|
|
218
246
|
"Steps:\n" +
|
|
219
247
|
"1. Best-effort worktree hygiene (failures do not block): if\n" +
|
|
220
|
-
"
|
|
248
|
+
" " + sq(crewHomeExpanded + "/crew-state.db") + " exists, list the repo_path values\n" +
|
|
221
249
|
" from its projects table and run 'git worktree prune' in each. Ignore errors.\n" +
|
|
222
|
-
"2. Run: rm -rf
|
|
223
|
-
"3. Verify: test ! -e
|
|
250
|
+
"2. Run: rm -rf " + sq(crewHomeExpanded) + "\n" +
|
|
251
|
+
"3. Verify: test ! -e " + sq(crewHomeExpanded) + " — fail closed (passed: false)\n" +
|
|
224
252
|
" if the path still exists.\n" +
|
|
225
253
|
"4. Return { passed: true, summary: { removed: true } }.",
|
|
226
254
|
{
|
|
@@ -248,8 +276,27 @@ if (gateFacts.homeExists) {
|
|
|
248
276
|
if (!homeResult.passed || !homeResult.summary || homeResult.summary.removed !== true) {
|
|
249
277
|
return { __hatchWorkflowControl: "blocked", result: { blocked_reason: "Crew home removal failed", message: "The crew home could not be deleted. Its crons were already removed; delete " + crewHomeExpanded + " manually." } };
|
|
250
278
|
}
|
|
251
|
-
|
|
252
|
-
|
|
279
|
+
// ── Razor witness: the agent's `removed` flag is testimony, not evidence.
|
|
280
|
+
// Confirm the directory is actually gone before claiming deletion. Gone is
|
|
281
|
+
// asserted only when the re-read says DIR_GONE without DIR_PRESENT (the
|
|
282
|
+
// check command itself names both markers, so requiring the absence of
|
|
283
|
+
// DIR_PRESENT keeps a mere echo of the command from counting as proof).
|
|
284
|
+
var homeGone = false;
|
|
285
|
+
try {
|
|
286
|
+
var homeCheckOut = String(await agent(
|
|
287
|
+
"Run in shell and return the stdout verbatim:\ntest -d " + sq(crewHomeExpanded) + " && echo DIR_PRESENT || echo DIR_GONE",
|
|
288
|
+
{ key: "uninstall-home-verify", label: "Re-checking crew home directory after deletion" }
|
|
289
|
+
) || "");
|
|
290
|
+
homeGone = homeCheckOut.indexOf("DIR_GONE") !== -1 && homeCheckOut.indexOf("DIR_PRESENT") === -1;
|
|
291
|
+
} catch (e) {
|
|
292
|
+
homeGone = false;
|
|
293
|
+
}
|
|
294
|
+
if (homeGone) {
|
|
295
|
+
homeRemoved = true;
|
|
296
|
+
log("Crew home deleted: " + crewHomeExpanded + " (directory re-read confirms it is gone)");
|
|
297
|
+
} else {
|
|
298
|
+
log("Crew home was reported deleted by the agent, but the directory is still present (or the re-check failed): " + crewHomeExpanded);
|
|
299
|
+
}
|
|
253
300
|
} else {
|
|
254
301
|
log("Crew home already gone — nothing to delete.");
|
|
255
302
|
}
|
package/workflows/standard.js
CHANGED
|
@@ -454,9 +454,7 @@ async function recordPublishLedger(entry, rework) {
|
|
|
454
454
|
label: "Recording publish attempt in ledger",
|
|
455
455
|
schema: { type: "object", properties: { result: { type: "string" } }, required: ["result"] } }
|
|
456
456
|
);
|
|
457
|
-
|
|
458
|
-
log("Publish ledger: outcome '" + entry.outcome + "' for task " + taskId +
|
|
459
|
-
(ok ? " recorded." : " NOT confirmed (" + ((res && res.result) || "no output") + ")"));
|
|
457
|
+
log("Noted publish outcome '" + entry.outcome + "' for task " + taskId + " in ledger");
|
|
460
458
|
} catch (e) {
|
|
461
459
|
log("Publish ledger: write failed for task " + taskId + " (non-fatal, observability only): " + (e && e.message ? e.message : e));
|
|
462
460
|
}
|
|
@@ -2085,11 +2083,11 @@ while (i < STEPS.length) {
|
|
|
2085
2083
|
if (!postDeploy.deployed) {
|
|
2086
2084
|
return await parkTask("Post-deploy failed after a skipped publish: " + (postDeploy.output || "no output") + ". Nothing was published; worktree cleanup and lock state unknown — human attention needed.");
|
|
2087
2085
|
}
|
|
2088
|
-
log("Publish skipped cleanly for task " + taskId + " (no lock held) — post-deploy
|
|
2086
|
+
log("Publish skipped cleanly for task " + taskId + " (no lock held) — post-deploy step finished");
|
|
2089
2087
|
} else {
|
|
2090
2088
|
if (publishFailure) {
|
|
2091
2089
|
return await parkTask(publishFailure + (postDeploy.deployed
|
|
2092
|
-
? " Post-deploy
|
|
2090
|
+
? " Post-deploy step finished (cleanup status unknown)."
|
|
2093
2091
|
: " Post-deploy also failed (" + (postDeploy.output || "no output") + ") — worktree and lock state unknown."));
|
|
2094
2092
|
}
|
|
2095
2093
|
if (!publishBuildLanded) {
|
|
@@ -2102,7 +2100,7 @@ while (i < STEPS.length) {
|
|
|
2102
2100
|
// commit (design §1.5: "the trigger was sent for commit <short-sha>").
|
|
2103
2101
|
if (publishUnknownFields) publishUnknownFields.commitShortSha = mergeCommitShortForPublish;
|
|
2104
2102
|
return await parkTask(composeUnattributedParkReason(publishUnknownFields) + (postDeploy.deployed
|
|
2105
|
-
? " Post-deploy
|
|
2103
|
+
? " Post-deploy step finished (cleanup status unknown)."
|
|
2106
2104
|
: " Post-deploy also failed (" + (postDeploy.output || "no output") + ") — worktree and lock state unknown."));
|
|
2107
2105
|
}
|
|
2108
2106
|
if (!postDeploy.deployed) {
|
|
@@ -2131,7 +2129,7 @@ while (i < STEPS.length) {
|
|
|
2131
2129
|
// artifact_edit would trigger a duplicate build.
|
|
2132
2130
|
if (publishSkippedNoLock) {
|
|
2133
2131
|
instructions = "Publish was skipped deterministically by the workflow before your step — do NOT call artifact_edit, artifact_status, setprovenance, or post-deploy yourself; doing so would disturb the finalized state.\n\n" +
|
|
2134
|
-
"Integrate reported MERGED_EMPTY (the task branch had no commits ahead of main), so no merge lock was taken and there is nothing to ship.
|
|
2132
|
+
"Integrate reported MERGED_EMPTY (the task branch had no commits ahead of main), so no merge lock was taken and there is nothing to ship. Post-deploy step finished per its return — do NOT run post-deploy yourself.\n\n" +
|
|
2135
2133
|
"Write plain prose describing the skip, then on its own line: VERDICT: PASS\n" +
|
|
2136
2134
|
"The VERDICT line must be the last line of your report.";
|
|
2137
2135
|
} else {
|
package/workflows/upgrade.js
CHANGED
|
@@ -79,6 +79,12 @@ const NEXT_PHASE_ROUTED = (typeof inputs.next_phase === "string" && inputs.next_
|
|
|
79
79
|
// (2026-09-16: the silent default let a run resolve against the dev home).
|
|
80
80
|
if (!inputs.crewHome) throw new Error("crewHome is required — pass the crew home explicitly; no default");
|
|
81
81
|
const crewHome = inputs.crewHome;
|
|
82
|
+
// sq(p) — single-quote a path for safe interpolation into shell commands the
|
|
83
|
+
// agents run. A bare double-quoted path breaks on spaces, $, backticks, or
|
|
84
|
+
// quotes; single-quote wrapping is always safe.
|
|
85
|
+
function sq(p) {
|
|
86
|
+
return "'" + String(p).split("'").join("'\\''") + "'";
|
|
87
|
+
}
|
|
82
88
|
// Crew API: the workflow calls the crew-owned CLI, not the dashboard.
|
|
83
89
|
// The CLI implements the API.md contract against $CREW_HOME/crew-state.db.
|
|
84
90
|
const CREW_API_SRC = crewHome + "/current/lib/crew-api.js";
|
|
@@ -91,7 +97,7 @@ let CREW_API = CREW_API_SRC;
|
|
|
91
97
|
// returns the stdout verbatim (the CLI emits JSON on stdout).
|
|
92
98
|
function crewCmd(command, args) {
|
|
93
99
|
var json = JSON.stringify(args || {}).replace(/'/g, "'\\''");
|
|
94
|
-
return "node " + CREW_API + " --crew-home " + crewHome + " " + command + " --json '" + json + "'";
|
|
100
|
+
return "node " + sq(CREW_API) + " --crew-home " + sq(crewHome) + " " + command + " --json '" + json + "'";
|
|
95
101
|
}
|
|
96
102
|
// Telemetry (2026-09-13): structured run timeline. The workflow mints a
|
|
97
103
|
// telemetry run_id at startup via record-run-start (the API generates it
|
|
@@ -178,7 +184,7 @@ const LAUNCH_PROJECT_ID = inputs.project_id || "";
|
|
|
178
184
|
// parks fail-closed at Triage.
|
|
179
185
|
const REPO_PATH = projectConfig.repo_path || "";
|
|
180
186
|
// Env prefix baked into every lifecycle invocation the agents run.
|
|
181
|
-
const LIFECYCLE_ENV = "CREW_HOME=" + crewHome + " CREW_REPO=" + REPO_PATH + " ";
|
|
187
|
+
const LIFECYCLE_ENV = "CREW_HOME=" + sq(crewHome) + " CREW_REPO=" + REPO_PATH + " ";
|
|
182
188
|
// The stable release script — deploys, reports the live release.
|
|
183
189
|
const DEPLOY_SCRIPT = crewHome + "/crew-release.sh";
|
|
184
190
|
|
|
@@ -195,7 +201,7 @@ if (!taskId) {
|
|
|
195
201
|
function pinLifecycle(key) {
|
|
196
202
|
return agent(
|
|
197
203
|
"Snapshot lifecycle scripts for version pinning.\n" +
|
|
198
|
-
"Run: mkdir -p " + RUN_LIB + " && cp " + LIFECYCLE_SRC + " " + LIFECYCLE + " && cp " + MERGE_LOCK_SRC + " " + MERGE_LOCK + " && cp " + PUBLISH_NPM_SRC + " " + PUBLISH_NPM + " && cp " + CREW_API_SRC + " " + CREW_API_PINNED + " && cp " + SCHEMA_SQL_SRC + " " + SCHEMA_SQL_PINNED + " && cp " + CLASSIFY_SURFACE_SRC + " " + CLASSIFY_SURFACE + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK + " " + PUBLISH_NPM + " && ls -1 " + RUN_LIB + "\n" +
|
|
204
|
+
"Run: mkdir -p " + sq(RUN_LIB) + " && cp " + sq(LIFECYCLE_SRC) + " " + sq(LIFECYCLE) + " && cp " + sq(MERGE_LOCK_SRC) + " " + sq(MERGE_LOCK) + " && cp " + sq(PUBLISH_NPM_SRC) + " " + sq(PUBLISH_NPM) + " && cp " + sq(CREW_API_SRC) + " " + sq(CREW_API_PINNED) + " && cp " + sq(SCHEMA_SQL_SRC) + " " + sq(SCHEMA_SQL_PINNED) + " && cp " + sq(CLASSIFY_SURFACE_SRC) + " " + sq(CLASSIFY_SURFACE) + " && chmod +x " + sq(LIFECYCLE) + " " + sq(MERGE_LOCK) + " " + sq(PUBLISH_NPM) + " && ls -1 " + sq(RUN_LIB) + "\n" +
|
|
199
205
|
"Return the verbatim output of the ls -1 command as { \"listing\": \"<verbatim output>\" } and nothing else.",
|
|
200
206
|
{ key: key, label: "Pinning lifecycle scripts",
|
|
201
207
|
schema: { type: "object", properties: { listing: { type: "string" } }, required: ["listing"] } }
|
|
@@ -258,7 +264,7 @@ async function computeUpgradePlan(evidenceKey, noteIdentity) {
|
|
|
258
264
|
(sourceIsRepo
|
|
259
265
|
? "2. The workflow was told source is repo. Check that " + REPO_PATH + "/workflows is a directory AND " + REPO_PATH + "/lib/crew-release.sh exists — repo_ok is true only if both hold. Run: git -C " + REPO_PATH + " rev-parse HEAD and capture the sha as head (empty string if the command fails).\n"
|
|
260
266
|
: "2. The workflow was told source is not repo. Skip all repo checks: return repo_ok false and head as an empty string.\n") +
|
|
261
|
-
"3. Run: " + DEPLOY_SCRIPT + " current " + crewHome + " — capture the full trimmed stdout as current.\n" +
|
|
267
|
+
"3. Run: " + sq(DEPLOY_SCRIPT) + " current " + sq(crewHome) + " — capture the full trimmed stdout as current.\n" +
|
|
262
268
|
"Return JSON { \"source_line\": \"<verbatim>\", \"repo_ok\": <bool>, \"head\": \"<sha or empty>\", \"current\": \"<trimmed stdout>\" } and nothing else.",
|
|
263
269
|
{
|
|
264
270
|
key: evidenceKey,
|
|
@@ -583,8 +589,8 @@ while (i < STEPS.length) {
|
|
|
583
589
|
const stagingDir = crewHome + "/.upgrade-staging/" + upgradePlan.version;
|
|
584
590
|
const deployShell =
|
|
585
591
|
(upgradePlan.source === "repo")
|
|
586
|
-
? DEPLOY_SCRIPT + " deploy " + REPO_PATH + " " + crewHome
|
|
587
|
-
: "STAGING
|
|
592
|
+
? sq(DEPLOY_SCRIPT) + " deploy " + REPO_PATH + " " + sq(crewHome)
|
|
593
|
+
: "STAGING=" + sq(stagingDir) + " && mkdir -p \"$STAGING\" && cd \"$STAGING\" && npm init -y >/dev/null 2>&1 && npm install muse-crew@" + upgradePlan.version;
|
|
588
594
|
const deploySteps =
|
|
589
595
|
"1. Run the install (npm source only):\n" + (upgradePlan.source === "repo" ? " (skipped — repo source has nothing to install)\n" : " " + deployShell + "\n") +
|
|
590
596
|
"2. Run the deploy:\n " + (upgradePlan.source === "repo"
|
|
@@ -594,10 +600,10 @@ while (i < STEPS.length) {
|
|
|
594
600
|
// so the deploy source is the installed package root, not the
|
|
595
601
|
// staging root. Do not rely on a $STAGING shell variable persisting
|
|
596
602
|
// between separate shell invocations.
|
|
597
|
-
: DEPLOY_SCRIPT + " deploy
|
|
603
|
+
: sq(DEPLOY_SCRIPT) + " deploy " + sq(stagingDir + "/node_modules/muse-crew") + " " + sq(crewHome)) +
|
|
598
604
|
" — capture ALL of the command's output and its exit code (run the command, then echo EXIT_CODE=$?).\n" +
|
|
599
605
|
(upgradePlan.source === "npm"
|
|
600
|
-
? "3. Remove the staging dir: rm -rf
|
|
606
|
+
? "3. Remove the staging dir: rm -rf " + sq(stagingDir) + " — best-effort, ALWAYS, even when the deploy fails. Log whether the removal succeeded. Never let cleanup change the deploy outcome.\n"
|
|
601
607
|
: "") +
|
|
602
608
|
"Do not run git checkout, git pull, or any repo-mutating command. Do not publish to npm — the npm source only INSTALLS the published package. Do not touch the scheduler.";
|
|
603
609
|
var deployResult;
|
|
@@ -630,9 +636,12 @@ while (i < STEPS.length) {
|
|
|
630
636
|
await recordPhase("Deploy", step.identity, activeSessionId, "failed", msg);
|
|
631
637
|
return await parkTask(msg);
|
|
632
638
|
}
|
|
633
|
-
|
|
639
|
+
// Razor: exit 0 is the deploy agent's testimony, not proof the release
|
|
640
|
+
// swap landed. Success is claimed only after Verify's independent checks
|
|
641
|
+
// pass below.
|
|
642
|
+
log("Deploy agent returned exit 0 for task " + taskId + " — target " + upgradePlan.target + " (Verify checks pending)");
|
|
634
643
|
await recordPhase("Deploy", step.identity, activeSessionId, "completed",
|
|
635
|
-
"
|
|
644
|
+
"deploy command exit 0 for " + upgradePlan.target + " (" + upgradePlan.source + ") — Verify checks pending\ndeploy output:\n" + deployOutput.slice(0, 1500));
|
|
636
645
|
i++;
|
|
637
646
|
continue;
|
|
638
647
|
}
|
|
@@ -663,7 +672,7 @@ while (i < STEPS.length) {
|
|
|
663
672
|
curCheck = await agent(
|
|
664
673
|
TOOL_CHECK_PREAMBLE +
|
|
665
674
|
"Read the live release identity.\n" +
|
|
666
|
-
"Run: " + DEPLOY_SCRIPT + " current " + crewHome + "\n" +
|
|
675
|
+
"Run: " + sq(DEPLOY_SCRIPT) + " current " + sq(crewHome) + "\n" +
|
|
667
676
|
"Return JSON { \"current\": \"<trimmed stdout>\" } and nothing else.",
|
|
668
677
|
{
|
|
669
678
|
key: "verify-current",
|
|
@@ -695,7 +704,7 @@ while (i < STEPS.length) {
|
|
|
695
704
|
regCheck = await agent(
|
|
696
705
|
TOOL_CHECK_PREAMBLE +
|
|
697
706
|
"Read the deployed registry.\n" +
|
|
698
|
-
"Run: cat " + crewHome + "/workflows/registry.json\n" +
|
|
707
|
+
"Run: cat " + sq(crewHome + "/workflows/registry.json") + "\n" +
|
|
699
708
|
"Return JSON { \"raw\": \"<the file's full content, verbatim>\" } and nothing else.",
|
|
700
709
|
{
|
|
701
710
|
key: "verify-registry",
|
|
@@ -740,7 +749,7 @@ while (i < STEPS.length) {
|
|
|
740
749
|
gsCheck = await agent(
|
|
741
750
|
TOOL_CHECK_PREAMBLE +
|
|
742
751
|
"Check the crew database is readable by the OLD release's CLI. Use the PINNED path below — NOT " + crewHome + "/current — because the workflow swapped `current` under itself at Deploy and the live path may already point at the new release.\n" +
|
|
743
|
-
"Run: node " + CREW_API_PINNED + " --crew-home " + crewHome + " get-state --json '{}'; echo EXIT_CODE=$?\n" +
|
|
752
|
+
"Run: node " + sq(CREW_API_PINNED) + " --crew-home " + sq(crewHome) + " get-state --json '{}'; echo EXIT_CODE=$?\n" +
|
|
744
753
|
"Return JSON { \"exit\": <the exit code as an integer> } and nothing else.",
|
|
745
754
|
{
|
|
746
755
|
key: "verify-get-state",
|
|
@@ -762,6 +771,11 @@ while (i < STEPS.length) {
|
|
|
762
771
|
}
|
|
763
772
|
log("Verify check 3 passed for task " + taskId + ": pinned crew-api.js get-state exits 0");
|
|
764
773
|
|
|
774
|
+
// The success claim lives here now: all three independent checks re-read
|
|
775
|
+
// the world and confirm the swap — the deploy agent's exit 0 alone never
|
|
776
|
+
// proved it.
|
|
777
|
+
log("Deploy succeeded for task " + taskId + " — target " + target + " (Verify: live release, registry, and pinned CLI all confirm)");
|
|
778
|
+
|
|
765
779
|
await agent(
|
|
766
780
|
"Log the completed upgrade.\n" +
|
|
767
781
|
"Run in shell and return the stdout verbatim:\n" + crewCmd("log-event", {
|