@bongos/core 1.19.811 → 1.19.812
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 +95 -35
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +24 -0
- package/clients/bongos-client/index.cjs +24 -0
- package/clients/bongos-client/index.d.ts +39 -0
- package/clients/bongos-client/index.mjs +24 -0
- package/docs/adr/0296-a-role-speciality-is-data-over-three-existing-surfaces.md +71 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +867 -73
- package/docs/api-reference.md +19 -3
- package/docs/copy-inventory.md +48 -20
- package/docs/copy-registry.json +275 -23
- package/docs/module-api-changelog.md +2 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +24 -0
- package/modules/hall-ui/public/settings-specialities.js +241 -0
- package/modules/hall-ui/public/settings.css +56 -0
- package/modules/hall-ui/public/settings.html +65 -0
- package/modules/lifecycle/db-claim-reads.js +5 -1
- package/modules/memory/learnings.js +21 -2
- package/modules/provisioning/starter-bundles.js +1 -0
- package/modules/specialities/CLAUDE.md +15 -0
- package/modules/specialities/db.js +234 -0
- package/modules/specialities/migrations/specialities_001_specialities.sql +143 -0
- package/modules/specialities/module.json +12 -0
- package/modules/specialities/routes/specialities.js +379 -0
- package/modules/specialities/specialities.js +198 -0
- package/modules/ui-design/kit/serve.js +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/publish-manifest.js +1 -0
- package/scripts/gds/search-ingest-db.js +37 -0
- package/src/bongos/routes/me.js +17 -0
- package/src/module-api.js +1 -1
- package/tests/module_loader.mjs +1 -1
- package/tests/own_scope_guard.mjs +10 -0
- package/tests/specialities.mjs +214 -0
- package/tests/speciality_corpus_scope.mjs +97 -0
- package/tests/speciality_db_shape.mjs +124 -0
- package/tests/speciality_injection.mjs +107 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// tests/speciality_injection.mjs — the path a speciality contract takes to reach
|
|
2
|
+
// a session (task 1003855).
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS IS ITS OWN FILE. specialities.mjs covers the RULES; this covers the
|
|
5
|
+
// PLUMBING, and the plumbing is where a silent failure lives: if the contract
|
|
6
|
+
// never arrives, nothing errors — the session simply behaves as though the
|
|
7
|
+
// builder had no speciality, which is indistinguishable from not having one.
|
|
8
|
+
//
|
|
9
|
+
// The Conductor is driven directly with fixtures rather than through a live /me,
|
|
10
|
+
// so the craft-matching rule is asserted without a database or a server.
|
|
11
|
+
import assert from 'node:assert/strict';
|
|
12
|
+
import test from 'node:test';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
20
|
+
const conductor = require(path.join(ROOT, '.claude', 'hooks', 'conductor.js'));
|
|
21
|
+
|
|
22
|
+
const sp = (over = {}) => ({
|
|
23
|
+
inject: true,
|
|
24
|
+
contract: 'Speciality: Economics (under ideator).',
|
|
25
|
+
discipline: 'ideator',
|
|
26
|
+
name: 'Economics',
|
|
27
|
+
...over,
|
|
28
|
+
});
|
|
29
|
+
const claim = (discipline) => ({ task_id: '1', title: 't', discipline });
|
|
30
|
+
const ids = (out) => out.map((a) => a.id);
|
|
31
|
+
|
|
32
|
+
test('the contract is injected when its craft matches the claim in hand', () => {
|
|
33
|
+
const out = conductor.evaluateTriggers({ activeClaims: [claim('ideator')], specialities: [sp()] });
|
|
34
|
+
assert.ok(ids(out).includes('speciality:ideator'), `expected the ideator contract, got ${JSON.stringify(ids(out))}`);
|
|
35
|
+
const entry = out.find((a) => a.id === 'speciality:ideator');
|
|
36
|
+
assert.match(entry.text, /Economics/);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('a speciality for a DIFFERENT craft is not injected', () => {
|
|
40
|
+
// An artist's expert contract in an engineering session is noise at best and
|
|
41
|
+
// actively misleading at worst. This is the assertion that keeps the selection
|
|
42
|
+
// honest — without it, "inject everything active" would pass the test above.
|
|
43
|
+
const out = conductor.evaluateTriggers({
|
|
44
|
+
activeClaims: [claim('engineer')],
|
|
45
|
+
specialities: [sp({ discipline: 'artist', contract: 'Speciality: Pixel art (under artist).' })],
|
|
46
|
+
});
|
|
47
|
+
assert.equal(ids(out).filter((i) => String(i).startsWith('speciality:')).length, 0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('with no active claim there is no craft, so nothing is injected', () => {
|
|
51
|
+
// Silence beats guessing which expertise applies.
|
|
52
|
+
const out = conductor.evaluateTriggers({ activeClaims: [], specialities: [sp()] });
|
|
53
|
+
assert.equal(ids(out).filter((i) => String(i).startsWith('speciality:')).length, 0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('inject:false is honoured — an empty speciality spends no context', () => {
|
|
57
|
+
const out = conductor.evaluateTriggers({
|
|
58
|
+
activeClaims: [claim('ideator')],
|
|
59
|
+
specialities: [sp({ inject: false })],
|
|
60
|
+
});
|
|
61
|
+
assert.equal(ids(out).filter((i) => String(i).startsWith('speciality:')).length, 0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('one contract per craft, and two crafts in play get one each', () => {
|
|
65
|
+
const out = conductor.evaluateTriggers({
|
|
66
|
+
activeClaims: [claim('ideator'), claim('engineer')],
|
|
67
|
+
specialities: [sp(), sp({ discipline: 'engineer', contract: 'Speciality: Compilers (under engineer).' })],
|
|
68
|
+
});
|
|
69
|
+
const got = ids(out).filter((i) => String(i).startsWith('speciality:'));
|
|
70
|
+
assert.deepEqual(got.sort(), ['speciality:engineer', 'speciality:ideator']);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('malformed entries are skipped rather than thrown on', () => {
|
|
74
|
+
const out = conductor.evaluateTriggers({
|
|
75
|
+
activeClaims: [claim('ideator')],
|
|
76
|
+
specialities: [null, { inject: true }, sp({ contract: '' }), sp()],
|
|
77
|
+
});
|
|
78
|
+
assert.equal(ids(out).filter((i) => String(i).startsWith('speciality:')).length, 1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('no specialities at all changes nothing about the hook', () => {
|
|
82
|
+
const before = conductor.evaluateTriggers({ activeClaims: [claim('ideator')] });
|
|
83
|
+
const after = conductor.evaluateTriggers({ activeClaims: [claim('ideator')], specialities: [] });
|
|
84
|
+
assert.deepEqual(ids(before), ids(after));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ---------- the wiring the contract depends on, asserted against the source ----------
|
|
88
|
+
// These are the three edits that make the path exist at all. Each one is invisible
|
|
89
|
+
// when it breaks, so each is pinned rather than trusted.
|
|
90
|
+
const src = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
|
91
|
+
|
|
92
|
+
test('the active-claims read carries the task craft — the Conductor cannot match without it', () => {
|
|
93
|
+
assert.match(src('modules/lifecycle/db-claim-reads.js'), /t\.discipline/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('GET /me resolves the port rather than requiring the module', () => {
|
|
97
|
+
const me = src('src/bongos/routes/me.js');
|
|
98
|
+
assert.match(me, /resolveOptional\('specialities'\)/);
|
|
99
|
+
assert.match(me, /describeActiveFor/);
|
|
100
|
+
assert.doesNotMatch(me, /require\([^)]*modules\/specialities/, 'the core must keep working with the module off');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('the adoption read selects the contract itself', () => {
|
|
104
|
+
// It once did not, and the injected fragment silently lost the expert's terms
|
|
105
|
+
// while still looking well-formed.
|
|
106
|
+
assert.match(src('modules/specialities/db.js'), /SELECT s\.id, s\.slug, s\.name, s\.discipline, s\.summary, s\.contract_md/);
|
|
107
|
+
});
|