@bongos/core 1.21.150 → 1.21.152
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 +52 -52
- package/docs/copy-inventory.md +16 -16
- package/docs/copy-registry.json +16 -16
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +29 -0
- package/docs/page-readings.json +27 -26
- package/modules/agents/module.json +1 -0
- package/modules/autonomy/module.json +1 -0
- package/modules/builder-settings/module.json +1 -0
- package/modules/copy-desk/module.json +1 -0
- package/modules/design-styles/module.json +1 -0
- package/modules/discord/module.json +1 -0
- package/modules/economy/module.json +1 -0
- package/modules/government/module.json +1 -0
- package/modules/grading/module.json +1 -0
- package/modules/hall-ui/module.json +1 -0
- package/modules/hall-ui/public/modules.js +16 -4
- package/modules/ideas/module.json +1 -0
- package/modules/lifecycle/module.json +1 -0
- package/modules/memory/module.json +1 -0
- package/modules/npm-release/module.json +1 -0
- package/modules/onboarding/module.json +1 -0
- package/modules/page-notes/module.json +1 -0
- package/modules/pixel-art/module.json +1 -0
- package/modules/platform-identity/module.json +1 -0
- package/modules/provisioning/module.json +1 -0
- package/modules/public-landing/module.json +1 -0
- package/modules/render-deploy/module.json +1 -0
- package/modules/security/module.json +1 -0
- package/modules/sessions/module.json +1 -0
- package/modules/specialities/module.json +1 -0
- package/modules/status-ui/module.json +1 -0
- package/modules/ui-design/module.json +1 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/credential-presence.js +10 -0
- package/scripts/gds/module-test-runner/README.md +3 -3
- package/scripts/gds/module-test-runner/module-tests.yml +18 -6
- package/scripts/gds/module-test-runner.js +92 -36
- package/scripts/gds/module.js +4 -2
- package/src/module-api.js +1 -1
- package/src/module-loader/catalog.js +15 -1
- package/src/module-loader/manifest-schema.js +40 -1
- package/tests/hall_catalog_world.mjs +4 -1
- package/tests/module_catalog.mjs +34 -0
- package/tests/module_manifest.mjs +36 -0
- package/tests/module_test_runner.mjs +70 -17
|
@@ -18,9 +18,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
18
18
|
const require = createRequire(import.meta.url);
|
|
19
19
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
20
20
|
const runner = require('../scripts/gds/module-test-runner.js');
|
|
21
|
-
const { runnerConfig, maybeDispatch, collectResults, sweepTick, readZipEntry, validateSignal, runName, artifactName } = runner;
|
|
21
|
+
const { runnerConfig, archiveCore, maybeDispatch, collectResults, sweepTick, readZipEntry, validateSignal, runName, artifactName } = runner;
|
|
22
22
|
|
|
23
|
-
const CFG = { repo: 'owner/module-tests', token: 't0ken'
|
|
23
|
+
const CFG = { repo: 'owner/module-tests', token: 't0ken' };
|
|
24
|
+
const SHA = 'a1'.repeat(20);
|
|
25
|
+
const coreRef = () => SHA;
|
|
26
|
+
const archive = async () => Buffer.from('core-tgz');
|
|
24
27
|
const VER = { id: 7, module_key: 'weather', version: '1.0.0', artifact_path: 'weather/weather-1.0.0.tgz' };
|
|
25
28
|
const quiet = { warn() {}, error() {} };
|
|
26
29
|
|
|
@@ -104,36 +107,75 @@ test('unconfigured: no runner, so Tests stays plain pending and nothing is sent'
|
|
|
104
107
|
assert.equal(db.signals.length, 0);
|
|
105
108
|
});
|
|
106
109
|
|
|
107
|
-
test('config: owner/name is required
|
|
110
|
+
test('config: owner/name is required; there is no core repo to name', () => {
|
|
108
111
|
assert.throws(() => runnerConfig({ MODULE_TEST_REPO: 'nope', MODULE_TEST_TOKEN: 'x' }), /owner\/name/);
|
|
109
|
-
|
|
110
|
-
assert.
|
|
111
|
-
assert.equal(runnerConfig({ MODULE_TEST_REPO: 'o/r', MODULE_TEST_TOKEN: 'x', MODULE_TEST_REF: 'trunk' }, { origin: () => 'o/core' }).ref, 'trunk');
|
|
112
|
+
assert.deepEqual(runnerConfig({ MODULE_TEST_REPO: 'o/r', MODULE_TEST_TOKEN: 'x', MODULE_TEST_CORE_REPO: 'o/core' }), { repo: 'o/r', token: 'x', ref: 'main' });
|
|
113
|
+
assert.equal(runnerConfig({ MODULE_TEST_REPO: 'o/r', MODULE_TEST_TOKEN: 'x', MODULE_TEST_REF: 'trunk' }).ref, 'trunk');
|
|
112
114
|
});
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
const short = (c) => `${c.method} ${c.url.replace('https://api.github.com/repos/owner/module-tests', '')}`;
|
|
117
|
+
const PUSH = ['POST /git/blobs', 'POST /git/trees', 'POST /git/commits', 'POST /git/refs'];
|
|
118
|
+
const DISPATCH = 'POST /actions/workflows/module-tests.yml/dispatches';
|
|
119
|
+
|
|
120
|
+
test('the first dispatch on a core commit pushes the core to core/<sha>, then the tarball, then dispatches', async () => {
|
|
115
121
|
const db = fakeDb();
|
|
116
122
|
const gh = fakeGitHub(dispatchRoutes);
|
|
117
|
-
const r = await maybeDispatch(VER, { db, cfg: CFG, fetchImpl: gh.fetchImpl, readTarball: async () => Buffer.from('tgz'), coreRef
|
|
123
|
+
const r = await maybeDispatch(VER, { db, cfg: CFG, fetchImpl: gh.fetchImpl, readTarball: async () => Buffer.from('tgz'), coreRef, archive, log: quiet });
|
|
118
124
|
assert.match(r.nonce, /^[0-9a-f]{32}$/);
|
|
119
125
|
assert.equal(r.branch, `module-test/${r.nonce}`);
|
|
120
|
-
assert.
|
|
121
|
-
|
|
122
|
-
assert.
|
|
123
|
-
assert.equal(gh.calls[
|
|
124
|
-
assert.
|
|
125
|
-
assert.
|
|
126
|
+
assert.equal(r.core_ref, SHA);
|
|
127
|
+
assert.deepEqual(gh.calls.map(short), [`GET /git/ref/heads/core/${SHA}`, ...PUSH, ...PUSH, DISPATCH]);
|
|
128
|
+
assert.equal(Buffer.from(gh.calls[1].body.content, 'base64').toString(), 'core-tgz');
|
|
129
|
+
assert.equal(gh.calls[2].body.tree[0].path, 'core.tgz');
|
|
130
|
+
assert.deepEqual(gh.calls[3].body.parents, [], 'the core snapshot is an orphan commit');
|
|
131
|
+
assert.equal(gh.calls[4].body.ref, `refs/heads/core/${SHA}`);
|
|
132
|
+
assert.deepEqual(gh.calls[7].body.parents, [], 'an orphan commit: the branch carries only the tarball');
|
|
133
|
+
assert.equal(gh.calls[8].body.ref, `refs/heads/module-test/${r.nonce}`);
|
|
134
|
+
assert.equal(gh.calls[9].body.ref, 'main');
|
|
135
|
+
assert.deepEqual(gh.calls[9].body.inputs, { version_id: '7', module_key: 'weather', nonce: r.nonce, core_ref: SHA });
|
|
126
136
|
assert.equal(db.signals.length, 1);
|
|
127
137
|
assert.equal(db.signals[0].outcome, 'pending');
|
|
128
138
|
assert.equal(db.signals[0].detail.runner.nonce, r.nonce);
|
|
129
139
|
});
|
|
130
140
|
|
|
141
|
+
test('a later dispatch on the same core commit reuses core/<sha> and never archives again', async () => {
|
|
142
|
+
const gh = fakeGitHub({ ...dispatchRoutes, [`GET /git/ref/heads/core/${SHA}$`]: { ref: `refs/heads/core/${SHA}` } });
|
|
143
|
+
let archived = 0;
|
|
144
|
+
const r = await maybeDispatch(VER, { db: fakeDb(), cfg: CFG, fetchImpl: gh.fetchImpl, readTarball: async () => Buffer.from('tgz'), coreRef, archive: async () => { archived += 1; return Buffer.from('x'); }, log: quiet });
|
|
145
|
+
assert.ok(r);
|
|
146
|
+
assert.equal(archived, 0);
|
|
147
|
+
assert.deepEqual(gh.calls.map(short), [`GET /git/ref/heads/core/${SHA}`, ...PUSH, DISPATCH]);
|
|
148
|
+
assert.equal(gh.calls[1].body.content, Buffer.from('tgz').toString('base64'), 'only the module tarball is pushed');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('two dispatches racing on a new commit: the loser\'s refused ref create still dispatches', async () => {
|
|
152
|
+
const gh = fakeGitHub({ ...dispatchRoutes, 'POST /git/refs$': (url, init) => (JSON.parse(init.body).ref.startsWith('refs/heads/core/') ? new Response('{"message":"Reference already exists"}', { status: 422 }) : { ref: 'x' }) });
|
|
153
|
+
assert.ok(await maybeDispatch(VER, { db: fakeDb(), cfg: CFG, fetchImpl: gh.fetchImpl, readTarball: async () => Buffer.from('tgz'), coreRef, archive, log: quiet }));
|
|
154
|
+
assert.equal(gh.calls.at(-1).url.endsWith('/dispatches'), true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('an oversized core snapshot, or an unknown core commit, fails the dispatch loudly and pushes nothing', async () => {
|
|
158
|
+
for (const opts of [{ archive: async () => Buffer.alloc(73 * 1024 * 1024) }, { coreRef: () => null, archive }]) {
|
|
159
|
+
const db = fakeDb();
|
|
160
|
+
const gh = fakeGitHub(dispatchRoutes);
|
|
161
|
+
const errors = [];
|
|
162
|
+
assert.equal(await maybeDispatch(VER, { db, cfg: CFG, fetchImpl: gh.fetchImpl, readTarball: async () => Buffer.from('tgz'), coreRef, log: { warn() {}, error: (m) => errors.push(m) }, ...opts }), null);
|
|
163
|
+
assert.match(errors[0], /over the 72 MB|core commit is unknown/);
|
|
164
|
+
assert.ok(!gh.calls.some((c) => c.method === 'POST'), 'nothing was pushed or dispatched');
|
|
165
|
+
assert.equal(db.signals.length, 0);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('a core commit git cannot find fails with a cause a person can act on, not git\'s bare error', async () => {
|
|
170
|
+
await assert.rejects(archiveCore('0'.repeat(40)), /must run from a git checkout that holds it/);
|
|
171
|
+
});
|
|
172
|
+
|
|
131
173
|
test('a version with a real Tests result, or a run already in flight, is not dispatched again', async () => {
|
|
132
174
|
for (const existing of [{ outcome: 'scored', detail: {} }, { outcome: 'pending', detail: { runner: { nonce: 'a'.repeat(32) } } }]) {
|
|
133
175
|
const db = fakeDb();
|
|
134
176
|
db.signals.push({ id: 1, version_id: 7, part: 'tests', ...existing });
|
|
135
177
|
const gh = fakeGitHub(dispatchRoutes);
|
|
136
|
-
assert.equal(await maybeDispatch(VER, { db, cfg: CFG, fetchImpl: gh.fetchImpl, readTarball: async () => Buffer.from('x'), coreRef
|
|
178
|
+
assert.equal(await maybeDispatch(VER, { db, cfg: CFG, fetchImpl: gh.fetchImpl, readTarball: async () => Buffer.from('x'), coreRef, archive, log: quiet }), null);
|
|
137
179
|
assert.equal(gh.calls.length, 0);
|
|
138
180
|
}
|
|
139
181
|
});
|
|
@@ -142,13 +184,13 @@ test('a plain pending (from before the runner was set up) is dispatched', async
|
|
|
142
184
|
const db = fakeDb();
|
|
143
185
|
db.signals.push({ id: 1, version_id: 7, part: 'tests', outcome: 'pending', detail: {} });
|
|
144
186
|
const gh = fakeGitHub(dispatchRoutes);
|
|
145
|
-
assert.ok(await maybeDispatch(VER, { db, cfg: CFG, fetchImpl: gh.fetchImpl, readTarball: async () => Buffer.from('x'), coreRef
|
|
187
|
+
assert.ok(await maybeDispatch(VER, { db, cfg: CFG, fetchImpl: gh.fetchImpl, readTarball: async () => Buffer.from('x'), coreRef, archive, log: quiet }));
|
|
146
188
|
});
|
|
147
189
|
|
|
148
190
|
test('a failed dispatch deletes the branch, records nothing and never throws', async () => {
|
|
149
191
|
const db = fakeDb();
|
|
150
192
|
const gh = fakeGitHub({ ...dispatchRoutes, 'POST /dispatches$': () => new Response('{"message":"nope"}', { status: 422 }), 'DELETE /git/refs/heads/module-test/': null });
|
|
151
|
-
assert.equal(await maybeDispatch(VER, { db, cfg: CFG, fetchImpl: gh.fetchImpl, readTarball: async () => Buffer.from('x'), coreRef
|
|
193
|
+
assert.equal(await maybeDispatch(VER, { db, cfg: CFG, fetchImpl: gh.fetchImpl, readTarball: async () => Buffer.from('x'), coreRef, archive, log: quiet }), null);
|
|
152
194
|
assert.equal(gh.calls.at(-1).method, 'DELETE');
|
|
153
195
|
assert.equal(db.signals.length, 0);
|
|
154
196
|
});
|
|
@@ -274,7 +316,18 @@ test('the workflow template: no permissions for the job that runs module code, n
|
|
|
274
316
|
assert.match(yml, /^run-name: module-tests \$\{\{ inputs\.version_id \}\} \$\{\{ inputs\.nonce \}\}$/m);
|
|
275
317
|
assert.equal(runName(7, 'n'), 'module-tests 7 n', 'collect matches the template\'s run-name');
|
|
276
318
|
const testJob = yml.slice(yml.indexOf('\n test:'));
|
|
319
|
+
const fetchJob = yml.slice(yml.indexOf('\n fetch:'), yml.indexOf('\n test:'));
|
|
277
320
|
assert.match(testJob, /permissions: \{\}/);
|
|
321
|
+
// The core repo is private (task 1004739): the test job checks out nothing by
|
|
322
|
+
// name; the fetch job reads the snapshot dispatch pushed to core/<sha> and hands
|
|
323
|
+
// it over as an artifact.
|
|
324
|
+
assert.doesNotMatch(testJob, /actions\/checkout/, 'the job that runs module code checks out no repo');
|
|
325
|
+
assert.doesNotMatch(yml, /repository:|core_repo/, 'no repo is named anywhere');
|
|
326
|
+
assert.match(fetchJob, /ref: "core\/\$\{\{ inputs\.core_ref \}\}"/);
|
|
327
|
+
assert.match(fetchJob, /name: "core-\$\{\{ inputs\.nonce \}\}", path: core-snapshot\/core\.tgz/);
|
|
328
|
+
assert.match(fetchJob, /\^\[0-9a-f\]\{40\}\$/, 'the core ref is checked before it is used');
|
|
329
|
+
assert.match(testJob, /name: "core-\$\{\{ inputs\.nonce \}\}", path: core-in/);
|
|
330
|
+
assert.ok(testJob.indexOf('Unpack the core') < testJob.indexOf('npm ci'), 'the core is unpacked before it is installed');
|
|
278
331
|
assert.match(testJob, /MODULE_TEST_SANDBOX=1/);
|
|
279
332
|
// Module code runs as an unprivileged user whose packets the firewall rejects off loopback.
|
|
280
333
|
const cut = testJob.indexOf('Cut the network');
|