@bongos/core 1.19.1074 → 1.19.1076
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 +196 -211
- package/.claude/skills/blocker-solve/SKILL.md +1 -1
- package/README.md +1 -1
- package/bin/bongos.js +1 -3
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +1 -1
- package/docs/adr/0035-builder-onboarding-three-paths.md +1 -1
- package/docs/adr/0044-per-box-live-game-preview.md +1 -1
- package/docs/adr/0045-devbox-desktop-app.md +1 -1
- package/docs/adr/0046-sandbox-first-review-gate.md +1 -1
- package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +1 -1
- package/docs/adr/0053-scoped-dev-box-session.md +1 -1
- package/docs/adr/0055-server-mediated-branch-publish.md +1 -1
- package/docs/adr/0057-container-cost-ledger.md +1 -1
- package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +1 -1
- package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +1 -1
- package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +1 -1
- package/docs/adr/0072-dev-box-code-staleness-visibility.md +1 -1
- package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +1 -1
- package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +1 -1
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +1 -1
- package/docs/adr/0145-devbox-app-branding-driven-module.md +1 -1
- package/docs/adr/0148-task-scoped-box-source-access.md +2 -2
- package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +1 -1
- package/docs/adr/0193-pause-task-scoped-box-slices.md +1 -1
- package/docs/adr/0277-a-box-is-in-use-only-while-a-human-is-attached.md +1 -1
- package/docs/adr/0346-dev-boxes-are-retired.md +69 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +4 -4
- package/docs/architecture.md +5 -6
- package/docs/branding-contract.md +2 -2
- package/docs/canonical-permissions.md +10 -18
- package/docs/copy-inventory.md +3 -3
- package/docs/copy-registry.json +3 -3
- package/docs/design/gate-navigation-direction.md +1 -1
- package/docs/design/hall-direction-v2.md +3 -3
- package/docs/design/modular-architecture/00-research-report.md +1 -1
- package/docs/design/modular-architecture/01-architecture.md +3 -3
- package/docs/design/modular-architecture/02-module-map.md +1 -2
- package/docs/design/modular-architecture/03-builder-flows.md +22 -20
- package/docs/design/modular-architecture/04-module-lifecycle.md +1 -1
- package/docs/design/modular-architecture/README.md +4 -2
- package/docs/design/reviews/hall-v2/README.md +2 -2
- package/docs/design/vanilla-hall-ui-redesign-scope.md +4 -4
- package/docs/file-map.md +5 -5
- package/docs/handoff-template.md +1 -1
- package/docs/module-api-changelog.md +8 -0
- package/docs/modules-contract.md +11 -14
- package/docs/page-readings.json +63 -63
- package/docs/recipes/bongos-cli-release.md +1 -2
- package/docs/recipes/multi-builder-merge.md +1 -1
- package/docs/recipes/ops-gotchas.md +0 -9
- package/docs/recipes/self-host.md +1 -1
- package/docs/recipes/windows-builders.md +1 -1
- package/migrations/core_258_drop_dev_box_tables.sql +3 -0
- package/modules/copy-desk/routes/copy-desk.js +1 -1
- package/modules/discord/ship-broadcast.js +3 -3
- package/modules/economy/credits.js +2 -2
- package/modules/hall-ui/public/palette.js +143 -3
- package/modules/ideas/projection.js +1 -1
- package/modules/lifecycle/db-ship.js +4 -4
- package/modules/lifecycle/github-push.js +12 -14
- package/modules/lifecycle/publish-reconciler.js +10 -10
- package/modules/lifecycle/routes/tasks.js +8 -8
- package/modules/lifecycle/ship-card.js +1 -1
- package/modules/lifecycle/task-visuals.js +2 -2
- package/modules/memory/routes/memory.js +2 -2
- package/modules/platform-identity/routes/sso.js +3 -2
- package/modules/provisioning/capacity.js +1 -1
- package/modules/provisioning/provisioning.js +4 -3
- package/modules/provisioning/routes/provisioning.js +4 -4
- package/modules/sessions/db.js +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +28 -0
- package/scripts/gds/artifact-staleness.js +2 -2
- package/scripts/gds/backfill-task-rewards.js +5 -5
- package/scripts/gds/claude-materialize.js +2 -2
- package/scripts/gds/cli-lib.js +7 -7
- package/scripts/gds/codemod-rename-src-gds.js +1 -1
- package/scripts/gds/context-pack.js +5 -7
- package/scripts/gds/dev-box-guard.js +165 -0
- package/scripts/gds/doctor.js +3 -3
- package/scripts/gds/fitness-checks-packaging.js +4 -4
- package/scripts/gds/fitness-lib.js +10 -0
- package/scripts/gds/fitness-ratchets.js +1 -1
- package/scripts/gds/fitness.js +2 -10
- package/scripts/gds/gen-api-client.js +4 -4
- package/scripts/gds/gen-api-docs.js +1 -1
- package/scripts/gds/gen-repo-map.js +13 -14
- package/scripts/gds/gen-session-index.js +8 -8
- package/scripts/gds/leak-scan-allowlist.js +1 -1
- package/scripts/gds/plain-cards.js +1 -1
- package/scripts/gds/push-path-brief.js +10 -10
- package/scripts/gds/rename-history-check.js +1 -1
- package/scripts/gds/run-unit-tests.js +4 -0
- package/scripts/gds/session-digest-build.js +2 -2
- package/scripts/gds/ship-deploy-target.js +13 -13
- package/scripts/gds/ship-flow.js +1 -1
- package/scripts/gds/ship-land.js +6 -6
- package/scripts/gds/ship-merge.js +4 -4
- package/scripts/gds/ship-preflight-steps.js +4 -4
- package/scripts/gds/ship-regen.js +9 -12
- package/scripts/gds/ship.js +9 -9
- package/scripts/gds/skill-preflight.js +9 -8
- package/src/bongos/api-prefix.js +4 -4
- package/src/bongos/module-scope-map.js +1 -1
- package/src/bongos/platform-visibility-gate.js +1 -1
- package/src/bongos/routes/me.js +1 -1
- package/src/bongos/routes/security.js +3 -3
- package/src/bongos/routes.js +2 -2
- package/src/bongos/serve-internal.js +5 -6
- package/src/bongos-downloads.js +1 -6
- package/src/branding.js +2 -2
- package/src/build-info.js +4 -6
- package/src/instance-config.js +2 -2
- package/src/module-api.js +3 -3
- package/src/module-loader/manifest-schema.js +1 -1
- package/tests/api_alias_caller_ratchet.mjs +1 -1
- package/tests/api_alias_exceptions.mjs +1 -1
- package/tests/api_client.mjs +1 -1
- package/tests/api_path_404.mjs +1 -1
- package/tests/backfill_task_rewards.mjs +1 -1
- package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
- package/tests/cli_sessions.mjs +1 -1
- package/tests/context_pack.mjs +13 -13
- package/tests/copy_inventory.mjs +7 -7
- package/tests/core_258_box_session_claims_db.mjs +180 -0
- package/tests/credit_grant.mjs +2 -2
- package/tests/criterion_suggest.mjs +1 -1
- package/tests/design_tokens_sync.mjs +3 -2
- package/tests/fitness.mjs +89 -0
- package/tests/fitness_ratchets.mjs +2 -2
- package/tests/github_push_land_proof.mjs +1 -1
- package/tests/goal_advisory.mjs +3 -3
- package/tests/goal_suggest.mjs +3 -3
- package/tests/government_abuse_matrix.mjs +4 -3
- package/tests/hall_audit.mjs +1 -1
- package/tests/hall_palette.mjs +224 -11
- package/tests/helpers.mjs +1 -1
- package/tests/host_topology_skips.mjs +2 -2
- package/tests/migration_namespace.mjs +6 -6
- package/tests/module_api.mjs +2 -2
- package/tests/module_cli.mjs +5 -5
- package/tests/module_manifest.mjs +13 -13
- package/tests/module_route_rank.mjs +2 -2
- package/tests/modules_route_wiring.mjs +1 -1
- package/tests/onboarding_route_signals.mjs +1 -1
- package/tests/paste_token_session_store.mjs +1 -1
- package/tests/platform_visibility_gate.mjs +1 -1
- package/tests/project_modules_ui.mjs +17 -17
- package/tests/projects_hub_module_picker.mjs +75 -74
- package/tests/provisioning_capacity.mjs +1 -1
- package/tests/provisioning_recommendations.mjs +3 -2
- package/tests/publish_branch_route.mjs +1 -1
- package/tests/publish_manifest.mjs +1 -1
- package/tests/publish_reconciler.mjs +8 -8
- package/tests/push_path_brief.mjs +10 -9
- package/tests/regrade_eligibility.mjs +1 -1
- package/tests/rename_history_restraint.mjs +1 -1
- package/tests/repo_map.mjs +9 -9
- package/tests/seam_wiring_guard.mjs +11 -11
- package/tests/search_isolation.mjs +3 -3
- package/tests/session_records.mjs +1 -1
- package/tests/session_rename_fallback.mjs +1 -1
- package/tests/session_start_freshness.mjs +3 -3
- package/tests/session_token_hash_db.mjs +1 -1
- package/tests/ship_card.mjs +1 -1
- package/tests/ship_ci_deploy.mjs +17 -17
- package/tests/ship_premerge.mjs +1 -1
- package/tests/ship_resume.mjs +1 -1
- package/tests/skill_preflight.mjs +2 -2
- package/tests/skip_is_not_pass.mjs +1 -1
- package/tests/terms_acceptance.mjs +1 -1
- package/docs/design/reviews/hall-v2/harbor--archon.webp +0 -0
- package/docs/design/reviews/hall-v2/harbor--metic.webp +0 -0
- package/docs/design/reviews/hall-v2/harbor--xenos.webp +0 -0
- package/docs/design/reviews/hall-v2/pair--archon.webp +0 -0
- package/docs/design/reviews/hall-v2/pair--metic.webp +0 -0
- package/docs/design/reviews/hall-v2/pair--xenos.webp +0 -0
package/tests/goal_advisory.mjs
CHANGED
|
@@ -72,16 +72,16 @@ await test("kind='bug' with no goal at all is quiet too — a fix needs no home
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
await test('a REAL goal is silent — null, not an empty object', () => {
|
|
75
|
-
assert.equal(adviseGoal({ versionId: V, goalId: 1000025, goalTitle: 'Builder experience — hall,
|
|
75
|
+
assert.equal(adviseGoal({ versionId: V, goalId: 1000025, goalTitle: 'Builder experience — hall, onboarding, and skills' }), null);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
await test("kind='bug' under a real goal is silent too — already silent, kind changes nothing", () => {
|
|
79
|
-
assert.equal(adviseGoal({ versionId: V, goalId: 1000025, goalTitle: 'Builder experience — hall,
|
|
79
|
+
assert.equal(adviseGoal({ versionId: V, goalId: 1000025, goalTitle: 'Builder experience — hall, onboarding, and skills', kind: 'bug' }), null);
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
// ---- adviseGoal: the third reason — no_criterion_link (task 1003157) ----
|
|
83
83
|
|
|
84
|
-
const REAL_GOAL_TITLE = 'Builder experience — hall,
|
|
84
|
+
const REAL_GOAL_TITLE = 'Builder experience — hall, onboarding, and skills';
|
|
85
85
|
const SOME_CRITERIA = [
|
|
86
86
|
{ id: 11, criterion_md: 'Every onboarding page renders under 2s' },
|
|
87
87
|
{ id: 12, criterion_md: 'The hall shows a builder their own karma trend' },
|
package/tests/goal_suggest.mjs
CHANGED
|
@@ -30,7 +30,7 @@ const { test, summary } = makeRunner();
|
|
|
30
30
|
const V = 'BONGOS-V1';
|
|
31
31
|
const GOALS = [
|
|
32
32
|
{ id: 1000024, title: 'Build-pipeline integrity', scope_modules: ['lifecycle', 'grading'] },
|
|
33
|
-
{ id: 1000025, title: 'Builder experience — hall,
|
|
33
|
+
{ id: 1000025, title: 'Builder experience — hall, tool box, and skills', scope_modules: ['hall-ui', 'specialities'] },
|
|
34
34
|
{ id: 1000071, title: 'Idea routing — work that has a home lands in it', scope_modules: ['ideas', 'discord'] },
|
|
35
35
|
{ id: 1000003, title: `${V} — general`, scope_modules: [] },
|
|
36
36
|
];
|
|
@@ -76,8 +76,8 @@ await test('RANKS: ties break by ascending id so a picker renders stably', () =>
|
|
|
76
76
|
|
|
77
77
|
await test('LIMIT (known + accepted): token overlap can match on an incidental word', () => {
|
|
78
78
|
// Real example found while building this: "the goals page search BOX is broken"
|
|
79
|
-
// scores the
|
|
80
|
-
// matching cannot tell "search box" from "
|
|
79
|
+
// scores the "tool box" goal, because both contain the token 'box'. Bag-of-words
|
|
80
|
+
// matching cannot tell "search box" from "tool box".
|
|
81
81
|
//
|
|
82
82
|
// This is NOT hidden — it is why every suggestion carries `score` and
|
|
83
83
|
// `matched_terms`: a caller can show WHY it matched and a human decides. A
|
|
@@ -365,9 +365,10 @@ test('V8: a forced principal grant resolves to nothing (structurally dropped)',
|
|
|
365
365
|
});
|
|
366
366
|
|
|
367
367
|
test('V9: an undeclared session source is refused outright, never treated as "full"', () => {
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
368
|
+
// core_258 (task 1003895) deleted the rows with source 'box' and narrowed the source
|
|
369
|
+
// CHECK to cli/web. 'box' must still be undeclared to BOTH loaders, so a stray row of
|
|
370
|
+
// that source is refused by requireBuilder and the page gate rather than honoured
|
|
371
|
+
// (tests/government_session_scope.mjs runs the gate).
|
|
371
372
|
for (const source of ['box', 'not-a-source']) {
|
|
372
373
|
assert.equal(sessionScopes.scopeFor(source), null, `"${source}" must not be a declared session source`);
|
|
373
374
|
}
|
package/tests/hall_audit.mjs
CHANGED
|
@@ -464,7 +464,7 @@ test('the colour-literal guard CATCHES a reintroduction, and tolerates a task re
|
|
|
464
464
|
assert.ok('a { color: #a04040; }'.match(re), 'a bare hex in a colour value is caught');
|
|
465
465
|
assert.ok('x = \'<span style="color:var(--olive,#5b7a4d)">\''.match(colourLiteralRe()),
|
|
466
466
|
'a hex FALLBACK behind a var() is caught — that is how --olive hid');
|
|
467
|
-
assert.equal('// #807: the
|
|
467
|
+
assert.equal('// #807: the ship panel'.match(colourLiteralRe()), null,
|
|
468
468
|
'a task reference in a comment is not a colour');
|
|
469
469
|
assert.equal('a { color: var(--ok); }'.match(colourLiteralRe()), null,
|
|
470
470
|
'a token is exactly what this rule wants');
|
package/tests/hall_palette.mjs
CHANGED
|
@@ -28,21 +28,41 @@ const SHELL_SRC = read('shell.js');
|
|
|
28
28
|
|
|
29
29
|
// Boot palette.js against a DOM stub whose sidebar carries `navItems`, and whose
|
|
30
30
|
// `visibleIds` decide which section groups are on offer. Returns window.OTBPalette.
|
|
31
|
-
|
|
31
|
+
//
|
|
32
|
+
// `registry` memoizes querySelector by selector string (keyed globally within one
|
|
33
|
+
// boot, same as a real single-instance-id contract) so `build()`'s one-time
|
|
34
|
+
// `root.querySelector('#palette-input')` / `'#palette-list'` calls hand back the
|
|
35
|
+
// SAME stub every time — real enough to fire DOM events at and read innerHTML
|
|
36
|
+
// back from, which the pure-compute-only harness never needed before task 1003460
|
|
37
|
+
// wired a real fetch() into the input handler.
|
|
38
|
+
function bootPalette({ navItems = [], visibleIds = ['nav-standing', 'nav-work', 'shell-user'], modules = null, fetchImpl = undefined } = {}) {
|
|
39
|
+
const registry = {};
|
|
32
40
|
const makeNav = (it) => ({
|
|
33
41
|
hidden: !!it.hidden,
|
|
34
42
|
target: it.external ? '_blank' : '',
|
|
35
43
|
getAttribute: (n) => (n === 'href' ? it.href : null),
|
|
36
44
|
querySelector: (sel) => (sel === '.nav-item__label' ? { textContent: it.label } : null),
|
|
37
45
|
});
|
|
38
|
-
const stubEl = () =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
const stubEl = (sel) => {
|
|
47
|
+
const listeners = {};
|
|
48
|
+
const el = {
|
|
49
|
+
hidden: false, dataset: {}, textContent: '', value: '', className: '',
|
|
50
|
+
classList: { add() {}, remove() {}, contains() { return false; }, toggle() { return false; } },
|
|
51
|
+
setAttribute() {}, getAttribute() { return null; },
|
|
52
|
+
addEventListener(type, fn) { (listeners[type] = listeners[type] || []).push(fn); },
|
|
53
|
+
fire(type, ev) { (listeners[type] || []).forEach((fn) => fn(ev || {})); },
|
|
54
|
+
appendChild() {}, focus() {},
|
|
55
|
+
querySelector(childSel) {
|
|
56
|
+
if (!registry[childSel]) registry[childSel] = stubEl(childSel);
|
|
57
|
+
return registry[childSel];
|
|
58
|
+
},
|
|
59
|
+
querySelectorAll() { return []; },
|
|
60
|
+
set innerHTML(v) { this._html = v; },
|
|
61
|
+
get innerHTML() { return this._html || ''; },
|
|
62
|
+
};
|
|
63
|
+
if (sel) registry[sel] = el;
|
|
64
|
+
return el;
|
|
65
|
+
};
|
|
46
66
|
|
|
47
67
|
const documentObj = {
|
|
48
68
|
body: { dataset: { page: 'home' }, appendChild() {} },
|
|
@@ -62,17 +82,34 @@ function bootPalette({ navItems = [], visibleIds = ['nav-standing', 'nav-work',
|
|
|
62
82
|
const windowObj = { location: { hash: '', assign() {} }, addEventListener() {}, open() {} };
|
|
63
83
|
const sandbox = {
|
|
64
84
|
window: windowObj, document: documentObj, navigator: { platform: 'Win32', userAgent: 'test' },
|
|
65
|
-
console,
|
|
85
|
+
console, setTimeout, clearTimeout, Promise, JSON, Number, String, Array, Object,
|
|
66
86
|
};
|
|
87
|
+
if (fetchImpl) sandbox.fetch = fetchImpl;
|
|
67
88
|
sandbox.globalThis = sandbox;
|
|
68
89
|
vm.createContext(sandbox);
|
|
69
90
|
vm.runInContext(PALETTE_SRC, sandbox, { filename: 'palette.js' });
|
|
70
91
|
// Copy results across the realm boundary — a vm-created array has the sandbox's
|
|
71
92
|
// Array.prototype, which deepStrictEqual rejects even when the values match.
|
|
72
93
|
const P = windowObj.OTBPalette;
|
|
73
|
-
return {
|
|
94
|
+
return {
|
|
95
|
+
compute: (q) => Array.from(P.compute(q), (e) => ({ ...e })),
|
|
96
|
+
contentEntry: (hit) => { const e = P.contentEntry(hit); return e ? { ...e } : e; },
|
|
97
|
+
open: () => P.open(),
|
|
98
|
+
close: () => P.close(),
|
|
99
|
+
listEl: () => registry['#palette-list'],
|
|
100
|
+
type(value) {
|
|
101
|
+
const el = registry['#palette-input'];
|
|
102
|
+
el.value = value;
|
|
103
|
+
el.fire('input');
|
|
104
|
+
},
|
|
105
|
+
};
|
|
74
106
|
}
|
|
75
107
|
|
|
108
|
+
const tick = (ms = 0) => new Promise((r) => setTimeout(r, ms));
|
|
109
|
+
// Real timers (palette.js's debounce is a real setTimeout) — settle past the
|
|
110
|
+
// 300ms debounce plus a few microtask/macrotask turns for the fetch chain.
|
|
111
|
+
async function settleSearch() { await tick(400); }
|
|
112
|
+
|
|
76
113
|
const NAV_FIXTURE = [
|
|
77
114
|
{ label: 'Home', href: '/' },
|
|
78
115
|
{ label: 'Tasks', href: '/work' },
|
|
@@ -207,3 +244,179 @@ test('every page that loads the shell also loads the palette', () => {
|
|
|
207
244
|
const missing = withShell.filter((f) => !/src="\/builders\/palette\.js/.test(read(f)));
|
|
208
245
|
assert.deepEqual(missing, [], 'Ctrl-K must work on every hall page');
|
|
209
246
|
});
|
|
247
|
+
|
|
248
|
+
// ---- server-backed content search (task 1003460) ---------------------------
|
|
249
|
+
|
|
250
|
+
const TASK_HIT = { id: 1, source_kind: 'db', source_ref: 'task:1003460', title: "Ctrl-K searches the project's memory", heading_path: null };
|
|
251
|
+
const IDEA_HIT = { id: 2, source_kind: 'db', source_ref: 'idea:42', title: 'A homeless idea' };
|
|
252
|
+
const BLOCKER_HIT = { id: 3, source_kind: 'db', source_ref: 'blocker:7', title: 'Needs the owner' };
|
|
253
|
+
const DOC_HIT = { id: 4, source_kind: 'doc', source_ref: 'docs/adr/0060-gds-retrieval-layer.md', title: 'ADR 0060' };
|
|
254
|
+
const SESSION_LOG_HIT = { id: 5, source_kind: 'db', source_ref: 'session_log:9', title: 'Session log 9 · task 1003460' };
|
|
255
|
+
|
|
256
|
+
test('contentEntry resolves task/idea/blocker hits to real hall destinations', () => {
|
|
257
|
+
const P = bootPalette({ navItems: NAV_FIXTURE });
|
|
258
|
+
assert.deepEqual(
|
|
259
|
+
{ label: P.contentEntry(TASK_HIT).label, href: P.contentEntry(TASK_HIT).href, overlay: !!P.contentEntry(TASK_HIT).overlay },
|
|
260
|
+
{ label: "Ctrl-K searches the project's memory", href: '/task/1003460', overlay: false },
|
|
261
|
+
);
|
|
262
|
+
assert.deepEqual(
|
|
263
|
+
{ href: P.contentEntry(IDEA_HIT).href, overlay: P.contentEntry(IDEA_HIT).overlay },
|
|
264
|
+
{ href: '#/idea/42', overlay: true },
|
|
265
|
+
);
|
|
266
|
+
assert.deepEqual(
|
|
267
|
+
{ href: P.contentEntry(BLOCKER_HIT).href, overlay: P.contentEntry(BLOCKER_HIT).overlay },
|
|
268
|
+
{ href: '#/blocker/7', overlay: true },
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('contentEntry drops hits the hall has no page for (doc/session-log/etc)', () => {
|
|
273
|
+
const P = bootPalette({ navItems: NAV_FIXTURE });
|
|
274
|
+
assert.equal(P.contentEntry(DOC_HIT), null, 'a repo doc chunk has no hall page to jump to');
|
|
275
|
+
assert.equal(P.contentEntry(SESSION_LOG_HIT), null, 'a session-log chunk has no hall page to jump to');
|
|
276
|
+
assert.equal(P.contentEntry(null), null);
|
|
277
|
+
assert.equal(P.contentEntry({ source_kind: 'db', source_ref: 'commit:abc123' }), null, 'an unrecognized db ref resolves to nothing, not a crash');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('contentEntry normalizes a dash the way cleanTitle() does everywhere else (V4, docs/voice.md)', () => {
|
|
281
|
+
// A task/idea/blocker title is quoted ledger data, not copy this project
|
|
282
|
+
// authored — V4 binds the dash ban to exactly that, enforced at render.
|
|
283
|
+
const P = bootPalette({ navItems: NAV_FIXTURE });
|
|
284
|
+
const hit = { source_kind: 'db', source_ref: 'task:99', title: 'V4.R18 — Ctrl-K searches the memory (follow-up)' };
|
|
285
|
+
assert.equal(P.contentEntry(hit).label, 'V4.R18, Ctrl-K searches the memory');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('contentEntry truncates a pathologically long indexed title before it becomes markup', () => {
|
|
289
|
+
const P = bootPalette({ navItems: NAV_FIXTURE });
|
|
290
|
+
const longTitle = 'x'.repeat(400);
|
|
291
|
+
const hit = { source_kind: 'db', source_ref: 'task:1', title: longTitle };
|
|
292
|
+
const label = P.contentEntry(hit).label;
|
|
293
|
+
assert.ok(label.length <= 120, `label must be capped, got ${label.length} chars`);
|
|
294
|
+
assert.ok(label.endsWith('…'), 'a truncated label is marked as such');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('typing prose merges in content matches from POST /api/bongos/search, on top of local matches', async () => {
|
|
298
|
+
const calls = [];
|
|
299
|
+
const fetchImpl = async (url, init) => {
|
|
300
|
+
calls.push({ url, body: JSON.parse(init.body) });
|
|
301
|
+
return { ok: true, json: async () => ({ query: 'ctrl-k memory', count: 1, results: [TASK_HIT] }) };
|
|
302
|
+
};
|
|
303
|
+
const P = bootPalette({ navItems: NAV_FIXTURE, fetchImpl });
|
|
304
|
+
P.open();
|
|
305
|
+
P.type('ctrl-k memory');
|
|
306
|
+
// Local compute() ran synchronously — pages/sections still searchable immediately.
|
|
307
|
+
assert.deepEqual(P.compute('ctrl-k memory'), [], 'sanity: no local page/section matches this prose');
|
|
308
|
+
|
|
309
|
+
await settleSearch();
|
|
310
|
+
|
|
311
|
+
assert.equal(calls.length, 1, 'exactly one debounced request, not one per keystroke');
|
|
312
|
+
assert.equal(calls[0].url, '/api/bongos/search');
|
|
313
|
+
assert.equal(calls[0].body.q, 'ctrl-k memory');
|
|
314
|
+
assert.equal(calls[0].body.source_kind, 'db', 'a doc/session-log/etc hit would only ever be dropped by contentEntry — ask the server to not spend the result budget on it');
|
|
315
|
+
assert.match(P.listEl().innerHTML, /Content/, 'the merged row carries the Content group');
|
|
316
|
+
assert.match(P.listEl().innerHTML, /Ctrl-K searches the project's memory/, 'and the task title from the server hit');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('killing the search endpoint leaves the palette exactly as useful as today', async () => {
|
|
320
|
+
const fetchImpl = async () => { throw new Error('endpoint is down'); };
|
|
321
|
+
const P = bootPalette({ navItems: NAV_FIXTURE, fetchImpl });
|
|
322
|
+
P.open();
|
|
323
|
+
P.type('economy');
|
|
324
|
+
const beforeHtml = P.listEl().innerHTML;
|
|
325
|
+
assert.match(beforeHtml, /Economy/, 'the local page match rendered synchronously');
|
|
326
|
+
|
|
327
|
+
await settleSearch();
|
|
328
|
+
|
|
329
|
+
assert.equal(P.listEl().innerHTML, beforeHtml, 'a failed fetch changes nothing — no error row, no crash');
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test('a 404 (endpoint removed) degrades the same as a network failure', async () => {
|
|
333
|
+
const fetchImpl = async () => ({ ok: false, status: 404, json: async () => ({ error: 'not_found' }) });
|
|
334
|
+
const P = bootPalette({ navItems: NAV_FIXTURE, fetchImpl });
|
|
335
|
+
P.open();
|
|
336
|
+
P.type('economy');
|
|
337
|
+
const beforeHtml = P.listEl().innerHTML;
|
|
338
|
+
|
|
339
|
+
await settleSearch();
|
|
340
|
+
|
|
341
|
+
assert.equal(P.listEl().innerHTML, beforeHtml);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test('a query under SEARCH_MIN_LEN never calls the endpoint at all', async () => {
|
|
345
|
+
let called = false;
|
|
346
|
+
const fetchImpl = async () => { called = true; return { ok: true, json: async () => ({ results: [] }) }; };
|
|
347
|
+
const P = bootPalette({ navItems: NAV_FIXTURE, fetchImpl });
|
|
348
|
+
P.open();
|
|
349
|
+
// 2 chars, not 1: the endpoint's least-selective case (falls through FTS to an
|
|
350
|
+
// unindexed trigram scan plus a zero-result corpusHealth() probe) is exactly
|
|
351
|
+
// the length this gate exists to keep off the wire.
|
|
352
|
+
P.type('ab');
|
|
353
|
+
|
|
354
|
+
await settleSearch();
|
|
355
|
+
|
|
356
|
+
assert.equal(called, false, 'two characters is not worth the endpoint\'s worst-case query plan');
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test('closing the palette invalidates an in-flight search — it cannot land after close', async () => {
|
|
360
|
+
let resolveFetch;
|
|
361
|
+
const fetchImpl = () => new Promise((resolve) => { resolveFetch = resolve; });
|
|
362
|
+
const P = bootPalette({ navItems: NAV_FIXTURE, fetchImpl });
|
|
363
|
+
P.open();
|
|
364
|
+
P.type('ctrl-k memory');
|
|
365
|
+
await tick(350); // past the debounce — the request is now in flight
|
|
366
|
+
P.close();
|
|
367
|
+
resolveFetch({ ok: true, json: async () => ({ results: [TASK_HIT] }) });
|
|
368
|
+
await tick(20);
|
|
369
|
+
|
|
370
|
+
// isOpen() is false, so runSearch's own guard must have thrown the response away.
|
|
371
|
+
// (compute() is pure/local and would never carry a remote hit either way — the
|
|
372
|
+
// rendered DOM is the only place a landed-too-late merge could show up.)
|
|
373
|
+
assert.ok(!/Ctrl-K searches/.test(P.listEl().innerHTML), 'a response that lands after close must not render');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test('a duplicate destination (already resolved locally) is not appended twice', async () => {
|
|
377
|
+
const fetchImpl = async () => ({ ok: true, json: async () => ({ results: [TASK_HIT] }) });
|
|
378
|
+
const P = bootPalette({ navItems: NAV_FIXTURE, fetchImpl });
|
|
379
|
+
P.open();
|
|
380
|
+
P.type('1003460'); // id syntax already resolves this to the same /task/1003460 href
|
|
381
|
+
await settleSearch();
|
|
382
|
+
|
|
383
|
+
const rows = P.listEl().innerHTML.match(/data-i="\d+"/g) || [];
|
|
384
|
+
assert.equal(rows.length, 1, 'the server hit for the same task must not add a second row for the same href');
|
|
385
|
+
assert.match(P.listEl().innerHTML, /Task 1003460/, 'the id-syntax row is the one that stays');
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test('a repeated query within one open() replays the cache instead of re-fetching', async () => {
|
|
389
|
+
const calls = [];
|
|
390
|
+
const fetchImpl = async () => { calls.push(1); return { ok: true, json: async () => ({ results: [TASK_HIT] }) }; };
|
|
391
|
+
const P = bootPalette({ navItems: NAV_FIXTURE, fetchImpl });
|
|
392
|
+
P.open();
|
|
393
|
+
P.type('ctrl-k memory');
|
|
394
|
+
await settleSearch();
|
|
395
|
+
assert.equal(calls.length, 1);
|
|
396
|
+
|
|
397
|
+
// Backspace, then retype back to the exact same query (the common case: a
|
|
398
|
+
// reader pauses mid-edit and returns to what they had).
|
|
399
|
+
P.type('ctrl-k mem');
|
|
400
|
+
P.type('ctrl-k memory');
|
|
401
|
+
await settleSearch();
|
|
402
|
+
|
|
403
|
+
assert.equal(calls.length, 1, 'the identical query was served from the per-open cache, not re-fetched');
|
|
404
|
+
assert.match(P.listEl().innerHTML, /Ctrl-K searches the project's memory/, 'the cached hit still renders');
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test('closing and reopening clears the cache — a fresh open may see new DB state', async () => {
|
|
408
|
+
const calls = [];
|
|
409
|
+
const fetchImpl = async () => { calls.push(1); return { ok: true, json: async () => ({ results: [TASK_HIT] }) }; };
|
|
410
|
+
const P = bootPalette({ navItems: NAV_FIXTURE, fetchImpl });
|
|
411
|
+
P.open();
|
|
412
|
+
P.type('ctrl-k memory');
|
|
413
|
+
await settleSearch();
|
|
414
|
+
assert.equal(calls.length, 1);
|
|
415
|
+
|
|
416
|
+
P.close();
|
|
417
|
+
P.open();
|
|
418
|
+
P.type('ctrl-k memory');
|
|
419
|
+
await settleSearch();
|
|
420
|
+
|
|
421
|
+
assert.equal(calls.length, 2, 'a fresh open must not trust a memo carried over from the previous one');
|
|
422
|
+
});
|
package/tests/helpers.mjs
CHANGED
|
@@ -58,7 +58,7 @@ export function makeSqlAwareClient(handler) {
|
|
|
58
58
|
// tests/ is PUBLISHABLE, but the paths some tests assert are on
|
|
59
59
|
// publish-manifest.js's PROTECTED_TOPOLOGY_EXCLUDE list (`.claude/hooks/`,
|
|
60
60
|
// `.claude/settings.json`, `infra/`, `.github/`, `scripts/deploy/`) or are
|
|
61
|
-
// instance-only content (`
|
|
61
|
+
// instance-only content (`config/<instance>.json`). A neutral
|
|
62
62
|
// core checkout carries none of it, so an unconditional load CRASHED the file
|
|
63
63
|
// at import and red-X'd the unit gate for every consumer.
|
|
64
64
|
//
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// tests/ is publishable, but some tests assert paths the neutral core deliberately does
|
|
4
4
|
// not carry: publish-manifest.js's PROTECTED_TOPOLOGY_EXCLUDE (`.claude/hooks/`,
|
|
5
5
|
// `.claude/settings.json`, `infra/`, `.github/`, `scripts/deploy/`) plus instance-only
|
|
6
|
-
// content (`
|
|
6
|
+
// content (`config/<instance>.json`, an uninstalled module). Those files
|
|
7
7
|
// used to CRASH at import and red-X the unit gate for every consumer; they now skip
|
|
8
8
|
// cleanly via the helpers in tests/helpers.mjs and run in full wherever the topology is
|
|
9
9
|
// materialized.
|
|
@@ -33,7 +33,7 @@ const ALLOWED = {
|
|
|
33
33
|
allow_rule_guard: '.claude/hooks/allow-rule-guard.js (package-excluded)',
|
|
34
34
|
art_stage: 'scripts/gds/art-stage.js (art-pipeline tooling, not in the core)',
|
|
35
35
|
bongos_downloads: 'config/bongos-cli.json (instance content)',
|
|
36
|
-
|
|
36
|
+
cli_allowlist_permissions: '.claude/settings.json (package-excluded)',
|
|
37
37
|
conductor: '.claude/hooks/conductor.js (package-excluded)',
|
|
38
38
|
deploy_instance: 'scripts/deploy/deploy-instance.sh (host topology)',
|
|
39
39
|
eviction_policy: '.claude/hooks/eviction-policy.js (package-excluded)',
|
|
@@ -46,8 +46,8 @@ t('parseMigrationName: core_NNN_slug.sql', () => {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
t('parseMigrationName: multi-word module prefix splits at the number, not greedily', () => {
|
|
49
|
-
const p = ns.parseMigrationName('
|
|
50
|
-
assert.equal(p.prefix, '
|
|
49
|
+
const p = ns.parseMigrationName('platform_identity_003_add_col.sql');
|
|
50
|
+
assert.equal(p.prefix, 'platform_identity', 'platform_identity_003 → prefix platform_identity, not platform');
|
|
51
51
|
assert.equal(p.number, 3);
|
|
52
52
|
assert.equal(p.slug, 'add_col');
|
|
53
53
|
});
|
|
@@ -114,10 +114,10 @@ t('filenameViolation: NEW module migration must be <key>_-prefixed', () => {
|
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
t('filenameViolation: hyphenated module key maps to underscore prefix', () => {
|
|
117
|
-
assert.equal(ns.sqlNamespace('
|
|
118
|
-
assert.equal(ns.filenameViolation('module', '
|
|
119
|
-
const v = ns.filenameViolation('module', '003_x.sql', { moduleKey: '
|
|
120
|
-
assert.ok(v && /
|
|
117
|
+
assert.equal(ns.sqlNamespace('platform-identity'), 'platform_identity');
|
|
118
|
+
assert.equal(ns.filenameViolation('module', 'platform_identity_003_x.sql', { moduleKey: 'platform-identity' }), null);
|
|
119
|
+
const v = ns.filenameViolation('module', '003_x.sql', { moduleKey: 'platform-identity' });
|
|
120
|
+
assert.ok(v && /platform_identity_-prefixed|platform_identity_003_x/.test(v));
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
// ---- stemCollisions --------------------------------------------------------
|
package/tests/module_api.mjs
CHANGED
|
@@ -16,8 +16,8 @@ const seams = require(path.join(ROOT, 'src', 'module-seams.js'));
|
|
|
16
16
|
// removing/renaming a key is normally a MAJOR bump (a breaking change to every
|
|
17
17
|
// module). Exception (BV1.R68 / ADR 0091 §3): relocating a DOMAIN query whose
|
|
18
18
|
// sole in-repo consumer moves in lockstep is treated as MINOR — e.g.
|
|
19
|
-
//
|
|
20
|
-
// was added; no enabled module at its pinned coreVersion lost a capability.
|
|
19
|
+
// a per-builder setter left the kernel for its own module's db.js at 1.5.0, when
|
|
20
|
+
// `withTx` was added; no enabled module at its pinned coreVersion lost a capability.
|
|
21
21
|
const PUBLISHED_SURFACE = [
|
|
22
22
|
'CORE_VERSION',
|
|
23
23
|
'requireBuilder', 'requireRank', 'rankMeetsThreshold', 'requireBfgPrincipal', 'allowBoxScope', 'resolveSession',
|
package/tests/module_cli.mjs
CHANGED
|
@@ -63,11 +63,11 @@ test('scaffoldModule: writes a valid, loader-discoverable module with no core ed
|
|
|
63
63
|
|
|
64
64
|
test('scaffoldModule: a kebab-case key produces matching dir, route file, and title', () => {
|
|
65
65
|
const root = tmpModulesRoot();
|
|
66
|
-
const { dir, manifest } = mod.scaffoldModule({ key: '
|
|
67
|
-
assert.equal(manifest.title, '
|
|
68
|
-
assert.deepEqual(manifest.contributes.routes, ['
|
|
69
|
-
assert.ok(fs.existsSync(path.join(dir, 'routes', '
|
|
70
|
-
assert.match(fs.readFileSync(path.join(dir, 'routes', '
|
|
66
|
+
const { dir, manifest } = mod.scaffoldModule({ key: 'copy-desk', modulesDir: root, core: '1.0.0' });
|
|
67
|
+
assert.equal(manifest.title, 'Copy Desk');
|
|
68
|
+
assert.deepEqual(manifest.contributes.routes, ['copy-desk']);
|
|
69
|
+
assert.ok(fs.existsSync(path.join(dir, 'routes', 'copy-desk.js')));
|
|
70
|
+
assert.match(fs.readFileSync(path.join(dir, 'routes', 'copy-desk.js'), 'utf8'), /copyDeskRoutes/);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
test('scaffoldModule: rejects an invalid key', () => {
|
|
@@ -12,11 +12,11 @@ const { satisfies, isValidRange, parseVersion } = require(path.join(ROOT, 'src',
|
|
|
12
12
|
const { validateManifest, assertValidManifest } = require(path.join(ROOT, 'src', 'module-loader', 'manifest-schema.js'));
|
|
13
13
|
|
|
14
14
|
const baseManifest = () => ({
|
|
15
|
-
key: '
|
|
16
|
-
title: '
|
|
17
|
-
description: '
|
|
15
|
+
key: 'discord',
|
|
16
|
+
title: 'Discord',
|
|
17
|
+
description: 'A bridge to the team chat.',
|
|
18
18
|
version: '1.0.0', coreVersion: '^1.0.0',
|
|
19
|
-
contributes: { routes: ['
|
|
19
|
+
contributes: { routes: ['discord'], pollers: ['discord-bot'], uiSections: ['chat'] },
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
// --- semver ---
|
|
@@ -91,7 +91,7 @@ test('missing required fields each produce an error', () => {
|
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
test('bad key format is rejected', () => {
|
|
94
|
-
const r = validateManifest({ ...baseManifest(), key: '
|
|
94
|
+
const r = validateManifest({ ...baseManifest(), key: 'Discord_Bot' });
|
|
95
95
|
assert.equal(r.valid, false);
|
|
96
96
|
assert.ok(r.errors.some((e) => e.startsWith('key')));
|
|
97
97
|
});
|
|
@@ -112,13 +112,13 @@ test('version must be an exact X.Y.Z — a range or junk is rejected (task 10037
|
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
test('unknown contributes key is rejected (typo guard)', () => {
|
|
115
|
-
const r = validateManifest({ ...baseManifest(), contributes: { routs: ['
|
|
115
|
+
const r = validateManifest({ ...baseManifest(), contributes: { routs: ['discord'] } });
|
|
116
116
|
assert.equal(r.valid, false);
|
|
117
117
|
assert.ok(r.errors.some((e) => e.includes('contributes.routs')));
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
test('non-array contributes.routes is rejected', () => {
|
|
121
|
-
const r = validateManifest({ ...baseManifest(), contributes: { routes: '
|
|
121
|
+
const r = validateManifest({ ...baseManifest(), contributes: { routes: 'discord' } });
|
|
122
122
|
assert.equal(r.valid, false);
|
|
123
123
|
assert.ok(r.errors.some((e) => e.includes('contributes.routes')));
|
|
124
124
|
});
|
|
@@ -296,7 +296,7 @@ test('every bundled module declares a maintenance posture (the catalog has no un
|
|
|
296
296
|
test('declarativeSeams: a valid exemption naming a declared seam + entry passes', () => {
|
|
297
297
|
const r = validateManifest({
|
|
298
298
|
...baseManifest(),
|
|
299
|
-
declarativeSeams: { pollers: ['
|
|
299
|
+
declarativeSeams: { pollers: ['discord-bot'], uiSections: ['chat'] },
|
|
300
300
|
});
|
|
301
301
|
assert.equal(r.valid, true, r.errors.join('; '));
|
|
302
302
|
});
|
|
@@ -304,8 +304,8 @@ test('declarativeSeams: a valid exemption naming a declared seam + entry passes'
|
|
|
304
304
|
test('declarativeSeams: an exemption for a seam the module does not declare is an ERROR', () => {
|
|
305
305
|
const r = validateManifest({
|
|
306
306
|
...baseManifest(),
|
|
307
|
-
contributes: { routes: ['
|
|
308
|
-
declarativeSeams: { pollers: ['
|
|
307
|
+
contributes: { routes: ['discord'] }, // no pollers declared
|
|
308
|
+
declarativeSeams: { pollers: ['discord-bot'] },
|
|
309
309
|
});
|
|
310
310
|
assert.equal(r.valid, false);
|
|
311
311
|
assert.ok(r.errors.some((e) => /does not declare contributes\.pollers/.test(e)), r.errors.join('; '));
|
|
@@ -314,10 +314,10 @@ test('declarativeSeams: an exemption for a seam the module does not declare is a
|
|
|
314
314
|
test('declarativeSeams: an exemption naming an ENTRY the module does not list is an ERROR (the typo case)', () => {
|
|
315
315
|
const r = validateManifest({
|
|
316
316
|
...baseManifest(),
|
|
317
|
-
declarativeSeams: { pollers: ['
|
|
317
|
+
declarativeSeams: { pollers: ['discord-bto'] }, // transposed letters
|
|
318
318
|
});
|
|
319
319
|
assert.equal(r.valid, false);
|
|
320
|
-
assert.ok(r.errors.some((e) => /"
|
|
320
|
+
assert.ok(r.errors.some((e) => /"discord-bto" is not in contributes\.pollers/.test(e)), r.errors.join('; '));
|
|
321
321
|
});
|
|
322
322
|
|
|
323
323
|
test('declarativeSeams: the per-entry check spans EVERY seam, not just bare string arrays', () => {
|
|
@@ -344,7 +344,7 @@ test('declarativeSeams: an unknown seam key is an ERROR, not a silent no-op', ()
|
|
|
344
344
|
|
|
345
345
|
test('declarativeSeams: shape errors — non-object block, non-string-array entries', () => {
|
|
346
346
|
assert.equal(validateManifest({ ...baseManifest(), declarativeSeams: ['pollers'] }).valid, false);
|
|
347
|
-
assert.equal(validateManifest({ ...baseManifest(), declarativeSeams: { pollers: '
|
|
347
|
+
assert.equal(validateManifest({ ...baseManifest(), declarativeSeams: { pollers: 'discord-bot' } }).valid, false);
|
|
348
348
|
assert.equal(validateManifest({ ...baseManifest(), declarativeSeams: { pollers: [''] } }).valid, false);
|
|
349
349
|
});
|
|
350
350
|
|
|
@@ -91,9 +91,9 @@ test('checkRouteRanks: an absent modules/ dir leaves the core result clean (byte
|
|
|
91
91
|
|
|
92
92
|
// ---- the grade-time trigger fires on a module route change ----
|
|
93
93
|
test('ROUTE_FILE_RE matches a module route path (a module route change triggers the rank pre-pass)', () => {
|
|
94
|
-
assert.match('modules/
|
|
94
|
+
assert.match('modules/discord/routes/discord.js', rrc.ROUTE_FILE_RE);
|
|
95
95
|
assert.match('src/bongos/routes/tasks.js', rrc.ROUTE_FILE_RE); // core still matches
|
|
96
|
-
assert.ok(!rrc.ROUTE_FILE_RE.test('modules/
|
|
96
|
+
assert.ok(!rrc.ROUTE_FILE_RE.test('modules/discord/module.json'), 'a non-route module file does not trigger the pre-pass');
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
// ---- a rank DRIFT on a pinned MODULE route reds the build (BV1.R61) ----
|
|
@@ -79,7 +79,7 @@ test('1002857: POST /modules/:key/enable rejects a non-boolean before touching t
|
|
|
79
79
|
// is why the next assertion matters more than this one.
|
|
80
80
|
const router = buildModulesRouter();
|
|
81
81
|
const res = await invoke(handlerFor(router, 'post', '/modules/:key/enable'), {
|
|
82
|
-
params: { key: '
|
|
82
|
+
params: { key: 'discord' }, query: {}, body: { enabled: 'yes-please' }, builder: { id: 1 },
|
|
83
83
|
});
|
|
84
84
|
assert.equal(res.body.error, 'invalid_body');
|
|
85
85
|
});
|
|
@@ -137,7 +137,7 @@ await t('no live signals → discord/cli both uncleared, and no retired box step
|
|
|
137
137
|
assert.equal(r.status, 200);
|
|
138
138
|
assert.equal(stepCleared(r.json, 'join_discord'), false);
|
|
139
139
|
assert.equal(stepCleared(r.json, 'cli_setup'), false);
|
|
140
|
-
assert.equal(stepCleared(r.json, 'box_connect'), undefined, 'the
|
|
140
|
+
assert.equal(stepCleared(r.json, 'box_connect'), undefined, 'the retired box_connect step is no longer on the checklist (task 1004310)');
|
|
141
141
|
});
|
|
142
142
|
|
|
143
143
|
await t('discord linked → join_discord clears from ITS OWN signal [the scootermack regression]', async () => {
|
|
@@ -139,7 +139,7 @@ test('pasting a token for instance B PRESERVES instance A — the bug this close
|
|
|
139
139
|
assert.equal(stored.token, 'B'.repeat(64));
|
|
140
140
|
|
|
141
141
|
// 3. The active pointer still follows the paste, in the same place every existing
|
|
142
|
-
// reader (skills, hooks
|
|
142
|
+
// reader (skills, hooks) looks. The fix is additive or it is a
|
|
143
143
|
// different, quieter outage.
|
|
144
144
|
const pointer = readJson(active);
|
|
145
145
|
assert.equal(pointer.api_base, base, 'the active pointer must follow the paste');
|
|
@@ -162,7 +162,7 @@ test('the exempt list: the door, the manifest, the probes, the downloads — on
|
|
|
162
162
|
'/api/gds/instance', '/api/bongos/instance', '/api/gds/healthz',
|
|
163
163
|
'/api/bongos/provisioning/tls-check', '/api/gds/provisioning/health',
|
|
164
164
|
'/healthz', '/version', '/metrics', '/__stealth',
|
|
165
|
-
'/downloads/bongos/install.sh', '/downloads/
|
|
165
|
+
'/downloads/bongos/install.sh', '/downloads/bongos/latest.json',
|
|
166
166
|
// Express routes case-insensitively and with a trailing slash — the list sees what the router sees
|
|
167
167
|
'/API/GDS/INSTANCE', '/api/gds/instance/', '/HEALTHZ', '/api/bongos/auth/',
|
|
168
168
|
]) assert.equal(isExempt(p), true, `${p} must answer a stranger`);
|