@bongos/core 1.19.1033 → 1.19.1035
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 +204 -99
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/grade-recover/SKILL.md +2 -1
- package/.claude/skills/grader-health/SKILL.md +4 -0
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +6 -0
- package/clients/bongos-client/index.cjs +6 -0
- package/clients/bongos-client/index.d.ts +8 -0
- package/clients/bongos-client/index.mjs +6 -0
- package/docs/adr/0089-modular-multi-model-adversarial-grader.md +15 -0
- package/docs/adr/README.md +1 -1
- package/docs/api/openapi.json +120 -4
- package/docs/api-reference.md +9 -2
- package/docs/architecture.md +3 -2
- package/docs/canonical-permissions.md +2 -2
- package/docs/copy-inventory.md +34 -30
- package/docs/copy-registry.json +69 -33
- package/docs/module-api-changelog.md +5 -1
- package/docs/recipes/local-dev.md +45 -3
- package/docs/recipes/packaging-the-core.md +1 -1
- package/modules/autonomy/cadence.js +52 -0
- package/modules/autonomy/db.js +19 -0
- package/modules/autonomy/routes/autonomy.js +28 -0
- package/modules/government/catalog.js +2 -2
- package/modules/government/protected-surfaces.json +1 -0
- package/modules/grading/grade-engine.js +267 -0
- package/modules/grading/grader-subagent.js +4 -3
- package/modules/grading/grader-workers/index.js +67 -11
- package/modules/grading/grader-workers/worker-verdict.js +32 -2
- package/modules/grading/grader.js +109 -88
- package/modules/hall-ui/public/deploy.js +35 -0
- package/modules/hall-ui/public/project-settings.css +7 -0
- package/modules/lifecycle/routes/tasks.js +19 -4
- package/modules/lifecycle/ship-assurance.js +17 -1
- package/modules/provisioning/control-plane-guard.js +14 -13
- package/modules/provisioning/core-upgrade.js +30 -17
- package/modules/provisioning/public-refusal.js +44 -0
- package/modules/provisioning/routes/disconnect.js +2 -1
- package/modules/provisioning/routes/env-manifest.js +5 -4
- package/modules/provisioning/routes/provisioning.js +16 -15
- package/modules/provisioning/routes/repo-private.js +2 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +80 -0
- package/scripts/gds/autobongos-run.js +10 -2
- package/scripts/gds/backfill-cost.js +20 -9
- package/scripts/gds/claim.js +2 -2
- package/scripts/gds/credential-presence.js +281 -0
- package/scripts/gds/deploy-main-audit-gate.sh +11 -4
- package/scripts/gds/dev-lib.js +212 -5
- package/scripts/gds/dev.js +102 -50
- package/scripts/gds/doctor.js +3 -2
- package/scripts/gds/grade-correlation-audit.js +280 -0
- package/scripts/gds/grade-replay.js +237 -0
- package/scripts/gds/hooks/pre-push.js +7 -3
- package/scripts/gds/main-audit.js +19 -10
- package/scripts/gds/memory-map.js +3 -1
- package/scripts/gds/onboarding-config.js +3 -2
- package/scripts/gds/plain-cards.js +4 -0
- package/scripts/gds/preflight.js +12 -4
- package/scripts/gds/provision-core-upgrade.js +155 -4
- package/scripts/gds/provision-repo-auth.js +15 -0
- package/scripts/gds/provision.js +1 -1
- package/scripts/gds/publish-credential-check.js +3 -0
- package/scripts/gds/push-main.js +14 -3
- package/scripts/gds/push-path-brief.js +3 -2
- package/scripts/gds/run-routine.js +18 -5
- package/scripts/gds/run-unit-tests.js +14 -3
- package/scripts/gds/session-cost.js +12 -3
- package/scripts/gds/ship-deploy-target.js +11 -6
- package/scripts/gds/ship-grade-steps.js +33 -0
- package/scripts/gds/ship-land.js +157 -11
- package/scripts/gds/ship-merge.js +3 -2
- package/scripts/gds/ship-preflight-steps.js +30 -8
- package/scripts/gds/ship.js +1 -1
- package/scripts/gds/upgrade.js +3 -3
- package/scripts/hall-preview/README.md +20 -3
- package/scripts/hall-preview/live-identity.js +1 -1
- package/scripts/hall-preview/refresh-fixtures.js +71 -11
- package/scripts/hall-preview/scrub-identity.js +296 -0
- package/src/bongos/core-update.js +23 -1
- package/src/bongos/routes/core-update.js +27 -2
- package/src/bongos/routes/security.js +3 -3
- package/src/build-info.js +4 -2
- package/src/module-api.js +1 -1
- package/tests/adr_0089_scope.mjs +70 -0
- package/tests/autobongos_cadence.mjs +74 -0
- package/tests/consumer_layout_boot.mjs +363 -31
- package/tests/core_upgrade_door.mjs +40 -6
- package/tests/core_upgrade_runner.mjs +240 -4
- package/tests/credential_presence.mjs +153 -0
- package/tests/deploy_audit_gate.mjs +5 -2
- package/tests/deploy_page_projects.mjs +63 -2
- package/tests/dev_lib.mjs +2 -1
- package/tests/dev_windows.mjs +324 -0
- package/tests/env_knobs_neutral_prefix.mjs +203 -0
- package/tests/fixtures/grade-replay/recorded-panel-grades.json +27 -0
- package/tests/government_protected_surfaces.mjs +10 -0
- package/tests/grade_correlation_audit.mjs +159 -0
- package/tests/grade_engine.mjs +249 -0
- package/tests/grade_engine_replay.mjs +99 -0
- package/tests/grader_prompt_window_bound.mjs +132 -0
- package/tests/grader_unbacked_score.mjs +194 -0
- package/tests/hall_preview_scrub.mjs +214 -0
- package/tests/integration_parked_ratchet.mjs +24 -1
- package/tests/provisioning_public_refusal.mjs +195 -0
- package/tests/publish_status_branch.mjs +128 -0
- package/tests/routine_observability_api.mjs +195 -0
- package/tests/ship_assurance.mjs +86 -3
- package/tests/ship_land_red_pr.mjs +236 -0
- package/tests/ship_preflight.mjs +67 -1
- package/tests/start_env_guard.mjs +16 -0
- package/tests/upgrade.mjs +20 -1
|
@@ -57,6 +57,10 @@ function finalizeVerdict({ verdict, scores, issues, extra = {}, notesPrefix, not
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// The marker the policy below stamps on a worker it downgraded. Exported so every
|
|
61
|
+
// reader (aggregatePanel, the ship card) names the one string instead of retyping it.
|
|
62
|
+
const UNBACKED_SCORE_DOWNGRADE = 'subthreshold_score_without_substantive_finding';
|
|
63
|
+
|
|
60
64
|
// #1002663 severity-gate policy: style findings are advisory, and a
|
|
61
65
|
// sub-threshold score must be BACKED by a substantive finding. Every worker
|
|
62
66
|
// prompt already demands "a score below threshold must be accompanied by a
|
|
@@ -81,9 +85,35 @@ function applySeverityGatePolicy(result, { threshold, substantiveFail = false })
|
|
|
81
85
|
return {
|
|
82
86
|
...result,
|
|
83
87
|
verdict: 'concerns',
|
|
84
|
-
policy_downgrade:
|
|
88
|
+
policy_downgrade: UNBACKED_SCORE_DOWNGRADE,
|
|
85
89
|
notes_md: `${result.notes_md}\n[severity-gate] sub-threshold score with no major/blocker finding — fail downgraded to concerns (style findings are advisory)`.trim(),
|
|
86
90
|
};
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
|
|
93
|
+
// task 1002903 — the UNBACKED low scores in a panel: every sub-threshold axis of a
|
|
94
|
+
// worker the policy above downgraded, as [{ kind, axis, score }] in worker order.
|
|
95
|
+
//
|
|
96
|
+
// WHY THIS EXISTS. A downgraded worker still carries its low score, and that score
|
|
97
|
+
// still reaches the record (the axis median, the blended score) and the server's
|
|
98
|
+
// ff/cq gate. So the grade reads "FAIL 5.5" with no finding behind it, and the card
|
|
99
|
+
// told the builder to "fix the issues above" when there were none — the shape task
|
|
100
|
+
// 1002721 was filed as. Naming these lets the record and the card say what they
|
|
101
|
+
// are: a low mark nobody could point at, not a rejection with work to do.
|
|
102
|
+
//
|
|
103
|
+
// Keyed on the policy's own marker, so it can only ever list what the policy
|
|
104
|
+
// already decided: a low score that IS backed (the worker stays 'fail'), an errored
|
|
105
|
+
// worker, and the exempt Hacker (never downgraded) cannot appear. Pure; tolerates
|
|
106
|
+
// any input shape because signals.workers comes back off the wire.
|
|
107
|
+
function unbackedLowScores(workerResults, { threshold }) {
|
|
108
|
+
const out = [];
|
|
109
|
+
for (const w of (Array.isArray(workerResults) ? workerResults : [])) {
|
|
110
|
+
if (!w || typeof w !== 'object' || w.policy_downgrade !== UNBACKED_SCORE_DOWNGRADE) continue;
|
|
111
|
+
const scores = w.scores && typeof w.scores === 'object' ? w.scores : {};
|
|
112
|
+
for (const [axis, score] of Object.entries(scores)) {
|
|
113
|
+
if (Number.isFinite(score) && score < threshold) out.push({ kind: String(w.kind || '?'), axis, score });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = { failResult, parseCommon, finalizeVerdict, applySeverityGatePolicy, unbackedLowScores, UNBACKED_SCORE_DOWNGRADE };
|
|
@@ -46,7 +46,9 @@
|
|
|
46
46
|
// (narc + quality + hacker + efficiency — the full 4-worker panel, so an
|
|
47
47
|
// honest ship always clears hasPanelEvidence's ≥3-worker floor). Wraps
|
|
48
48
|
// subprocess failures so resolveClaim/applyGrade can always write
|
|
49
|
-
// task_grades.
|
|
49
|
+
// task_grades. Since task 1002839 it is the code panel (CODE_WORK_TYPE,
|
|
50
|
+
// below) run through the central grade engine (grade-engine.js), which
|
|
51
|
+
// owns the pipeline, the deterministic pre-passes and the veto.
|
|
50
52
|
//
|
|
51
53
|
// aggregatePanel(workerResults, meta)
|
|
52
54
|
// → final grade-result from per-worker WorkerResults. Applies the
|
|
@@ -82,7 +84,7 @@ const path = require('node:path');
|
|
|
82
84
|
const fs = require('node:fs');
|
|
83
85
|
const { spawn } = require('node:child_process');
|
|
84
86
|
|
|
85
|
-
const { ASSUMED_BUILDER_MODEL, DEFAULT_GRADER_MODEL, DEFAULT_SHOTS, FALLTHROUGH_CONFIDENCE, RUBRIC, THRESHOLD, V1, V3, VERSION,
|
|
87
|
+
const { ASSUMED_BUILDER_MODEL, DEFAULT_GRADER_MODEL, DEFAULT_SHOTS, FALLTHROUGH_CONFIDENCE, RUBRIC, THRESHOLD, V1, V3, VERSION, generatedProvenance, permissionPathPrePass, routeRankPrePass, workers } = require('./grader-config.js');
|
|
86
88
|
const { clamp, isTrivialDiff, median, r1, selectGraderModel, workerModelFor } = require('./grader-model.js');
|
|
87
89
|
const { buildPrompt, extractJson } = require('./grader-prompt.js');
|
|
88
90
|
// task 1004011: the second grade species — a grade over the CLAIM on an
|
|
@@ -90,6 +92,10 @@ const { buildPrompt, extractJson } = require('./grader-prompt.js');
|
|
|
90
92
|
const { GRADER_KIND: NO_ARTIFACT_GRADER_KIND, buildNoArtifactPrompt, gradeNoArtifact } = require('./grader-no-artifact.js');
|
|
91
93
|
const { aggregateShots, changeMagnitude, hasPanelEvidence, isSubstantiveIssue, serverSideGuards, validateAndScore } = require('./grader-score.js');
|
|
92
94
|
const { buildContextBundle, diffAddedLinesByFile, extractAssistantResult, extractCostUsd, runOneShot, runSubagent, runSubagentCached } = require('./grader-subagent.js');
|
|
95
|
+
// task 1002903: the severity gate's unbacked low scores, named once for the record.
|
|
96
|
+
const { unbackedLowScores } = require('./grader-workers/worker-verdict.js');
|
|
97
|
+
// task 1002839: the central engine the code panel below registers into (ADR 0089 §1).
|
|
98
|
+
const engine = require('./grade-engine.js');
|
|
93
99
|
|
|
94
100
|
// task 1003313: render the WHY of a total outage into the verdict line.
|
|
95
101
|
//
|
|
@@ -159,6 +165,18 @@ function aggregatePanel(workerResults, meta = {}) {
|
|
|
159
165
|
const cqEffective = cq != null && economy != null ? r1(clamp(cq - economyPenalty, 0, 10)) : cq;
|
|
160
166
|
const blended = ff != null && cq != null ? r1((ff + cq) / 2) : 0;
|
|
161
167
|
|
|
168
|
+
// task 1002903: low scores the severity gate found no major/blocker finding
|
|
169
|
+
// behind (worker-verdict.js applySeverityGatePolicy). Recorded, never hidden —
|
|
170
|
+
// the scores above keep them — so the card can say "nothing listed to fix".
|
|
171
|
+
const unbackedScores = unbackedLowScores(workerResults, { threshold: THRESHOLD });
|
|
172
|
+
// The two axes the SERVER gates on. validateAndScore re-checks ff and cq from
|
|
173
|
+
// the scores this panel forwards, so either one under threshold fails the ship
|
|
174
|
+
// whatever the verdict below says (see the gatingAllPass branch).
|
|
175
|
+
const serverGatedBelow = [
|
|
176
|
+
...(ff != null && ff < THRESHOLD ? [`functional_fidelity ${ff}`] : []),
|
|
177
|
+
...(cq != null && cq < THRESHOLD ? [`code_quality ${cq}`] : []),
|
|
178
|
+
];
|
|
179
|
+
|
|
162
180
|
// Verdict counts.
|
|
163
181
|
const passCount = gateResults.filter((w) => w.verdict === 'pass').length;
|
|
164
182
|
const concernsCount = gateResults.filter((w) => w.verdict === 'concerns').length;
|
|
@@ -356,6 +374,19 @@ function aggregatePanel(workerResults, meta = {}) {
|
|
|
356
374
|
verdict = `CONCERNS — ${reasons.join('; ')} (manual-confirm available)`;
|
|
357
375
|
panelOutcome = 'concerns';
|
|
358
376
|
forwardedConfidence = FALLTHROUGH_CONFIDENCE;
|
|
377
|
+
} else if (gatingAllPass && serverGatedBelow.length > 0) {
|
|
378
|
+
// task 1002903: every gating worker passed, but a server-gated axis is under
|
|
379
|
+
// threshold. Today that is one shape — the ADVISORY Narc scored ff low with no
|
|
380
|
+
// finding, so the severity gate made it 'concerns' and the branch below would
|
|
381
|
+
// call the panel a PASS. The server then re-checks ff from the forwarded score
|
|
382
|
+
// and fails the ship anyway, so the stored grade said FAIL with a PASS verdict
|
|
383
|
+
// inside it. Say what will actually happen: manual-confirm, the same
|
|
384
|
+
// fall-through as every other concerns branch. The ship was already not
|
|
385
|
+
// passing, so this is no new gate — and an unbacked low score still does not
|
|
386
|
+
// pass, so it is no loosening either.
|
|
387
|
+
verdict = `CONCERNS — ${serverGatedBelow.join(' and ')} ${serverGatedBelow.length > 1 ? 'are' : 'is'} below ${THRESHOLD} with every gating worker passing; the server's score gate still applies (manual-confirm available)`;
|
|
388
|
+
panelOutcome = 'concerns';
|
|
389
|
+
forwardedConfidence = FALLTHROUGH_CONFIDENCE;
|
|
359
390
|
} else if (gatingAllPass) {
|
|
360
391
|
// Every gating worker passed. A Narc 'concerns' here is advisory only.
|
|
361
392
|
verdict = `PASS — ${gating.length}/${gating.length} gating worker(s) cleared${advisoryConcerns ? ' (narc advisory: concerns)' : ''}`;
|
|
@@ -402,6 +433,9 @@ function aggregatePanel(workerResults, meta = {}) {
|
|
|
402
433
|
if (efficiencyScore != null) lines.push(` efficiency_score (EffMng): ${efficiencyScore}/10`);
|
|
403
434
|
lines.push(` blended: ${blended}/10 (threshold ${THRESHOLD}, gating workers must PASS; narc advisory — #481)`);
|
|
404
435
|
lines.push(` forwarded confidence: ${forwardedConfidence ?? '?'} (panel: ${panelOutcome})`);
|
|
436
|
+
for (const u of unbackedScores) {
|
|
437
|
+
lines.push(` unbacked score: ${u.kind} ${u.axis} ${u.score}/10 is below ${THRESHOLD} with no major/blocker finding behind it (severity gate) — nothing listed to fix`);
|
|
438
|
+
}
|
|
405
439
|
lines.push('');
|
|
406
440
|
lines.push('Per-worker:');
|
|
407
441
|
for (const w of workerResults) {
|
|
@@ -471,6 +505,9 @@ function aggregatePanel(workerResults, meta = {}) {
|
|
|
471
505
|
fail_count: failCount,
|
|
472
506
|
error_count: errorCount,
|
|
473
507
|
panel_cost_usd: panelCostUsd, // #486: mirror into signals for the audit trail
|
|
508
|
+
// task 1002903: present only when there is one, so an ordinary grade's
|
|
509
|
+
// signals keep their exact shape.
|
|
510
|
+
...(unbackedScores.length ? { unbacked_scores: unbackedScores } : {}),
|
|
474
511
|
...meta,
|
|
475
512
|
},
|
|
476
513
|
notes_md: lines.join('\n'),
|
|
@@ -531,96 +568,56 @@ function graderUnavailableResult({ reason, note, meta = {} } = {}) {
|
|
|
531
568
|
};
|
|
532
569
|
}
|
|
533
570
|
|
|
534
|
-
//
|
|
535
|
-
// grader model and returns the panel-aggregated grade-result.
|
|
571
|
+
// ---------- the code panel as work-type #1 of the grade engine (task 1002839) ----------
|
|
536
572
|
//
|
|
537
|
-
//
|
|
538
|
-
//
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
//
|
|
544
|
-
//
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
//
|
|
549
|
-
//
|
|
550
|
-
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
return graderUnavailableResult({
|
|
557
|
-
reason,
|
|
558
|
-
note: 'Grader bypassed before worker spawn (GRADER_BYPASS_ENABLED / runOpts.bypass) — no panel ran. Land via manual-confirm (/confirm).',
|
|
559
|
-
meta: { builder_model_assumed: builderModel || ASSUMED_BUILDER_MODEL, bypassed: true },
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
const panel = workers.resolvePanel(workerKinds ?? workers.DEFAULT_PANEL);
|
|
563
|
-
const graderModel = selectGraderModel({ builderModel });
|
|
564
|
-
|
|
565
|
-
// Deterministic route-rank pre-pass — its findings feed the Hacker as input;
|
|
566
|
-
// its pseudo-worker result is appended to the panel below.
|
|
567
|
-
const routeRank = routeRankPrePass(changedFiles);
|
|
568
|
-
// Deterministic permission-path pre-pass (SEC #863) — a sub-Metic diff that
|
|
569
|
-
// touches the rank/permission/ship-grade/deploy machinery hard-fails.
|
|
570
|
-
const permPath = permissionPathPrePass(changedFiles, builderRank);
|
|
571
|
-
const deterministicFindings = [
|
|
572
|
-
...(routeRank ? routeRank.findings : []),
|
|
573
|
-
...(permPath ? permPath.findings : []),
|
|
574
|
-
];
|
|
575
|
-
|
|
573
|
+
// ADR 0089 §1: the pipeline (select → pre_pass → evidence → fan_out → aggregate →
|
|
574
|
+
// gate → persist) lives in grade-engine.js and is identical for every work-type;
|
|
575
|
+
// a work-type supplies only what is its own. For code that is the v4 adversarial
|
|
576
|
+
// worker panel, and every hook below is what gradeSubagent's body did inline
|
|
577
|
+
// before the extraction — same calls, same arguments, same order. The engine
|
|
578
|
+
// owns the rest: the bypass short-circuit, grader-model selection, BOTH
|
|
579
|
+
// deterministic pre-passes (the code panel cannot skip them — nothing here names
|
|
580
|
+
// them), the parallel fan-out, the deterministic veto and the record meta.
|
|
581
|
+
const CODE_WORK_TYPE = Object.freeze({
|
|
582
|
+
key: 'code',
|
|
583
|
+
title: 'Code — the v4 adversarial worker panel (narc / quality / hacker / efficiency, + the advisory reader on copy-bearing ships)',
|
|
584
|
+
// Part of the llm-cache key: kept at the pre-engine value so every cached
|
|
585
|
+
// worker reply stays a hit.
|
|
586
|
+
cacheTaskType: 'grade_worker',
|
|
587
|
+
// The judges: an explicit list of worker kinds, else the full 4-worker
|
|
588
|
+
// panel. An unknown kind throws — a typo in a custom panel surfaces at once.
|
|
589
|
+
resolveJudges: (kinds) => workers.resolvePanel(kinds ?? workers.DEFAULT_PANEL),
|
|
590
|
+
// task 1002660: the rubric tier override per worker (cross-model-guarded).
|
|
591
|
+
modelFor: (worker, graderModel, builderModel) => workerModelFor(worker.KIND, graderModel, builderModel),
|
|
576
592
|
// task 1002660: one read of the changed files, shared by all four workers —
|
|
577
593
|
// kills the per-worker re-exploration that dominated panel cost.
|
|
578
594
|
// diff (task 1002905): lets the bundle prove a truncated prefix actually shows
|
|
579
595
|
// the change instead of handing a worker a head fragment as "post-change".
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
priorRound: priorRound || null, // task 1002662: delta-aware re-grade context
|
|
606
|
-
contextBundle, // task 1002660: post-change file contents, read once
|
|
607
|
-
}));
|
|
608
|
-
const workerResults = await Promise.all(workerPromises);
|
|
609
|
-
// Append the deterministic checks as pseudo-workers so the any-fail gate
|
|
610
|
-
// treats a write-route 'unknown' OR a sub-Metic permission-path edit as a
|
|
611
|
-
// hard block with no gate-logic change.
|
|
612
|
-
if (routeRank) workerResults.push(routeRank.workerResult);
|
|
613
|
-
if (permPath) workerResults.push(permPath.workerResult);
|
|
614
|
-
const totalMs = Date.now() - startedAt;
|
|
615
|
-
|
|
616
|
-
return aggregatePanel(workerResults, {
|
|
617
|
-
builder_model_assumed: builderModel || ASSUMED_BUILDER_MODEL,
|
|
618
|
-
grader_model: graderModel,
|
|
619
|
-
total_ms: totalMs,
|
|
620
|
-
// task 1002905: what the panel could actually SEE, recorded on every grade.
|
|
621
|
-
// Diagnosing the fabricated all-blocker FAIL cost a session because the grade
|
|
622
|
-
// record said nothing about the read root or how much of each file the workers
|
|
623
|
-
// were shown; a "the panel read the wrong tree" claim is now one field away.
|
|
596
|
+
gatherEvidence: async ({ changedFiles, diff, runOpts }) => ({
|
|
597
|
+
contextBundle: Object.assign(...await Promise.all([buildContextBundle({ changedFiles, cwd: runOpts.cwd, diff }), generatedProvenance.verifyGeneratedProvenanceAsync({ changedFiles, cwd: runOpts.cwd }).then((generatedProvenance) => ({ generatedProvenance }))])), // task 1002795 — verified once, concurrently with the bundle read, off the prompt path
|
|
598
|
+
}),
|
|
599
|
+
runJudge: (worker, ctx) => workers.runWorker(worker, {
|
|
600
|
+
task: ctx.task,
|
|
601
|
+
valueSummary: ctx.valueSummary,
|
|
602
|
+
diff: ctx.diff,
|
|
603
|
+
changedFiles: ctx.changedFiles,
|
|
604
|
+
diffStats: ctx.diffStats,
|
|
605
|
+
model: ctx.model,
|
|
606
|
+
runOpts: ctx.runOpts,
|
|
607
|
+
runSubagentFn: ctx.runSubagentFn,
|
|
608
|
+
extractAssistantResultFn: ctx.extractAssistantResultFn,
|
|
609
|
+
// Only the Hacker renders these; the route-rank + permission-path findings.
|
|
610
|
+
deterministicFindings: ctx.deterministicFindings.length ? ctx.deterministicFindings : null,
|
|
611
|
+
priorRound: ctx.priorRound || null, // task 1002662: delta-aware re-grade context
|
|
612
|
+
contextBundle: ctx.evidence.contextBundle, // task 1002660: post-change file contents, read once
|
|
613
|
+
}),
|
|
614
|
+
aggregate: aggregatePanel,
|
|
615
|
+
unavailable: graderUnavailableResult,
|
|
616
|
+
// task 1002905: what the panel could actually SEE, recorded on every grade.
|
|
617
|
+
// Diagnosing the fabricated all-blocker FAIL cost a session because the grade
|
|
618
|
+
// record said nothing about the read root or how much of each file the workers
|
|
619
|
+
// were shown; a "the panel read the wrong tree" claim is now one field away.
|
|
620
|
+
recordEvidence: ({ contextBundle }, { runOpts }) => ({
|
|
624
621
|
context_bundle: {
|
|
625
622
|
read_root: path.resolve(runOpts.cwd || process.cwd()),
|
|
626
623
|
bundled: contextBundle.files.map((f) => ({
|
|
@@ -628,6 +625,29 @@ async function gradeSubagent({ task, valueSummary, diff, changedFiles, diffStats
|
|
|
628
625
|
})),
|
|
629
626
|
skipped: contextBundle.skipped,
|
|
630
627
|
},
|
|
628
|
+
}),
|
|
629
|
+
});
|
|
630
|
+
engine.registerWorkType(CODE_WORK_TYPE, { isDefault: true });
|
|
631
|
+
|
|
632
|
+
// End-to-end v4 grader: the code panel through the grade engine. Returns the
|
|
633
|
+
// panel-aggregated grade-result.
|
|
634
|
+
//
|
|
635
|
+
// workers: defaults to DEFAULT_PANEL (narc + quality + hacker + efficiency —
|
|
636
|
+
// the full 4-worker panel since Phase 2.1/2.2). An array of worker kinds
|
|
637
|
+
// (strings) like ['narc', 'quality']. A default-panel run always satisfies
|
|
638
|
+
// hasPanelEvidence's ≥3-worker floor, so an honest unanimous zero-issue
|
|
639
|
+
// PASS is waived, never manual-confirm-gated (task 1002664 / ADR 0164).
|
|
640
|
+
// builderModel: the model the builder used. Defaults to ASSUMED_BUILDER_MODEL
|
|
641
|
+
// (Opus, per CLAUDE.md §2). Drives selectGraderModel — workers pick the
|
|
642
|
+
// cross-family alternative.
|
|
643
|
+
// bypass (#943): runOpts.bypass or GRADER_BYPASS_ENABLED returns the distinct
|
|
644
|
+
// 'unavailable' result BEFORE any worker spawns — see engine.runGrade.
|
|
645
|
+
async function gradeSubagent({ task, valueSummary, diff, changedFiles, diffStats, workers: workerKinds, builderModel, builderRank, priorRound, runOpts = {} }) {
|
|
646
|
+
return engine.runGrade({
|
|
647
|
+
workType: CODE_WORK_TYPE.key,
|
|
648
|
+
task, valueSummary, diff, changedFiles, diffStats,
|
|
649
|
+
judges: workerKinds,
|
|
650
|
+
builderModel, builderRank, priorRound, runOpts,
|
|
631
651
|
});
|
|
632
652
|
}
|
|
633
653
|
|
|
@@ -842,7 +862,8 @@ function gradeDeterministic({ task, verification, actualMinutes, valueSummary, l
|
|
|
842
862
|
module.exports = {
|
|
843
863
|
// v4 surface (active panel-based gate)
|
|
844
864
|
aggregatePanel,
|
|
845
|
-
gradeSubagent, // panel by default
|
|
865
|
+
gradeSubagent, // panel by default — the code work-type through the grade engine
|
|
866
|
+
gradeEngine: engine, // task 1002839: the central pipeline + work-type registry (ADR 0089 §1), code panel registered
|
|
846
867
|
gradeNoArtifact, // task 1004011: the no-diff species — grades the CLAIM, not a diff
|
|
847
868
|
buildNoArtifactPrompt, // exported so tests can read the prompt without spawning a model
|
|
848
869
|
NO_ARTIFACT_GRADER_KIND, // task 1004011: 'subagent-no-artifact' — the species marker
|
|
@@ -207,6 +207,21 @@
|
|
|
207
207
|
${detail}
|
|
208
208
|
</div>`;
|
|
209
209
|
}
|
|
210
|
+
// LANDED, BUT GITHUB DID NOT GET IT (task 1004065). The runner pushes a hosted
|
|
211
|
+
// project's new pin to its repo after the move; when it could not, the move is still
|
|
212
|
+
// done — the site runs the new core — and `warning` says why the repo was not updated.
|
|
213
|
+
// Its own treatment, never the success one: "and the box confirmed it" on its own is
|
|
214
|
+
// the sentence that hid this while the box and the repo drifted apart.
|
|
215
|
+
if (m.state === 'done' && m.warning && m.target_version === p.served_version) {
|
|
216
|
+
return `
|
|
217
|
+
<div class="dp-settled dp-settled--warn">
|
|
218
|
+
<p class="ov-row__sub"><strong>Now running core ${escapeHtml(String(m.target_version))} — but GitHub was not updated.</strong>
|
|
219
|
+
Moved ${escapeHtml(ago(m.at))}, and the box confirmed it. The project’s repository on GitHub
|
|
220
|
+
still names the old core, so changes pushed there may not reach the live site until the two match.</p>
|
|
221
|
+
<p class="ov-row__sub">${escapeHtml(String(m.warning.text || ''))}</p>
|
|
222
|
+
${reconnectHtml(p, m.warning.code)}
|
|
223
|
+
</div>`;
|
|
224
|
+
}
|
|
210
225
|
if (m.state === 'done' && m.target_version === p.served_version) {
|
|
211
226
|
return `
|
|
212
227
|
<div class="dp-settled dp-settled--ok">
|
|
@@ -217,6 +232,26 @@
|
|
|
217
232
|
return '';
|
|
218
233
|
}
|
|
219
234
|
|
|
235
|
+
// The one fix a page can hand the owner for a repo that was not updated: sign in again
|
|
236
|
+
// with repository access, which stores the token the runner pushes with. Offered only
|
|
237
|
+
// for the two causes it can cure — a sign-in does nothing for GitHub being ahead of the
|
|
238
|
+
// box, or for a project with no repo. It says the access is short-lived (an hour, per
|
|
239
|
+
// GITHUB_TOKEN_TTL_MS) rather than promising the repo will simply catch up.
|
|
240
|
+
const RECONNECT_CURES = new Set(['no_access', 'refused']);
|
|
241
|
+
function reconnectHtml(p, code) {
|
|
242
|
+
if (!RECONNECT_CURES.has(code)) return '';
|
|
243
|
+
// Back to THIS page. An absolute https URL survives the callback's host (the
|
|
244
|
+
// allowlist admits the apex's subdomains); where there is none to read, the page's
|
|
245
|
+
// own path is the fallback.
|
|
246
|
+
let back = '';
|
|
247
|
+
try { back = String((window.location && window.location.href) || ''); } catch (e) { back = ''; }
|
|
248
|
+
if (!/^https:\/\//.test(back)) back = `/deploy${view.isSelf ? '' : `?project=${encodeURIComponent(String(p.slug))}`}`;
|
|
249
|
+
const href = `${API}/auth/web/start?repos=1&return=${encodeURIComponent(back)}`;
|
|
250
|
+
return `
|
|
251
|
+
<p class="ov-row__sub">If you own this project, <a href="${escapeHtml(href)}">reconnect GitHub with repository access</a>.
|
|
252
|
+
The hub keeps that access for about an hour, so the next upgrade run within the hour saves this change to GitHub along with its own.</p>`;
|
|
253
|
+
}
|
|
254
|
+
|
|
220
255
|
function stepOneHtml(p) {
|
|
221
256
|
return `
|
|
222
257
|
<p class="ov-row__sub">Core <strong>${escapeHtml(String(p.recommended_version))}</strong> is published and
|
|
@@ -160,6 +160,13 @@
|
|
|
160
160
|
border-left: 3px solid var(--danger);
|
|
161
161
|
padding-left: 0.7rem;
|
|
162
162
|
}
|
|
163
|
+
/* It landed, but the project's GitHub repo did not get the new pin (task 1004065). Not
|
|
164
|
+
the ok mark — the box and the repo now disagree — and not the danger one, because the
|
|
165
|
+
project is running exactly what was asked for. */
|
|
166
|
+
.dp-settled--warn {
|
|
167
|
+
border-left: 3px solid var(--warn);
|
|
168
|
+
padding-left: 0.7rem;
|
|
169
|
+
}
|
|
163
170
|
/* A failure the box has already moved past (task 1004172). It keeps a mark, because it
|
|
164
171
|
is still a thing that happened and worth reading — but not the danger mark, which on
|
|
165
172
|
this page means "your project is in this state now". A two-day-old refusal wearing the
|
|
@@ -53,6 +53,11 @@ const githubPush = require('../github-push');
|
|
|
53
53
|
const prAssign = require('../pr-assign');
|
|
54
54
|
const shipAssurance = require('../ship-assurance');
|
|
55
55
|
const RECONCILE_PROOF = Object.freeze({ kind: 'proof', reason: 'publish-status reconcile: land proven' });
|
|
56
|
+
// task 1003764: publish-status with no ?branch= and no PR titled for the task — the shape
|
|
57
|
+
// getPublishStatus gives a branch with no PR, plus how to ask about one. Never a 400.
|
|
58
|
+
const NO_PR_STATUS = Object.freeze({
|
|
59
|
+
pr_state: 'none', merged: false, merge_sha: null, deploy_status: 'none', pr_url: null, pr_number: null, pr_title: null, pr_body: null,
|
|
60
|
+
branch_hint: 'No pull request titled for this task is among the 100 most recent. Pass ?branch=<name> to ask about a specific branch.' });
|
|
56
61
|
const seams = api;
|
|
57
62
|
const { classifyTaskKind, VALID_KINDS, VALID_DISCIPLINES, VOTABLE_TASK_STATUSES, TASK_FIELD_BOUNDS } = require('../task-classifier');
|
|
58
63
|
const { isModuleKey, moduleKeys } = api.moduleScopeMap;
|
|
@@ -1250,15 +1255,25 @@ module.exports = function buildTasksRouter() {
|
|
|
1250
1255
|
// GET /tasks/:id/publish-status?branch=<branch> — poll the PR + prod-deploy
|
|
1251
1256
|
// state for a server-mediated publish so ship.js can confirm the land WITHOUT
|
|
1252
1257
|
// gh on the box. Ownership-gated like the POST. Derives state live from GitHub.
|
|
1258
|
+
// `branch` is optional (task 1003764): without it the server finds the task's own PR
|
|
1259
|
+
// (both publish paths title it "task <id>: …") and the answer names the `branch` it
|
|
1260
|
+
// used and its `branch_source`, so the check agents are sent to instead of git probes
|
|
1261
|
+
// never refuses a live publish for want of a branch name.
|
|
1253
1262
|
router.get('/tasks/:id/publish-status', auth.requireBuilder, async (req, res) => {
|
|
1254
1263
|
const id = parseId(req, res, { code: 'bad_task_id' });
|
|
1255
1264
|
if (id === null) return;
|
|
1256
1265
|
if (!(await gateTaskOwnership(req, res, id))) return;
|
|
1257
1266
|
if (!githubPush.isConfigured()) return res.fail('push_unconfigured', { status: 503, message: PUSH_UNCONFIGURED_MSG });
|
|
1258
|
-
|
|
1259
|
-
|
|
1267
|
+
// A fully-qualified ref names the same branch, so it is accepted rather than refused.
|
|
1268
|
+
const asked = typeof req.query.branch === 'string' ? req.query.branch.replace(/^refs\/heads\//, '') : req.query.branch;
|
|
1269
|
+
if (asked != null && asked !== '' && !githubPush.isSafeBranch(asked)) {
|
|
1270
|
+
return res.fail('bad_branch', { status: 400, message: 'branch must be a plain branch name such as task-123, or left out so the server finds the task\'s own PR.' });
|
|
1271
|
+
}
|
|
1260
1272
|
try {
|
|
1261
|
-
const
|
|
1273
|
+
const found = asked ? null : await githubPush.findTaskPullRequest({ taskId: id });
|
|
1274
|
+
const branch = asked || (found && found.branch) || null;
|
|
1275
|
+
const branch_source = asked ? 'query' : (branch ? 'task_pr' : null);
|
|
1276
|
+
const status = branch ? await githubPush.getPublishStatus({ branch }) : NO_PR_STATUS;
|
|
1262
1277
|
// task 1049 (B — server-side reconcile-on-read): when GitHub shows the PR
|
|
1263
1278
|
// MERGED and deploy-prod SUCCEEDED, finish the task right here so a
|
|
1264
1279
|
// confirmed-but-unshipped task never strands waiting on a human to re-poll
|
|
@@ -1318,7 +1333,7 @@ module.exports = function buildTasksRouter() {
|
|
|
1318
1333
|
log.error('[gds] publish-status merge-driver (non-blocking):', e && e.message);
|
|
1319
1334
|
}
|
|
1320
1335
|
}
|
|
1321
|
-
return res.json({ ok: true, ...status, reconciled, reconcile_blocked, merge_driver });
|
|
1336
|
+
return res.json({ ok: true, ...status, branch, branch_source, reconciled, reconcile_blocked, merge_driver });
|
|
1322
1337
|
} catch (e) {
|
|
1323
1338
|
if (e.code === 'NO_REPO_INFO') return res.fail('no_repo_info', 500);
|
|
1324
1339
|
if (e.code === 'APP_NOT_INSTALLED') return res.fail('push_unconfigured', { status: 503, message: appNotInstalledMessage(e) });
|
|
@@ -74,10 +74,26 @@ async function deriveShipAssurance({ taskId, body = {}, db, githubPush, onProbeE
|
|
|
74
74
|
// before landProvenForTask ever saw a recorded tip, so a commit provably on main
|
|
75
75
|
// was refused as "not proven" (strands 1003285/1003286, task 1003434). Each step
|
|
76
76
|
// now fails on its own: a status miss degrades to status=null, never to no-proof.
|
|
77
|
+
//
|
|
78
|
+
// The status probe needs a BRANCH (task 1004275). It used to be called with
|
|
79
|
+
// { taskId, publishedSha }, which getPublishStatus does not read, so it threw
|
|
80
|
+
// BAD_BRANCH every time and both PR-witness proofs in landProvenForTask (the
|
|
81
|
+
// squash/out-of-band land, and the no-tip fallback) could never fire on this path,
|
|
82
|
+
// while the reconciler proved the same land ~5 min later. So find the task's own PR
|
|
83
|
+
// first, by the "task <id>: …" title both publish paths give it (the same lookup
|
|
84
|
+
// GET /publish-status does since task 1003764), and ask about that branch. No PR,
|
|
85
|
+
// or a failed search, leaves status=null: the tip proof still runs.
|
|
77
86
|
let publishedSha = null;
|
|
78
87
|
try { publishedSha = await db.getClaimPublishedSha(taskId); } catch (e) { report(e); }
|
|
88
|
+
let branch = null;
|
|
89
|
+
try {
|
|
90
|
+
const found = await githubPush.findTaskPullRequest({ taskId });
|
|
91
|
+
branch = (found && found.branch) || null;
|
|
92
|
+
} catch (e) { report(e); }
|
|
79
93
|
let status = null;
|
|
80
|
-
|
|
94
|
+
if (branch) {
|
|
95
|
+
try { status = await githubPush.getPublishStatus({ branch }); } catch (e) { report(e); }
|
|
96
|
+
}
|
|
81
97
|
try {
|
|
82
98
|
const proof = await githubPush.landProvenForTask({ taskId, publishedSha, status });
|
|
83
99
|
if (proof && proof.proven) return { assurance: { kind: 'proof', reason: proof.reason } };
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
// that file sits on the oversized-file ratchet; a guard whose whole value is being
|
|
25
25
|
// read before it is edited should not be the thing that reds the build either.
|
|
26
26
|
|
|
27
|
+
const { PublicRefusal } = require('./public-refusal');
|
|
28
|
+
|
|
27
29
|
// The only acts that cannot destroy the box they run on.
|
|
28
30
|
//
|
|
29
31
|
// core-upgrade — moves the pin, health-gated, auto-rollback on failure. This is
|
|
@@ -86,25 +88,24 @@ function intentAllowedForShape(action, hostingShape) {
|
|
|
86
88
|
* @param {{query: Function}} db
|
|
87
89
|
* @param {number|string} instanceId
|
|
88
90
|
* @param {string} action an INTENT_ACTIONS member
|
|
89
|
-
* @throws {
|
|
91
|
+
* @throws {PublicRefusal} code 'control_plane_protected', status 409
|
|
90
92
|
*/
|
|
91
93
|
async function assertIntentAllowed(db, instanceId, action) {
|
|
92
94
|
const { rows } = await db.query(
|
|
93
95
|
'SELECT hosting_shape FROM provisioning_instances WHERE id = $1', [instanceId]);
|
|
94
96
|
const shape = rows[0] && rows[0].hosting_shape;
|
|
95
97
|
if (!shape || intentAllowedForShape(action, shape)) return;
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
// a route forgot
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
throw err;
|
|
98
|
+
// A PublicRefusal, so every route answers it as a 409 in the owner's words (task
|
|
99
|
+
// 1004126). This used to rest on "every action that can reach this throw has its own
|
|
100
|
+
// earlier refusal", and that did not hold: teardown, force-teardown and disconnect reach
|
|
101
|
+
// it with none, and the owner read "internal error". Each route still logs the stack, so
|
|
102
|
+
// a route that forgot its own check stays as visible to us as it was.
|
|
103
|
+
throw new PublicRefusal({
|
|
104
|
+
code: 'control_plane_protected',
|
|
105
|
+
status: 409,
|
|
106
|
+
publicMessage: `That action cannot be aimed at the machine the platform itself runs on. Only ${CONTROL_PLANE_ACTIONS.join(' and ')} are allowed there.`,
|
|
107
|
+
message: `'${action}' cannot be aimed at the control plane — only ${CONTROL_PLANE_ACTIONS.join(' and ')} may run against the box the platform itself runs on`,
|
|
108
|
+
});
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
module.exports = { CONTROL_PLANE_ACTIONS, intentAllowedForShape, assertIntentAllowed };
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
// AT the 1500-line fitness ratchet, and at that ceiling a new function costs more
|
|
30
30
|
// than the build will give back.
|
|
31
31
|
|
|
32
|
+
const { failFrom } = require('./public-refusal');
|
|
32
33
|
|
|
33
34
|
// Exactly the states in which there is a running instance whose pin can move.
|
|
34
35
|
// Narrower than RESTART_FROM on purpose: an instance mid-standup has no settled pin
|
|
@@ -209,18 +210,10 @@ function makeCoreUpgradeHandler({ provisioning, pool, parseId, validateOrRespond
|
|
|
209
210
|
});
|
|
210
211
|
} catch (err) {
|
|
211
212
|
log.error('[provisioning] POST /provisioning/instances/:id/core-upgrade', err);
|
|
212
|
-
// A REFUSAL IS NOT AN OUTAGE
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
// in the repo honoured `publicMessage`; it was assigned once and read never
|
|
217
|
-
// (task 1004125). Only an error that has already declared itself public is
|
|
218
|
-
// forwarded — anything else still gets the opaque 500, because an unexpected
|
|
219
|
-
// throw may carry anything.
|
|
220
|
-
if (err && err.publicMessage && err.code && err.status) {
|
|
221
|
-
return res.fail(err.code, { status: err.status, message: err.publicMessage });
|
|
222
|
-
}
|
|
223
|
-
return res.fail('core_upgrade_failed', { status: 500, message: 'internal error' });
|
|
213
|
+
// A REFUSAL IS NOT AN OUTAGE (task 1004125): a guard refusal reaches the owner as
|
|
214
|
+
// its own sentence, and an unexpected throw, which may carry anything, stays the
|
|
215
|
+
// opaque 500. The one definition of "public" is public-refusal.js (task 1004126).
|
|
216
|
+
return failFrom(res, err, 'core_upgrade_failed');
|
|
224
217
|
}
|
|
225
218
|
};
|
|
226
219
|
}
|
|
@@ -256,6 +249,23 @@ function lastMoveSuperseded(target, served, recommended) {
|
|
|
256
249
|
return !!(recommended && String(recommended) !== String(target));
|
|
257
250
|
}
|
|
258
251
|
|
|
252
|
+
/**
|
|
253
|
+
* The note a DONE move carries when the upgrade landed but its pin never reached the
|
|
254
|
+
* project's GitHub repo (task 1004065) — `{ code, text }`, or null. PURE.
|
|
255
|
+
*
|
|
256
|
+
* The runner writes it into the intent's last_error as `repo_not_updated:<code>: <text>`
|
|
257
|
+
* (scripts/gds/provision-core-upgrade.js repoNote; re-stated here because a module may
|
|
258
|
+
* not require a script, and round-tripped by tests/core_upgrade_runner.mjs). ONLY that
|
|
259
|
+
* shape is read: any other text on a done row is not turned into a warning, because
|
|
260
|
+
* inventing one would tell an owner their repo is stale on no evidence.
|
|
261
|
+
*/
|
|
262
|
+
const REPO_NOTE_RE = /^repo_not_updated:([a-z_]+): ([\s\S]+)$/;
|
|
263
|
+
function parseRepoNote(text) {
|
|
264
|
+
const m = REPO_NOTE_RE.exec(String(text || ''));
|
|
265
|
+
// Bounded because it reaches a DOM, like the error beside it.
|
|
266
|
+
return m ? { code: m[1], text: m[2].slice(0, 600) } : null;
|
|
267
|
+
}
|
|
268
|
+
|
|
259
269
|
/**
|
|
260
270
|
* The cached dry-run verdict, IF it is about the move now on offer. PURE (task 1004173).
|
|
261
271
|
*
|
|
@@ -344,8 +354,11 @@ function previewFromSnapshot(inst, snap, openIntent, lastMove) {
|
|
|
344
354
|
// versions, and this is a version question wearing a different hat.
|
|
345
355
|
superseded: lastMoveSuperseded(lastMove.target_version, served, recommended),
|
|
346
356
|
// Truncated because it reaches a DOM and upgrade.js's refusals can carry a whole
|
|
347
|
-
// `git status` (the dirty-tree one lists every file).
|
|
348
|
-
|
|
357
|
+
// `git status` (the dirty-tree one lists every file). Never set on a DONE move:
|
|
358
|
+
// a done row's last_error is the note below, and a move that worked is not an error.
|
|
359
|
+
error: lastMove.state !== 'done' && lastMove.last_error ? String(lastMove.last_error).slice(0, 600) : null,
|
|
360
|
+
// It LANDED, but GitHub did not get the new pin (task 1004065) — parseRepoNote.
|
|
361
|
+
warning: lastMove.state === 'done' ? parseRepoNote(lastMove.last_error) : null,
|
|
349
362
|
} : null,
|
|
350
363
|
};
|
|
351
364
|
}
|
|
@@ -379,7 +392,7 @@ function makeCoreUpgradePreviewHandler({ provisioning, pool, parseId, log }) {
|
|
|
379
392
|
return res.json({ ok: true, preview: await loadPreview(pool, provisioning, inst) });
|
|
380
393
|
} catch (err) {
|
|
381
394
|
log.error('[provisioning] GET /provisioning/instances/:id/core-upgrade', err);
|
|
382
|
-
return res
|
|
395
|
+
return failFrom(res, err, 'core_upgrade_preview_failed');
|
|
383
396
|
}
|
|
384
397
|
};
|
|
385
398
|
}
|
|
@@ -410,10 +423,10 @@ function makeControlPlaneHandler({ provisioning, pool, log }) {
|
|
|
410
423
|
return res.json({ ok: true, preview: await loadPreview(pool, provisioning, rows[0]) });
|
|
411
424
|
} catch (err) {
|
|
412
425
|
log.error('[provisioning] GET /provisioning/control-plane', err);
|
|
413
|
-
return res
|
|
426
|
+
return failFrom(res, err, 'control_plane_read_failed');
|
|
414
427
|
}
|
|
415
428
|
};
|
|
416
429
|
}
|
|
417
430
|
|
|
418
|
-
module.exports = { CORE_UPGRADE_FROM, lastCoreMove, lastMoveSuperseded, preflightForOffer, UPGRADE_SHAPES, VERSION_RE, isCoreVersion, coreUpgradeFault, enqueueCoreUpgrade, makeCoreUpgradeHandler, previewFromSnapshot, makeCoreUpgradePreviewHandler, makeControlPlaneHandler };
|
|
431
|
+
module.exports = { CORE_UPGRADE_FROM, lastCoreMove, lastMoveSuperseded, parseRepoNote, preflightForOffer, UPGRADE_SHAPES, VERSION_RE, isCoreVersion, coreUpgradeFault, enqueueCoreUpgrade, makeCoreUpgradeHandler, previewFromSnapshot, makeCoreUpgradePreviewHandler, makeControlPlaneHandler };
|
|
419
432
|
|