@bongos/core 1.19.1065 → 1.19.1067

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.
@@ -11,10 +11,10 @@
11
11
  // 3. it says where it came from
12
12
  // 4. it files at backlog, never ready
13
13
  //
14
- // The production table ships EMPTY (R04 writes the first rule), so the rules
15
- // below are the test's OWN declarations — which is also the point: a mechanism
16
- // proved by a rule its consumer has not written yet is proved against the
17
- // contract rather than against one example.
14
+ // The mechanism is proved by the test's OWN declarations, not a production
15
+ // rule — which is also the point: a mechanism proved against the contract
16
+ // rather than one example survives its rules coming and going (the artist
17
+ // review, R04's, was removed by TW14).
18
18
  //
19
19
  // DB-free: the runner takes its three database calls as injected functions.
20
20
 
@@ -185,7 +185,13 @@ await test('the production table is valid, and a duplicate key is refused', () =
185
185
  assert.ok(shape.some((p) => /body/.test(p)));
186
186
  });
187
187
 
188
- // --- the first production rule: the artist review (R04, task 1003115) --------
188
+ // --- the artist review is GONE: Tweak Mode replaced it (TW14, ADR 0341 D9) ----
189
+ //
190
+ // R04 (task 1003115) wrote `artist-review-on-ship`, which filed an artist review
191
+ // for every copy/visual ship. Task 1004325 removed it: a ship that changes a
192
+ // page's words writes NOTHING, and the page reads "changed since last tweak"
193
+ // because drift is derived (D8). tests/tweak_replaces_artist_review.mjs proves
194
+ // that second half on the whole path; these pin the first.
189
195
 
