@bongos/core 1.19.296 → 1.19.297
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 +33 -23
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +3 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +74 -2
- package/docs/api-reference.md +3 -2
- package/docs/file-map.md +1 -1
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
- package/modules/ideas/inbox.js +86 -2
- package/modules/ideas/migrations/ideas_005_grade_developed_by.sql +85 -0
- package/modules/ideas/routes/inbox.js +154 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/spark.js +119 -5
- package/src/module-api.js +3 -1
- package/tests/idea_develop_spark.mjs +399 -0
- package/tests/idea_spark_queue.mjs +11 -3
|
@@ -593,6 +593,160 @@ module.exports = function buildInboxRouter() {
|
|
|
593
593
|
});
|
|
594
594
|
}, { errorCode: 'idea_unspark_failed' }));
|
|
595
595
|
|
|
596
|
+
// POST /inbox/:id/develop — DEVELOP A SPARK INTO A FULL IDEA
|
|
597
|
+
// (task 1002922 / BV1.R146, goal 1000062).
|
|
598
|
+
//
|
|
599
|
+
// The pickup half of the handoff R144 and R145 set up: the author consented by
|
|
600
|
+
// marking the spark, the queue made it findable, and now another builder fills
|
|
601
|
+
// in the five template fields and the thought becomes a Full Idea.
|
|
602
|
+
//
|
|
603
|
+
// OPEN TO ANY BUILDER, INCLUDING THE AUTHOR. Cross-builder development is the
|
|
604
|
+
// entire point, so there is no author check here — the mirror image of R144,
|
|
605
|
+
// where the gate was the whole feature. The consent was already given and is
|
|
606
|
+
// already recorded (this row is a spark BECAUSE its author said so), and
|
|
607
|
+
// re-asking for it would make the queue unpickable. Self-development is
|
|
608
|
+
// likewise legal: an author may mark a thought and come back to it themselves.
|
|
609
|
+
//
|
|
610
|
+
// ONLY A SPARK CAN BE DEVELOPED, and "only once" is enforced by the DATABASE,
|
|
611
|
+
// not by this handler. developSpark's UPDATE carries `AND grade = 'spark'`, so
|
|
612
|
+
// two builders picking the same spark off the queue at the same instant cannot
|
|
613
|
+
// both succeed — the second matches zero rows. The pre-read below exists ONLY
|
|
614
|
+
// to tell a 404 apart from a 409 and to give a useful message; it is not the
|
|
615
|
+
// guard, and it deliberately does not try to be.
|
|
616
|
+
//
|
|
617
|
+
// WHAT THIS ROUTE DELIBERATELY DOES NOT DO: pay the stream-2 "Full Idea passed"
|
|
618
|
+
// credit. Capture and resubmit both fire it, but on those paths the recipient
|
|
619
|
+
// is unambiguously the author. Here there are two people, and choosing between
|
|
620
|
+
// them is R147's job (task 1002923 — "karma for a ratified Full Idea, scaled by
|
|
621
|
+
// completeness and SPLIT WITH THE SPARK AUTHOR"). Firing it here would not just
|
|
622
|
+
// pre-empt that decision, it would corrupt it: awardIdeaFullPassCredit's
|
|
623
|
+
// idempotency predicate is (builder_id, `idea.credit.full_pass:<id>`), so a
|
|
624
|
+
// payment to the DEVELOPER does not block a later payment to the AUTHOR — one
|
|
625
|
+
// idea would pay twice. The credit is idempotent per idea+builder, so R147 can
|
|
626
|
+
// still pay it at the right time, to the right people, in the right shares.
|
|
627
|
+
//
|
|
628
|
+
// The board window IS opened, because it is recipient-agnostic (it takes an
|
|
629
|
+
// idea and a score, not a builder) and without it a developed Full Idea could
|
|
630
|
+
// never reach ratification — the handoff would dead-end one step from done.
|
|
631
|
+
// Fail-open like every other caller of that port.
|
|
632
|
+
//
|
|
633
|
+
// rank: any-builder — development is the pickup, and the author's mark is the
|
|
634
|
+
// consent. Deliberately NOT pinned in route-rank-check: there is no gate here
|
|
635
|
+
// to protect, and pinning it would only assert that an open route stays open.
|
|
636
|
+
router.post('/inbox/:id/develop', api.requireBuilder, api.asyncHandler('POST /inbox/:id/develop', async (req, res) => {
|
|
637
|
+
const id = parseId(req, res, { code: 'bad_id', positiveInt: true });
|
|
638
|
+
if (id === null) return;
|
|
639
|
+
if (validateOrRespond(req, res, {
|
|
640
|
+
field_values: { required: true, type: 'object' },
|
|
641
|
+
template_key: { type: 'string', maxLength: 64 },
|
|
642
|
+
})) return;
|
|
643
|
+
|
|
644
|
+
const idea = await inbox.getIdea(id);
|
|
645
|
+
if (!idea) return res.fail('idea_not_found', 404);
|
|
646
|
+
if (idea.grade !== 'spark') {
|
|
647
|
+
return res.fail('not_a_spark', 409, {
|
|
648
|
+
grade: idea.grade,
|
|
649
|
+
...(idea.developed_by ? { developed_by: idea.developed_by } : {}),
|
|
650
|
+
reason: idea.grade === 'full'
|
|
651
|
+
? 'this idea has already been developed into a Full Idea'
|
|
652
|
+
: 'only a Big Idea Spark can be developed — its author has to mark it first, which is what makes developing it fair',
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
// A SPARK IS NOT NECESSARILY STILL DEVELOPABLE. Triage writes
|
|
656
|
+
// idea_inbox.status and never touches ideas_grades, so an idea a triager
|
|
657
|
+
// discarded, merged or promoted keeps grade='spark' forever. Developing one
|
|
658
|
+
// would resurrect content the triage flow already decided was dead — and
|
|
659
|
+
// worse, open a fresh board ratification window for it. The R144 mark and the
|
|
660
|
+
// R145 queue both require status='open'; this is the same rule on the write.
|
|
661
|
+
// (developSpark's predicate carries it too, for the mid-request race.)
|
|
662
|
+
if (idea.status !== 'open') {
|
|
663
|
+
return res.fail('idea_not_open', 409, {
|
|
664
|
+
status: idea.status,
|
|
665
|
+
reason: 'this idea is no longer open, so it is not awaiting development',
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Same validation the capture and resubmit paths use, against the same
|
|
670
|
+
// template declaration — a developed Full Idea is a Full Idea, and must not
|
|
671
|
+
// be held to a looser standard just because someone else wrote it.
|
|
672
|
+
const templateKey = req.body.template_key ?? idea.template_key ?? undefined;
|
|
673
|
+
let v;
|
|
674
|
+
try {
|
|
675
|
+
v = validateFieldValues(req.body.field_values, templateKey ? { templateKey } : undefined);
|
|
676
|
+
} catch (validationErr) {
|
|
677
|
+
api.logger('ideas').error(`POST /inbox/:id/develop validation threw: ${validationErr && validationErr.message}`);
|
|
678
|
+
return res.fail('invalid_field_values', 400, { errors: [{ field: null, reason: 'unvalidatable' }] });
|
|
679
|
+
}
|
|
680
|
+
if (!v.ok) return res.fail('invalid_field_values', 400, { errors: v.errors, template_key: templateKey ?? null });
|
|
681
|
+
|
|
682
|
+
const row = await inbox.developSpark(id, {
|
|
683
|
+
developedBy: req.builder.id,
|
|
684
|
+
...(templateKey ? { templateKey } : {}),
|
|
685
|
+
fieldValues: req.body.field_values,
|
|
686
|
+
completenessScore: completenessScore(req.body.field_values, templateKey ? { templateKey } : undefined),
|
|
687
|
+
});
|
|
688
|
+
// Zero rows means the guard fired: somebody else developed this spark between
|
|
689
|
+
// the read above and this write. Their work stands; this caller is told so
|
|
690
|
+
// rather than silently overwriting it.
|
|
691
|
+
if (!row) {
|
|
692
|
+
// The guard fired between the read above and the write. THREE causes now,
|
|
693
|
+
// and guessing would misinform: another developer got there first, or a
|
|
694
|
+
// triager closed the idea. One extra read on a rare path buys an honest
|
|
695
|
+
// answer instead of a plausible one.
|
|
696
|
+
const now = await inbox.getIdea(id);
|
|
697
|
+
if (now && now.status !== 'open') {
|
|
698
|
+
return res.fail('idea_not_open', 409, {
|
|
699
|
+
status: now.status,
|
|
700
|
+
reason: 'a triager closed this idea while you were developing it',
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
return res.fail('spark_already_developed', 409, {
|
|
704
|
+
...(now && now.developed_by ? { developed_by: now.developed_by } : {}),
|
|
705
|
+
reason: 'another builder developed this spark first — their version stands',
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const out = {
|
|
710
|
+
...idea,
|
|
711
|
+
grade: row.grade,
|
|
712
|
+
template_key: row.template_key,
|
|
713
|
+
field_values: row.field_values,
|
|
714
|
+
completeness_score: row.completeness_score,
|
|
715
|
+
developed_by: row.developed_by,
|
|
716
|
+
developed_at: row.developed_at,
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
let board_window;
|
|
720
|
+
try {
|
|
721
|
+
const government = api.resolveOptional && api.resolveOptional('government');
|
|
722
|
+
if (government && government.openBoardWindowForIdea) {
|
|
723
|
+
const w = await government.openBoardWindowForIdea({ ideaId: id, completenessScore: row.completeness_score });
|
|
724
|
+
if (w && w.opened && w.item) board_window = { item_id: w.item.id, closes_at: w.item.closes_at };
|
|
725
|
+
else if (w && w.reason === 'below_bar') {
|
|
726
|
+
// The same honest feedback the resubmit path gives: which section is
|
|
727
|
+
// thinnest, computed from what was just written. Derived, never stored.
|
|
728
|
+
const flag = weakestSectionPayload(req.body.field_values, templateKey ? { templateKey } : undefined);
|
|
729
|
+
if (flag) out.rejection = flag;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
} catch (boardErr) {
|
|
733
|
+
// Fail-open, like every other caller of this port: the development landed
|
|
734
|
+
// and must not be rolled back because the board could not be told.
|
|
735
|
+
api.logger('ideas').error(`develop: board-window open failed (development intact, window skipped): ${boardErr && boardErr.message}`);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return res.json({
|
|
739
|
+
idea: out,
|
|
740
|
+
developed: true,
|
|
741
|
+
// Named explicitly so a caller never has to compare two ids to find out,
|
|
742
|
+
// and so the R147 split reads its two parties off one response.
|
|
743
|
+
author_id: idea.captured_by ?? null,
|
|
744
|
+
developer_id: row.developed_by,
|
|
745
|
+
self_developed: idea.captured_by != null && String(idea.captured_by) === String(row.developed_by),
|
|
746
|
+
...(board_window ? { board_window } : {}),
|
|
747
|
+
});
|
|
748
|
+
}, { errorCode: 'idea_develop_failed' }));
|
|
749
|
+
|
|
596
750
|
// Metic+ (#360 / ADR 0018): idea triage (promote/discard/merge) shapes the
|
|
597
751
|
// backlog. Criterion C2 assigns triage to Metic, so this widened from
|
|
598
752
|
// archon-only to metic+archon when the three-rank model went live. Any
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.297",
|
|
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.297",
|
|
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.297",
|
|
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/spark.js
CHANGED
|
@@ -19,7 +19,17 @@
|
|
|
19
19
|
// are one mental model, and a queue nobody can read is not a pickup surface. The
|
|
20
20
|
// queue is readable by ANY builder: the author's mark is already the consent.
|
|
21
21
|
//
|
|
22
|
+
// --develop is the PICKUP (task 1002922 / BV1.R146): fill in the five template
|
|
23
|
+
// fields and the spark becomes a Full Idea, in place, with the original author
|
|
24
|
+
// preserved and you recorded as its developer. Open to any builder — the author's
|
|
25
|
+
// mark was the consent. The five fields are prose, so they come from a JSON file
|
|
26
|
+
// rather than five shell flags; --template prints the shape to fill in. The hall
|
|
27
|
+
// form (R152) and conversational authoring (R155) are the richer surfaces; this
|
|
28
|
+
// is the terminal path, and all three hit the same route.
|
|
29
|
+
//
|
|
22
30
|
// Usage:
|
|
31
|
+
// node scripts/gds/spark.js --template # print the five fields to fill in
|
|
32
|
+
// node scripts/gds/spark.js --develop 123 --fields-file idea.json
|
|
23
33
|
// node scripts/gds/spark.js --list # the spark queue: what awaits development
|
|
24
34
|
// node scripts/gds/spark.js --list --limit 10
|
|
25
35
|
// node scripts/gds/spark.js 123 # mark idea 123 as a spark
|
|
@@ -30,7 +40,12 @@
|
|
|
30
40
|
// 1 api / network error, or the server refused (not yours / not a quick idea)
|
|
31
41
|
// 2 bad invocation
|
|
32
42
|
|
|
43
|
+
const fs = require('node:fs/promises');
|
|
33
44
|
const { cliClient, requireSession, hasFlag, arg, hallBase } = require('./cli-lib');
|
|
45
|
+
// A CLI may reach into its own domain module — the capture.js / autonomy-gate.js
|
|
46
|
+
// precedent. Pure and DB-free: this only reads the R141 template declaration so
|
|
47
|
+
// the prompt below cannot drift from what the server validates against.
|
|
48
|
+
const { BASE_TEMPLATE } = require('../../modules/ideas/templates');
|
|
34
49
|
|
|
35
50
|
// The server's refusals, in the ideator's own words. Anything unmapped falls
|
|
36
51
|
// through to the raw envelope — a new server-side rule should be visible, not
|
|
@@ -39,6 +54,24 @@ const REFUSALS = {
|
|
|
39
54
|
not_your_idea: (d) =>
|
|
40
55
|
'That idea is not yours to mark. The spark mark is the author\'s consent to have their\n'
|
|
41
56
|
+ 'idea developed, so only the builder who filed it can give or withdraw it.',
|
|
57
|
+
not_a_spark: (d) =>
|
|
58
|
+
d.grade === 'full'
|
|
59
|
+
? `That idea has already been developed into a Full Idea${d.developed_by ? ` (by builder ${d.developed_by})` : ''}.\n`
|
|
60
|
+
+ 'A spark can only be developed once.'
|
|
61
|
+
: 'That idea is not a Big Idea Spark, so it is not available to develop.\n'
|
|
62
|
+
+ 'Its author has to mark it first — that mark is what makes developing it fair.',
|
|
63
|
+
spark_already_developed: () =>
|
|
64
|
+
'Another builder developed this spark first, moments ago. Their version stands.\n'
|
|
65
|
+
+ 'Run --list to see what else is waiting.',
|
|
66
|
+
invalid_field_values: (d) => {
|
|
67
|
+
const errs = Array.isArray(d) ? d : (d.errors || []);
|
|
68
|
+
const lines = ['That is not a complete Full Idea yet:'];
|
|
69
|
+
for (const e of errs) {
|
|
70
|
+
lines.push(` - ${e.field || '(the whole set)'}: ${String(e.reason || '').replace(/_/g, ' ')}`);
|
|
71
|
+
}
|
|
72
|
+
lines.push('Run --template to see the five fields and re-run with the file fixed.');
|
|
73
|
+
return lines.join('\n');
|
|
74
|
+
},
|
|
42
75
|
not_a_quick_idea: (d) =>
|
|
43
76
|
`That idea is already a Full Idea (grade=${d.grade || 'full'}). It carries the developed\n`
|
|
44
77
|
+ 'picture the spark mark asks for — there is nothing to hand off.',
|
|
@@ -104,10 +137,89 @@ async function listQueue() {
|
|
|
104
137
|
console.log(renderQueue(r.data || {}));
|
|
105
138
|
}
|
|
106
139
|
|
|
140
|
+
// The five fields, printed as a fill-in-the-blanks JSON skeleton. Read from the
|
|
141
|
+
// R141 declaration rather than restated, so it cannot drift from what the server
|
|
142
|
+
// validates — and it names the substance floor, because "present but three words"
|
|
143
|
+
// is the failure the scorer actually punishes.
|
|
144
|
+
function templateSkeleton() {
|
|
145
|
+
const lines = ['The five fields of a Full Idea (template ' + BASE_TEMPLATE.key + '):', ''];
|
|
146
|
+
for (const f of BASE_TEMPLATE.fields) {
|
|
147
|
+
lines.push(` ${f.key}`);
|
|
148
|
+
lines.push(` ${f.label}`);
|
|
149
|
+
if (f.substanceMin) lines.push(` at least ~${f.substanceMin} characters of real content — padding scores nothing`);
|
|
150
|
+
}
|
|
151
|
+
lines.push('');
|
|
152
|
+
lines.push('Write them into a JSON file, then develop a spark with it:');
|
|
153
|
+
lines.push('');
|
|
154
|
+
lines.push(' ' + JSON.stringify(Object.fromEntries(BASE_TEMPLATE.fields.map((f) => [f.key, '...'])), null, 2).replace(/\n/g, '\n '));
|
|
155
|
+
lines.push('');
|
|
156
|
+
lines.push(' node scripts/gds/spark.js --develop <idea-id> --fields-file <that-file.json>');
|
|
157
|
+
return lines.join('\n');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// What the developer is told once it lands. The completeness score is the number
|
|
161
|
+
// the R147 credit split and the board bar both read, so it is the headline.
|
|
162
|
+
function renderDeveloped(data) {
|
|
163
|
+
const { idea, author_id, developer_id, self_developed, board_window } = data || {};
|
|
164
|
+
const lines = [];
|
|
165
|
+
lines.push(`Idea ${idea.id} is now a Full Idea — completeness ${idea.completeness_score ?? 'unscored'}/100.`);
|
|
166
|
+
lines.push(` ${hallBase()}/#/idea/${idea.id}`);
|
|
167
|
+
lines.push(self_developed
|
|
168
|
+
? ' You had the thought and you developed it.'
|
|
169
|
+
: ` The thought stays credited to builder ${author_id}; you are recorded as its developer (${developer_id}).`);
|
|
170
|
+
if (board_window) {
|
|
171
|
+
lines.push(` A board ratification window is open until ${board_window.closes_at}.`);
|
|
172
|
+
} else if (idea.rejection) {
|
|
173
|
+
lines.push(` Not yet at the bar — thinnest section: ${idea.rejection.section_key}.`);
|
|
174
|
+
if (idea.rejection.reason_md) lines.push(` ${idea.rejection.reason_md}`);
|
|
175
|
+
}
|
|
176
|
+
return lines.join('\n');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function developSpark(ideaId) {
|
|
180
|
+
const file = arg('--fields-file');
|
|
181
|
+
if (!file) {
|
|
182
|
+
console.error('--develop needs --fields-file <path.json> (the five fields).');
|
|
183
|
+
console.error('Run: node scripts/gds/spark.js --template');
|
|
184
|
+
process.exit(2);
|
|
185
|
+
}
|
|
186
|
+
let fieldValues;
|
|
187
|
+
try {
|
|
188
|
+
fieldValues = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.error(`could not read ${file} as JSON: ${err.message}`);
|
|
191
|
+
process.exit(2);
|
|
192
|
+
}
|
|
193
|
+
const api = await cliClient();
|
|
194
|
+
const templateKey = arg('--template-key');
|
|
195
|
+
const r = await api.inbox.postInboxIdDevelop({
|
|
196
|
+
id: ideaId,
|
|
197
|
+
body: { field_values: fieldValues, ...(templateKey ? { template_key: templateKey } : {}) },
|
|
198
|
+
});
|
|
199
|
+
if (!r.ok) {
|
|
200
|
+
console.error(explain(r.status, r.data));
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
console.log(renderDeveloped(r.data));
|
|
204
|
+
}
|
|
205
|
+
|
|
107
206
|
async function main() {
|
|
108
207
|
await requireSession();
|
|
109
208
|
|
|
209
|
+
if (hasFlag('--template')) {
|
|
210
|
+
console.log(templateSkeleton());
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
110
213
|
if (hasFlag('--list')) return listQueue();
|
|
214
|
+
const developIdRaw = arg('--develop');
|
|
215
|
+
if (developIdRaw != null) {
|
|
216
|
+
const devId = Number(developIdRaw);
|
|
217
|
+
if (!Number.isInteger(devId) || devId <= 0) {
|
|
218
|
+
console.error('usage: node scripts/gds/spark.js --develop <idea-id> --fields-file <path.json>');
|
|
219
|
+
process.exit(2);
|
|
220
|
+
}
|
|
221
|
+
return developSpark(devId);
|
|
222
|
+
}
|
|
111
223
|
|
|
112
224
|
const positional = process.argv.slice(2).filter((a) => !a.startsWith('-'));
|
|
113
225
|
const id = Number(positional[0]);
|
|
@@ -116,6 +228,8 @@ async function main() {
|
|
|
116
228
|
if (!Number.isInteger(id) || id <= 0) {
|
|
117
229
|
console.error('usage: node scripts/gds/spark.js <idea-id> [--undo]');
|
|
118
230
|
console.error(' node scripts/gds/spark.js --list [--limit N]');
|
|
231
|
+
console.error(' node scripts/gds/spark.js --develop <idea-id> --fields-file <path.json>');
|
|
232
|
+
console.error(' node scripts/gds/spark.js --template');
|
|
119
233
|
process.exit(2);
|
|
120
234
|
}
|
|
121
235
|
|
|
@@ -141,11 +255,11 @@ async function main() {
|
|
|
141
255
|
}
|
|
142
256
|
}
|
|
143
257
|
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
// through explain(), and a
|
|
148
|
-
module.exports = { explain, renderQueue };
|
|
258
|
+
// The four renderers are exported because each is pinned by a test — explain
|
|
259
|
+
// against the real envelope shape, and the three renderers for the shapes a
|
|
260
|
+
// human actually reads. REFUSALS stays private: it is the message table,
|
|
261
|
+
// reachable through explain(), and a fifth export would be dead surface.
|
|
262
|
+
module.exports = { explain, renderQueue, renderDeveloped, templateSkeleton };
|
|
149
263
|
|
|
150
264
|
if (require.main === module) {
|
|
151
265
|
main().catch((err) => {
|
package/src/module-api.js
CHANGED
|
@@ -1019,8 +1019,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
1019
1019
|
// landed since 1.19.294 with no explicit bump. run 32590508145. (task 1002620)
|
|
1020
1020
|
// 1.19.296 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1021
1021
|
// landed since 1.19.295 with no explicit bump. run 32591155887. (task 1002620)
|
|
1022
|
+
// 1.19.297 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1023
|
+
// landed since 1.19.296 with no explicit bump. run 32592502621. (task 1002620)
|
|
1022
1024
|
// ---------------------------------------------------------------------------
|
|
1023
|
-
const CORE_VERSION = '1.19.
|
|
1025
|
+
const CORE_VERSION = '1.19.297'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
1024
1026
|
|
|
1025
1027
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
1026
1028
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|