@bongos/core 1.17.1 → 1.17.3
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 +521 -281
- package/.claude/skills/blocker-review/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +2 -0
- package/.claude/skills/builder-exit/SKILL.md +2 -2
- package/.claude/skills/builder-redteam/SKILL.md +3 -3
- package/.claude/skills/builder-setup/SKILL.md +2 -2
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/dev/SKILL.md +1 -1
- package/.claude/skills/idea-triage/SKILL.md +2 -2
- package/.claude/skills/ideate/SKILL.md +1 -1
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/.claude/skills/new-project/SKILL.md +3 -0
- package/.claude/skills/paint/SKILL.md +1 -1
- package/GOVERNANCE.md +1 -1
- package/README.md +1 -1
- 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 +4 -2
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0001-stack-choice-single-vps.md +1 -1
- package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
- package/docs/adr/0019-red-team-bounty-payout-table.md +3 -3
- package/docs/adr/0020-security-model-public-stub.md +1 -1
- package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +1 -1
- package/docs/adr/0024-cloneable-repo-local-first-memory.md +1 -1
- package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +4 -4
- package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +3 -3
- package/docs/adr/0030-builders-hall-live-updates.md +1 -1
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +4 -4
- package/docs/adr/0032-discord-mirror-of-gds.md +2 -2
- package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
- package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
- package/docs/adr/0044-per-box-live-game-preview.md +1 -1
- package/docs/adr/0054-session-token-reward-cost-plus.md +10 -10
- package/docs/adr/0057-container-cost-ledger.md +3 -3
- package/docs/adr/0062-medusa-instance-model-decoupling.md +4 -4
- package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +2 -2
- package/docs/adr/0085-remove-default-per-builder-spend-cap.md +1 -1
- package/docs/adr/0086-goal-scoped-work-hierarchy.md +1 -1
- package/docs/adr/0096-require-reward-before-workable.md +9 -9
- package/docs/adr/0097-retroactive-reward-backfill.md +3 -3
- package/docs/adr/0100-two-repo-instance-model.md +1 -1
- package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +3 -3
- package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +3 -3
- package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +1 -1
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +6 -6
- package/docs/adr/0136-update-channel-subscription-policy.md +51 -0
- package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +40 -0
- package/docs/adr/0138-bundled-module-deps-declared-per-module.md +45 -0
- package/docs/adr/README.md +9 -6
- package/docs/api/openapi.json +65 -26
- package/docs/api-reference.md +3 -2
- package/docs/architecture.md +2 -2
- package/docs/branding-contract.md +5 -4
- package/docs/canonical-permissions.md +1 -1
- package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
- package/docs/file-map.md +18 -17
- package/docs/handoff-template.md +2 -2
- package/docs/modules-contract.md +6 -1
- package/docs/onboarding/diagrams/README.md +7 -7
- package/docs/onboarding/drachmae-and-karma.md +18 -18
- package/docs/onboarding/primer.md +9 -9
- package/docs/recipes/audit-replay.md +2 -2
- package/docs/recipes/builder-box-lifecycle.md +2 -2
- package/docs/recipes/claude-design-loop.md +1 -1
- package/docs/recipes/ops-gotchas.md +1 -1
- package/docs/recipes/private-npm-distribution.md +17 -0
- package/docs/recipes/staging-environment.md +1 -1
- package/docs/recipes/upgrading-the-core.md +55 -0
- package/docs/research/cursor-competitive-analysis.md +1 -1
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +1 -1
- package/modules/discord/module.json +2 -1
- package/modules/economy/achievements.js +9 -1
- package/modules/economy/credits.js +10 -1
- package/modules/hall-ui/public/builders.js +137 -620
- package/modules/hall-ui/public/goal-inbox.js +232 -0
- package/modules/hall-ui/public/goals-page.js +4 -1
- package/modules/hall-ui/public/goals.html +8 -0
- package/modules/hall-ui/public/hall-kit.js +474 -0
- package/modules/hall-ui/public/hall-widgets.js +14 -5
- package/modules/hall-ui/public/index.html +3 -0
- package/modules/hall-ui/public/instances-panel.js +24 -3
- package/modules/hall-ui/public/new-project-wizard.js +7 -5
- package/modules/hall-ui/public/style.css +168 -0
- package/modules/hall-ui/public/work.css +194 -0
- package/modules/hall-ui/public/work.html +46 -374
- package/modules/hall-ui/public/work.js +137 -102
- package/modules/lifecycle/criterion-suggest.js +80 -0
- package/modules/lifecycle/db.js +42 -5
- package/modules/lifecycle/routes/goals.js +26 -0
- package/modules/lifecycle/routes/tasks.js +21 -0
- package/modules/provisioning/CLAUDE.md +15 -0
- package/modules/provisioning/migrations/provisioning_001_tables.sql +160 -0
- package/modules/provisioning/migrations/provisioning_002_standalone_shape.sql +11 -0
- package/modules/provisioning/migrations/provisioning_003_github_app_manifest.sql +95 -0
- package/modules/provisioning/module.json +17 -0
- package/modules/provisioning/onboard-plan.js +37 -0
- package/modules/provisioning/provisioning.js +539 -0
- package/modules/provisioning/rate-limit.js +35 -0
- package/modules/provisioning/routes/provisioning.js +607 -0
- package/modules/provisioning/tests/provisioning.mjs +232 -0
- package/modules/provisioning/ui/.gitkeep +0 -0
- package/modules/security/secret-scrub.js +10 -0
- package/package-lock.json +2 -2
- package/package.json +7 -2
- package/scripts/discord/README.md +1 -1
- package/scripts/gds/claim.js +98 -48
- package/scripts/gds/claude-materialize.js +30 -9
- package/scripts/gds/cutover-codemod.js +241 -0
- package/scripts/gds/fitness.js +54 -0
- package/scripts/gds/init.js +59 -3
- package/scripts/gds/lint-infra-exec.js +70 -0
- package/scripts/gds/lint-workflow-exec.js +63 -0
- package/scripts/gds/mirror-lineage-check.js +14 -5
- package/scripts/gds/mirror-redact.js +8 -0
- package/scripts/gds/onboard.js +4 -0
- package/scripts/gds/package-core.js +18 -3
- package/scripts/gds/publish-manifest.js +60 -2
- package/scripts/gds/run-routine.js +51 -13
- package/scripts/gds/run-unit-tests.js +4 -0
- package/scripts/gds/update-channel.js +186 -0
- package/scripts/gds/upgrade.js +116 -9
- package/scripts/hall-preview/README.md +103 -0
- package/scripts/hall-preview/fixtures/access-requests.json +1 -0
- package/scripts/hall-preview/fixtures/achievements.json +76 -0
- package/scripts/hall-preview/fixtures/analytics__builder.json +251 -0
- package/scripts/hall-preview/fixtures/audit-log.json +1 -0
- package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +1 -0
- package/scripts/hall-preview/fixtures/builders__active-claims.json +1 -0
- package/scripts/hall-preview/fixtures/builders__directory.json +81 -0
- package/scripts/hall-preview/fixtures/builders__roster.json +1 -0
- package/scripts/hall-preview/fixtures/goals.json +502 -0
- package/scripts/hall-preview/fixtures/goals__25.json +96 -0
- package/scripts/hall-preview/fixtures/goals__26.json +67 -0
- package/scripts/hall-preview/fixtures/goals__3.json +103 -0
- package/scripts/hall-preview/fixtures/goals__35.json +49 -0
- package/scripts/hall-preview/fixtures/goals__conflicts.json +154 -0
- package/scripts/hall-preview/fixtures/goals__detail.json +96 -0
- package/scripts/hall-preview/fixtures/goals__inbox.json +5 -0
- package/scripts/hall-preview/fixtures/goals__requests.json +3 -0
- package/scripts/hall-preview/fixtures/grades__by-builder.json +1 -0
- package/scripts/hall-preview/fixtures/inbox.json +330 -0
- package/scripts/hall-preview/fixtures/instance.json +124 -0
- package/scripts/hall-preview/fixtures/leaderboard.json +184 -0
- package/scripts/hall-preview/fixtures/me.json +476 -0
- package/scripts/hall-preview/fixtures/me__recent-ships.json +304 -0
- package/scripts/hall-preview/fixtures/override-requests.json +1 -0
- package/scripts/hall-preview/fixtures/provisioning__fleet.json +1 -0
- package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +1 -0
- package/scripts/hall-preview/fixtures/provisioning__instances.json +1 -0
- package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +516 -0
- package/scripts/hall-preview/fixtures/public__bounty-table.json +37 -0
- package/scripts/hall-preview/fixtures/public__cost-summary.json +742 -0
- package/scripts/hall-preview/fixtures/public__estimation-drift.json +41 -0
- package/scripts/hall-preview/fixtures/public__grades.json +50 -0
- package/scripts/hall-preview/fixtures/public__progress.json +124 -0
- package/scripts/hall-preview/fixtures/public__recent-shipped.json +426 -0
- package/scripts/hall-preview/fixtures/public__repo-health.json +160 -0
- package/scripts/hall-preview/fixtures/public__repo-info.json +5 -0
- package/scripts/hall-preview/fixtures/security__docs.json +1 -0
- package/scripts/hall-preview/fixtures/security__reports.json +1 -0
- package/scripts/hall-preview/fixtures/sessions__search.json +1 -0
- package/scripts/hall-preview/fixtures/tasks.json +2420 -0
- package/scripts/hall-preview/fixtures/tasks__claimable.json +2555 -0
- package/scripts/hall-preview/fixtures/tasks__detail.json +111 -0
- package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +330 -0
- package/scripts/hall-preview/fixtures/versions.json +89 -0
- package/scripts/hall-preview/refresh-fixtures.js +77 -0
- package/scripts/hall-preview/server.js +178 -0
- package/scripts/migrate.sh +29 -4
- package/src/bongos/auth.js +14 -1
- package/src/bongos/optimizer.js +37 -2
- package/src/bongos/serve-internal.js +10 -1
- package/src/branding.js +1 -0
- package/src/module-api.js +12 -1
- package/src/module-loader/loader.js +36 -4
- package/src/module-loader/manifest-schema.js +16 -1
- package/tests/branding.mjs +11 -0
- package/tests/claim_error_surface.mjs +107 -0
- package/tests/claude_materialize.mjs +20 -0
- package/tests/consumer_layout_boot.mjs +302 -0
- package/tests/criterion_suggest.mjs +106 -0
- package/tests/cutover_codemod.mjs +184 -0
- package/tests/device_flow_start.mjs +25 -2
- package/tests/hook_core_resolve.mjs +125 -0
- package/tests/infra_exec_paths.mjs +104 -0
- package/tests/init.mjs +47 -0
- package/tests/migrate_root_resolution.mjs +97 -0
- package/tests/mirror_lineage_proof.mjs +14 -0
- package/tests/module_loader.mjs +42 -0
- package/tests/module_manifest.mjs +19 -0
- package/tests/onboard.mjs +13 -0
- package/tests/package_core.mjs +8 -0
- package/tests/provisioning_github_app_routes.mjs +15 -0
- package/tests/publish_manifest.mjs +52 -1
- package/tests/run_routine.mjs +26 -1
- package/tests/run_routine_two_root.mjs +77 -0
- package/tests/scrubber_corpus.mjs +17 -0
- package/tests/update_channel.mjs +186 -0
- package/tests/upgrade.mjs +131 -0
- package/tests/workflow_exec_paths.mjs +98 -0
- package/docs/design/container-exterior-tiles.md +0 -49
- package/docs/design/container-exterior.md +0 -81
- package/docs/design/container-interior.md +0 -77
- package/docs/design/world-map-scroll.md +0 -41
- package/docs/recipes/claude-gemini-bongos-doors.md +0 -155
- package/docs/recipes/cloudbongos-staging-gate.md +0 -139
- package/docs/recipes/cloudbongos-standup.md +0 -111
- package/docs/recipes/parallel-load-results-2026-05-30.md +0 -63
- package/docs/recipes/parallel-load-test.md +0 -32
- package/docs/recipes/pixel-art-pipeline.md +0 -411
- package/docs/recipes/staging-data.md +0 -50
- package/migrations/instance/018_pms_v3_planning_seed.sql +0 -131
- package/migrations/instance/080_gds_v4_planning_seed.sql +0 -75
- package/migrations/instance/110_medusa_planning_seed.sql +0 -70
- package/migrations/instance/120_bongos_v1_planning_seed.sql +0 -69
- package/migrations/instance/144_bongos_v1_module_system_criterion.sql +0 -26
- package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +0 -29
- package/migrations/instance/172_v2_world_planning_seed.sql +0 -163
- package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +0 -33
- package/migrations/instance/182_gdsv4_bongos_consolidation.sql +0 -132
- package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +0 -116
- package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +0 -69
- package/scripts/gds/art-stage.js +0 -152
- package/server.js +0 -212
- package/tests/art_key.mjs +0 -121
- package/tests/day_cycle.mjs +0 -197
- package/tests/snapshot_picker.mjs +0 -233
- package/tests/sprite_facing.mjs +0 -35
- package/tests/terrain_picker.mjs +0 -298
- package/tests/turn_in_place.mjs +0 -280
- package/tests/walkability.mjs +0 -188
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/hall-preview/server.js — the hall preview harness (task 2167).
|
|
3
|
+
//
|
|
4
|
+
// The dev-box sandbox previews the GAME only (/builders 404s there, idea 490),
|
|
5
|
+
// so hall-ui changes are reviewed HERE: a tiny express app that serves the real
|
|
6
|
+
// modules/hall-ui/public files through the REAL serving seams (boot-time ?v=
|
|
7
|
+
// stamping, {{token}} replacement, __BRANDING__/__MODULES__ injection, theme
|
|
8
|
+
// markup) with the /api/gds|bongos reads answered from checked-in fixtures.
|
|
9
|
+
// Boots DB-less (pg pools connect lazily). Proven recipe from tasks 1723/1742,
|
|
10
|
+
// finally checked in.
|
|
11
|
+
//
|
|
12
|
+
// node scripts/hall-preview/server.js [--port 4600] [--brand <file>]
|
|
13
|
+
// [--modules-off dev-box,provisioning] [--empty]
|
|
14
|
+
//
|
|
15
|
+
// --port listen port (default 4600)
|
|
16
|
+
// --brand branding pack file — default config/branding.neutral.json
|
|
17
|
+
// (vanilla); pass config/branding.json for the instance pack
|
|
18
|
+
// --modules-off comma list of module keys to report disabled in
|
|
19
|
+
// window.__MODULES__ (verifies moduleOn() gating)
|
|
20
|
+
// --empty serve every fixture with its arrays emptied — the
|
|
21
|
+
// empty-state screenshot pass
|
|
22
|
+
//
|
|
23
|
+
// Browse http://builders.localhost:<port>/ — Chromium resolves *.localhost,
|
|
24
|
+
// and the `builders.` host prefix exercises the real subdomain serving path.
|
|
25
|
+
// Refresh fixtures from live data: node scripts/hall-preview/refresh-fixtures.js
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
const path = require('path');
|
|
29
|
+
const fs = require('fs');
|
|
30
|
+
|
|
31
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
32
|
+
const PUB = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
33
|
+
const FIXTURES = path.join(__dirname, 'fixtures');
|
|
34
|
+
|
|
35
|
+
// ---- flags (before any src/ require: --brand must win the branding resolve) --
|
|
36
|
+
function arg(name) {
|
|
37
|
+
const i = process.argv.indexOf(name);
|
|
38
|
+
return i > -1 ? process.argv[i + 1] : null;
|
|
39
|
+
}
|
|
40
|
+
const PORT = Number(arg('--port')) || 4600;
|
|
41
|
+
const EMPTY = process.argv.includes('--empty');
|
|
42
|
+
const MODULES_OFF = String(arg('--modules-off') || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
43
|
+
// --modules-on: force-report modules as enabled — a dev box's local config
|
|
44
|
+
// often lacks prod's instance modules (provisioning, dev-box), and their
|
|
45
|
+
// widgets can't be reviewed without it (task 2169).
|
|
46
|
+
const MODULES_ON = String(arg('--modules-on') || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
47
|
+
const BRAND_FILE = arg('--brand') || 'config/branding.neutral.json';
|
|
48
|
+
process.env.GDS_BRANDING_FILE = path.resolve(ROOT, BRAND_FILE);
|
|
49
|
+
|
|
50
|
+
/* eslint-disable import/no-dynamic-require */
|
|
51
|
+
// express comes from this tree's node_modules when installed, else node's
|
|
52
|
+
// normal upward walk finds the main checkout's copy (a git worktree under
|
|
53
|
+
// .claude/worktrees/ has no node_modules of its own). The src/ seams below are
|
|
54
|
+
// ALWAYS taken from THIS tree so a worktree's edits are what gets previewed.
|
|
55
|
+
let express;
|
|
56
|
+
try { express = require(path.join(ROOT, 'node_modules', 'express')); }
|
|
57
|
+
catch (_) { express = require('express'); }
|
|
58
|
+
const { buildStampedHtml } = require(path.join(ROOT, 'src', 'asset-version.js'));
|
|
59
|
+
const { injectBrandingGlobal, mountInternalSurfaces } = require(path.join(ROOT, 'src', 'bongos', 'serve-internal.js'));
|
|
60
|
+
const { clientModules } = require(path.join(ROOT, 'src', 'modules.js'));
|
|
61
|
+
|
|
62
|
+
// ---- fixtures ---------------------------------------------------------------
|
|
63
|
+
// File naming: the API path with slashes as "__" — /me/recent-ships →
|
|
64
|
+
// me__recent-ships.json. Query strings are ignored when matching. Id-bearing
|
|
65
|
+
// paths fall back to the generic names below so one fixture serves any id.
|
|
66
|
+
const FIXTURE_FALLBACKS = [
|
|
67
|
+
[/^goals\/\d+\/conflicts$/, 'goals__conflicts'],
|
|
68
|
+
[/^goals\/\d+\/requests$/, 'goals__requests'],
|
|
69
|
+
[/^goals\/\d+$/, 'goals__detail'],
|
|
70
|
+
[/^tasks\/\d+$/, 'tasks__detail'],
|
|
71
|
+
[/^analytics\/builder\/\d+$/, 'analytics__builder'],
|
|
72
|
+
[/^builders\/\d+\/active-claims$/, 'builders__active-claims'],
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
function loadFixture(name) {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(fs.readFileSync(path.join(FIXTURES, `${name}.json`), 'utf8'));
|
|
78
|
+
} catch (_) { return undefined; }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --empty: the same pages, with nothing in them — every array drains so each
|
|
82
|
+
// page's empty states render. Scalars/strings survive (identity stays real).
|
|
83
|
+
function emptied(v) {
|
|
84
|
+
if (Array.isArray(v)) return [];
|
|
85
|
+
if (v && typeof v === 'object') {
|
|
86
|
+
const out = {};
|
|
87
|
+
for (const [k, val] of Object.entries(v)) out[k] = emptied(val);
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
return v;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function fixtureFor(apiPath) {
|
|
94
|
+
const exact = loadFixture(apiPath.replace(/\//g, '__'));
|
|
95
|
+
if (exact !== undefined) return exact;
|
|
96
|
+
for (const [re, name] of FIXTURE_FALLBACKS) {
|
|
97
|
+
if (re.test(apiPath)) {
|
|
98
|
+
const hit = loadFixture(name);
|
|
99
|
+
if (hit !== undefined) return hit;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---- app ----------------------------------------------------------------------
|
|
106
|
+
const app = express();
|
|
107
|
+
|
|
108
|
+
// The API stub — intercepts every /api/gds/*, /api/bongos/*, /api/bongos/v1/*
|
|
109
|
+
// request BEFORE mountInternalSurfaces can reach the real routes (which would
|
|
110
|
+
// touch Postgres — the harness is DB-less by design). A plain function
|
|
111
|
+
// middleware, not a path mount: regex-mount semantics differ across express
|
|
112
|
+
// majors and a silent non-match here means a pg crash.
|
|
113
|
+
app.use((req, res, next) => {
|
|
114
|
+
const m = req.path.match(/^\/api\/(?:gds|bongos)(?:\/v1)?\/(.+)$/);
|
|
115
|
+
if (!m) return next();
|
|
116
|
+
const apiPath = m[1].replace(/\/$/, '');
|
|
117
|
+
|
|
118
|
+
// SSE live channel: emit one nudge (so "live refresh" paths run once), then
|
|
119
|
+
// keep-alive comments. Pages treat this exactly like prod's stream.
|
|
120
|
+
if (apiPath === 'live') {
|
|
121
|
+
res.set({ 'content-type': 'text/event-stream', 'cache-control': 'no-cache', connection: 'keep-alive' });
|
|
122
|
+
res.flushHeaders();
|
|
123
|
+
setTimeout(() => res.write('event: nudge\ndata: {}\n\n'), 1200);
|
|
124
|
+
const tick = setInterval(() => res.write(': keep-alive\n\n'), 25000);
|
|
125
|
+
req.on('close', () => clearInterval(tick));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (req.method !== 'GET') {
|
|
130
|
+
return res.status(501).json({ error: { code: 'preview_readonly', message: 'the preview harness serves fixtures; writes are not wired' } });
|
|
131
|
+
}
|
|
132
|
+
const fix = fixtureFor(apiPath);
|
|
133
|
+
if (fix === undefined) {
|
|
134
|
+
console.log(` no fixture: ${apiPath} (add scripts/hall-preview/fixtures/${apiPath.replace(/\//g, '__')}.json)`);
|
|
135
|
+
return res.status(404).json({ error: { code: 'no_fixture', message: apiPath } });
|
|
136
|
+
}
|
|
137
|
+
res.json(EMPTY ? emptied(fix) : fix);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// The hall pages, pre-auth-gate, through the real seams: boot-time stamped
|
|
141
|
+
// (?v= content hashes), {{token}}-replaced, __BRANDING__/__MODULES__ injected,
|
|
142
|
+
// theme markup appended — byte-for-byte what prod serves after its gates.
|
|
143
|
+
const stamped = buildStampedHtml(PUB, '/builders/');
|
|
144
|
+
const mods = JSON.parse(JSON.stringify(clientModules()));
|
|
145
|
+
for (const key of MODULES_OFF) {
|
|
146
|
+
if (mods.enabled && key in mods.enabled) mods.enabled[key] = false;
|
|
147
|
+
else (mods.enabled = mods.enabled || {})[key] = false;
|
|
148
|
+
}
|
|
149
|
+
for (const key of MODULES_ON) {
|
|
150
|
+
(mods.enabled = mods.enabled || {})[key] = true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function sendPage(res, name) {
|
|
154
|
+
const html = stamped[name];
|
|
155
|
+
if (!html) return false;
|
|
156
|
+
res.type('html').send(injectBrandingGlobal(html, undefined, mods));
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
app.get('/', (req, res, next) => { if (!sendPage(res, 'index.html')) next(); });
|
|
161
|
+
app.get(/^\/task\/\d+$/, (req, res, next) => { if (!sendPage(res, 'task.html')) next(); });
|
|
162
|
+
app.get(/^\/([a-z-]+)$/, (req, res, next) => {
|
|
163
|
+
if (!sendPage(res, `${req.params[0]}.html`)) next();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Everything else — /builders/*.css|js assets, /lib/bongos-client.js, diagram
|
|
167
|
+
// PNGs — falls through to the real internal-surface mounting. Our handlers
|
|
168
|
+
// above registered first, so the hall's auth gates in there never fire for
|
|
169
|
+
// the page routes we already served.
|
|
170
|
+
mountInternalSurfaces(app);
|
|
171
|
+
|
|
172
|
+
// Loopback only — this is a pre-auth preview of hall pages with fixture data;
|
|
173
|
+
// *.localhost resolves to 127.0.0.1 so nothing else is needed.
|
|
174
|
+
app.listen(PORT, '127.0.0.1', () => {
|
|
175
|
+
console.log(`hall-preview: http://builders.localhost:${PORT}/`);
|
|
176
|
+
console.log(` brand: ${BRAND_FILE}${EMPTY ? ' (EMPTY fixtures)' : ''}${MODULES_OFF.length ? ` modules off: ${MODULES_OFF.join(',')}` : ''}`);
|
|
177
|
+
console.log(' pages:', Object.keys(stamped).map((f) => f.replace(/\.html$/, '')).sort().join(' '));
|
|
178
|
+
});
|
package/scripts/migrate.sh
CHANGED
|
@@ -12,18 +12,43 @@ set -euo pipefail
|
|
|
12
12
|
# alongside the instance, so both resolve to the SAME dir and this is byte-identical to the
|
|
13
13
|
# old `cd "$(dirname "$0")/.."`. Once the core installs from node_modules, resolveCoreRoot()
|
|
14
14
|
# points into the package (core migrations + src/) while resolveInstanceRoot() stays the host
|
|
15
|
-
# repo (env → bongos.config → cwd).
|
|
16
|
-
#
|
|
17
|
-
#
|
|
15
|
+
# repo (env → bongos.config → cwd).
|
|
16
|
+
#
|
|
17
|
+
# CORE root is resolved SCRIPT-relative (the package's own files sit next to this script), so it
|
|
18
|
+
# never depends on the caller's cwd. The INSTANCE root is the OPPOSITE: it MUST come from where
|
|
19
|
+
# migrate was INVOKED. `npm run migrate` from the instance sets INIT_CWD (and $PWD) to the
|
|
20
|
+
# instance dir, where bongos.config.json lives. Resolving the instance root script-relative would
|
|
21
|
+
# `cd` into the core PACKAGE first — whose dir has no bongos.config.json — so with no
|
|
22
|
+
# OTB_INSTANCE_ROOT env it falls back to the package dir as the "instance" and reads the core's
|
|
23
|
+
# VANILLA module config, SILENTLY skipping every host + flag-gated module migration (R89 rehearsal
|
|
24
|
+
# F6, task 2180: literal `npm run migrate` applied 115 rows and created no game schema; from the
|
|
25
|
+
# instance cwd it applied 121). So resolve the instance root from the invocation cwd, preferring an
|
|
26
|
+
# EXPLICIT pointer (OTB_INSTANCE_ROOT env, else the invocation cwd's bongos.config.json).
|
|
27
|
+
# resolveInstanceRootExplicit() returns '' (NOT process.cwd()) when neither is present, so we fall
|
|
28
|
+
# back to the script-relative DEFAULT_ROOT — the monolith case (cwd==core==instance), byte-identical
|
|
29
|
+
# to the old behavior, and a stray cwd can never masquerade as the instance. Fail open to
|
|
30
|
+
# DEFAULT_ROOT if node is unavailable so a deploy never breaks.
|
|
31
|
+
INVOKE_CWD="${INIT_CWD:-$PWD}"
|
|
18
32
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
19
33
|
DEFAULT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
20
34
|
CORE_ROOT="$(cd "$DEFAULT_ROOT" && node -e "process.stdout.write(require('./src/instance-config').resolveCoreRoot())" 2>/dev/null || echo "$DEFAULT_ROOT")"
|
|
21
|
-
INSTANCE_ROOT="$(cd "$
|
|
35
|
+
INSTANCE_ROOT="$(cd "$INVOKE_CWD" && node -e "process.stdout.write(require('$DEFAULT_ROOT/src/instance-config').resolveInstanceRootExplicit() || '')" 2>/dev/null || true)"
|
|
36
|
+
INSTANCE_ROOT="${INSTANCE_ROOT:-$DEFAULT_ROOT}"
|
|
22
37
|
# Run from the instance root so instance-relative paths resolve (the post-deploy art check below)
|
|
23
38
|
# AND so the enabledModules() read finds the instance's config/modules.json (resolveInstanceRoot
|
|
24
39
|
# falls back to cwd).
|
|
25
40
|
cd "$INSTANCE_ROOT"
|
|
26
41
|
|
|
42
|
+
# Test/ops seam (task 2180): print the resolved roots and exit BEFORE any DB work, so a DB-free
|
|
43
|
+
# unit test (tests/migrate_root_resolution.mjs) can assert the INSTANCE root resolves from the
|
|
44
|
+
# invocation cwd (a consumer's bongos.config.json), not script-relative into the core package —
|
|
45
|
+
# the F6 regression. Also handy in the field: `npm run migrate -- --print-roots` shows what
|
|
46
|
+
# migrate sees.
|
|
47
|
+
if [ "${1:-}" = "--print-roots" ]; then
|
|
48
|
+
printf 'INVOKE_CWD=%s\nCORE_ROOT=%s\nINSTANCE_ROOT=%s\n' "$INVOKE_CWD" "$CORE_ROOT" "$INSTANCE_ROOT"
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
27
52
|
DB="${PGDATABASE:-amazonprimea}"
|
|
28
53
|
|
|
29
54
|
# --- Instance-seed policy (task 1704 / ADR 0105) --------------------------
|
package/src/bongos/auth.js
CHANGED
|
@@ -230,7 +230,20 @@ async function startDeviceFlow() {
|
|
|
230
230
|
// device_code silently produced a broken flow / an opaque 500 (the 2026-07-08 demo
|
|
231
231
|
// login failure). Mirror the token-exchange guard (data.error, above); task 2126.
|
|
232
232
|
if (data.error) {
|
|
233
|
-
|
|
233
|
+
// device_flow_disabled is self-inflicted + common: GitHub defaults Device Flow OFF on new
|
|
234
|
+
// Apps and the one-click manifest can't enable it (task 2129). Make the CLI error self-
|
|
235
|
+
// service by naming the right settings surface. We hold only client_id here (not the app
|
|
236
|
+
// slug — that lives on the control plane), so we can't build /settings/apps/<slug>; but the
|
|
237
|
+
// id PREFIX distinguishes a GitHub App (Iv*) from an OAuth App (Ov*), which picks the right
|
|
238
|
+
// listing to open.
|
|
239
|
+
let hint = '';
|
|
240
|
+
if (data.error === 'device_flow_disabled') {
|
|
241
|
+
const where = /^Iv/i.test(creds.client_id || '')
|
|
242
|
+
? 'https://github.com/settings/apps (org-owned apps: https://github.com/organizations/<org>/settings/apps)'
|
|
243
|
+
: 'https://github.com/settings/developers';
|
|
244
|
+
hint = ` — terminal sign-in needs Device Flow enabled on the app: open ${where}, open your app, check "Enable Device Flow", and save. Web sign-in is unaffected.`;
|
|
245
|
+
}
|
|
246
|
+
throw new Error(`GitHub rejected device-flow start: ${data.error}${data.error_description ? ` — ${data.error_description}` : ''}${hint}`);
|
|
234
247
|
}
|
|
235
248
|
if (!data.device_code) {
|
|
236
249
|
throw new Error('GitHub device/code returned no device_code (unexpected response shape).');
|
package/src/bongos/optimizer.js
CHANGED
|
@@ -53,10 +53,45 @@ async function optimizeSession({ builderId, mode, timeBudgetMin, maxParallel })
|
|
|
53
53
|
// tasks_session_fit (derived fits_session_modes[] + credits_per_minute).
|
|
54
54
|
// Also exclude tasks this builder already has an active claim on, mirroring
|
|
55
55
|
// listClaimableTasks in db.js.
|
|
56
|
+
// task 2181 (incident, blocker 74): the view bodies (migration core_190) are
|
|
57
|
+
// inlined so this query takes only ACCESS SHARE on base tables — a detached
|
|
58
|
+
// backend queued an ACCESS EXCLUSIVE DDL on the views and parked every view
|
|
59
|
+
// SELECT behind it. Plan-identical (Postgres inlines simple views); change in
|
|
60
|
+
// lockstep with the views / listClaimableTasks if a migration alters them.
|
|
56
61
|
const { rows } = await pool.query(
|
|
57
62
|
`SELECT ct.*, sf.fits_session_modes, sf.credits_per_minute
|
|
58
|
-
FROM
|
|
59
|
-
|
|
63
|
+
FROM (
|
|
64
|
+
SELECT t.*, pms_calibrated_minutes(t.est_minutes, t.kind) AS est_minutes_calibrated
|
|
65
|
+
FROM tasks t
|
|
66
|
+
WHERE t.status = 'ready'
|
|
67
|
+
AND t.title NOT LIKE '\\_\\_smoke\\_\\_%' ESCAPE '\\'
|
|
68
|
+
AND NOT EXISTS (SELECT 1 FROM task_blockers tb WHERE tb.task_id = t.id)
|
|
69
|
+
AND NOT EXISTS (
|
|
70
|
+
SELECT 1 FROM claims c
|
|
71
|
+
JOIN tasks t2 ON t2.id = c.task_id
|
|
72
|
+
WHERE c.released_at IS NULL
|
|
73
|
+
AND t2.touches && t.touches
|
|
74
|
+
)
|
|
75
|
+
) ct
|
|
76
|
+
JOIN (
|
|
77
|
+
SELECT t.id,
|
|
78
|
+
CASE
|
|
79
|
+
WHEN t.automation_tag = 'overnight-safe' AND COALESCE(t.manual_degree, 5) <= 2
|
|
80
|
+
AND NOT EXISTS (SELECT 1 FROM task_blockers tb WHERE tb.task_id = t.id)
|
|
81
|
+
THEN ARRAY['autonomous','check-in','active']
|
|
82
|
+
WHEN t.automation_tag = 'live-only' OR COALESCE(t.manual_degree, 5) >= 4
|
|
83
|
+
THEN ARRAY['active']
|
|
84
|
+
ELSE ARRAY['check-in','active']
|
|
85
|
+
END AS fits_session_modes,
|
|
86
|
+
CASE
|
|
87
|
+
WHEN t.credits_reward > 0
|
|
88
|
+
AND pms_calibrated_minutes(t.est_minutes, t.kind) IS NOT NULL
|
|
89
|
+
AND pms_calibrated_minutes(t.est_minutes, t.kind) > 0
|
|
90
|
+
THEN ROUND(t.credits_reward::numeric / pms_calibrated_minutes(t.est_minutes, t.kind), 3)
|
|
91
|
+
ELSE NULL
|
|
92
|
+
END AS credits_per_minute
|
|
93
|
+
FROM tasks t
|
|
94
|
+
) sf ON sf.id = ct.id
|
|
60
95
|
WHERE $1 = ANY(sf.fits_session_modes)
|
|
61
96
|
AND NOT EXISTS (
|
|
62
97
|
SELECT 1 FROM claims c
|
|
@@ -318,7 +318,16 @@ function mountInternalSurfaces(app) {
|
|
|
318
318
|
// confirmation can commit-PIN (deployedCommit === mergeCommit) rather than only
|
|
319
319
|
// checking liveness via /healthz. Low-sensitivity (a public commit sha).
|
|
320
320
|
app.get('/version', (_req, res) => {
|
|
321
|
-
|
|
321
|
+
// coreVersion — the @bongos/core doorway version this process is running
|
|
322
|
+
// (src/module-api CORE_VERSION), read lazily to avoid a load-time cycle
|
|
323
|
+
// (module-api requires build-info at load, so a top-level require here
|
|
324
|
+
// would be circular). Additive: lets fleet/upgrade tooling confirm which
|
|
325
|
+
// core an instance actually materialized after a `bongos upgrade`,
|
|
326
|
+
// alongside the deployed git commit below. (Added in 1.17.2, task 2148 —
|
|
327
|
+
// the throwaway `updateCanary` proof marker was removed in phase E.)
|
|
328
|
+
let coreVersion = null;
|
|
329
|
+
try { coreVersion = require('../module-api').CORE_VERSION; } catch { /* fail-soft: version unknown */ }
|
|
330
|
+
res.json({ ...buildInfo(), coreVersion });
|
|
322
331
|
});
|
|
323
332
|
|
|
324
333
|
// Generated typed client for classic <script> web pages (task 1996 / R09).
|
package/src/branding.js
CHANGED
|
@@ -42,6 +42,7 @@ const ENV_OVERRIDES = [
|
|
|
42
42
|
['BUILDERS_ORIGIN', ['domains', 'buildersOrigin']],
|
|
43
43
|
['STATUS_ORIGIN', ['domains', 'statusOrigin']],
|
|
44
44
|
['OAUTH_ORIGIN', ['domains', 'oauthOrigin']],
|
|
45
|
+
['PROVISIONING_ORIGIN', ['domains', 'provisioningOrigin']],
|
|
45
46
|
['COOKIE_DOMAIN', ['domains', 'cookieDomain']],
|
|
46
47
|
];
|
|
47
48
|
|
package/src/module-api.js
CHANGED
|
@@ -188,8 +188,19 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
188
188
|
// REQUIRED because 1.17.0 was already published to npm and task 2121 shipped
|
|
189
189
|
// without one, so `bongos package-core --publish` on 1.17.0 would be rejected as a
|
|
190
190
|
// re-publish. Every `^1.x` / `^1.17` module pin is satisfied by 1.17.1.
|
|
191
|
+
// 1.17.2 — task 2148 (canary upgrade-proof on demo.cloudbongos.com): a controlled
|
|
192
|
+
// PATCH bump used to round-trip the real `bongos upgrade` channel on the live
|
|
193
|
+
// demo instance with an OBSERVABLE code marker (the channel shipped in 1.16.0
|
|
194
|
+
// and was proven on emersonian-circles by task 1976, but never on demo with a
|
|
195
|
+
// served marker). No doorway export is added, changed, or removed — the exported
|
|
196
|
+
// surface is byte-identical to 1.17.1. What changed: the served /version payload
|
|
197
|
+
// now carries `coreVersion` (the running doorway version — a PERMANENT additive
|
|
198
|
+
// read so upgrade/fleet tooling can confirm which core an instance materialized),
|
|
199
|
+
// plus a THROWAWAY `updateCanary` marker removed in the phase-E follow-up. A
|
|
200
|
+
// backwards-compatible additive change with no API removal is exactly semver
|
|
201
|
+
// PATCH. Every `^1.x` / `^1.17` module pin is satisfied by 1.17.2.
|
|
191
202
|
// ---------------------------------------------------------------------------
|
|
192
|
-
const CORE_VERSION = '1.17.
|
|
203
|
+
const CORE_VERSION = '1.17.2'; // see the changelog above — 1.17.2 is the task-2148 canary upgrade-proof bump (adds /version coreVersion; no doorway export change)
|
|
193
204
|
|
|
194
205
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
195
206
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -19,11 +19,42 @@ const fs = require('node:fs');
|
|
|
19
19
|
const path = require('node:path');
|
|
20
20
|
const { validateManifest } = require('./manifest-schema');
|
|
21
21
|
const { satisfies } = require('./semver');
|
|
22
|
-
const { resolveCoreRoot } = require('../instance-config');
|
|
22
|
+
const { resolveCoreRoot, resolveInstanceRoot } = require('../instance-config');
|
|
23
23
|
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
24
|
+
// Collapse roots that resolve to the SAME absolute path, preserving order.
|
|
25
|
+
// Normalizes via path.resolve so a cwd that arrives with a trailing '.'/'//'
|
|
26
|
+
// segment still dedupes against the core root. Returns the resolved (canonical)
|
|
27
|
+
// paths. (Exported for the loader tests.)
|
|
28
|
+
function dedupeRoots(roots) {
|
|
29
|
+
const out = [];
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
for (const r of roots) {
|
|
32
|
+
const key = path.resolve(r);
|
|
33
|
+
if (seen.has(key)) continue;
|
|
34
|
+
seen.add(key);
|
|
35
|
+
out.push(key);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ADR 0108 §1 + task 2133 (R89-prereq): discovery must span TWO roots — the core
|
|
41
|
+
// PACKAGE's own modules/ (resolveCoreRoot()) AND the consumer/instance's own
|
|
42
|
+
// modules/ (resolveInstanceRoot()). Post-split the host modules (game /
|
|
43
|
+
// art-pipeline / provisioning) live at the instance root and are EXCLUDED from
|
|
44
|
+
// the @bongos/core package, so a core-only scan boots a split instance with none
|
|
45
|
+
// of its host modules.
|
|
46
|
+
//
|
|
47
|
+
// DEDUP BY RESOLVED PATH IS MANDATORY. In a single checkout resolveInstanceRoot()
|
|
48
|
+
// falls back to process.cwd(), which equals resolveCoreRoot() (the repo root), so
|
|
49
|
+
// the two candidates are the SAME directory. discoverModules() treats a repeated
|
|
50
|
+
// module KEY as a hard ERROR (not a skip), so scanning one root twice would turn
|
|
51
|
+
// every module into a "duplicate module key" error. dedupeRoots() collapses the
|
|
52
|
+
// coincident roots, so a single checkout scans ONE root (byte-identical to
|
|
53
|
+
// before) while a diverged split scans BOTH.
|
|
54
|
+
const DEFAULT_ROOTS = dedupeRoots([
|
|
55
|
+
path.join(resolveCoreRoot(), 'modules'),
|
|
56
|
+
path.join(resolveInstanceRoot(), 'modules'),
|
|
57
|
+
]);
|
|
27
58
|
|
|
28
59
|
// Lazy — avoids an eval-time require cycle (modules.js → loader → module-api →
|
|
29
60
|
// auth → …). By call time the graph is loaded; falls back to the known initial
|
|
@@ -263,6 +294,7 @@ module.exports = {
|
|
|
263
294
|
stopModulePollers,
|
|
264
295
|
loaderSnapshot,
|
|
265
296
|
coreVersion,
|
|
297
|
+
dedupeRoots,
|
|
266
298
|
DEFAULT_ROOTS,
|
|
267
299
|
_reset,
|
|
268
300
|
};
|
|
@@ -30,7 +30,11 @@
|
|
|
30
30
|
// },
|
|
31
31
|
// "provides": ["reward"], // OPTIONAL. seam PORTS this module registers a provider for.
|
|
32
32
|
// "consumes": ["grade"], // OPTIONAL. seam ports this module resolves (required capabilities).
|
|
33
|
-
// "prerequisites": { "modules": ["economy"] } // OPTIONAL. other modules that must be enabled.
|
|
33
|
+
// "prerequisites": { "modules": ["economy"] }, // OPTIONAL. other modules that must be enabled.
|
|
34
|
+
// "dependencies": { "discord.js": "^14.26.4" } // OPTIONAL. external npm deps this module needs at
|
|
35
|
+
// // runtime. Core stays lean (express+pg, ADR 0108); a bundled
|
|
36
|
+
// // module that needs more declares it HERE, and the consumer
|
|
37
|
+
// // composes the deps of the modules it ENABLES (ADR 0138).
|
|
34
38
|
// }
|
|
35
39
|
//
|
|
36
40
|
// validateManifest(obj) -> { valid, errors[], manifest } — pure, no I/O.
|
|
@@ -126,6 +130,17 @@ function validateManifest(obj) {
|
|
|
126
130
|
}
|
|
127
131
|
}
|
|
128
132
|
|
|
133
|
+
// --- optional runtime npm dependencies (ADR 0138 / task 2160) ---
|
|
134
|
+
// A bundled module's own external npm deps. @bongos/core stays lean (express+pg, ADR 0108);
|
|
135
|
+
// a module that needs more (e.g. discord.js) declares it here, and the consumer composes the
|
|
136
|
+
// deps of the modules it ENABLES (scripts/gds/init.js). Shape: { "<npm-name>": "<semver-range>" }.
|
|
137
|
+
if ('dependencies' in obj) {
|
|
138
|
+
const d = obj.dependencies;
|
|
139
|
+
if (!isObj(d) || !Object.entries(d).every(([k, v]) => isStr(k) && isStr(v))) {
|
|
140
|
+
E('dependencies: must be an object of { "<npm-name>": "<semver-range>" } string pairs');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
129
144
|
return { valid: errors.length === 0, errors, manifest: errors.length === 0 ? obj : null };
|
|
130
145
|
}
|
|
131
146
|
|
package/tests/branding.mjs
CHANGED
|
@@ -52,6 +52,17 @@ test('env override is ignored when empty', () => {
|
|
|
52
52
|
assert.equal(b.domains.publicOrigin, 'https://amazonprimea.com');
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
+
test('provisioningOrigin: optional, passes through config, env-overridable (task 2101)', () => {
|
|
56
|
+
// Not required — unset resolves to undefined, and callers fall back to publicOrigin.
|
|
57
|
+
assert.equal(resolveBranding({ neutral: NEUTRAL, instance: OTB }).domains.provisioningOrigin, undefined);
|
|
58
|
+
// Set in the instance pack, it survives the deep-merge.
|
|
59
|
+
const inst = resolveBranding({ neutral: NEUTRAL, instance: { ...OTB, domains: { ...OTB.domains, provisioningOrigin: 'https://get.cloudbongos.com' } } });
|
|
60
|
+
assert.equal(inst.domains.provisioningOrigin, 'https://get.cloudbongos.com');
|
|
61
|
+
// And <PREFIX>_PROVISIONING_ORIGIN beats config (same precedence as the other origins).
|
|
62
|
+
const env = resolveBranding({ neutral: NEUTRAL, instance: OTB, env: { OTB_PROVISIONING_ORIGIN: 'https://get.cloudbongos.com' } });
|
|
63
|
+
assert.equal(env.domains.provisioningOrigin, 'https://get.cloudbongos.com');
|
|
64
|
+
});
|
|
65
|
+
|
|
55
66
|
test('missing required field throws', () => {
|
|
56
67
|
assert.throws(() => resolveBranding({ neutral: { identity: { productName: 'X' } } }), /missing required field/);
|
|
57
68
|
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// tests/claim_error_surface.mjs
|
|
2
|
+
//
|
|
3
|
+
// task 1476 — a failed /builder-claim must read as GUIDANCE, not a raw object
|
|
4
|
+
// dump. The API returns the R01 error envelope (ADR 0116):
|
|
5
|
+
// { error: { code, message, details } }
|
|
6
|
+
// so claim.js's old `r.data.error === 'CODE'` string branches all silently
|
|
7
|
+
// missed (error is now an object) and the refusal printed as `[object Object]`.
|
|
8
|
+
// This exercises the pure formatClaimFailure() surface DB-free, over BOTH the
|
|
9
|
+
// current envelope shape and the legacy flat shape.
|
|
10
|
+
//
|
|
11
|
+
// Run: node tests/claim_error_surface.mjs
|
|
12
|
+
|
|
13
|
+
import { strict as assert } from 'node:assert';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const { formatClaimFailure } = require('../scripts/gds/claim.js');
|
|
18
|
+
|
|
19
|
+
let passed = 0;
|
|
20
|
+
let failed = 0;
|
|
21
|
+
function t(name, fn) {
|
|
22
|
+
try { fn(); passed++; console.log(` PASS ${name}`); }
|
|
23
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const envelope = (code, details = {}, message = code, status = 409) => ({
|
|
27
|
+
status,
|
|
28
|
+
data: { error: { code, message, details } },
|
|
29
|
+
});
|
|
30
|
+
const text = (r) => r.lines.join('\n');
|
|
31
|
+
|
|
32
|
+
// The regression guard: no refusal may ever render a raw object.
|
|
33
|
+
t('never dumps [object Object] for any known code', () => {
|
|
34
|
+
const codes = [
|
|
35
|
+
'stage_required', 'session_already_has_active_claim', 'session_claim_check_failed',
|
|
36
|
+
'newcomer_must_ship_first', 'ALREADY_CLAIMED', 'MODULE_CONFLICT',
|
|
37
|
+
'TOUCHES_CONFLICT', 'TASK_NOT_READY', 'TASK_NOT_FOUND', 'DEPS_NOT_SHIPPED',
|
|
38
|
+
'some_unmapped_future_code',
|
|
39
|
+
];
|
|
40
|
+
for (const c of codes) {
|
|
41
|
+
const out = text(formatClaimFailure(envelope(c, { active_task_id: 5 }, 'msg'), 12));
|
|
42
|
+
assert.ok(!out.includes('[object Object]'), `${c} produced a raw object dump`);
|
|
43
|
+
assert.ok(out.trim().length > 0, `${c} produced no output`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// The core 1476 case: one-claim-per-session refusal is guidance + teaches parallel.
|
|
48
|
+
t('session_already_has_active_claim: names the in-flight task and teaches parallelism', () => {
|
|
49
|
+
const out = text(formatClaimFailure(
|
|
50
|
+
envelope('session_already_has_active_claim', {
|
|
51
|
+
active_task_id: 42, active_task_title: 'Foo', what_to_do: 'ship 42 first',
|
|
52
|
+
}), 99));
|
|
53
|
+
assert.match(out, /#42/, 'should name the in-flight task id');
|
|
54
|
+
assert.match(out, /Foo/, 'should name the in-flight task title');
|
|
55
|
+
assert.match(out, /ship 42 first/, 'should relay the server what_to_do');
|
|
56
|
+
assert.match(out, /PARALLEL/i, 'should teach how to parallelize');
|
|
57
|
+
assert.match(out, /worktree|bulk-claim/i, 'should point at separate worktree or bulk-claim');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
t('session_claim_check_failed shares the same guidance branch', () => {
|
|
61
|
+
const out = text(formatClaimFailure(envelope('session_claim_check_failed', {}), 3));
|
|
62
|
+
assert.match(out, /active claim/i);
|
|
63
|
+
assert.match(out, /PARALLEL/i);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Envelope + legacy flat shape must BOTH resolve the code + fields.
|
|
67
|
+
t('TASK_NOT_READY: envelope shape reads details.currentStatus + hint', () => {
|
|
68
|
+
const out = text(formatClaimFailure(
|
|
69
|
+
envelope('TASK_NOT_READY', { currentStatus: 'backlog', hint: 'promote it first' }), 7));
|
|
70
|
+
assert.match(out, /not ready/i);
|
|
71
|
+
assert.match(out, /backlog/);
|
|
72
|
+
assert.match(out, /promote it first/);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
t('TASK_NOT_READY: legacy flat shape still resolves', () => {
|
|
76
|
+
const flat = { status: 409, data: { error: 'TASK_NOT_READY', currentStatus: 'blocked' } };
|
|
77
|
+
const out = text(formatClaimFailure(flat, 7));
|
|
78
|
+
assert.match(out, /not ready/i);
|
|
79
|
+
assert.match(out, /blocked/);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
t('DEPS_NOT_SHIPPED: lists each blocking dependency', () => {
|
|
83
|
+
const out = text(formatClaimFailure(
|
|
84
|
+
envelope('DEPS_NOT_SHIPPED', { deps: [{ id: 10, status: 'ready', title: 'Dep A' }] }), 8));
|
|
85
|
+
assert.match(out, /#10/);
|
|
86
|
+
assert.match(out, /Dep A/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
t('MODULE_CONFLICT + TOUCHES_CONFLICT: list conflicting task titles', () => {
|
|
90
|
+
const mc = text(formatClaimFailure(
|
|
91
|
+
envelope('MODULE_CONFLICT', { module_key: 'economy', conflictTaskTitles: ['Bar'] }), 4));
|
|
92
|
+
assert.match(mc, /economy/);
|
|
93
|
+
assert.match(mc, /Bar/);
|
|
94
|
+
const tc = text(formatClaimFailure(
|
|
95
|
+
envelope('TOUCHES_CONFLICT', { conflictTaskTitles: ['Baz'] }), 4));
|
|
96
|
+
assert.match(tc, /Baz/);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
t('unknown code: fallback carries code + message, exit 1', () => {
|
|
100
|
+
const r = formatClaimFailure(envelope('weird_thing', {}, 'boom', 500), 1);
|
|
101
|
+
assert.equal(r.exit, 1);
|
|
102
|
+
assert.match(text(r), /weird_thing/);
|
|
103
|
+
assert.match(text(r), /boom/);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
console.log(`\nclaim_error_surface: ${passed} passed, ${failed} failed`);
|
|
107
|
+
process.exit(failed === 0 ? 0 : 1);
|
|
@@ -108,6 +108,26 @@ t('materializeClaude: copies + rewrites skills into a fresh instance dir (no lef
|
|
|
108
108
|
rmSync(dir, { recursive: true, force: true });
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
t('materializeClaude: materializes scheduled-tasks — bodies→bongos, companions keep _core-resolve (task 2138)', () => {
|
|
112
|
+
const dir = mkdtempSync(join(tmpdir(), 'w3-rt-'));
|
|
113
|
+
const res = m.materializeClaude({ coreRoot: ROOT, instanceDir: dir, dryRun: false });
|
|
114
|
+
assert.ok(res.scheduledTasks > 0, 'at least one scheduled-task file materialized');
|
|
115
|
+
const rt = join(dir, '.claude', 'scheduled-tasks');
|
|
116
|
+
assert.ok(existsSync(rt), '.claude/scheduled-tasks created in the instance');
|
|
117
|
+
// A prose/judgment body's core invocation is bongosified so a consumer-layout model can run it.
|
|
118
|
+
const stale = readFileSync(join(rt, 'stale-claim-release', 'SKILL.md'), 'utf8');
|
|
119
|
+
assert.ok(stale.includes('bongos exec scripts/gds/pulse.js'), 'body core-invocation rewritten to bongos exec');
|
|
120
|
+
assert.ok(!stale.includes('node scripts/gds/pulse.js'), 'no bare relative node scripts/gds left in the body');
|
|
121
|
+
// A deterministic routine's `.claude/...` companion-step command is NOT a core invocation → preserved.
|
|
122
|
+
const dd = readFileSync(join(rt, 'diagram-drift', 'SKILL.md'), 'utf8');
|
|
123
|
+
assert.ok(dd.includes('node .claude/scheduled-tasks/diagram-drift/check.js'), 'companion-path step command preserved');
|
|
124
|
+
// Companion .js resolves its core require()s via the materialized _core-resolve.js sibling (task 2139 pattern).
|
|
125
|
+
const check = readFileSync(join(rt, 'diagram-drift', 'check.js'), 'utf8');
|
|
126
|
+
assert.ok(check.includes("require('../../hooks/_core-resolve.js')"), 'companion resolves core via _core-resolve');
|
|
127
|
+
assert.ok(!check.includes("require('../../../scripts/gds/"), 'no bare ../../../scripts/gds require left in the companion');
|
|
128
|
+
rmSync(dir, { recursive: true, force: true });
|
|
129
|
+
});
|
|
130
|
+
|
|
111
131
|
t('materializeClaude: SELF-COPY guard — target === core root is a no-op (byte-identical for the live instance)', () => {
|
|
112
132
|
const res = m.materializeClaude({ coreRoot: ROOT, instanceDir: ROOT, dryRun: false });
|
|
113
133
|
assert.equal(res.skipped, true, 'materializing onto the core root must skip');
|