190
196
  const shippedCopy = (over = {}) => ({
191
197
  id: '7001', version_id: 'BONGOS-V1', goal_id: '1000074',
@@ -194,66 +200,59 @@ const shippedCopy = (over = {}) => ({
194
200
  ...over,
195
201
  });
196
202
 
197
- await test('the artist review is keyed and disciplined, and the table stays well-formed', () => {
198
- // This pinned CASCADES.length === 1 until task 1004090 appended the removal
199
- // proposal (BV2.R15). A count is the wrong pin for a table whose whole contract
200
- // is "extend by appending an entry" (ADR 0242) — it fails on the one change it
201
- // is meant to permit, and says nothing about WHICH rules are there.
202
- //
203
- // So it pins the rule by KEY instead, and pins the SET of keys separately below:
204
- // a rule vanishing and a rule being added are different events and should fail
205
- // differently.
206
- const r = cascade.CASCADES.find((x) => x.key === 'artist-review-on-ship');
207
- assert.ok(r, 'the artist review rule is gone from CASCADES');
208
- assert.equal(r.discipline, 'artist', 'the review is FOR the artist, whatever role shipped the work');
209
- assert.equal(r.kind, 'unclassified', 'a valid kind — not the invalid "chore" the spec used to default to');
210
- assert.deepEqual(cascade.validateCascades(), [], 'and the shipped table is well-formed');
203
+ // A test rule that records the ctx the runner handed it, so the runner's
204
+ // resolution of policy and changed files is still pinned with no production
205
+ // rule reading either.
206
+ const ctxProbe = () => {
207
+ const seen = [];
208
+ return {
209
+ seen,
210
+ rule: {
211
+ key: 'test-ctx-probe',
212
+ discipline: 'artist',
213
+ when: (t, ctx) => { seen.push(ctx); return false; },
214
+ title: () => 'never',
215
+ body: () => 'never',
216
+ },
217
+ };
218
+ };
219
+
220
+ await test('the artist review rule is gone from CASCADES, and the table stays well-formed', () => {
221
+ assert.equal(cascade.CASCADES.find((x) => x.key === 'artist-review-on-ship'), undefined,
222
+ 'Tweak Mode replaced the per-ship review (ADR 0341 D9)');
223
+ assert.equal(cascade.artistReviewBody, undefined, 'its body went with it');
224
+ assert.deepEqual(cascade.validateCascades(), [], 'the shipped table is well-formed');
211
225
  });
212
226
 
213
227
  await test('the production rule set is exactly what is declared', () => {
214
- // The other half of the pin above: adding a rule is a deliberate act, so it
215
- // should have to be recorded here. Duplicate keys would collide on source_ref
216
- // and silently suppress one another, which validateCascades already refuses.
217
- assert.deepEqual(
218
- cascade.CASCADES.map((r) => r.key).sort(),
219
- ['artist-review-on-ship', 'removal-proposed-on-ship'],
220
- );
228
+ // Adding a rule is a deliberate act, so it should have to be recorded here,
229
+ // and a rule vanishing fails here too. Duplicate keys would collide on
230
+ // source_ref and silently suppress one another, which validateCascades refuses.
231
+ assert.deepEqual(cascade.CASCADES.map((r) => r.key).sort(), ['removal-proposed-on-ship']);
232
+ });
233
+
234
+ await test('a copy/visual ship files NO artist review, at every artist-gate level', () => {
235
+ for (const ctx of [undefined, {}, { artistGate: 'strict' }, { artistGate: 'advisory' }, { artistGate: 'off' }, { artistGate: undefined }]) {
236
+ const { matched, errors } = cascade.cascadesFor(shippedCopy(), cascade.CASCADES, ctx);
237
+ assert.deepEqual(errors, []);
238
+ assert.equal(matched.length, 0, `nothing filed with ctx ${JSON.stringify(ctx)}`);
239
+ }
240
+ // Every shape that USED to file one: a copy proposal landing, an artist's own
241
+ // work, an unlabelled ship that changed a page file, and a truncated file list.
242
+ const shapes = [
243
+ [shippedCopy({ module_key: null, source: 'copy-proposal' }), {}],
244
+ [shippedCopy({ module_key: null, discipline: 'artist' }), {}],
245
+ [shippedCopy({ module_key: null }), { changedFiles: ['modules/hall-ui/public/studio.html'] }],
246
+ [shippedCopy({ module_key: null }), { changedFiles: [], changedFilesTruncated: true }],
247
+ ];
248
+ for (const [task, ctx] of shapes) {
249
+ assert.equal(cascade.cascadesFor(task, cascade.CASCADES, ctx).matched.length, 0, JSON.stringify(ctx));
250
+ }
221
251
  });
222
252
 
223
- await test('a copy/visual ship generates the two-step artist review', () => {
224
- const { matched } = cascade.cascadesFor(shippedCopy());
225
- assert.equal(matched.length, 1);
226
- const spec = matched[0].spec;
227
- assert.equal(spec.discipline, 'artist');
228
- assert.equal(spec.kind, 'unclassified');
229
- assert.equal(spec.status, 'backlog', 'a request that triage may dismiss — not claimable, not nagging');
230
- assert.match(spec.title, /7001/);
231
- // Both steps, and both routes of step 2, are in the body.
232
- //
233
- // STEP 1's label tracks ADR 0298 (task 1003937), and it has now turned twice.
234
- // It first read "REVIEW THE GRADER … coach it", which asked for something no
235
- // artist could do: the panel's four lenses read completion, code quality,
236
- // security and runtime cost, so there was no copy verdict to coach. It was
237
- // rewritten to ask for the RECORD instead, as an explicitly INTERIM wording,
238
- // pinned here so a well-meaning copy-edit could not restore the unfollowable
239
- // version without reading why.
240
- //
241
- // Task 1003977 shipped the advisory reader lens and the voice guide it waited
242
- // on (task 1003976), so the step is coaching again — and this pin moves with
243
- // it. The two must turn TOGETHER: the wording is the signal for which world
244
- // we are in, so a body that still said "no lens reads a sentence" after the
245
- // lens landed would send every artist looking for a counterpart that is
246
- // sitting right there in the grade record.
247
- assert.match(spec.description, /STEP 1 — COACH THE READER/);
248
- assert.match(spec.description, /docs\/voice\.md/, 'and names the written standard to coach against');
249
- assert.doesNotMatch(spec.description, /none of them reads a sentence/,
250
- 'the interim claim is gone — it stopped being true when the reader shipped');
251
- assert.match(spec.description, /ADR 0298/, 'the step names the decision that shaped it');
252
- assert.match(spec.description, /STEP 2 — REMAKE OR TWEAK/);
253
- assert.match(spec.description, /OUTSIDE the AI chat/);
254
- assert.match(spec.description, /WITH it/);
255
- assert.match(spec.description, /DISMISS this in triage/);
256
- assert.match(spec.description, /blocks nothing that shipped/, 'it says out loud that it is not a gate');
253
+ await test('hasCopyOrVisualWork STAYS, because the grader\'s reader lens is scoped by it', () => {
254
+ assert.equal(typeof cascade.hasCopyOrVisualWork, 'function');
255
+ assert.equal(cascade.hasCopyOrVisualWork(shippedCopy()), true, 'a hall-ui ship is still copy/visual work');
257
256
  });
258
257
 
259
258
  await test('"copy or visuals" is a NARROW predicate — three ways in, and a default of no', () => {
@@ -262,49 +261,33 @@ await test('"copy or visuals" is a NARROW predicate — three ways in, and a def
262
261
  assert.equal(H(shippedCopy({ module_key: 'art-pipeline' })), true);
263
262
  assert.equal(H(shippedCopy({ module_key: null, source: 'copy-proposal' })), true, 'a landed copy proposal');
264
263
  assert.equal(H(shippedCopy({ module_key: null, source: 'manual', discipline: 'artist' })), true, 'work an artist did');
265
- // The default is NO — a backend/infra ship is not an artist review.
264
+ // The default is NO — a backend/infra ship is not copy/visual work.
266
265
  assert.equal(H(shippedCopy({ module_key: 'lifecycle', discipline: 'engineer', source: 'manual' })), false);
267
266
  assert.equal(H(shippedCopy({ module_key: null, discipline: 'engineer', source: 'manual' })), false);
268
267
  assert.equal(H(null), false);
269
268
  });
270
269
 
271
- await test('THE ANTI-RECURSION GUARD: an artist review shipping does NOT generate a review of itself', () => {
272
- // The generated review is discipline:'artist' + source:'cascade'. Its own
273
- // predicate (discipline artist) would match — so without the source guard it
274
- // would cascade forever. This is the test that proves it is a single hop.
275
- const reviewTaskThatShipped = shippedCopy({
276
- discipline: 'artist', source: cascade.CASCADE_SOURCE, module_key: null, id: '7100',
270
+ await test('THE ANTI-RECURSION GUARD: a cascade-generated task shipping matches no production rule', () => {
271
+ // The removal rule's guard: even a cascade task whose ship deleted earlier
272
+ // work files nothing, so generation stays a single hop.
273
+ const cascaded = shippedCopy({ discipline: 'artist', source: cascade.CASCADE_SOURCE, module_key: null, id: '7100' });
274
+ const { matched } = cascade.cascadesFor(cascaded, cascade.CASCADES, {
275
+ removals: [{ taskId: '42', paths: ['a.js'] }],
277
276
  });
278
- const { matched } = cascade.cascadesFor(reviewTaskThatShipped);
279
- assert.equal(matched.length, 0, 'a cascade-generated task never triggers another cascade');
277
+ assert.equal(matched.length, 0);
280
278
  });
281
279
 
282
- await test('the review inherits the shipped task goal and version, not goal 1000074 by fiat', () => {
283
- // A copy ship in some other goal gets its review filed next to that work.
284
- const { matched } = cascade.cascadesFor(shippedCopy({ goal_id: '1000046', version_id: 'BONGOS-V1' }));
285
- assert.equal(matched[0].spec.goalId, 1000046);
286
- assert.equal(matched[0].spec.versionId, 'BONGOS-V1');
287
- });
280
+ // --- the runner still resolves its policy and the changed files --------------
288
281
 
289
- // --- the project's artist gate decides whether it is filed at all (ADR 0241) --
290
-
291
- await test("artist_gate 'off' files NO artist review — the one level at which the rule is silent", () => {
292
- assert.equal(cascade.cascadesFor(shippedCopy(), cascade.CASCADES, { artistGate: 'off' }).matched.length, 0);
293
- // The other two levels both FILE; what separates them is whether an open review
294
- // holds a deploy, which is not this file's business at all.
295
- assert.equal(cascade.cascadesFor(shippedCopy(), cascade.CASCADES, { artistGate: 'advisory' }).matched.length, 1);
296
- assert.equal(cascade.cascadesFor(shippedCopy(), cascade.CASCADES, { artistGate: 'strict' }).matched.length, 1);
297
- });
298
-
299
- await test('an UNREAD policy files — silence is never read as an opt-out', () => {
300
- // No context at all (an old caller), and an explicitly-undefined level (a reader
301
- // that threw) must both behave like the gate's default, which is 'strict'.
302
- assert.equal(cascade.cascadesFor(shippedCopy()).matched.length, 1);
303
- assert.equal(cascade.cascadesFor(shippedCopy(), cascade.CASCADES, {}).matched.length, 1);
304
- assert.equal(cascade.cascadesFor(shippedCopy(), cascade.CASCADES, { artistGate: undefined }).matched.length, 1);
305
- });
282
+ await test('the runner still hands the artist gate to the rules, and a reader that THROWS is reported', async () => {
283
+ const ok = ctxProbe();
284
+ await cascade.runCascadesForShip({ id: '7001' }, {
285
+ rules: [ok.rule], getTask: async () => shippedCopy(),
286
+ findTaskBySourceRef: async () => null, createTask: async () => ({ id: '1' }),
287
+ artistGateMode: () => 'advisory',
288
+ });
289
+ assert.equal(ok.seen[0].artistGate, 'advisory');
306
290
 
307
- await test('a gate reader that THROWS costs the run nothing — it is reported, and the review is still filed', async () => {
308
291
  const created = [];
309
292
  const report = await cascade.runCascadesForShip({ id: '7001' }, {
310
293
  getTask: async () => shippedCopy(),
@@ -312,24 +295,11 @@ await test('a gate reader that THROWS costs the run nothing — it is reported,
312
295
  createTask: async (spec) => { created.push(spec); return { id: '7002' }; },
313
296
  artistGateMode: () => { throw new Error('pack unreadable'); },
314
297
  });
315
- assert.equal(created.length, 1, 'an unreadable policy must not silently behave like off');
316
- assert.ok(report.errors.some((e) => /artistGateMode\(\) threw/.test(e)), 'and it says so');
317
- });
318
-
319
- await test("runCascadesForShip honours 'off' end to end — nothing is created, nothing errors", async () => {
320
- let creates = 0;
321
- const report = await cascade.runCascadesForShip({ id: '7001' }, {
322
- getTask: async () => shippedCopy(),
323
- findTaskBySourceRef: async () => null,
324
- createTask: async () => { creates++; return { id: '7002' }; },
325
- artistGateMode: () => 'off',
326
- });
327
- assert.equal(creates, 0);
328
- assert.deepEqual(report.created, []);
329
- assert.deepEqual(report.errors, [], 'a project that switched the gate off has no problem to report');
298
+ assert.equal(created.length, 0, 'and the production table files nothing either way');
299
+ assert.ok(report.errors.some((e) => /artistGateMode\(\) threw/.test(e)), 'the failed read says so');
330
300
  });
331
301
 
332
- // --- the review keys on the FILES a ship changed, not its label (task 1004201) --
302
+ // --- the changed files (task 1004201) ----------------------------------------
333
303
 
334
304
  await test('a page file changed under modules/*/public/ is visual work whatever the label says', () => {
335
305
  const H = cascade.hasCopyOrVisualWork;
@@ -358,67 +328,43 @@ await test('a backend file in a UI module is NOT visual work — page files only
358
328
  assert.equal(H(unlabelled, null), false);
359
329
  });
360
330
 
361
- await test('the ship runner resolves the changed files ONCE and the artist review files on them', async () => {
362
- const created = [];
331
+ await test('the ship runner resolves the changed files ONCE into ctx, and a page change files nothing', async () => {
332
+ const probe = ctxProbe();
363
333
  let asked = 0;
364
- const deps = {
365
- getTask: async () => shippedCopy({ module_key: null, discipline: 'engineer' }),
366
- findTaskBySourceRef: async () => null,
367
- createTask: async (spec) => { created.push(spec); return { id: String(9300 + created.length) }; },
368
- changedFiles: async () => { asked++; return { paths: ['modules/hall-ui/public/goal-forest-render.js'], truncated: false }; },
369
- };
370
- const report = await cascade.runCascadesForShip({ id: '7001' }, deps);
334
+ const changedFiles = async () => { asked++; return { paths: ['modules/hall-ui/public/goal-forest-render.js'], truncated: true }; };
335
+ await cascade.runCascadesForShip({ id: '7001' }, {
336
+ rules: [probe.rule], getTask: async () => shippedCopy({ module_key: null }),
337
+ findTaskBySourceRef: async () => null, createTask: async () => ({ id: '1' }), changedFiles,
338
+ });
371
339
  assert.equal(asked, 1);
372
- assert.equal(created.filter((s) => s.discipline === 'artist').length, 1, 'the unlabelled UI ship gets its review');
373
- assert.deepEqual(report.errors, []);
374
- });
375
-
376
- await test('a changed-files list cut off at the read cap FILES the review — a padded PR cannot skip it', async () => {
377
- const run = async (changed) => {
378
- const created = [];
379
- await cascade.runCascadesForShip({ id: '7001' }, {
380
- getTask: async () => shippedCopy({ module_key: null, discipline: 'engineer' }),
381
- findTaskBySourceRef: async () => null,
382
- createTask: async (spec) => { created.push(spec); return { id: '9500' }; },
383
- changedFiles: async () => changed,
384
- });
385
- return created.filter((s) => s.discipline === 'artist').length;
386
- };
387
- const padding = Array.from({ length: 300 }, (_, i) => `docs/pad-${i}.md`);
388
- assert.equal(await run({ paths: padding, truncated: true }), 1);
389
- assert.equal(await run({ paths: padding, truncated: false }), 0, 'the same list, complete, is not UI work');
390
- });
340
+ assert.deepEqual(probe.seen[0].changedFiles, ['modules/hall-ui/public/goal-forest-render.js']);
341
+ assert.equal(probe.seen[0].changedFilesTruncated, true);
391
342
 
392
- await test('a filed review is one the deploy gate HOLDS on — the rule and the gate agree on its identity', async () => {
343
+ // The production table, on the same ship: no artist review, nothing the
344
+ // deploy gate would hold, no error.
393
345
  const gate = require_(path.join(HERE, '..', '..', '..', 'src', 'bongos', 'artist-gate.js'));
394
346
  const created = [];
395
- await cascade.runCascadesForShip({ id: '7001' }, {
396
- getTask: async () => shippedCopy({ module_key: null, discipline: 'engineer' }),
347
+ const report = await cascade.runCascadesForShip({ id: '7001' }, {
348
+ getTask: async () => shippedCopy({ module_key: null }),
397
349
  findTaskBySourceRef: async () => null,
398
350
  createTask: async (spec) => { created.push(spec); return { id: '9600' }; },
399
- changedFiles: async () => ({ paths: ['modules/public-landing/public/projects.html'], truncated: false }),
351
+ changedFiles, artistGateMode: () => 'strict',
400
352
  });
401
- const review = created.find((s) => s.discipline === 'artist');
402
- assert.ok(review, 'the review was filed');
403
- assert.equal(gate.isArtistReview({ source: review.source, source_ref: review.sourceRef }), true);
353
+ assert.deepEqual(created, []);
354
+ assert.equal(created.some((s) => gate.isArtistReview({ source: s.source, source_ref: s.sourceRef })), false);
355
+ assert.deepEqual(report.errors, []);
404
356
  });
405
357
 
406
- await test('a changed-files read that fails is REPORTED, never thrown — the label match still stands', async () => {
407
- const run = async (task) => {
408
- const created = [];
409
- const report = await cascade.runCascadesForShip({ id: '7001' }, {
410
- getTask: async () => task,
411
- findTaskBySourceRef: async () => null,
412
- createTask: async (spec) => { created.push(spec); return { id: '9400' }; },
413
- changedFiles: async () => { throw new Error('github down'); },
414
- });
415
- return { created, report };
416
- };
417
- const unlabelled = await run(shippedCopy({ module_key: null, discipline: 'engineer' }));
418
- assert.equal(unlabelled.created.length, 0);
419
- assert.match(unlabelled.report.errors.join('\n'), /changedFiles\(\) threw \(github down\)/);
420
- const labelled = await run(shippedCopy({ module_key: 'hall-ui' }));
421
- assert.equal(labelled.created.filter((s) => s.discipline === 'artist').length, 1, 'an outage never costs a labelled review');
358
+ await test('a changed-files read that fails is REPORTED, never thrown', async () => {
359
+ const created = [];
360
+ const report = await cascade.runCascadesForShip({ id: '7001' }, {
361
+ getTask: async () => shippedCopy(),
362
+ findTaskBySourceRef: async () => null,
363
+ createTask: async (spec) => { created.push(spec); return { id: '9400' }; },
364
+ changedFiles: async () => { throw new Error('github down'); },
365
+ });
366
+ assert.equal(created.length, 0);
367
+ assert.match(report.errors.join('\n'), /changedFiles\(\) threw \(github down\)/);
422
368
  });
423
369
 
424
370
  const shipFiles = require_(path.join(HERE, '..', 'ship-files.js'));
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.1065",
3
+ "version": "1.19.1067",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.1065",
9
+ "version": "1.19.1067",
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.1065",
3
+ "version": "1.19.1067",
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",
@@ -7691,5 +7691,17 @@
7691
7691
  "id": "1004323",
7692
7692
  "text": "An artist can now approve a tweaked page from the studio. A new Approval queue button opens a glass panel that shows the applied page as real pictures, before and after, on desktop and phone, in light and dark, with the change"
7693
7693
  }
7694
+ ],
7695
+ "1.19.1066": [
7696
+ {
7697
+ "id": "1004324",
7698
+ "text": "When an artist's page rewrite is approved and goes live, the artist is now paid 30 credits for it, the same value an artist review used to carry. The payment lands at the moment the page ships, never at approval, and a page th"
7699
+ }
7700
+ ],
7701
+ "1.19.1067": [
7702
+ {
7703
+ "id": "1004325",
7704
+ "text": "A ship that changes a page's words no longer files an \"Artist review\" task. Those reviews came one per ship, and most were thrown away unread. Instead, the page itself shows \"changed since last tweak\" in Tweak Mode, worked out"
7705
+ }
7694
7706
  ]
7695
7707
  }
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.1065'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.1067'; // 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');