@bongos/core 1.19.880 → 1.19.881
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 +27 -27
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/api/openapi.json +0 -4
- package/docs/api-reference.md +1 -1
- package/docs/copy-inventory.md +46 -46
- package/docs/copy-registry.json +57 -57
- package/docs/module-api-changelog.md +2 -0
- package/docs/project-context.template.md +2 -2
- package/modules/hall-ui/public/primer.js +0 -1
- package/modules/hall-ui/public/roadmap.css +0 -11
- package/modules/hall-ui/public/roadmap.js +7 -15
- package/modules/lifecycle/db-versions.js +17 -13
- package/modules/lifecycle/routes/versions.js +1 -11
- package/modules/lifecycle/version-autoclose.js +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/init.js +0 -3
- package/scripts/gds/provision-repo.js +5 -4
- package/src/module-api.js +1 -1
- package/tests/init.mjs +15 -12
- package/tests/provision.mjs +8 -3
- package/tests/version_first_goal.mjs +4 -2
- package/tests/versions_route.mjs +37 -12
|
@@ -23,7 +23,7 @@ const { createGoal } = require('./db-goals.js');
|
|
|
23
23
|
|
|
24
24
|
async function listVersions() {
|
|
25
25
|
const { rows } = await pool.query(
|
|
26
|
-
`SELECT id, name,
|
|
26
|
+
`SELECT id, name, status, scope_doc_path, started_at, shipped_at, done_when
|
|
27
27
|
FROM versions
|
|
28
28
|
ORDER BY started_at`
|
|
29
29
|
);
|
|
@@ -46,7 +46,7 @@ async function getVersion(id, deps = {}) {
|
|
|
46
46
|
throw new Error('getVersion({ forUpdate }) requires deps.client — a row lock outside a transaction holds nothing');
|
|
47
47
|
}
|
|
48
48
|
const { rows } = await executorFor(deps, pool).query(
|
|
49
|
-
`SELECT id, name,
|
|
49
|
+
`SELECT id, name, status, scope_doc_path, started_at, shipped_at, done_when
|
|
50
50
|
FROM versions WHERE id = $1${deps.forUpdate ? ' FOR UPDATE' : ''}`,
|
|
51
51
|
[id]
|
|
52
52
|
);
|
|
@@ -167,10 +167,14 @@ async function versionsWithStatus(status, deps = {}) {
|
|
|
167
167
|
// goal-less criteria, and ADR 0183 criteria satisfy themselves off LINKED tasks.
|
|
168
168
|
// Criteria nobody can link a task to can never close. Stamping goal_id here is
|
|
169
169
|
// what gives them something to hang from.
|
|
170
|
-
async function createVersion({ id, name, status = 'planning',
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
170
|
+
async function createVersion({ id, name, status = 'planning', scopeDocPath = null, doneWhen = null, criteria = [], firstGoal = null, createdBy = null }, deps = {}) {
|
|
171
|
+
// No `track`: the two-track (product/internal) model was retired by task
|
|
172
|
+
// 1002006 and its code unwound by task 1003665. versions.track survives as a
|
|
173
|
+
// column, NOT NULL but carrying DEFAULT 'product' since core_205, so an INSERT
|
|
174
|
+
// that omits it is legal — which is what lets this path stop naming it. The
|
|
175
|
+
// comment that used to sit here said the column had NO default; that was true
|
|
176
|
+
// when it was written and stale by core_205, and believing it is why five
|
|
177
|
+
// insert paths kept supplying a value nobody read.
|
|
174
178
|
//
|
|
175
179
|
// deps.pool is the module's existing test seam (listVersions, versionProgress):
|
|
176
180
|
// honoured ONLY under NODE_ENV=test, so production can never be pointed at an
|
|
@@ -179,10 +183,10 @@ async function createVersion({ id, name, status = 'planning', track = 'product',
|
|
|
179
183
|
const txPool = (deps.pool && process.env.NODE_ENV === 'test') ? deps.pool : pool;
|
|
180
184
|
return withTx(async (client) => {
|
|
181
185
|
const { rows } = await client.query(
|
|
182
|
-
`INSERT INTO versions (id, name, status,
|
|
183
|
-
VALUES ($1, $2, $3, $4, $5
|
|
184
|
-
RETURNING id, name, status,
|
|
185
|
-
[id, name, status,
|
|
186
|
+
`INSERT INTO versions (id, name, status, scope_doc_path, done_when)
|
|
187
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
188
|
+
RETURNING id, name, status, scope_doc_path, started_at, shipped_at, done_when`,
|
|
189
|
+
[id, name, status, scopeDocPath, doneWhen]
|
|
186
190
|
);
|
|
187
191
|
const version = rows[0];
|
|
188
192
|
// The first goal. Titled for the version it opens unless the caller names one —
|
|
@@ -340,7 +344,7 @@ async function promotePlanningVersion(exec) {
|
|
|
340
344
|
SET status = 'building', started_at = COALESCE(started_at, now())
|
|
341
345
|
WHERE id = (SELECT id FROM versions WHERE status = 'planning' ORDER BY id LIMIT 1)
|
|
342
346
|
AND NOT EXISTS (SELECT 1 FROM versions WHERE status = 'building')
|
|
343
|
-
RETURNING id, name,
|
|
347
|
+
RETURNING id, name, status, started_at`
|
|
344
348
|
);
|
|
345
349
|
return rows[0] ?? null;
|
|
346
350
|
}
|
|
@@ -352,7 +356,7 @@ async function closeVersion({ versionId, plan = [], reason, planningVersionId =
|
|
|
352
356
|
await client.query('BEGIN');
|
|
353
357
|
|
|
354
358
|
const { rows: vRows } = await client.query(
|
|
355
|
-
`SELECT id, name,
|
|
359
|
+
`SELECT id, name, status, scope_doc_path, started_at, shipped_at, done_when
|
|
356
360
|
FROM versions WHERE id = $1 FOR UPDATE`,
|
|
357
361
|
[versionId]
|
|
358
362
|
);
|
|
@@ -576,7 +580,7 @@ async function closeVersion({ versionId, plan = [], reason, planningVersionId =
|
|
|
576
580
|
const { rows: closed } = await client.query(
|
|
577
581
|
`UPDATE versions SET status = 'shipped', shipped_at = now()
|
|
578
582
|
WHERE id = $1
|
|
579
|
-
RETURNING id, name,
|
|
583
|
+
RETURNING id, name, status, scope_doc_path, started_at, shipped_at, done_when`,
|
|
580
584
|
[versionId]
|
|
581
585
|
);
|
|
582
586
|
|
|
@@ -30,10 +30,6 @@ const { asyncHandler, validateOrRespond } = api;
|
|
|
30
30
|
// 'building'; 'shipped'/'frozen' are end states reached via the close flow.
|
|
31
31
|
const VALID_VERSION_STATUSES = new Set(['planning', 'building', 'shipped', 'frozen']);
|
|
32
32
|
|
|
33
|
-
// versions.track CHECK (migration 003) — NOT NULL with no DB default, so the
|
|
34
|
-
// create path must always supply one. 'product' is the caller-facing default:
|
|
35
|
-
// an instance seeding its first version is seeding its product track.
|
|
36
|
-
const VALID_VERSION_TRACKS = new Set(['product', 'internal']);
|
|
37
33
|
|
|
38
34
|
module.exports = function buildVersionsRouter() {
|
|
39
35
|
const router = express.Router();
|
|
@@ -99,7 +95,6 @@ module.exports = function buildVersionsRouter() {
|
|
|
99
95
|
id: { required: true, type: 'string', maxLength: 64, minLength: 1 },
|
|
100
96
|
name: { required: true, type: 'string', maxLength: 200, minLength: 1 },
|
|
101
97
|
status: { type: 'string', maxLength: 32 },
|
|
102
|
-
track: { type: 'string', maxLength: 16 },
|
|
103
98
|
done_when: { type: 'string', maxLength: 8000 },
|
|
104
99
|
scope_doc_path: { type: 'string', maxLength: 256 },
|
|
105
100
|
criteria: { type: 'array', maxItems: 100 },
|
|
@@ -116,11 +111,6 @@ module.exports = function buildVersionsRouter() {
|
|
|
116
111
|
return res.fail('bad_status', 400, { valid: Array.from(VALID_VERSION_STATUSES) });
|
|
117
112
|
}
|
|
118
113
|
|
|
119
|
-
const track = body.track || 'product';
|
|
120
|
-
if (!VALID_VERSION_TRACKS.has(track)) {
|
|
121
|
-
return res.fail('bad_track', 400, { valid: Array.from(VALID_VERSION_TRACKS) });
|
|
122
|
-
}
|
|
123
|
-
|
|
124
114
|
if (await db.getVersion(id)) {
|
|
125
115
|
return res.fail('version_exists', { status: 409, message: `Version '${id}' already exists.` });
|
|
126
116
|
}
|
|
@@ -187,7 +177,7 @@ module.exports = function buildVersionsRouter() {
|
|
|
187
177
|
let version, goal, created;
|
|
188
178
|
try {
|
|
189
179
|
({ version, goal, criteria: created } = await db.createVersion({
|
|
190
|
-
id, name, status,
|
|
180
|
+
id, name, status,
|
|
191
181
|
doneWhen: body.done_when ?? null,
|
|
192
182
|
scopeDocPath: body.scope_doc_path ?? null,
|
|
193
183
|
criteria,
|
|
@@ -65,7 +65,7 @@ async function autoCloseVersionIfComplete(exec, versionId) {
|
|
|
65
65
|
AND g.status = 'open'
|
|
66
66
|
AND ${NOT_MAINTENANCE}
|
|
67
67
|
)
|
|
68
|
-
RETURNING v.id, v.name, v.
|
|
68
|
+
RETURNING v.id, v.name, v.status, v.shipped_at`,
|
|
69
69
|
[versionId]
|
|
70
70
|
);
|
|
71
71
|
return rows[0] ?? null;
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.881",
|
|
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.881",
|
|
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.881",
|
|
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",
|
package/scripts/gds/init.js
CHANGED
|
@@ -391,9 +391,6 @@ function versionPayload(spec) {
|
|
|
391
391
|
id: v.id,
|
|
392
392
|
name: v.name,
|
|
393
393
|
status: v.status || 'building',
|
|
394
|
-
// versions.track is NOT NULL (no DB default) — always send it explicitly.
|
|
395
|
-
// An instance's first version is its product track unless the spec says otherwise.
|
|
396
|
-
track: v.track === 'internal' ? 'internal' : 'product',
|
|
397
394
|
done_when: v.done_when,
|
|
398
395
|
criteria: [{ criterion_id: v.criterion_id ? slugify(v.criterion_id) : 'first-goal', criterion_md: v.done_when }],
|
|
399
396
|
};
|
|
@@ -55,8 +55,9 @@ function migrateCmd(inst, { privileged = false } = {}) {
|
|
|
55
55
|
// ONLY IF the versions table is empty: idempotent, safe to re-run, and it never
|
|
56
56
|
// disturbs a project that already has versions. Mirrors seedViaApi's shape — V1
|
|
57
57
|
// (status 'building') + one done-when criterion so /status has a finish line from day
|
|
58
|
-
// one. `track`
|
|
59
|
-
//
|
|
58
|
+
// one. No `track` column: the two-track model was retired (task 1002006, unwound
|
|
59
|
+
// by task 1003665). The seed runs on a freshly-MIGRATED database, so core_205's
|
|
60
|
+
// DEFAULT 'product' is in place and an INSERT that omits the column is legal.
|
|
60
61
|
//
|
|
61
62
|
// The id/name/done_when come from instanceInitSpec so the starter version has ONE
|
|
62
63
|
// source of truth (the same spec the greenfield `bongos init` seed uses). SQL string
|
|
@@ -68,8 +69,8 @@ function seedFirstVersionCmd(inst, { privileged = false } = {}) {
|
|
|
68
69
|
const v = instanceInitSpec(inst).version;
|
|
69
70
|
const q = (s) => `$v$${s}$v$`; // dollar-quoted literal (no value contains a single quote or $)
|
|
70
71
|
const sql =
|
|
71
|
-
`INSERT INTO versions (id, name,
|
|
72
|
-
`SELECT ${q(v.id)}, ${q(v.name)}, $v$
|
|
72
|
+
`INSERT INTO versions (id, name, status, done_when) ` +
|
|
73
|
+
`SELECT ${q(v.id)}, ${q(v.name)}, $v$building$v$, ${q(v.done_when)} ` +
|
|
73
74
|
`WHERE NOT EXISTS (SELECT 1 FROM versions); ` +
|
|
74
75
|
`INSERT INTO done_when_criteria (version_id, criterion_id, criterion_md, sort_order) ` +
|
|
75
76
|
`SELECT ${q(v.id)}, $v$first-goal$v$, ${q(v.done_when)}, 10 ` +
|
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.881'; // 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');
|
package/tests/init.mjs
CHANGED
|
@@ -204,16 +204,18 @@ test('versionPayload: explicit criterion_id is slugified', () => {
|
|
|
204
204
|
assert.equal(init.versionPayload(spec).criteria[0].criterion_id, 'prove-the-loop');
|
|
205
205
|
});
|
|
206
206
|
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
207
|
+
// The inverse of the case this replaces. That case pinned "always send an
|
|
208
|
+
// explicit track", guarding the 1.18.18 bug where omitting it 500ed every
|
|
209
|
+
// POST /versions against a NOT NULL column with no default. core_205 gave the
|
|
210
|
+
// column DEFAULT 'product' and task 1003665 retired the concept, so the payload
|
|
211
|
+
// must now NOT carry it — and a spec that still names one must not smuggle it
|
|
212
|
+
// through to a route that no longer accepts the field.
|
|
213
|
+
test('versionPayload: sends no track, and drops one a spec still names', () => {
|
|
214
|
+
assert.equal('track' in init.versionPayload(fullSpec()), false);
|
|
215
|
+
const legacy = fullSpec();
|
|
216
|
+
legacy.version.track = 'internal';
|
|
217
|
+
assert.equal('track' in init.versionPayload(legacy), false,
|
|
218
|
+
'an old spec file cannot reintroduce the field');
|
|
217
219
|
});
|
|
218
220
|
|
|
219
221
|
// ---- provisioning arm (ADR 0111 §3, task 1946) ----
|
|
@@ -661,12 +663,13 @@ async function runSeedTests() {
|
|
|
661
663
|
assert.ok(findTaskPost(api, /Production topology/), 'filed the manual fallback task on error');
|
|
662
664
|
});
|
|
663
665
|
|
|
664
|
-
await at('seedViaApi: version POST carries
|
|
666
|
+
await at('seedViaApi: the version POST carries NO track (task 1003665)', async () => {
|
|
665
667
|
const api = fakeApi();
|
|
666
668
|
await init.seedViaApi(provSpec({}), { log: () => {}, api });
|
|
667
669
|
const vpost = api.calls.find((c) => c.path.endsWith('/versions'));
|
|
668
670
|
assert.ok(vpost, 'POSTed the version');
|
|
669
|
-
assert.equal(vpost.body
|
|
671
|
+
assert.equal('track' in vpost.body, false,
|
|
672
|
+
'POST /versions no longer accepts the field; the DB default supplies the column');
|
|
670
673
|
});
|
|
671
674
|
|
|
672
675
|
await at('seedViaApi: the ideator kickoff task POSTs kind=decision + discipline=ideator', async () => {
|
package/tests/provision.mjs
CHANGED
|
@@ -59,16 +59,21 @@ t('dbCreateCmd / migrateCmd use the slug db + GDS-only migrate', () => {
|
|
|
59
59
|
assert.match(P.migrateCmd(coTenant), /PGDATABASE=mercury MEDUSA_GDS_ONLY=1 \.\/scripts\/migrate\.sh/);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
t('seedFirstVersionCmd: idempotent (only-if-empty) V1 seed against the slug db, track
|
|
62
|
+
t('seedFirstVersionCmd: idempotent (only-if-empty) V1 seed against the slug db, no track, one criterion — the fix for the empty-versions bug (task 2119)', () => {
|
|
63
63
|
const cmd = P.seedFirstVersionCmd(coTenant);
|
|
64
64
|
// targets the instance's OWN db, matching createdb/migrate
|
|
65
65
|
assert.match(cmd, /psql .*-d mercury /, 'seeds the instance db');
|
|
66
66
|
assert.match(cmd, /ON_ERROR_STOP=1/, 'aborts on any error (atomic, no half-seed)');
|
|
67
67
|
// the version the greenfield init spec defines — ONE source of truth
|
|
68
68
|
const v = P.instanceInitSpec(coTenant).version;
|
|
69
|
-
assert.match(cmd, /INSERT INTO versions \(id, name,
|
|
69
|
+
assert.match(cmd, /INSERT INTO versions \(id, name, status, done_when\)/);
|
|
70
70
|
assert.match(cmd, new RegExp(`\\$v\\$${reEsc(v.id)}\\$v\\$`), 'seeds the spec version id (V1)');
|
|
71
|
-
|
|
71
|
+
// task 1003665 retired the two-track model. The seed names no track: it runs on
|
|
72
|
+
// a freshly-MIGRATED database, so core_205's DEFAULT 'product' fills the still-
|
|
73
|
+
// NOT NULL column. If that default is ever dropped, THIS seed 500s first and a
|
|
74
|
+
// new instance cannot stand up — which is why the assertion is stated that way.
|
|
75
|
+
assert.doesNotMatch(cmd, /\btrack\b/, 'the seed names no track column');
|
|
76
|
+
assert.doesNotMatch(cmd, /\$v\$product\$v\$/, 'nor smuggles its value in positionally');
|
|
72
77
|
assert.match(cmd, /\$v\$building\$v\$/, 'status building, mirroring seedViaApi');
|
|
73
78
|
assert.ok(cmd.includes(v.done_when), 'carries the spec done_when verbatim (incl. the → arrows)');
|
|
74
79
|
// the IDEMPOTENT guard — only seeds when there is NO version yet, so it never disturbs an existing project
|
|
@@ -57,7 +57,9 @@ function fakeDb({ goalRow = { id: 77, title: 'Foundations' } } = {}) {
|
|
|
57
57
|
query: async (sql, params) => {
|
|
58
58
|
log.push({ sql, params });
|
|
59
59
|
if (/^\s*(BEGIN|COMMIT|ROLLBACK)/i.test(sql)) return { rows: [] };
|
|
60
|
-
|
|
60
|
+
// params are positional: (id, name, status, scope_doc_path, done_when).
|
|
61
|
+
// `track` sat at index 3 until task 1003665 removed it from the INSERT.
|
|
62
|
+
if (/INSERT INTO versions/i.test(sql)) return { rows: [{ id: params[0], name: params[1], status: params[2] }] };
|
|
61
63
|
if (/INSERT INTO goals/i.test(sql)) return { rows: [goalRow] };
|
|
62
64
|
if (/INSERT INTO done_when_criteria/i.test(sql)) {
|
|
63
65
|
return { rows: [{ id: log.length, version_id: params[0], criterion_id: params[1], criterion_md: params[2], sort_order: params[3], goal_id: params[4], satisfied: false }] };
|
|
@@ -83,7 +85,7 @@ const CRITERIA = [
|
|
|
83
85
|
console.log('createVersion — the version, its first goal, and its criteria:');
|
|
84
86
|
|
|
85
87
|
const out = await createVersion({
|
|
86
|
-
id: 'V1', name: 'Foundations', status: 'building',
|
|
88
|
+
id: 'V1', name: 'Foundations', status: 'building',
|
|
87
89
|
doneWhen: 'The loop closes.', criteria: CRITERIA, createdBy: 7,
|
|
88
90
|
}, { pool: db.pool });
|
|
89
91
|
|
package/tests/versions_route.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { strict as assert } from 'node:assert';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
|
+
import fs from 'node:fs';
|
|
13
14
|
|
|
14
15
|
const require = createRequire(import.meta.url);
|
|
15
16
|
const { validate } = require('../src/bongos/routes/_helpers.js');
|
|
@@ -23,12 +24,14 @@ function test(name, fn) {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
// The EXACT schema declared in versions.js → POST /versions validateOrRespond().
|
|
26
|
-
// Keep in sync if the route's schema changes
|
|
27
|
+
// Keep in sync if the route's schema changes — and note that "keep in sync" is a
|
|
28
|
+
// promise a copy cannot keep on its own, so the track case below reads the
|
|
29
|
+
// ROUTE SOURCE rather than this object. A copy that still declared `track` would
|
|
30
|
+
// happily prove the old contract forever.
|
|
27
31
|
const VERSIONS_POST_SCHEMA = {
|
|
28
32
|
id: { required: true, type: 'string', maxLength: 64, minLength: 1 },
|
|
29
33
|
name: { required: true, type: 'string', maxLength: 200, minLength: 1 },
|
|
30
34
|
status: { type: 'string', maxLength: 32 },
|
|
31
|
-
track: { type: 'string', maxLength: 16 },
|
|
32
35
|
done_when: { type: 'string', maxLength: 8000 },
|
|
33
36
|
scope_doc_path: { type: 'string', maxLength: 256 },
|
|
34
37
|
criteria: { type: 'array', maxItems: 100 },
|
|
@@ -75,18 +78,40 @@ test('INIT: unknown extra fields pass through (forward-compat)', () => {
|
|
|
75
78
|
assert.equal(validate({ id: 'V1', name: 'x', future_field: 42 }, VERSIONS_POST_SCHEMA), null);
|
|
76
79
|
});
|
|
77
80
|
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
// The two-track (product/internal) model was retired by task 1002006 and its code
|
|
82
|
+
// unwound by task 1003665: role/discipline is the sole work-type axis. These read
|
|
83
|
+
// the ROUTE'S OWN SOURCE, not the copied schema above — the point is that the
|
|
84
|
+
// concept is gone from the file that enforces it.
|
|
85
|
+
//
|
|
86
|
+
// What made the removal safe, and what would make it unsafe again: versions.track
|
|
87
|
+
// is still NOT NULL, so an INSERT omitting it is legal ONLY because core_205 gave
|
|
88
|
+
// the column DEFAULT 'product'. Drop that default and every create 500s. The
|
|
89
|
+
// comments this task deleted asserted the opposite ("no DB default") and were
|
|
90
|
+
// stale from core_205 onward — which is precisely why five insert paths went on
|
|
91
|
+
// supplying a value nothing read.
|
|
92
|
+
const routeSrc = fs.readFileSync(
|
|
93
|
+
new URL('../modules/lifecycle/routes/versions.js', import.meta.url), 'utf8');
|
|
94
|
+
|
|
95
|
+
test('the route no longer knows what a track is', () => {
|
|
96
|
+
for (const gone of ['VALID_VERSION_TRACKS', 'bad_track']) {
|
|
97
|
+
assert.ok(!routeSrc.includes(gone), `${gone} is gone from POST /versions`);
|
|
98
|
+
}
|
|
99
|
+
assert.ok(!/\btrack\b/.test(routeSrc.replace(/tracked|tracking/g, '')),
|
|
100
|
+
'no track identifier survives in the versions route');
|
|
84
101
|
});
|
|
85
102
|
|
|
86
|
-
test('
|
|
87
|
-
|
|
88
|
-
assert.
|
|
89
|
-
assert.
|
|
103
|
+
test('a version is created WITHOUT a track, and that is the whole contract', () => {
|
|
104
|
+
// The create shape `bongos init` and provision-repo.js now send.
|
|
105
|
+
assert.equal(validate({ id: 'V1', name: 'x' }, VERSIONS_POST_SCHEMA), null);
|
|
106
|
+
assert.equal(validate({ id: 'GDS-V5', name: 'x', status: 'planning' }, VERSIONS_POST_SCHEMA), null);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('the INSERT does not name the column either', () => {
|
|
110
|
+
const dbSrc = fs.readFileSync(
|
|
111
|
+
new URL('../modules/lifecycle/db-versions.js', import.meta.url), 'utf8');
|
|
112
|
+
const insert = dbSrc.slice(dbSrc.indexOf('INSERT INTO versions'));
|
|
113
|
+
assert.ok(!/^[^`]*\btrack\b/.test(insert.slice(0, insert.indexOf('`'))),
|
|
114
|
+
'INSERT INTO versions names no track column — the DB default supplies it');
|
|
90
115
|
});
|
|
91
116
|
|
|
92
117
|
// ---- BAD shapes must return a structured 400, never throw / 500 ----
|