@bongos/core 1.18.19 → 1.19.2
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/.bongos-core.json +514 -194
- package/.claude/skills/builder-box/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +1 -1
- package/.claude/skills/goal-create/SKILL.md +1 -1
- package/.claude/skills/idea-triage/SKILL.md +1 -1
- package/.claude/skills/planning-session/SKILL.md +5 -5
- package/.claude/skills/scan-before-install/SKILL.md +165 -0
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +63 -41
- package/clients/bongos-client/index.cjs +63 -41
- package/clients/bongos-client/index.d.ts +78 -44
- package/clients/bongos-client/index.mjs +63 -41
- package/docs/adr/0020-security-model-public-stub.md +1 -1
- package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +2 -0
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +1 -1
- package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +2 -1
- package/docs/adr/0155-adopt-private-repo-widen-oauth-scope.md +51 -0
- package/docs/adr/0156-user-set-display-name-wins.md +62 -0
- package/docs/adr/0157-archon-is-rank-and-identity-only.md +93 -0
- package/docs/adr/0158-choke-point-verdict-taxonomy.md +174 -0
- package/docs/adr/0159-merge-gate-reconciliation.md +88 -0
- package/docs/adr/0160-main-audit-repo-verifiable-caller.md +77 -0
- package/docs/adr/README.md +8 -0
- package/docs/api/openapi.json +1209 -233
- package/docs/api-reference.md +61 -46
- package/docs/architecture.md +1 -1
- package/docs/canonical-permissions.md +61 -16
- package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
- package/docs/file-map.md +4 -1
- package/docs/onboarding/diagrams/assertions.json +3 -3
- package/docs/recipes/core-release-pipeline.md +179 -0
- package/docs/recipes/packaging-the-core.md +2 -0
- package/docs/recipes/parallel-goal-waves.md +1 -3
- package/docs/recipes/private-npm-distribution.md +2 -0
- package/docs/recipes/upgrading-the-core.md +2 -0
- package/migrations/core_197_display_name_source.sql +56 -0
- package/migrations/core_204_rekey_active_claim_per_session_worktree.sql +77 -0
- package/modules/autonomy/routes/autonomy.js +1 -1
- package/modules/builder-settings/db.js +7 -1
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +63 -41
- package/modules/dev-box/routes/box.js +11 -9
- package/modules/discord/auth.js +3 -1
- package/modules/discord/routes/discord.js +8 -2
- package/modules/discord/ship-broadcast.js +48 -1
- package/modules/governance/catalog.js +70 -20
- package/modules/governance/db.js +102 -0
- package/modules/governance/migrations/governance_006_version_create.sql +38 -0
- package/modules/governance/migrations/governance_007_delegate_archon_ops_to_metic.sql +72 -0
- package/modules/governance/routes/governance.js +235 -6
- package/modules/grading/grader-workers/index.js +18 -9
- package/modules/hall-ui/public/gate.js +6 -2
- package/modules/hall-ui/public/goals-page.js +6 -2
- package/modules/hall-ui/public/governance.css +100 -0
- package/modules/hall-ui/public/governance.html +77 -0
- package/modules/hall-ui/public/governance.js +353 -0
- package/modules/hall-ui/public/harbor.js +14 -8
- package/modules/hall-ui/public/instances-panel.js +3 -1
- package/modules/hall-ui/public/new-project-wizard.js +6 -0
- package/modules/hall-ui/public/people.html +51 -0
- package/modules/hall-ui/public/people.js +100 -0
- package/modules/hall-ui/public/profile.html +2 -1
- package/modules/hall-ui/public/profile.js +9 -3
- package/modules/hall-ui/public/sessions.js +4 -2
- package/modules/hall-ui/public/shell.js +28 -7
- package/modules/hall-ui/public/task-detail.js +6 -3
- package/modules/hall-ui/public/watch.js +17 -9
- package/modules/ideas/blockers.js +44 -7
- package/modules/ideas/routes/blockers.js +12 -7
- package/modules/ideas/routes/inbox.js +2 -2
- package/modules/lifecycle/db.js +84 -17
- package/modules/lifecycle/github-push.js +37 -10
- package/modules/lifecycle/publish-reconciler.js +128 -31
- package/modules/lifecycle/routes/claims.js +10 -4
- package/modules/lifecycle/routes/dependencies.js +2 -2
- package/modules/lifecycle/routes/done-when.js +4 -4
- package/modules/lifecycle/routes/gate-approvals.js +9 -3
- package/modules/lifecycle/routes/goals.js +5 -5
- package/modules/lifecycle/routes/tasks.js +16 -16
- package/modules/lifecycle/routes/versions.js +13 -2
- package/modules/lifecycle/ship-card.js +38 -7
- package/modules/memory/routes/memory.js +2 -2
- package/modules/onboarding/routes/access-requests.js +3 -3
- package/modules/platform-identity/routes/projects.js +10 -6
- package/modules/platform-identity/routes/scouting.js +2 -2
- package/modules/provisioning/migrations/provisioning_004_onboard_mode.sql +30 -0
- package/modules/provisioning/provisioning.js +24 -6
- package/modules/provisioning/routes/provisioning.js +24 -6
- package/modules/security/artifact-scan/aggregate.js +133 -0
- package/modules/security/artifact-scan/detectors/capabilities.js +53 -0
- package/modules/security/artifact-scan/detectors/config-obfuscation.js +41 -0
- package/modules/security/artifact-scan/detectors/credentials.js +112 -0
- package/modules/security/artifact-scan/detectors/dependencies.js +115 -0
- package/modules/security/artifact-scan/detectors/hooks.js +116 -0
- package/modules/security/artifact-scan/detectors/injection.js +118 -0
- package/modules/security/artifact-scan/detectors/mcp.js +102 -0
- package/modules/security/artifact-scan/detectors/network.js +67 -0
- package/modules/security/artifact-scan/detectors/npm-lifecycle.js +67 -0
- package/modules/security/artifact-scan/detectors/permissions.js +91 -0
- package/modules/security/artifact-scan/detectors/shared.js +219 -0
- package/modules/security/artifact-scan/fence-local.js +99 -0
- package/modules/security/artifact-scan/fetch.js +330 -0
- package/modules/security/artifact-scan/floor.js +319 -0
- package/modules/security/artifact-scan/limits.js +66 -0
- package/modules/security/artifact-scan/manifest.js +203 -0
- package/modules/security/artifact-scan/mirror.js +158 -0
- package/modules/security/artifact-scan/quarantine.js +149 -0
- package/modules/security/artifact-scan/rebuild-spec.js +120 -0
- package/modules/security/artifact-scan/report.js +155 -0
- package/modules/security/artifact-scan/sanitize.js +317 -0
- package/modules/security/migrations/security_001_tool_scans.sql +145 -0
- package/modules/security/module.json +2 -2
- package/modules/security/routes/reports.js +11 -7
- package/modules/security/routes/scans.js +115 -0
- package/modules/security/scan-store.js +214 -0
- package/modules/security/tests/aggregate.mjs +283 -0
- package/modules/security/tests/artifact_scan.mjs +591 -0
- package/modules/security/tests/floor.mjs +549 -0
- package/modules/security/tests/mirror.mjs +350 -0
- package/modules/security/tests/sanitize_rebuild.mjs +499 -0
- package/modules/security/tests/scan_store.mjs +250 -0
- package/modules/security/tests/tool_scans_migration.mjs +116 -0
- package/modules/sessions/routes/sessions.js +6 -5
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/create.html +13 -3
- package/scripts/gds/abandon.js +1 -1
- package/scripts/gds/audit-authorship.sh +6 -0
- package/scripts/gds/box.js +1 -1
- package/scripts/gds/claim.js +15 -0
- package/scripts/gds/cli-lib.js +16 -4
- package/scripts/gds/gating.js +28 -13
- package/scripts/gds/gen-api-docs.js +1 -1
- package/scripts/gds/goal.js +234 -0
- package/scripts/gds/init.js +70 -15
- package/scripts/gds/main-audit.js +80 -11
- package/scripts/gds/mirror-redact.js +6 -0
- package/scripts/gds/newcomer-floor.js +2 -2
- package/scripts/gds/provision.js +230 -94
- package/scripts/gds/run-unit-tests.js +9 -1
- package/scripts/gds/sandbox-stage.js +66 -9
- package/scripts/gds/search-ingest-md.js +1 -0
- package/scripts/gds/sequence.js +67 -8
- package/scripts/gds/ship.js +183 -50
- package/scripts/hall-preview/server.js +1 -1
- package/scripts/migrate.sh +5 -5
- package/src/bongos/auth.js +129 -27
- package/src/bongos/db-kernel.js +66 -5
- package/src/bongos/db.js +6 -7
- package/src/bongos/github-repos.js +19 -7
- package/src/bongos/goal-scope-check.js +1 -1
- package/src/bongos/module-scope-map.js +3 -1
- package/src/bongos/permission-path-check.js +1 -1
- package/src/bongos/route-rank-check.js +73 -30
- package/src/bongos/routes/audit-log.js +4 -2
- package/src/bongos/routes/auth.js +8 -4
- package/src/bongos/routes/backup.js +7 -4
- package/src/bongos/routes/builders.js +23 -11
- package/src/bongos/routes/github.js +15 -4
- package/src/bongos/routes/llm-cache.js +5 -2
- package/src/bongos/routes/modules.js +9 -5
- package/src/bongos/routes/security.js +21 -12
- package/src/bongos/serve-internal.js +22 -7
- package/src/module-api.js +86 -13
- package/src/platform-server.js +14 -0
- package/tests/api_client.mjs +18 -2
- package/tests/api_path_404.mjs +3 -0
- package/tests/art_stage.mjs +2 -1
- package/tests/atlas.mjs +32 -4
- package/tests/auth_admission_gate.mjs +17 -5
- package/tests/auth_error_brand.mjs +41 -21
- package/tests/auth_resolve_failure.mjs +155 -0
- package/tests/blocker_link.mjs +88 -0
- package/tests/bongos_downloads.mjs +2 -1
- package/tests/box_dns_repoint.mjs +2 -0
- package/tests/box_ship_permissions.mjs +2 -0
- package/tests/box_terminal_ssrf.mjs +23 -17
- package/tests/claim_error_surface.mjs +23 -0
- package/tests/claude_materialize.mjs +4 -0
- package/tests/cli_box_scope.mjs +40 -0
- package/tests/cloud_bongos_pack.mjs +34 -4
- package/tests/conductor.mjs +2 -1
- package/tests/criterion_authoring_gate.mjs +28 -5
- package/tests/currency_label.mjs +10 -1
- package/tests/deploy_instance.mjs +2 -0
- package/tests/devbox_downloads.mjs +2 -1
- package/tests/display_name.mjs +95 -6
- package/tests/eviction_policy.mjs +2 -1
- package/tests/file_map.mjs +10 -0
- package/tests/first_admin_bootstrap.mjs +10 -2
- package/tests/first_run_engine_check.mjs +2 -1
- package/tests/fitness.mjs +26 -6
- package/tests/gate_approvals.mjs +15 -2
- package/tests/github_push_land_proof.mjs +50 -4
- package/tests/github_repos_helpers.mjs +30 -5
- package/tests/github_token_store.mjs +23 -0
- package/tests/goal_abuse_matrix.mjs +13 -3
- package/tests/goal_cli.mjs +89 -0
- package/tests/governance_abuse_matrix.mjs +386 -0
- package/tests/governance_fold.mjs +156 -0
- package/tests/governance_fold_bench.mjs +50 -0
- package/tests/governance_live_proof.mjs +201 -0
- package/tests/governance_page_gates.mjs +21 -9
- package/tests/governance_parity.mjs +11 -2
- package/tests/governance_require_permission.mjs +125 -1
- package/tests/governance_routes.mjs +334 -0
- package/tests/grader.mjs +7 -3
- package/tests/hall_nav.mjs +14 -4
- package/tests/helpers.mjs +73 -0
- package/tests/hook_core_resolve.mjs +2 -1
- package/tests/host_topology_skips.mjs +97 -0
- package/tests/infra_exec_paths.mjs +14 -4
- package/tests/init.mjs +128 -0
- package/tests/known_failing_ratchet.mjs +89 -0
- package/tests/lifecycle-card.mjs +2 -1
- package/tests/main_audit.mjs +72 -1
- package/tests/memory_isolation.mjs +56 -9
- package/tests/migrate_root_resolution.mjs +1 -1
- package/tests/mirror_redact.mjs +19 -0
- package/tests/module_api.mjs +3 -1
- package/tests/module_loader.mjs +4 -0
- package/tests/modules.mjs +35 -7
- package/tests/modules_route.mjs +29 -5
- package/tests/onboarding_observability.mjs +49 -11
- package/tests/onboarding_route_signals.mjs +1 -0
- package/tests/one_active_claim_per_session.mjs +80 -0
- package/tests/pre_compact.mjs +2 -1
- package/tests/preview_server.mjs +2 -1
- package/tests/profile_route.mjs +1 -0
- package/tests/provision.mjs +170 -0
- package/tests/provisioning_onboard_mode.mjs +180 -0
- package/tests/publish_manifest.mjs +16 -3
- package/tests/publish_reconciler.mjs +181 -10
- package/tests/rank_tier_single_source.mjs +205 -0
- package/tests/recall_hints.mjs +2 -1
- package/tests/repo_map.mjs +4 -0
- package/tests/route_permission_check.mjs +14 -5
- package/tests/rubric_fixtures.mjs +3 -0
- package/tests/run_routine_two_root.mjs +2 -0
- package/tests/sandbox_stage.mjs +69 -7
- package/tests/search_ingest.mjs +7 -0
- package/tests/security_docs.mjs +28 -6
- package/tests/sequence.mjs +64 -0
- package/tests/session-cards.mjs +2 -1
- package/tests/session_start_freshness.mjs +2 -1
- package/tests/ship_broadcast_server.mjs +63 -1
- package/tests/ship_card.mjs +66 -0
- package/tests/ship_ci_deploy.mjs +66 -1
- package/tests/ship_smoke_gate.mjs +76 -0
- package/tests/versions_route.mjs +15 -0
- package/tests/workflow_exec_paths.mjs +16 -4
- package/tests/worktree_guard.mjs +2 -1
package/scripts/gds/ship.js
CHANGED
|
@@ -1103,6 +1103,34 @@ function shouldBailOnFetchFailure(fetchedMain, deployMode, via) {
|
|
|
1103
1103
|
return !serverPublish;
|
|
1104
1104
|
}
|
|
1105
1105
|
|
|
1106
|
+
// ---------- the land outcome (task 1002541, part 1) ----------
|
|
1107
|
+
//
|
|
1108
|
+
// The land chain used to answer with a bare boolean. Every bail site already
|
|
1109
|
+
// computes a precise reason and logs it, but `false` threw that reason away — so
|
|
1110
|
+
// the completion card had only the task's STATUS to go on ('confirmed') and
|
|
1111
|
+
// printed "Deploy landing automatically" even when nothing was landing and the
|
|
1112
|
+
// builder was, under ADR 0120, unpaid. The failure announced itself as success.
|
|
1113
|
+
//
|
|
1114
|
+
// "Not shipped" is not one thing, so this is a three-way, not a boolean:
|
|
1115
|
+
// landed() — merged + deployed; the task flips to shipped.
|
|
1116
|
+
// landPending() — published and auto-merge armed: it DOES land on its own
|
|
1117
|
+
// (CI / the server reconciler), so "landing automatically"
|
|
1118
|
+
// is honest here. A poll timeout is this, not a failure
|
|
1119
|
+
// (task 1049) — keep it out of the loud path.
|
|
1120
|
+
// landBailed(r, n) — nothing is landing. `r` is the reason already logged,
|
|
1121
|
+
// `n` the command that recovers it.
|
|
1122
|
+
// Only landBailed makes the card say NOT landed. Purely additive: no bail site
|
|
1123
|
+
// changes what it DOES, only what it now reports back.
|
|
1124
|
+
function landed() { return { ok: true, pending: false, reason: null, nextStep: null }; }
|
|
1125
|
+
function landPending() { return { ok: false, pending: true, reason: null, nextStep: null }; }
|
|
1126
|
+
function landBailed(reason, nextStep = null) { return { ok: false, pending: false, reason, nextStep }; }
|
|
1127
|
+
|
|
1128
|
+
// The short COMMAND form of strandNextStep (which is prose, sized for the log).
|
|
1129
|
+
// The card has room for one command, not a sentence.
|
|
1130
|
+
function strandCommand(taskId, via) {
|
|
1131
|
+
return via === 'server' ? `node scripts/gds/ship.js ${taskId}` : '/merge-mode';
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1106
1134
|
// ciLand — dispatch the 'ci' confirmed->shipped land to the local or the
|
|
1107
1135
|
// server-mediated path. Async because the server path awaits GDS API calls.
|
|
1108
1136
|
async function ciLand(taskId, branch, valueSummary) {
|
|
@@ -1129,7 +1157,7 @@ async function ciLandServer(taskId, branch, valueSummary) {
|
|
|
1129
1157
|
try {
|
|
1130
1158
|
if (!gitArgvOk(['bundle', 'create', bundlePath, `origin/main..${branch}`], { stdio: 'ignore' })) {
|
|
1131
1159
|
console.log(` ci-land: could not build the branch bundle (nothing new to push, or a git error); task stays at confirmed. ${strandNextStep(taskId, 'server')}`);
|
|
1132
|
-
return
|
|
1160
|
+
return landBailed('could not build the branch bundle', strandCommand(taskId, 'server'));
|
|
1133
1161
|
}
|
|
1134
1162
|
const bundleB64 = fs.readFileSync(bundlePath).toString('base64');
|
|
1135
1163
|
// allowBoxScope: handle the box_scope 403 locally (graceful "stays at
|
|
@@ -1142,16 +1170,16 @@ async function ciLandServer(taskId, branch, valueSummary) {
|
|
|
1142
1170
|
});
|
|
1143
1171
|
if (r.status === 403 && r.data && r.data.error === 'box_scope') {
|
|
1144
1172
|
console.log(' ci-land: this session is BOX-SCOPED — run /builder-reauth (browser, no terminal) to mint a real session, then re-ship. Task stays at confirmed.');
|
|
1145
|
-
return
|
|
1173
|
+
return landBailed('this CLI session is box-scoped', '/builder-reauth');
|
|
1146
1174
|
}
|
|
1147
1175
|
if (r.status === 503) {
|
|
1148
1176
|
console.log(' ci-land: server-mediated push is not configured on the server yet (no GitHub App nor GITHUB_PUSH_TOKEN — operator action, ADR 0055). Task stays at confirmed.');
|
|
1149
|
-
return
|
|
1177
|
+
return landBailed('server-mediated push is not configured (operator action)', strandCommand(taskId, 'server'));
|
|
1150
1178
|
}
|
|
1151
1179
|
if (!r.ok) {
|
|
1152
1180
|
const e = (r.data && (r.data.message || r.data.error)) || `HTTP ${r.status}`;
|
|
1153
1181
|
console.log(` ci-land: server publish failed — ${e}. Task stays at confirmed. ${strandNextStep(taskId, 'server')}`);
|
|
1154
|
-
return
|
|
1182
|
+
return landBailed(`server publish failed — ${e}`, strandCommand(taskId, 'server'));
|
|
1155
1183
|
}
|
|
1156
1184
|
console.log(` ci-land: server pushed ${branch} + PR ${r.data.pr_url} (auto-merge ${r.data.auto_merge ? 'enabled' : 'NOT enabled — merge it on GitHub'}).`);
|
|
1157
1185
|
return pollServerPublish(taskId, branch);
|
|
@@ -1230,17 +1258,33 @@ function strandNextStep(taskId, via) {
|
|
|
1230
1258
|
return `Run /merge-mode to land it — or re-run \`node scripts/gds/ship.js ${taskId}\`, which resumes from here (task 941).`;
|
|
1231
1259
|
}
|
|
1232
1260
|
|
|
1233
|
-
//
|
|
1261
|
+
// The standard "published — it'll land on its own" success-pending banner
|
|
1234
1262
|
// (task 1049). Shared by both poll paths so the message is identical and never
|
|
1235
1263
|
// reads as a failure. The task legitimately stays at 'confirmed' until CI lands
|
|
1236
1264
|
// it; the server reconciles it to 'shipped' (publish-status reconcile-on-read)
|
|
1237
1265
|
// and a re-run resumes (task 941).
|
|
1238
|
-
|
|
1266
|
+
//
|
|
1267
|
+
// task 1002606 (finding P-3): the banner used to end at "NOT a failure", so when
|
|
1268
|
+
// a land DID fail (hand-written conflict, PR closed unmerged, persistent red
|
|
1269
|
+
// check) the builder's last signal said everything was fine and the strand sat
|
|
1270
|
+
// invisible until they happened to look. The banner now tells the failure story
|
|
1271
|
+
// too: the server flags a strand needs_rebase (definitive shapes within a sweep,
|
|
1272
|
+
// ambiguous ones at ~30 min), /builder-start warns, and new claims gate on it.
|
|
1273
|
+
// Pure + exported so the wording is pinned by tests/ship_ci_deploy.mjs.
|
|
1274
|
+
function landPendingLines(taskId, { merged } = {}) {
|
|
1275
|
+
return [
|
|
1276
|
+
` ci-land: ✓ Published — auto-merge is armed${merged ? ' and the PR has merged' : ''}. This is NOT a failure.`,
|
|
1277
|
+
` CI is still finishing; GitHub will ${merged ? 'deploy' : 'merge + deploy'} automatically once the required checks pass,`,
|
|
1278
|
+
` and the task flips to 'shipped' on its own. Re-run \`node scripts/gds/ship.js ${taskId}\` anytime to confirm`,
|
|
1279
|
+
` it landed — it resumes from here (task 941). The task is at 'confirmed' until the deploy completes.`,
|
|
1280
|
+
` If it CANNOT land on its own (a hand-written merge conflict, the PR closed without merging, a check`,
|
|
1281
|
+
` that keeps failing), the server flags the task needs_rebase — /builder-start will warn you and new`,
|
|
1282
|
+
` claims are gated until you fix + re-land it (re-run the ship after fixing), or release it.`,
|
|
1283
|
+
];
|
|
1284
|
+
}
|
|
1285
|
+
function printLandPending(taskId, opts = {}) {
|
|
1239
1286
|
console.log('');
|
|
1240
|
-
|
|
1241
|
-
console.log(` CI is still finishing; GitHub will ${merged ? 'deploy' : 'merge + deploy'} automatically once the required checks pass,`);
|
|
1242
|
-
console.log(` and the task flips to 'shipped' on its own. Re-run \`node scripts/gds/ship.js ${taskId}\` anytime to confirm`);
|
|
1243
|
-
console.log(` it landed — it resumes from here (task 941). The task is at 'confirmed' until the deploy completes.`);
|
|
1287
|
+
for (const line of landPendingLines(taskId, opts)) console.log(line);
|
|
1244
1288
|
}
|
|
1245
1289
|
|
|
1246
1290
|
// One module-scope sleep command shared by both poll loops (task 1093 / C9). A
|
|
@@ -1264,17 +1308,17 @@ async function pollServerPublish(taskId, branch) {
|
|
|
1264
1308
|
const outcome = classifyLandOutcome(r.data);
|
|
1265
1309
|
if (outcome === 'shipped') {
|
|
1266
1310
|
console.log(` ci-land: merged (${String(r.data.merge_sha || '').slice(0, 8)}) + deploy-prod succeeded. Prod is live.`);
|
|
1267
|
-
return
|
|
1311
|
+
return landed();
|
|
1268
1312
|
}
|
|
1269
1313
|
if (outcome === 'failed') {
|
|
1270
1314
|
console.log(` ci-land: PR was CLOSED without merging (a required check failed, or it was closed manually). Fix it and re-run \`node scripts/gds/ship.js ${taskId}\` to resume. Task stays at 'confirmed'.`);
|
|
1271
|
-
return
|
|
1315
|
+
return landBailed('the PR was closed without merging (a check failed, or it was closed manually)', `node scripts/gds/ship.js ${taskId}`);
|
|
1272
1316
|
}
|
|
1273
1317
|
}
|
|
1274
1318
|
shellOk(POLL_SLEEP_CMD);
|
|
1275
1319
|
}
|
|
1276
1320
|
printLandPending(taskId);
|
|
1277
|
-
return
|
|
1321
|
+
return landPending();
|
|
1278
1322
|
}
|
|
1279
1323
|
|
|
1280
1324
|
// attestGateTrust — task 1169 (ADR 0058). Ask the SERVER to post the trustless
|
|
@@ -1346,7 +1390,7 @@ async function ciLandLocal(taskId, branch, valueSummary) {
|
|
|
1346
1390
|
console.log(` pushing ${branch} to origin...`);
|
|
1347
1391
|
if (!gitArgvOk(['push', 'origin', branch])) {
|
|
1348
1392
|
console.log(' ci-land: push failed; task stays at confirmed. Fix the push and re-ship.');
|
|
1349
|
-
return
|
|
1393
|
+
return landBailed(`could not push ${branch} to origin`, `node scripts/gds/ship.js ${taskId}`);
|
|
1350
1394
|
}
|
|
1351
1395
|
|
|
1352
1396
|
// task 1169 (ADR 0058): attest the author's trust tier on the head SHA BEFORE
|
|
@@ -1377,7 +1421,7 @@ async function ciLandLocal(taskId, branch, valueSummary) {
|
|
|
1377
1421
|
console.log(' ci-land: opening PR -> main...');
|
|
1378
1422
|
if (!ghArgvOk(buildPrCreateArgv(branch, title, body))) {
|
|
1379
1423
|
console.log(' ci-land: `gh pr create` failed. Is the GitHub CLI installed + authenticated (`gh auth status`)? Open the PR manually; task stays at confirmed.');
|
|
1380
|
-
return
|
|
1424
|
+
return landBailed('`gh pr create` failed (is gh installed + authenticated?)', `node scripts/gds/ship.js ${taskId}`);
|
|
1381
1425
|
}
|
|
1382
1426
|
} else {
|
|
1383
1427
|
console.log(` ci-land: reusing open PR ${existing.url}`);
|
|
@@ -1428,7 +1472,7 @@ function pollCiDeploy(taskId, branch, headSha) {
|
|
|
1428
1472
|
}
|
|
1429
1473
|
if (pr && pr.state === 'CLOSED') {
|
|
1430
1474
|
console.log(` ci-land: PR was CLOSED without merging (a required check failed, or it was closed manually). Fix it and re-run \`node scripts/gds/ship.js ${taskId}\` to resume. Task stays at 'confirmed'.`);
|
|
1431
|
-
return
|
|
1475
|
+
return landBailed('the PR was closed without merging (a check failed, or it was closed manually)', `node scripts/gds/ship.js ${taskId}`);
|
|
1432
1476
|
}
|
|
1433
1477
|
shellOk(POLL_SLEEP_CMD);
|
|
1434
1478
|
}
|
|
@@ -1436,7 +1480,7 @@ function pollCiDeploy(taskId, branch, headSha) {
|
|
|
1436
1480
|
// task 1049: PR open + auto-merge armed but not merged in-window = the publish
|
|
1437
1481
|
// SUCCEEDED and is still in CI — success-pending, not a failure.
|
|
1438
1482
|
printLandPending(taskId);
|
|
1439
|
-
return
|
|
1483
|
+
return landPending();
|
|
1440
1484
|
}
|
|
1441
1485
|
console.log(` ci-land: merged to main (${mergeSha.slice(0, 8)}). Waiting for the prod deploy...`);
|
|
1442
1486
|
|
|
@@ -1464,14 +1508,14 @@ function pollCiDeploy(taskId, branch, headSha) {
|
|
|
1464
1508
|
} else {
|
|
1465
1509
|
console.log(' ci-land: deploy run succeeded but /version + healthz probes both missed (likely transient). Treating the successful deploy run as authoritative.');
|
|
1466
1510
|
}
|
|
1467
|
-
return
|
|
1511
|
+
return landed();
|
|
1468
1512
|
}
|
|
1469
1513
|
shellOk(POLL_SLEEP_CMD);
|
|
1470
1514
|
}
|
|
1471
1515
|
// task 1049: merged but the deploy run hasn't confirmed in-window — still
|
|
1472
1516
|
// success-pending (it deploys on its own), not a failure.
|
|
1473
1517
|
printLandPending(taskId, { merged: true });
|
|
1474
|
-
return
|
|
1518
|
+
return landPending();
|
|
1475
1519
|
}
|
|
1476
1520
|
|
|
1477
1521
|
// ---------- Step 0: preflight (working tree + branch freshness) ----------
|
|
@@ -1997,6 +2041,32 @@ function runSmoke() {
|
|
|
1997
2041
|
return { ran: true, clean: okPms };
|
|
1998
2042
|
}
|
|
1999
2043
|
|
|
2044
|
+
// task 1002605 (G-18): the pre-resolve smoke gate's two message sets, pure so the
|
|
2045
|
+
// wording is pinned by a DB-free unit test (tests/ship_smoke_gate.mjs). A smoke
|
|
2046
|
+
// FAIL used to fall silently through into the claim resolve — claim consumed,
|
|
2047
|
+
// grading skipped with no message, task parked at 'completed', exit 0 — the
|
|
2048
|
+
// choke-point map's likeliest "my task vanished" shape. `abort` is the default
|
|
2049
|
+
// (printed BEFORE the resolve; the claim stays active); `parked` is the
|
|
2050
|
+
// --allow-smoke-fail continuation (printed where the grader would have run, so
|
|
2051
|
+
// the builder knows exactly where the task stopped and how to resume).
|
|
2052
|
+
function smokeGateMessages(taskId) {
|
|
2053
|
+
return {
|
|
2054
|
+
abort: [
|
|
2055
|
+
'Ship ABORTED: the production smoke test FAILED — see the FAIL line(s) above.',
|
|
2056
|
+
'No claim resolved, no credits awarded — your claim is still active and nothing changed.',
|
|
2057
|
+
'The smoke suite hits LIVE endpoints (your change is not deployed yet), so this usually means production is briefly degraded — not that your change broke it.',
|
|
2058
|
+
` - Re-run once it recovers: node scripts/gds/ship.js ${taskId}`,
|
|
2059
|
+
` - Ship anyway (deliberate): node scripts/gds/ship.js ${taskId} --allow-smoke-fail`,
|
|
2060
|
+
` (parks the task at 'completed' ungraded; resume later with --regrade)`,
|
|
2061
|
+
],
|
|
2062
|
+
parked: [
|
|
2063
|
+
'--allow-smoke-fail: smoke FAILED and the ship deliberately stops here.',
|
|
2064
|
+
`Task #${taskId} is parked at 'completed' — ungraded, unconfirmed, no credits awarded.`,
|
|
2065
|
+
`Resume when production is healthy: node scripts/gds/ship.js ${taskId} --regrade`,
|
|
2066
|
+
],
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2000
2070
|
// ---------- Step 3: merge + deploy chain ----------
|
|
2001
2071
|
|
|
2002
2072
|
function currentBranch() {
|
|
@@ -2263,7 +2333,10 @@ async function autoMerge(taskId, valueSummary, { dbOnly = false } = {}) {
|
|
|
2263
2333
|
const branch = currentBranch();
|
|
2264
2334
|
if (!branch || branch === 'main' || branch === 'HEAD') {
|
|
2265
2335
|
console.log(` auto-merge: skipped (current branch is ${branch})`);
|
|
2266
|
-
|
|
2336
|
+
// The merge source IS the current branch, so on main there is nothing to
|
|
2337
|
+
// merge. Half of the single-checkout catch-22 this task documents: the other
|
|
2338
|
+
// half is the "main worktree is on <branch>, not main" bail below.
|
|
2339
|
+
return landBailed(`nothing to merge — this checkout is on ${branch}, not a task branch`, '/merge-mode');
|
|
2267
2340
|
}
|
|
2268
2341
|
|
|
2269
2342
|
// Refresh the data-driven diagrams before computing commits-ahead, so a
|
|
@@ -2299,7 +2372,7 @@ async function autoMerge(taskId, valueSummary, { dbOnly = false } = {}) {
|
|
|
2299
2372
|
const fetchedMain = shellOk('git fetch origin main');
|
|
2300
2373
|
if (!fetchedMain && shouldBailOnFetchFailure(fetchedMain, deployMode, pushVia())) {
|
|
2301
2374
|
console.log(' auto-merge: fetch failed; task stays at confirmed for /merge-mode');
|
|
2302
|
-
return
|
|
2375
|
+
return landBailed('could not fetch origin/main', '/merge-mode');
|
|
2303
2376
|
}
|
|
2304
2377
|
const commitsAhead = gitOk('git rev-list origin/main..HEAD --count');
|
|
2305
2378
|
const verdict = zeroCommitVerdict(commitsAhead, dbOnly);
|
|
@@ -2318,11 +2391,11 @@ async function autoMerge(taskId, valueSummary, { dbOnly = false } = {}) {
|
|
|
2318
2391
|
console.error(' DB — nothing to merge or deploy), re-run with --db-only:');
|
|
2319
2392
|
console.error(` node scripts/gds/ship.js ${taskId} --db-only`);
|
|
2320
2393
|
console.error(' The task stays at confirmed; nothing was merged or deployed.');
|
|
2321
|
-
return
|
|
2394
|
+
return landBailed('no commits ahead of main, and --db-only was not passed', `node scripts/gds/ship.js ${taskId} --db-only`);
|
|
2322
2395
|
}
|
|
2323
2396
|
if (verdict === 'db-only') {
|
|
2324
2397
|
console.log(' auto-merge: deliberate empty ship (--db-only / --allow-empty) - no commits ahead of main; advancing to shipped without merge/deploy.');
|
|
2325
|
-
return
|
|
2398
|
+
return landed();
|
|
2326
2399
|
}
|
|
2327
2400
|
|
|
2328
2401
|
// #679 / ADR 0042: in 'ci' deploy mode the operator's machine never merges or
|
|
@@ -2336,7 +2409,7 @@ async function autoMerge(taskId, valueSummary, { dbOnly = false } = {}) {
|
|
|
2336
2409
|
console.log(` pushing ${branch} to origin...`);
|
|
2337
2410
|
if (!gitArgvOk(['push', 'origin', branch])) {
|
|
2338
2411
|
console.log(' auto-merge: push failed; task stays at confirmed for /merge-mode');
|
|
2339
|
-
return
|
|
2412
|
+
return landBailed(`could not push ${branch} to origin`, '/merge-mode');
|
|
2340
2413
|
}
|
|
2341
2414
|
|
|
2342
2415
|
// Serialize the shared-main-worktree merge (#462 C4; #269 C6). Concurrent
|
|
@@ -2345,7 +2418,7 @@ async function autoMerge(taskId, valueSummary, { dbOnly = false } = {}) {
|
|
|
2345
2418
|
// than racing its working tree.
|
|
2346
2419
|
if (!mergeLock.acquire(REPO_ROOT)) {
|
|
2347
2420
|
console.log(' auto-merge: another active ship or /merge-mode run holds the merge lock; task stays at confirmed for /merge-mode');
|
|
2348
|
-
return
|
|
2421
|
+
return landBailed('another ship or /merge-mode run holds the merge lock', '/merge-mode');
|
|
2349
2422
|
}
|
|
2350
2423
|
let mergedClean = false;
|
|
2351
2424
|
try {
|
|
@@ -2355,7 +2428,7 @@ async function autoMerge(taskId, valueSummary, { dbOnly = false } = {}) {
|
|
|
2355
2428
|
const busy = mainWorktreeBusy();
|
|
2356
2429
|
if (busy) {
|
|
2357
2430
|
console.log(` auto-merge: main worktree busy (${busy}); task stays at confirmed for /merge-mode`);
|
|
2358
|
-
return
|
|
2431
|
+
return landBailed(`the main worktree is busy (${busy})`, '/merge-mode');
|
|
2359
2432
|
}
|
|
2360
2433
|
|
|
2361
2434
|
console.log(` switching to main worktree (${REPO_ROOT}) for merge...`);
|
|
@@ -2369,19 +2442,26 @@ async function autoMerge(taskId, valueSummary, { dbOnly = false } = {}) {
|
|
|
2369
2442
|
}
|
|
2370
2443
|
if (!shellOk(`git fetch origin`, { cwd: REPO_ROOT })) {
|
|
2371
2444
|
console.log(' auto-merge: fetch failed; task stays at confirmed for /merge-mode');
|
|
2372
|
-
return
|
|
2445
|
+
return landBailed('could not fetch origin in the main worktree', '/merge-mode');
|
|
2373
2446
|
}
|
|
2374
2447
|
|
|
2375
2448
|
// The main worktree must currently have main checked out.
|
|
2376
2449
|
const onMain = gitOk('git rev-parse --abbrev-ref HEAD', { cwd: REPO_ROOT });
|
|
2377
2450
|
if (onMain !== 'main') {
|
|
2378
2451
|
console.log(` auto-merge: main worktree is on ${onMain}, not main; task stays at confirmed`);
|
|
2379
|
-
|
|
2452
|
+
// The other half of the single-checkout catch-22: the merge target tree is
|
|
2453
|
+
// the one holding the feature branch, so there is nowhere to merge INTO.
|
|
2454
|
+
// Name the cure, not just the symptom — this is the bail that used to be
|
|
2455
|
+
// invisible behind "landing automatically".
|
|
2456
|
+
return landBailed(
|
|
2457
|
+
`the main checkout is on ${onMain}, not main — work a task in its own worktree so the main checkout stays on main`,
|
|
2458
|
+
'/merge-mode',
|
|
2459
|
+
);
|
|
2380
2460
|
}
|
|
2381
2461
|
|
|
2382
2462
|
if (!gitArgvOk(['merge', '--ff-only', 'origin/main'], { cwd: REPO_ROOT })) {
|
|
2383
2463
|
console.log(' auto-merge: main is ahead of origin somehow; task stays at confirmed');
|
|
2384
|
-
return
|
|
2464
|
+
return landBailed('the local main is ahead of origin/main', '/merge-mode');
|
|
2385
2465
|
}
|
|
2386
2466
|
|
|
2387
2467
|
// #681: build the merge argv and run it WITHOUT a shell. valueSummary is
|
|
@@ -2389,13 +2469,13 @@ async function autoMerge(taskId, valueSummary, { dbOnly = false } = {}) {
|
|
|
2389
2469
|
// `$(...)`/backticks in it can never be interpreted as a command.
|
|
2390
2470
|
if (!gitArgvOk(buildMergeArgv(branch, valueSummary), { cwd: REPO_ROOT })) {
|
|
2391
2471
|
console.log(' auto-merge: merge failed (conflicts?); task stays at confirmed for manual /merge-mode');
|
|
2392
|
-
return
|
|
2472
|
+
return landBailed(`merging ${branch} into main failed (conflicts?)`, '/merge-mode');
|
|
2393
2473
|
}
|
|
2394
2474
|
|
|
2395
2475
|
console.log(' smoke against post-merge state...');
|
|
2396
2476
|
if (!runSmoke().clean) {
|
|
2397
2477
|
console.log(' auto-merge: post-merge smoke failed; task stays at confirmed; investigate');
|
|
2398
|
-
return
|
|
2478
|
+
return landBailed('post-merge smoke tests failed', '/merge-mode');
|
|
2399
2479
|
}
|
|
2400
2480
|
|
|
2401
2481
|
console.log(' pushing merged main to origin...');
|
|
@@ -2406,7 +2486,7 @@ async function autoMerge(taskId, valueSummary, { dbOnly = false } = {}) {
|
|
|
2406
2486
|
// cross-platform reason as the other node spawns here (#660).
|
|
2407
2487
|
if (!shellOk(`"${process.execPath}" ${JSON.stringify(path.join(__dirname, 'push-main.js'))}`, { cwd: REPO_ROOT })) {
|
|
2408
2488
|
console.log(' auto-merge: push failed; task stays at confirmed');
|
|
2409
|
-
return
|
|
2489
|
+
return landBailed('could not push the merged main to origin', '/merge-mode');
|
|
2410
2490
|
}
|
|
2411
2491
|
|
|
2412
2492
|
// Deploy step. The core carries NO deploy target of its own — under the
|
|
@@ -2445,11 +2525,11 @@ async function autoMerge(taskId, valueSummary, { dbOnly = false } = {}) {
|
|
|
2445
2525
|
console.log(' auto-merge: /healthz 200 on re-probe — deploy actually succeeded (deploy.sh raced the restart window). Proceeding.');
|
|
2446
2526
|
} else {
|
|
2447
2527
|
console.log(' auto-merge: deploy failed (/healthz never returned 200 after retries); task stays at confirmed');
|
|
2448
|
-
return
|
|
2528
|
+
return landBailed('merged to main, but the deploy failed (/healthz never returned 200)', '/merge-mode');
|
|
2449
2529
|
}
|
|
2450
2530
|
} else {
|
|
2451
2531
|
console.log(' auto-merge: ~/deploy.sh returned non-zero and no healthzUrl configured to re-probe; task stays at confirmed');
|
|
2452
|
-
return
|
|
2532
|
+
return landBailed('merged to main, but ~/deploy.sh returned non-zero (no healthz to re-probe)', '/merge-mode');
|
|
2453
2533
|
}
|
|
2454
2534
|
}
|
|
2455
2535
|
}
|
|
@@ -2470,7 +2550,7 @@ async function autoMerge(taskId, valueSummary, { dbOnly = false } = {}) {
|
|
|
2470
2550
|
|
|
2471
2551
|
console.log(' flipping task to shipped...');
|
|
2472
2552
|
mergedClean = true;
|
|
2473
|
-
return
|
|
2553
|
+
return landed();
|
|
2474
2554
|
} finally {
|
|
2475
2555
|
// Leave REPO_ROOT clean for the NEXT ship/merge no matter how we exited.
|
|
2476
2556
|
// A failed merge here wedges everyone else: a conflict leaves the tree
|
|
@@ -2759,14 +2839,19 @@ async function mergeAndDeploy({ taskId, summary, advanceToMerge, skipMerge, dbOn
|
|
|
2759
2839
|
if (advanceToMerge && skipMerge) {
|
|
2760
2840
|
console.log('');
|
|
2761
2841
|
console.log('--no-merge: stopping at confirmed. Run /merge-mode later to land it.');
|
|
2762
|
-
return;
|
|
2842
|
+
return landBailed('--no-merge was passed, so the land was skipped', '/merge-mode');
|
|
2763
2843
|
}
|
|
2764
|
-
|
|
2844
|
+
// Grade failed: the card renders the 'failed' stage ('not deployed'), so there
|
|
2845
|
+
// is no land outcome to report.
|
|
2846
|
+
if (!advanceToMerge) return null;
|
|
2765
2847
|
|
|
2766
2848
|
console.log('');
|
|
2767
2849
|
console.log('Auto-merge chain (confirmed → shipped):');
|
|
2768
2850
|
const merged = await autoMerge(taskId, summary, { dbOnly });
|
|
2769
|
-
|
|
2851
|
+
// NOTE: `merged` is an OBJECT now — every value is truthy, so this MUST test
|
|
2852
|
+
// .ok. A bare `if (!merged)` would fall through on a bail and POST /ship for a
|
|
2853
|
+
// task that never landed.
|
|
2854
|
+
if (!merged.ok) return merged;
|
|
2770
2855
|
|
|
2771
2856
|
const ship = await (await cliClient()).tasks.postTasksIdShip({ id: taskId, body: {} });
|
|
2772
2857
|
if (!ship.ok) {
|
|
@@ -2777,7 +2862,11 @@ async function mergeAndDeploy({ taskId, summary, advanceToMerge, skipMerge, dbOn
|
|
|
2777
2862
|
console.log(`Task #${taskId} is now: shipped (the server reconciled it from the merged PR).`);
|
|
2778
2863
|
} else {
|
|
2779
2864
|
console.error(` /tasks/${taskId}/ship failed:`, ship.data);
|
|
2780
|
-
|
|
2865
|
+
// The merge itself succeeded — only the status flip failed, so a re-run
|
|
2866
|
+
// resumes cleanly (task 941). Report it rather than letting the card claim
|
|
2867
|
+
// the land is in progress.
|
|
2868
|
+
const e = (ship.data && (ship.data.message || ship.data.error)) || `HTTP ${ship.status}`;
|
|
2869
|
+
return landBailed(`merged, but the ship call failed — ${e}`, `node scripts/gds/ship.js ${taskId}`);
|
|
2781
2870
|
}
|
|
2782
2871
|
} else {
|
|
2783
2872
|
console.log(`Task #${taskId} is now: shipped`);
|
|
@@ -2815,6 +2904,7 @@ async function mergeAndDeploy({ taskId, summary, advanceToMerge, skipMerge, dbOn
|
|
|
2815
2904
|
// server-side in ship-broadcast.postShipBroadcast, so it is preserved.
|
|
2816
2905
|
console.log(' Discord broadcast: deferred to the server — it posts #ship-feed after the deploy lands.');
|
|
2817
2906
|
}
|
|
2907
|
+
return merged;
|
|
2818
2908
|
}
|
|
2819
2909
|
|
|
2820
2910
|
// Memory + session-digest push, gated on a successful land (confirmed/shipped).
|
|
@@ -2891,7 +2981,10 @@ function postShipCloseout(taskId) {
|
|
|
2891
2981
|
}
|
|
2892
2982
|
|
|
2893
2983
|
async function finishShip({ taskId, summary, advanceToMerge, skipMerge, postGradeStatus, doneLine, card, dbOnly = false }) {
|
|
2894
|
-
|
|
2984
|
+
// task 1002541: the land outcome carries WHY a land didn't happen. Without it
|
|
2985
|
+
// the card had only the status to go on and printed 'landing automatically'
|
|
2986
|
+
// over a merge that had already bailed.
|
|
2987
|
+
const land = await mergeAndDeploy({ taskId, summary, advanceToMerge, skipMerge, dbOnly });
|
|
2895
2988
|
// task 1719: mergeAndDeploy may have landed the task to 'shipped' IN-SESSION — the
|
|
2896
2989
|
// enable-success poll (pollCiDeploy) or, since task 1718, the enable-failure server
|
|
2897
2990
|
// poll (pollServerPublish). finishShip was handed postGradeStatus from the CONFIRM
|
|
@@ -2924,7 +3017,7 @@ async function finishShip({ taskId, summary, advanceToMerge, skipMerge, postGrad
|
|
|
2924
3017
|
// always-printed fallback + the headless summary). Strictly best-effort.
|
|
2925
3018
|
// task 1719: pass the reconciled status so a just-landed ship shows SHIPPED (and
|
|
2926
3019
|
// writes no breadcrumb), not the stale pre-merge 'confirmed'.
|
|
2927
|
-
await printCompletionCard({ taskId, postGradeStatus: landedStatus, card, usage });
|
|
3020
|
+
await printCompletionCard({ taskId, postGradeStatus: landedStatus, card, usage, land });
|
|
2928
3021
|
// task 932: reconcile the working tree — ONLY on a successful land. A
|
|
2929
3022
|
// grade-failed ship stays at 'completed'; leave that tree untouched so the
|
|
2930
3023
|
// builder can fix findings + re-ship.
|
|
@@ -2959,7 +3052,7 @@ function diffstatFor(baseline) {
|
|
|
2959
3052
|
// in-feed HTML to a temp file and prints an [otb-card-html] marker the
|
|
2960
3053
|
// builder-ship skill picks up to render it in the chat feed. Every step is
|
|
2961
3054
|
// best-effort — a summary card must NEVER fail or undo a completed ship.
|
|
2962
|
-
async function printCompletionCard({ taskId, postGradeStatus, card, usage }) {
|
|
3055
|
+
async function printCompletionCard({ taskId, postGradeStatus, card, usage, land = null }) {
|
|
2963
3056
|
if (!card) return;
|
|
2964
3057
|
try {
|
|
2965
3058
|
const shipCard = require('../../modules/lifecycle/ship-card');
|
|
@@ -3029,6 +3122,10 @@ async function printCompletionCard({ taskId, postGradeStatus, card, usage }) {
|
|
|
3029
3122
|
credits: { awarded: card.creditsAwarded, total, perTaskCredit: rewardPolicy(serverReward).perTaskCredit },
|
|
3030
3123
|
usage: { tokens, costUsd, newTokens, cachedTokens },
|
|
3031
3124
|
deploy: { commit },
|
|
3125
|
+
// task 1002541: absent (the default) ⇒ the card behaves exactly as before,
|
|
3126
|
+
// which keeps every other caller — resumeMerge, the claim card, and
|
|
3127
|
+
// land-watch replaying breadcrumbs written by older versions — unchanged.
|
|
3128
|
+
land,
|
|
3032
3129
|
};
|
|
3033
3130
|
const model = shipCard.buildCardModel(cardInputs);
|
|
3034
3131
|
|
|
@@ -3179,7 +3276,7 @@ async function resumeMerge(taskId, { summary, skipMerge, force, task, dbOnly = f
|
|
|
3179
3276
|
// The first ship stored value_summary; reuse it so the merge commit + broadcast
|
|
3180
3277
|
// carry meaningful text even when --summary is omitted on the re-run.
|
|
3181
3278
|
const effSummary = summary || task?.value_summary || '';
|
|
3182
|
-
await mergeAndDeploy({ taskId, summary: effSummary, advanceToMerge: true, skipMerge, dbOnly });
|
|
3279
|
+
const land = await mergeAndDeploy({ taskId, summary: effSummary, advanceToMerge: true, skipMerge, dbOnly });
|
|
3183
3280
|
const usage = await postShipSync(taskId, 'confirmed');
|
|
3184
3281
|
console.log('');
|
|
3185
3282
|
console.log('Done — merge/deploy resume complete.');
|
|
@@ -3192,7 +3289,7 @@ async function resumeMerge(taskId, { summary, skipMerge, force, task, dbOnly = f
|
|
|
3192
3289
|
const t = await (await cliClient()).tasks.getTasksId({ id: taskId });
|
|
3193
3290
|
const status = (t.ok && t.data && t.data.task) ? t.data.task.status : 'shipped';
|
|
3194
3291
|
await printCompletionCard({
|
|
3195
|
-
taskId, postGradeStatus: status, usage,
|
|
3292
|
+
taskId, postGradeStatus: status, usage, land,
|
|
3196
3293
|
card: { title: task?.title, valueSummary: effSummary, notes: '', creditsAwarded: null },
|
|
3197
3294
|
});
|
|
3198
3295
|
} catch (_) { /* the resume already succeeded; the card is a bonus */ }
|
|
@@ -3205,7 +3302,7 @@ async function shipMain() {
|
|
|
3205
3302
|
const args = process.argv.slice(2);
|
|
3206
3303
|
const taskId = Number(args[0]);
|
|
3207
3304
|
if (!Number.isFinite(taskId)) {
|
|
3208
|
-
console.error('usage: node scripts/gds/ship.js <task-id> --notes "..." --summary "..." [--surprise "text" | --surprise none | --no-surprise] [--no-merge] [--db-only] [--verified] [--allow-empty] [--base <ref>]');
|
|
3305
|
+
console.error('usage: node scripts/gds/ship.js <task-id> --notes "..." --summary "..." [--surprise "text" | --surprise none | --no-surprise] [--no-merge] [--db-only] [--verified] [--allow-empty] [--allow-smoke-fail] [--base <ref>]');
|
|
3209
3306
|
process.exit(2);
|
|
3210
3307
|
}
|
|
3211
3308
|
const notes = arg('--notes', args) || '';
|
|
@@ -3235,6 +3332,11 @@ async function shipMain() {
|
|
|
3235
3332
|
// empty diff is refused (it used to slip through under --skip-grade — the
|
|
3236
3333
|
// task-1363 phantom ship). Never overrides a dirty working tree.
|
|
3237
3334
|
const allowEmpty = args.includes('--allow-empty');
|
|
3335
|
+
// task 1002605 (G-18): explicit opt-in to continue past a FAILED production
|
|
3336
|
+
// smoke run. Without it a smoke FAIL now ABORTS before the claim resolves
|
|
3337
|
+
// (claim intact); with it, the resolve proceeds and the task deliberately
|
|
3338
|
+
// parks at 'completed' ungraded — and the ship says so instead of "Done."
|
|
3339
|
+
const allowSmokeFail = args.includes('--allow-smoke-fail');
|
|
3238
3340
|
// Surprise flag handling — see captureSurpriseLearning(). Resolved below
|
|
3239
3341
|
// once we know which claim is being shipped (so touches[] can become tags).
|
|
3240
3342
|
const surpriseRaw = arg('--surprise', args);
|
|
@@ -3303,16 +3405,16 @@ async function shipMain() {
|
|
|
3303
3405
|
// ---- Hard cap: refuse to ship over the monthly budget (V3.R84 #302, C3) ----
|
|
3304
3406
|
// Shipping runs the subagent grader, which spends API $. If this builder is at
|
|
3305
3407
|
// or over their monthly budget, refuse here — BEFORE the grader runs and BEFORE
|
|
3306
|
-
// the claim resolves, so the claim stays active and no cost is incurred.
|
|
3307
|
-
//
|
|
3308
|
-
// (PATCH /builders/:id/budget
|
|
3309
|
-
// (e.g. an older server) never blocks a ship.
|
|
3408
|
+
// the claim resolves, so the claim stays active and no cost is incurred. Any
|
|
3409
|
+
// Metic+ builder lifts the block by raising the builder's budget
|
|
3410
|
+
// (PATCH /builders/:id/budget — builder.budget.set, metic-floored since ADR 0157).
|
|
3411
|
+
// Fail-open: a missing/unreadable cost status (e.g. an older server) never blocks a ship.
|
|
3310
3412
|
const costStatus = me.data.cost;
|
|
3311
3413
|
if (costStatus && costStatus.level === 'over') {
|
|
3312
3414
|
console.error('');
|
|
3313
3415
|
console.error(`Ship refused: monthly budget exceeded — $${costStatus.spend_mtd_usd} of $${costStatus.budget_mtd_usd} spent this month (${costStatus.pct_used}%).`);
|
|
3314
3416
|
console.error('Shipping runs the quality grader, which spends API budget. No claim resolved, no credits awarded.');
|
|
3315
|
-
console.error('Ask
|
|
3417
|
+
console.error('Ask any Metic or Archon to raise your monthly budget (PATCH /builders/:id/budget), then re-ship.');
|
|
3316
3418
|
process.exit(1);
|
|
3317
3419
|
}
|
|
3318
3420
|
|
|
@@ -3539,6 +3641,19 @@ async function shipMain() {
|
|
|
3539
3641
|
console.log(`Verification: smoke=${smoke.clean ? 'clean' : 'FAIL'}, overall=${passed ? 'PASS' : 'FAIL'}`);
|
|
3540
3642
|
console.log('');
|
|
3541
3643
|
|
|
3644
|
+
// task 1002605 (G-18): a smoke FAIL used to fall straight through into the
|
|
3645
|
+
// claim resolve — claim consumed, grading silently skipped, task parked at
|
|
3646
|
+
// 'completed', exit 0, and the builder read "Done." believing they shipped.
|
|
3647
|
+
// Abort BEFORE the resolve instead — the same claim-preserving posture as the
|
|
3648
|
+
// budget cap and the sandbox-review gate above. --allow-smoke-fail is the
|
|
3649
|
+
// deliberate continuation; the parked message where the grader would have run
|
|
3650
|
+
// says exactly where that leaves the task.
|
|
3651
|
+
if (!passed && !allowSmokeFail) {
|
|
3652
|
+
console.error('');
|
|
3653
|
+
for (const line of smokeGateMessages(taskId).abort) console.error(line);
|
|
3654
|
+
process.exit(1);
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3542
3657
|
// ---- Step 1: resolve the claim (lands at completed) ----
|
|
3543
3658
|
// V3.R18 (#209): resolveClaim no longer runs the v1 deterministic grader.
|
|
3544
3659
|
// It now lands the task at `completed` when verification (smoke) passes, and
|
|
@@ -3673,6 +3788,12 @@ async function shipMain() {
|
|
|
3673
3788
|
ideaBonusInfo = gradePost.data.ideaBonus ?? null;
|
|
3674
3789
|
|
|
3675
3790
|
printGradeOutcome(g, taskId);
|
|
3791
|
+
} else if (!passed && taskStatus === 'completed') {
|
|
3792
|
+
// task 1002605 (G-18): the --allow-smoke-fail continuation. The resolve above
|
|
3793
|
+
// consumed the claim with verification.passed=false, so the grader deliberately
|
|
3794
|
+
// does not run. This used to be a silent fall-through; say where the task is.
|
|
3795
|
+
console.log('');
|
|
3796
|
+
for (const line of smokeGateMessages(taskId).parked) console.log(line);
|
|
3676
3797
|
}
|
|
3677
3798
|
|
|
3678
3799
|
printCreditsAndAchievements({
|
|
@@ -3898,6 +4019,10 @@ module.exports = {
|
|
|
3898
4019
|
// #943: pure grade-outcome formatter — exported so the grader-unavailable vs
|
|
3899
4020
|
// FAIL display branch is unit-tested without capturing stdout.
|
|
3900
4021
|
formatGradeOutcome,
|
|
4022
|
+
// task 1002605 (G-18): the pre-resolve smoke gate's abort/parked wording —
|
|
4023
|
+
// exported pure so the loudness contract (claim intact on abort; parked +
|
|
4024
|
+
// resume path on --allow-smoke-fail) is pinned without running a ship.
|
|
4025
|
+
smokeGateMessages,
|
|
3901
4026
|
// task 941 (V4.R03): pure resume-action decision for a claim-less `ship.js <id>`
|
|
3902
4027
|
// re-run — exported so the truth table (completed→grade, confirmed→merge,
|
|
3903
4028
|
// ownership, terminal states) is unit-tested with no git/network.
|
|
@@ -3926,6 +4051,11 @@ module.exports = {
|
|
|
3926
4051
|
// dead-end strand at confirmed), enable-success watches GitHub. Exported so the
|
|
3927
4052
|
// regression test locks in that a worktree enable-failure still waits for the land.
|
|
3928
4053
|
landPollAfterAutoMerge,
|
|
4054
|
+
// task 1002541: the land-outcome constructors. Exported so the test can pin the
|
|
4055
|
+
// trap that made this a 3-way instead of a boolean — every outcome is a TRUTHY
|
|
4056
|
+
// object, so a consumer MUST test `.ok`; the old `if (!merged)` would sail past
|
|
4057
|
+
// a bail. Also pins pending (a poll timeout) as distinct from bailed.
|
|
4058
|
+
landed, landPending, landBailed, strandCommand,
|
|
3929
4059
|
// task 1719: pure post-land status reconciler — a task that reached 'shipped'
|
|
3930
4060
|
// in-session shows the SHIPPED banner + writes no breadcrumb; otherwise keep the
|
|
3931
4061
|
// pre-merge status (confirmed → next-turn breadcrumb fallback). Exported for tests.
|
|
@@ -3944,6 +4074,9 @@ module.exports = {
|
|
|
3944
4074
|
// task 1052 (E): box-aware "what to do next" for a confirmed-strand — a box
|
|
3945
4075
|
// (via 'server') is pointed at the resume, never at gh-only /merge-mode.
|
|
3946
4076
|
strandNextStep,
|
|
4077
|
+
// task 1002606: the success-pending banner lines, pure — pinned so the strand
|
|
4078
|
+
// detection story ("NOT a failure" + how you WILL find out if it fails) can't drift.
|
|
4079
|
+
landPendingLines,
|
|
3947
4080
|
// #857: resolveDeployMode (env + committed config) so doctor.js reports the
|
|
3948
4081
|
// active deploy mode exactly as ship will use it.
|
|
3949
4082
|
resolveDeployMode,
|
|
@@ -44,7 +44,7 @@ const EMPTY = process.argv.includes('--empty');
|
|
|
44
44
|
// --rank: view the hall as a different rank — the /me fixture's builder.rank is
|
|
45
45
|
// overridden in the response, so the Archon-gated pages (watch, harbor, gate)
|
|
46
46
|
// render their real content instead of the sealed notice. Preview-only theatre:
|
|
47
|
-
// the real pages are server-gated (
|
|
47
|
+
// the real pages are server-gated (requireGovernancePage / ADR 0016).
|
|
48
48
|
const RANK_OVERRIDE = arg('--rank');
|
|
49
49
|
const MODULES_OFF = String(arg('--modules-off') || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
50
50
|
// --modules-on: force-report modules as enabled — a dev box's local config
|
package/scripts/migrate.sh
CHANGED
|
@@ -51,7 +51,7 @@ fi
|
|
|
51
51
|
|
|
52
52
|
DB="${PGDATABASE:-amazonprimea}"
|
|
53
53
|
|
|
54
|
-
# Test/ops seam (
|
|
54
|
+
# Test/ops seam (added with the 1.18.19 space-in-path fix): --print-migrations walks the REAL core-migration loop but reports
|
|
55
55
|
# each path instead of applying it, and touches NO DB. tests/migrate_root_resolution.mjs uses it to
|
|
56
56
|
# assert that a checkout whose path contains a space still yields whole, readable paths. Reporting
|
|
57
57
|
# through the real loop (rather than a copy of it in the test) is the point: if the loop ever reverts
|
|
@@ -107,7 +107,7 @@ fi
|
|
|
107
107
|
# key, so moving a file between migrations/ and migrations/game/ does NOT re-run it on prod.
|
|
108
108
|
#
|
|
109
109
|
# The two counters are what the no-op guard below reads: a run that LISTED core migrations but
|
|
110
|
-
# neither applied nor recognized a single one is the space-in-path failure (
|
|
110
|
+
# neither applied nor recognized a single one is the space-in-path failure (fixed in 1.18.19) and must
|
|
111
111
|
# be fatal, not silent. Both loops that call this run in the CURRENT shell (herestring / plain glob,
|
|
112
112
|
# never a `... | while` pipeline), so these increments are visible to the caller.
|
|
113
113
|
MIGRATIONS_APPLIED=0
|
|
@@ -160,7 +160,7 @@ fi
|
|
|
160
160
|
# affects whether it re-runs on OTB prod. Today CORE_ROOT == INSTANCE_ROOT (single checkout);
|
|
161
161
|
# once the core installs from node_modules the two roots diverge and this still merges correctly.
|
|
162
162
|
#
|
|
163
|
-
# PATHS WITH SPACES (
|
|
163
|
+
# PATHS WITH SPACES (fixed in 1.18.19): this list is NEWLINE-separated and must be read a line at a
|
|
164
164
|
# time. The old `for f in $MIGRATIONS` word-split it on IFS — which includes space — so any checkout
|
|
165
165
|
# whose absolute path contains a space ("/Users/.../Hermeslines Marketing/...") fragmented every
|
|
166
166
|
# path, the `[ -e ]` guard below silently dropped all of them, and ZERO core migrations ran.
|
|
@@ -191,7 +191,7 @@ while IFS= read -r f; do
|
|
|
191
191
|
apply_migration "$f"
|
|
192
192
|
done <<< "$MIGRATIONS"
|
|
193
193
|
|
|
194
|
-
# --- No-op guard (
|
|
194
|
+
# --- No-op guard (added in 1.18.19) -------------------------------------------
|
|
195
195
|
# Silence is what made the space-in-path bug expensive to diagnose: core migrations were skipped
|
|
196
196
|
# without a word, and the first error the operator saw came from an unrelated MODULE migration
|
|
197
197
|
# hitting the empty schema. If the glob LISTED core migrations but we neither applied nor recognized
|
|
@@ -289,7 +289,7 @@ if [ "${OTB_POST_DEPLOY_CHECK:-0}" = "1" ]; then
|
|
|
289
289
|
echo "post-deploy: no prior score ledger — running in baseline-only mode"
|
|
290
290
|
fi
|
|
291
291
|
# `${VAR:+"$VAR"}` expands to NOTHING when empty and to the quoted value otherwise. A bare
|
|
292
|
-
# $BASELINE_FLAG relied on word-splitting to vanish (the habit that caused
|
|
292
|
+
# $BASELINE_FLAG relied on word-splitting to vanish (the habit that caused the space-in-path migration skip), and
|
|
293
293
|
# quoting it outright would instead pass an empty argv entry that argparse rejects. An array
|
|
294
294
|
# would need bash 4 — `set -u` + `"${arr[@]}"` on an empty array aborts under the bash 3.2 that
|
|
295
295
|
# macOS still ships, and this script's shebang takes whatever bash is on PATH.
|