@bongos/core 1.19.758 → 1.19.760
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 +13 -13
- package/docs/module-api-changelog.md +4 -0
- package/modules/lifecycle/ship-card.js +75 -5
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/serve-internal.js +9 -1
- package/src/module-api.js +1 -1
- package/tests/hall_page_gate_map.mjs +9 -9
- package/tests/ship_card.mjs +95 -0
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.760",
|
|
6
|
+
"core_contract": "1.19.760",
|
|
7
|
+
"source_commit": "d95227cc76780c216d4662c4b5d1979114a2841f",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-16T03:30:46.130Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 483,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2623,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "0aa4534bb30ad7aebdab2abc87c30d522f473d9d154e17820e73fedaa79748f5",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2512,7 +2512,7 @@
|
|
|
2512
2512
|
{
|
|
2513
2513
|
"path": "docs/module-api-changelog.md",
|
|
2514
2514
|
"mode": "0000644",
|
|
2515
|
-
"sha256": "
|
|
2515
|
+
"sha256": "dac2227121e64e9f6516a5dfb80e9362b4b3c05da7abef17cd199b89bf861104"
|
|
2516
2516
|
},
|
|
2517
2517
|
{
|
|
2518
2518
|
"path": "docs/modules-contract.md",
|
|
@@ -5757,7 +5757,7 @@
|
|
|
5757
5757
|
{
|
|
5758
5758
|
"path": "modules/lifecycle/ship-card.js",
|
|
5759
5759
|
"mode": "0000644",
|
|
5760
|
-
"sha256": "
|
|
5760
|
+
"sha256": "0822f317a777c624880b86a02f9c63680fead1fdab79b13ba308801bed04a036"
|
|
5761
5761
|
},
|
|
5762
5762
|
{
|
|
5763
5763
|
"path": "modules/lifecycle/ship-preflight.js",
|
|
@@ -7567,12 +7567,12 @@
|
|
|
7567
7567
|
{
|
|
7568
7568
|
"path": "package-lock.json",
|
|
7569
7569
|
"mode": "0000644",
|
|
7570
|
-
"sha256": "
|
|
7570
|
+
"sha256": "578710b1ef47bdf6cdf4510cbe531c5ed92a8ae3758f820eeb75efe4c6bafa61"
|
|
7571
7571
|
},
|
|
7572
7572
|
{
|
|
7573
7573
|
"path": "package.json",
|
|
7574
7574
|
"mode": "0000644",
|
|
7575
|
-
"sha256": "
|
|
7575
|
+
"sha256": "34ee3354f9e18c01ed9b9f0b5cac46a1d4e64da68978e38519e37a540b2c8787"
|
|
7576
7576
|
},
|
|
7577
7577
|
{
|
|
7578
7578
|
"path": "public-docs/index.html",
|
|
@@ -9352,7 +9352,7 @@
|
|
|
9352
9352
|
{
|
|
9353
9353
|
"path": "src/bongos/serve-internal.js",
|
|
9354
9354
|
"mode": "0000644",
|
|
9355
|
-
"sha256": "
|
|
9355
|
+
"sha256": "405b8069c27ea596981cd4a2fd6a57b7e3637022cf63962d46a19b452fa675fc"
|
|
9356
9356
|
},
|
|
9357
9357
|
{
|
|
9358
9358
|
"path": "src/bongos/uptime-poller.js",
|
|
@@ -9382,7 +9382,7 @@
|
|
|
9382
9382
|
{
|
|
9383
9383
|
"path": "src/module-api.js",
|
|
9384
9384
|
"mode": "0000644",
|
|
9385
|
-
"sha256": "
|
|
9385
|
+
"sha256": "9080c03abc81fa68447459fcec10d19afb4c9f8a5816515a2af48adf4cce76dd"
|
|
9386
9386
|
},
|
|
9387
9387
|
{
|
|
9388
9388
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11067,7 +11067,7 @@
|
|
|
11067
11067
|
{
|
|
11068
11068
|
"path": "tests/hall_page_gate_map.mjs",
|
|
11069
11069
|
"mode": "0000644",
|
|
11070
|
-
"sha256": "
|
|
11070
|
+
"sha256": "10d8376f94e415d20b112a61e8d1f7f8f0f539eba5f685bdac4509624a69c13e"
|
|
11071
11071
|
},
|
|
11072
11072
|
{
|
|
11073
11073
|
"path": "tests/hall_palette.mjs",
|
|
@@ -12587,7 +12587,7 @@
|
|
|
12587
12587
|
{
|
|
12588
12588
|
"path": "tests/ship_card.mjs",
|
|
12589
12589
|
"mode": "0000644",
|
|
12590
|
-
"sha256": "
|
|
12590
|
+
"sha256": "67acdeb4d84a312af063c8a152724a5e81dfe7beb7d1d0b9bee2d942cc0bebd0"
|
|
12591
12591
|
},
|
|
12592
12592
|
{
|
|
12593
12593
|
"path": "tests/ship_card_injection.mjs",
|
|
@@ -1990,5 +1990,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1990
1990
|
landed since 1.19.756 with no explicit bump. run 35048307666. (task 1002620)
|
|
1991
1991
|
1.19.758 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1992
1992
|
landed since 1.19.757 with no explicit bump. run 35049006030. (task 1002620)
|
|
1993
|
+
1.19.759 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1994
|
+
landed since 1.19.758 with no explicit bump. run 35050268139. (task 1002620)
|
|
1995
|
+
1.19.760 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1996
|
+
landed since 1.19.759 with no explicit bump. run 35052040282. (task 1002620)
|
|
1993
1997
|
---------------------------------------------------------------------------
|
|
1994
1998
|
```
|
|
@@ -88,6 +88,39 @@ function stageFor(postGradeStatus) {
|
|
|
88
88
|
return 'failed';
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
// Did the grade fail to HAPPEN, rather than fail? (task 1002892)
|
|
92
|
+
//
|
|
93
|
+
// stageFor() collapses every non-shipped, non-confirmed outcome into 'failed',
|
|
94
|
+
// whose one presentation is red / "Needs fixes" / "grade failed". An OUTAGE lands
|
|
95
|
+
// there too, and then the card tells a builder their code was rejected when in
|
|
96
|
+
// fact nothing judged it: on task 1002459 a logged-out CLI killed all four panel
|
|
97
|
+
// workers and the builder got a 0/10 "grade failed" card with an EMPTY issues[],
|
|
98
|
+
// then went looking for code problems that did not exist. ship.js has always said
|
|
99
|
+
// this correctly in its CLI text ("GRADER UNAVAILABLE ... NOT a quality
|
|
100
|
+
// rejection") and the server excludes unavailable panels from its aggregates —
|
|
101
|
+
// only the card lagged.
|
|
102
|
+
//
|
|
103
|
+
// Returns the outage kind or null. Pure, and reads ONLY the signals the server
|
|
104
|
+
// already sets, so the card never re-derives a verdict of its own:
|
|
105
|
+
// 'panel' — the whole panel never produced a signal (#943). Unambiguous.
|
|
106
|
+
// 'cq_lens' — the gating code_quality lens alone went dark (task 1003318). This
|
|
107
|
+
// one carries a guard: it is only an outage while no lens raised a
|
|
108
|
+
// blocker/major. A dark lens beside a real finding is a rejection
|
|
109
|
+
// with a missing axis, and must keep the ordinary red card.
|
|
110
|
+
//
|
|
111
|
+
// The twin of this predicate is formatGradeOutcome in scripts/gds/ship-grade-steps.js,
|
|
112
|
+
// which decides the same two shapes for the CLI text. Change one, look at the other.
|
|
113
|
+
function gradeUnavailability(grade) {
|
|
114
|
+
const g = grade || {};
|
|
115
|
+
const s = g.signals || {};
|
|
116
|
+
if (s.panel_outcome === 'unavailable') return 'panel';
|
|
117
|
+
const issues = Array.isArray(g.issues) ? g.issues : [];
|
|
118
|
+
const substantive = issues.some((i) => i && (i.severity === 'blocker' || i.severity === 'major'));
|
|
119
|
+
const scores = g.rubric_scores || {};
|
|
120
|
+
if (s.cq_lens_unavailable === true && scores.code_quality === null && !substantive) return 'cq_lens';
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
91
124
|
// The one canonical journey every card draws, in order. Each node conjugates to
|
|
92
125
|
// three forms — done (past participle), active (present continuous), future (bare
|
|
93
126
|
// verb) — so the same stage reads 'built' / 'building' / 'build' depending on
|
|
@@ -103,6 +136,9 @@ const FORMS = Object.freeze({
|
|
|
103
136
|
ship: { done: 'shipped', active: 'shipping', future: 'ship' },
|
|
104
137
|
});
|
|
105
138
|
const GRADE_FAILED = 'grade failed';
|
|
139
|
+
// task 1002892: the confirm node's word when the grade did not RUN. Two words, so
|
|
140
|
+
// the " → " joint logic that already keeps 'grade failed' intact covers it too.
|
|
141
|
+
const NOT_GRADED = 'not graded';
|
|
106
142
|
|
|
107
143
|
// The journey, mapped: one entry per PIPELINE node, conjugated + classified by
|
|
108
144
|
// position relative to the current node (`cfg.at`). Both renderers consume this
|
|
@@ -118,7 +154,10 @@ function trailSteps(cfg) {
|
|
|
118
154
|
const f = FORMS[key];
|
|
119
155
|
if (i < at) return { word: f.done, state: 'done', checked: true, bold: false };
|
|
120
156
|
if (i === at) {
|
|
121
|
-
|
|
157
|
+
// task 1002892: the same node reads 'not graded' when the grade never ran.
|
|
158
|
+
// cfg.trailWord is set only by the outage override in buildCardModel; every
|
|
159
|
+
// other caller leaves it undefined and gets GRADE_FAILED exactly as before.
|
|
160
|
+
if (cfg.currentKind === 'failed') return { word: cfg.trailWord || GRADE_FAILED, state: 'current', checked: false, bold: true };
|
|
122
161
|
if (cfg.currentKind === 'done') return { word: f.done, state: 'current', checked: true, bold: true };
|
|
123
162
|
return { word: f.active, state: 'current', checked: false, bold: true }; // 'active'
|
|
124
163
|
}
|
|
@@ -279,9 +318,24 @@ function buildCardModel(input = {}) {
|
|
|
279
318
|
const landBailed = !!(stage === 'confirmed' && land && land.ok === false && !land.pending && land.reason);
|
|
280
319
|
// A bailed land is not "confirmed — landing". Recolor the banner to match the
|
|
281
320
|
// truth; the underlying STAGES entry is left untouched (it's frozen + shared).
|
|
321
|
+
// task 1002892: an outage is not a rejection. Same shape as landBailed above —
|
|
322
|
+
// recolor a copy, never mutate the frozen shared STAGES entry — and equally
|
|
323
|
+
// absent-signal-safe: a grade carrying no outage signals yields null here, so
|
|
324
|
+
// every pre-existing caller renders exactly as it did before.
|
|
325
|
+
// `input.grade`, not the `grade` local below — that is declared after this line.
|
|
326
|
+
const outage = stage === 'failed' ? gradeUnavailability(input.grade) : null;
|
|
282
327
|
const cfg = landBailed
|
|
283
328
|
? { ...baseCfg, tone: 'warning', icon: 'ti-alert-triangle', statusWord: 'Not landed', headline: (id) => `Task #${id} confirmed — NOT landed` }
|
|
284
|
-
:
|
|
329
|
+
: outage
|
|
330
|
+
? {
|
|
331
|
+
...baseCfg,
|
|
332
|
+
tone: 'warning',
|
|
333
|
+
icon: 'ti-cloud-off',
|
|
334
|
+
statusWord: 'Could not grade',
|
|
335
|
+
headline: (id) => `Task #${id} — could not grade`,
|
|
336
|
+
trailWord: NOT_GRADED,
|
|
337
|
+
}
|
|
338
|
+
: baseCfg;
|
|
285
339
|
const brand = input.brand || safeBrand();
|
|
286
340
|
const taskId = input.taskId;
|
|
287
341
|
const grade = input.grade || {};
|
|
@@ -319,7 +373,15 @@ function buildCardModel(input = {}) {
|
|
|
319
373
|
// Grade: pass · panel N/N | skipped | fail · N issues
|
|
320
374
|
let gradeText = null;
|
|
321
375
|
let gradeCls = 'cc-good';
|
|
322
|
-
if (
|
|
376
|
+
if (outage) {
|
|
377
|
+
// task 1002892: recoloring the banner alone was not enough — the Grade row
|
|
378
|
+
// and its HTML chip still read a red 'fail' right beside the amber "Could
|
|
379
|
+
// not grade", which is the same rejection signal this change exists to
|
|
380
|
+
// remove, just one line lower. There is no score to report when nothing
|
|
381
|
+
// ran, so say that instead of reporting a verdict.
|
|
382
|
+
gradeText = 'not graded';
|
|
383
|
+
gradeCls = '';
|
|
384
|
+
} else if (stage === 'failed') {
|
|
323
385
|
gradeText = issues.length ? `fail · ${issues.length} issue${issues.length === 1 ? '' : 's'}` : 'fail';
|
|
324
386
|
gradeCls = 'cc-bad';
|
|
325
387
|
} else if (grade.skipped) {
|
|
@@ -411,9 +473,17 @@ function buildCardModel(input = {}) {
|
|
|
411
473
|
|
|
412
474
|
// task 1002541: a bail knows the precise recovery command (re-run the ship,
|
|
413
475
|
// re-auth, pass --db-only…), which beats the stage's generic '/merge-mode'.
|
|
476
|
+
// task 1002892: 'Fix & re-grade' is wrong advice after an outage — there is
|
|
477
|
+
// nothing to fix, and telling a builder otherwise is what sent one hunting
|
|
478
|
+
// through a diff for findings that were never produced. Retry instead. For a
|
|
479
|
+
// total panel outage the CLI's diagnostic also points at /grader-health, but the
|
|
480
|
+
// card gets ONE action, and re-running the grade is the step that either works
|
|
481
|
+
// or reproduces the outage with its reason.
|
|
414
482
|
const action = (landBailed && land.nextStep)
|
|
415
483
|
? { kind: 'prompt', label: 'Land it', cmd: land.nextStep }
|
|
416
|
-
:
|
|
484
|
+
: outage
|
|
485
|
+
? { kind: 'prompt', label: 'Try grading again', cmd: `node scripts/gds/ship.js ${taskId} --regrade` }
|
|
486
|
+
: nextActionFor(stage, { taskId, discipline: input.discipline, sandboxUrl: input.sandboxUrl });
|
|
417
487
|
|
|
418
488
|
return {
|
|
419
489
|
stage, cfg, brand, taskId,
|
|
@@ -724,7 +794,7 @@ function emitCard(model, taskId, { log = console.log, widgetsEnabled = true } =
|
|
|
724
794
|
}
|
|
725
795
|
|
|
726
796
|
module.exports = {
|
|
727
|
-
STAGES, TONE, stageFor, buildCardModel, renderMonospaceCard, renderCardHtml, emitCard,
|
|
797
|
+
STAGES, TONE, stageFor, gradeUnavailability, buildCardModel, renderMonospaceCard, renderCardHtml, emitCard,
|
|
728
798
|
// task 1001730 — the guaranteed one-line confirmation (pure; every stage pinned).
|
|
729
799
|
confirmLine,
|
|
730
800
|
// task 1003155 — the one-line sanitizer + the whole-line cap, exported so the
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.760",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.760",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.760",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -1164,8 +1164,16 @@ function mountInternalSurfaces(app) {
|
|
|
1164
1164
|
// task 1003172 (C02): `collab` joins the same tier — every read behind it is
|
|
1165
1165
|
// any-builder and scoped to the caller by the SERVER (GET /help-requests/for-me
|
|
1166
1166
|
// derives crafts from the viewer's own profile), so signed-in is the right floor.
|
|
1167
|
+
// task 1003629 (idea 1000907): the BARE `task` and `idea` slugs join the tier
|
|
1168
|
+
// their own /:id forms already sit on. /builders/task/:id and /builders/idea/:id
|
|
1169
|
+
// are gated by TASK_DETAIL_PAGE_RE / IDEA_DETAIL_PAGE_RE below, but those
|
|
1170
|
+
// regexes require a numeric id, so the id-less slugs fell through to
|
|
1171
|
+
// BUILDERS_PAGE_RE — which resolves them to the SAME task.html / idea.html
|
|
1172
|
+
// shells — and matched no gate at all. The shell carries no data and every
|
|
1173
|
+
// read behind it is gated server-side, so this closed an inconsistency rather
|
|
1174
|
+
// than an exposure; the point is that one URL cannot be two tiers at once.
|
|
1167
1175
|
const SIGNEDIN_BUILDERS_PAGE_RE =
|
|
1168
|
-
/^\/builders\/(?:work|pair|settings|atlas|modules|goals|roadmap|copy-desk|studio|ideas|collab|primer(?:\.md)?|diagrams)(?:\.html)?\/?$/;
|
|
1176
|
+
/^\/builders\/(?:work|pair|settings|atlas|modules|goals|roadmap|copy-desk|studio|ideas|collab|task|idea|primer(?:\.md)?|diagrams)(?:\.html)?\/?$/;
|
|
1169
1177
|
// The diagram image assets under /builders/diagrams/ ride the same tier as the
|
|
1170
1178
|
// page that embeds them.
|
|
1171
1179
|
const SIGNEDIN_BUILDERS_PREFIX_RE = /^\/builders\/diagrams\//;
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.760'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -122,15 +122,15 @@ const ORACLE = {
|
|
|
122
122
|
'/builders/drachmae': null,
|
|
123
123
|
'/builders/profile': null,
|
|
124
124
|
|
|
125
|
-
//
|
|
126
|
-
// (task 1003448)
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
'/builders/task':
|
|
133
|
-
'/builders/idea':
|
|
125
|
+
// The bare detail slugs, gated by task 1003629 — the decision idea 1000907
|
|
126
|
+
// (task 1003448) asked for. They resolve to the same task.html / idea.html
|
|
127
|
+
// shells their /:id forms do, so they now sit on the same tier: the id-less
|
|
128
|
+
// form was never a different page, only a different spelling that the
|
|
129
|
+
// id-requiring TASK_DETAIL_PAGE_RE / IDEA_DETAIL_PAGE_RE could not see. A
|
|
130
|
+
// gate, not a redirect, because the shell is a real page at signed-in tier
|
|
131
|
+
// and redirecting would have to invent an id it does not have.
|
|
132
|
+
'/builders/task': 'requireBuilderPage',
|
|
133
|
+
'/builders/idea': 'requireBuilderPage',
|
|
134
134
|
|
|
135
135
|
// Gone, not public — the hall ships no my-projects page at all since task
|
|
136
136
|
// 1003308 (owner's decision: "my projects" is a user-level page the hub owns,
|
package/tests/ship_card.mjs
CHANGED
|
@@ -437,5 +437,100 @@ test('the ship + claim skills both instruct relaying [otb-confirm]', () => {
|
|
|
437
437
|
}
|
|
438
438
|
});
|
|
439
439
|
|
|
440
|
+
// ---- grader OUTAGE vs quality rejection (task 1002892) ----
|
|
441
|
+
//
|
|
442
|
+
// stageFor() collapses every non-shipped outcome into 'failed', whose one
|
|
443
|
+
// presentation was red / 'Needs fixes' / 'grade failed'. An outage landed there
|
|
444
|
+
// too, so the card told a builder their code was REJECTED when nothing had judged
|
|
445
|
+
// it — on task 1002459 a logged-out CLI killed all four workers and the builder
|
|
446
|
+
// got a 0/10 card with an EMPTY issues[] and went hunting for defects that did not
|
|
447
|
+
// exist. These pin that an outage now reads as 'could not grade', in amber, with a
|
|
448
|
+
// retry — and, just as importantly, that a REAL failing grade is untouched.
|
|
449
|
+
|
|
450
|
+
const failedInput = (grade) => ({
|
|
451
|
+
postGradeStatus: 'completed', taskId: 1266, title: 'T', valueSummary: 'v',
|
|
452
|
+
fileCount: 1, added: 1, removed: 0, grade, credits: {}, usage: {}, brand: BRAND,
|
|
453
|
+
});
|
|
454
|
+
const PANEL_OUTAGE = { passed: false, issues: [], signals: { panel_outcome: 'unavailable' } };
|
|
455
|
+
|
|
456
|
+
test('outage: a total panel outage reads as could-not-grade, not needs-fixes', () => {
|
|
457
|
+
const m = card.buildCardModel(failedInput(PANEL_OUTAGE));
|
|
458
|
+
assert.equal(m.stage, 'failed', 'the STAGE is unchanged — only its presentation moves');
|
|
459
|
+
assert.equal(m.cfg.statusWord, 'Could not grade');
|
|
460
|
+
assert.equal(m.cfg.tone, 'warning', 'amber, not danger — nothing was rejected');
|
|
461
|
+
assert.match(m.cfg.headline(1266), /could not grade/);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test('outage: the next action is a retry, not Fix & re-grade', () => {
|
|
465
|
+
const m = card.buildCardModel(failedInput(PANEL_OUTAGE));
|
|
466
|
+
assert.equal(m.action.label, 'Try grading again');
|
|
467
|
+
assert.doesNotMatch(m.action.label, /Fix/, 'there is nothing to fix after an outage');
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test('outage: the trail says not graded, not grade failed', () => {
|
|
471
|
+
const m = card.buildCardModel(failedInput(PANEL_OUTAGE));
|
|
472
|
+
const words = card.trailSteps(m.cfg).map((s) => s.word);
|
|
473
|
+
assert.ok(words.includes('not graded'), 'trail was ' + words.join(' -> '));
|
|
474
|
+
assert.ok(!words.includes('grade failed'));
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test('outage: the Grade row and its chip say not graded, never a red fail', () => {
|
|
478
|
+
// Recoloring the banner alone left 'fail' in red one line below it, which is the
|
|
479
|
+
// same rejection signal in a smaller font. Both render paths read the same two
|
|
480
|
+
// values, so both are pinned here.
|
|
481
|
+
const m = card.buildCardModel(failedInput(PANEL_OUTAGE));
|
|
482
|
+
assert.equal(row(m, 'Grade'), 'not graded');
|
|
483
|
+
const chip = m.facts.find((f) => f.label === 'grade');
|
|
484
|
+
assert.equal(chip.value, 'not graded');
|
|
485
|
+
assert.notEqual(chip.cls, 'cc-bad', 'the grade chip must not be painted as a failure');
|
|
486
|
+
assert.doesNotMatch(card.renderMonospaceCard(m), /\bfail\b/, 'the word fail must not appear on an outage card');
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test('outage with issues present still reports no grade, not a count', () => {
|
|
490
|
+
// An outage can carry incidental non-substantive issues; 'fail · 2 issues' would
|
|
491
|
+
// still be a verdict nothing produced.
|
|
492
|
+
const m = card.buildCardModel(failedInput({
|
|
493
|
+
passed: false,
|
|
494
|
+
issues: [{ severity: 'nit', summary: 'a' }, { severity: 'minor', summary: 'b' }],
|
|
495
|
+
signals: { panel_outcome: 'unavailable' },
|
|
496
|
+
}));
|
|
497
|
+
assert.equal(row(m, 'Grade'), 'not graded');
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test('a REAL failing grade is untouched — still red, still Needs fixes', () => {
|
|
501
|
+
const m = card.buildCardModel(failedInput({ passed: false, issues: [{ severity: 'major', summary: 'x' }], signals: {} }));
|
|
502
|
+
assert.equal(m.cfg.statusWord, 'Needs fixes');
|
|
503
|
+
assert.equal(m.cfg.tone, 'danger');
|
|
504
|
+
assert.equal(m.action.label, 'Fix & re-grade');
|
|
505
|
+
assert.ok(card.trailSteps(m.cfg).map((s) => s.word).includes('grade failed'));
|
|
506
|
+
assert.equal(row(m, 'Grade'), 'fail · 1 issue', 'a real rejection still reports its verdict and count');
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test('a grade carrying NO signals renders exactly as before (every old caller)', () => {
|
|
510
|
+
const m = card.buildCardModel(failedInput({ passed: false, issues: [] }));
|
|
511
|
+
assert.equal(m.cfg.statusWord, 'Needs fixes');
|
|
512
|
+
assert.equal(m.cfg.tone, 'danger');
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test('gradeUnavailability: a cq-lens outage counts, but NOT beside a real finding', () => {
|
|
516
|
+
const cq = { cq_lens_unavailable: true };
|
|
517
|
+
assert.equal(card.gradeUnavailability({ signals: cq, rubric_scores: { code_quality: null }, issues: [] }), 'cq_lens');
|
|
518
|
+
assert.equal(
|
|
519
|
+
card.gradeUnavailability({ signals: cq, rubric_scores: { code_quality: null }, issues: [{ severity: 'blocker', summary: 'x' }] }),
|
|
520
|
+
null,
|
|
521
|
+
'a dark lens beside a blocker is a rejection with a missing axis, not an outage');
|
|
522
|
+
assert.equal(card.gradeUnavailability({ signals: cq, rubric_scores: { code_quality: 7 }, issues: [] }), null,
|
|
523
|
+
'a scored cq axis means the lens ran');
|
|
524
|
+
assert.equal(card.gradeUnavailability({}), null);
|
|
525
|
+
assert.equal(card.gradeUnavailability(null), null);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
test('outage presentation never leaks onto a shipped or confirmed card', () => {
|
|
529
|
+
for (const status of ['shipped', 'confirmed']) {
|
|
530
|
+
const m = card.buildCardModel({ ...shippedInput, postGradeStatus: status, grade: { passed: true, issues: [], signals: { panel_outcome: 'unavailable' } } });
|
|
531
|
+
assert.notEqual(m.cfg.statusWord, 'Could not grade', status + ' must be unaffected');
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
|
|
440
535
|
console.log(`\nship_card: ${passed} passed, ${failed} failed`);
|
|
441
536
|
process.exit(failed ? 1 : 0);
|