@bongos/core 1.19.725 → 1.19.727
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 +179 -494
- package/.devcontainer/Dockerfile +1 -1
- package/.devcontainer/devcontainer.json +2 -2
- package/.devcontainer/healthcheck.sh +1 -1
- package/.devcontainer/init-firewall.sh +1 -1
- package/.devcontainer/post-create.sh +2 -2
- package/.env.local.example +1 -1
- package/Dockerfile +1 -1
- package/docs/copy-inventory.md +1 -1
- package/docs/copy-registry.json +9 -9
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/devbox-app-release.md +4 -4
- package/modules/dev-box/app/scripts/dmgbuild-settings.py +5 -5
- package/modules/dev-box/app/scripts/gen-brand.js +1 -1
- package/modules/dev-box/app/src/brand.js +1 -1
- package/modules/dev-box/app/test/unit.mjs +16 -16
- package/modules/dev-box/box-access.js +1 -1
- package/modules/dev-box/routes/box.js +1 -1
- package/modules/discord/discord-approvals.js +1 -1
- package/modules/grading/grader-model.js +1 -1
- package/modules/grading/grader-prompt.js +1 -1
- package/modules/grading/grader-workers/quality.js +1 -1
- package/modules/hall-ui/module.json +1 -1
- package/modules/hall-ui/public/brand-apply.js +1 -1
- package/modules/hall-ui/public/shell.js +1 -1
- package/modules/hall-ui/public/work.js +1 -1
- package/modules/lifecycle/conflict-resolve.js +1 -1
- package/modules/lifecycle/db-analytics.js +1 -1
- package/modules/lifecycle/routes/analytics.js +1 -1
- package/modules/onboarding/approval-broadcast.js +1 -1
- package/modules/onboarding/onboarding.js +1 -1
- package/modules/provisioning/tests/liveness.mjs +1 -1
- package/modules/public-landing/public/index.html +1 -1
- package/modules/public-landing/public/projects-floor.states.json +2 -2
- package/modules/public-landing/public/projects.probes.json +4 -4
- package/modules/public-landing/public/projects.states.json +2 -2
- package/modules/status-ui/module.json +1 -1
- package/modules/status-ui/public/status.js +4 -4
- package/modules/status-ui/public/style.css +1 -1
- package/modules/ui-design/kit/fixtures/cost-summary.json +1 -1
- package/modules/ui-design/kit/fixtures/featured-hub.json +1 -1
- package/modules/ui-design/kit/fixtures/featured.json +1 -1
- package/modules/ui-design/kit/fixtures/leaderboard.json +1 -1
- package/modules/ui-design/kit/fixtures/my-projects.json +1 -1
- package/modules/ui-design/kit/fixtures/progress.json +1 -1
- package/modules/ui-design/kit/fixtures/recent-shipped.json +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/cron/style_guide_audit.sh +1 -1
- package/scripts/discord/lib.js +1 -1
- package/scripts/discord/post.js +1 -1
- package/scripts/gds/audit-authorship.sh +1 -1
- package/scripts/gds/audit-replay.sh +29 -2
- package/scripts/gds/audit-ships.js +1 -1
- package/scripts/gds/backfill-sessions.js +2 -2
- package/scripts/gds/cf-tunnel.js +4 -4
- package/scripts/gds/claude-materialize.js +2 -2
- package/scripts/gds/cli-lib.js +6 -6
- package/scripts/gds/cutover-codemod.js +1 -1
- package/scripts/gds/dev-lib.js +2 -2
- package/scripts/gds/diagnose-achievements.js +2 -2
- package/scripts/gds/fitness-checks-identity.js +6 -4
- package/scripts/gds/gate-review.js +14 -6
- package/scripts/gds/gen-diagrams.js +1 -1
- package/scripts/gds/leak-scan-allowlist.js +293 -0
- package/scripts/gds/learning-capture.js +1 -1
- package/scripts/gds/linkify-refs.js +2 -2
- package/scripts/gds/local-preview-lib.js +1 -1
- package/scripts/gds/mirror-lineage-check.js +1 -1
- package/scripts/gds/mirror-redact.js +17 -2
- package/scripts/gds/newcomer-floor.templates.json +1 -1
- package/scripts/gds/package-core.js +18 -1
- package/scripts/gds/paste-token.js +1 -1
- package/scripts/gds/provision-units.js +2 -2
- package/scripts/gds/publish-manifest.js +15 -0
- package/scripts/gds/repo-metrics.js +1 -1
- package/scripts/gds/sandbox-stage.js +1 -1
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +1 -1
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +1 -1
- package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +1 -1
- package/scripts/gds/seed-bongos-tasks.js +2 -2
- package/scripts/gds/seed-gds-v4-tasks.js +1 -1
- package/scripts/gds/seed-medusa-tasks.js +10 -10
- package/scripts/gds/seed-module-upstreaming.js +1 -1
- package/scripts/gds/seed-productization-tasks.js +6 -6
- package/scripts/gds/seed-provisioning-tasks.js +2 -2
- package/scripts/gds/seed-staging.sh +19 -6
- package/scripts/gds/ship-deploy-target.js +1 -1
- package/scripts/gds/ship-flow.js +1 -1
- package/scripts/gds/smoke-gds.js +1 -1
- package/scripts/status-probe.mjs +2 -2
- package/src/bongos/auth-discord.js +2 -2
- package/src/bongos/live-channel.js +1 -1
- package/src/bongos/permission-path-check.js +80 -3
- package/src/bongos/pool.js +1 -1
- package/src/bongos/routes/auth.js +11 -11
- package/src/bongos/routes/security.js +1 -1
- package/src/bongos/routes.js +1 -1
- package/src/branding.js +1 -1
- package/src/devbox-downloads.js +1 -1
- package/src/instance-config.js +2 -2
- package/src/module-api.js +1 -1
- package/src/module-loader/provenance.js +2 -2
- package/tests/art_stage.mjs +2 -2
- package/tests/auth_anthropic_identity.mjs +1 -1
- package/tests/auth_duplicate_cookie.mjs +1 -1
- package/tests/auth_error_brand.mjs +1 -1
- package/tests/auth_inactive_builder.mjs +1 -1
- package/tests/backup_cli.mjs +4 -4
- package/tests/bongos_box_connect.mjs +2 -2
- package/tests/bongos_downloads.mjs +2 -2
- package/tests/bongos_login.mjs +5 -5
- package/tests/box_connect_e2e.mjs +2 -2
- package/tests/box_dns_repoint.mjs +10 -10
- package/tests/box_host_keys.mjs +1 -1
- package/tests/box_onboard.mjs +9 -9
- package/tests/box_scope_session.mjs +1 -1
- package/tests/box_terminal_ssrf.mjs +7 -7
- package/tests/boxes.mjs +9 -9
- package/tests/branding.mjs +2 -2
- package/tests/bump_version.mjs +2 -2
- package/tests/cf_tunnel.mjs +40 -40
- package/tests/cli_token_reissue.mjs +7 -7
- package/tests/cloud_bongos_pack.mjs +1 -1
- package/tests/currency_label.mjs +8 -8
- package/tests/dev_lib.mjs +4 -4
- package/tests/devbox_dmg_backdrop.mjs +1 -1
- package/tests/devbox_downloads.mjs +2 -2
- package/tests/devbox_icons.mjs +1 -1
- package/tests/discord_approvals.mjs +1 -1
- package/tests/discord_oauth.mjs +2 -2
- package/tests/executed_closure.mjs +6 -1
- package/tests/first_admin_bootstrap.mjs +1 -1
- package/tests/gate_approvals.mjs +5 -5
- package/tests/government_protected_surfaces.mjs +137 -0
- package/tests/grade_aggregates_unavailable.mjs +1 -1
- package/tests/hook_user_agents.mjs +2 -2
- package/tests/init.mjs +6 -6
- package/tests/instance_config.mjs +1 -1
- package/tests/instance_manifest.mjs +5 -5
- package/tests/instance_mismatch_guard.mjs +13 -13
- package/tests/leak_scan_allowlist.mjs +94 -0
- package/tests/migrate_root_resolution.mjs +4 -4
- package/tests/mirror_redact.mjs +28 -4
- package/tests/module_catalog.mjs +5 -5
- package/tests/module_submissions.mjs +3 -3
- package/tests/package_core.mjs +1 -1
- package/tests/parallel-load.mjs +6 -6
- package/tests/projects_hub.mjs +2 -2
- package/tests/provision.mjs +50 -50
- package/tests/provisioning_github_app_routes.mjs +1 -1
- package/tests/provisioning_onboard_mode.mjs +2 -2
- package/tests/publish_reconciler.mjs +4 -4
- package/tests/sandbox_stage.mjs +1 -1
- package/tests/scrubber_corpus.mjs +3 -3
- package/tests/search_isolation.mjs +1 -1
- package/tests/session_records.mjs +1 -1
- package/tests/ship_card.mjs +6 -6
- package/tests/ship_ci_deploy.mjs +1 -1
- package/tests/single_host_hall.mjs +9 -9
- package/tests/smoke_preflight.mjs +1 -1
- package/tests/smoke_target.mjs +1 -1
- package/tests/status-probe.mjs +1 -1
- package/tests/status_mirror_sync.mjs +1 -1
- package/docs/adr/0004-amazonprimea-name-and-trademark-acceptance.md +0 -45
- package/docs/design/mocks/_schemes.css +0 -604
- package/docs/design/mocks/component-sheet.html +0 -428
- package/docs/design/mocks/hall/_tokens.css +0 -370
- package/docs/design/mocks/hall/board/v1.html +0 -846
- package/docs/design/mocks/hall/board/v2.html +0 -858
- package/docs/design/mocks/hall/board/v3.html +0 -884
- package/docs/design/mocks/hall/goals/v1.html +0 -1212
- package/docs/design/mocks/hall/goals/v2.html +0 -1192
- package/docs/design/mocks/hall/goals/v3.html +0 -1205
- package/docs/design/mocks/hall/hall-sheet.html +0 -244
- package/docs/design/mocks/hall/reading/v1.html +0 -1125
- package/docs/design/mocks/hall/reading/v2.html +0 -1177
- package/docs/design/mocks/hall/reading/v3.html +0 -1150
- package/docs/design/mocks/hall/settings/v1.html +0 -1238
- package/docs/design/mocks/hall/settings/v2.html +0 -1235
- package/docs/design/mocks/hall/settings/v3.html +0 -1299
- package/docs/design/mocks/hall/task/v1.html +0 -633
- package/docs/design/mocks/hall/task/v2.html +0 -646
- package/docs/design/mocks/hall/task/v3.html +0 -647
- package/docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg.json +0 -13
- package/docs/design/mocks/hub/assets/r3-space-dust-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-dust-half.jpg.json +0 -13
- package/docs/design/mocks/hub/assets/r3-space-nebula-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-nebula-half.jpg.json +0 -13
- package/docs/design/mocks/hub/assets/r3-space-starfield-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-starfield-half.jpg.json +0 -13
- package/docs/design/mocks/hub/hub-sheet.html +0 -192
- package/docs/design/mocks/hub/mine.html +0 -465
- package/docs/design/mocks/hub/picker.html +0 -421
- package/docs/design/mocks/hub/planets.html +0 -356
- package/docs/design/mocks/hub/v1.html +0 -1209
- package/docs/design/mocks/hub/v2.html +0 -1257
- package/docs/design/mocks/hub/v3.html +0 -1496
- package/docs/design/mocks/hub/wizard.html +0 -421
- package/docs/design/mocks/landing/landing-sheet.html +0 -131
- package/docs/design/mocks/landing/v1.html +0 -956
- package/docs/design/mocks/landing/v2.html +0 -708
- package/docs/design/mocks/landing/v3.html +0 -911
- package/docs/design/mocks/landing-page-mock.html +0 -687
- package/docs/design/mocks/landing-palettes.html +0 -103
- package/docs/design/mocks/projects-hub-mock.html +0 -991
- package/docs/design/mocks/projects-hub-variants.html +0 -146
- package/docs/design/mocks/projects-page-mock.html +0 -167
- package/docs/design/mocks/world/blue-hud/_tokens.css +0 -246
- package/docs/design/mocks/world/blue-hud/hall.html +0 -595
- package/docs/design/mocks/world/blue-hud/landing.html +0 -424
- package/docs/design/mocks/world/blue-hud/status.html +0 -410
- package/docs/design/mocks/world/chrome-blobs/_tokens.css +0 -479
- package/docs/design/mocks/world/chrome-blobs/hall.html +0 -553
- package/docs/design/mocks/world/chrome-blobs/landing-b.html +0 -581
- package/docs/design/mocks/world/chrome-blobs/landing.html +0 -328
- package/docs/design/mocks/world/chrome-blobs/status.html +0 -416
- package/docs/design/mocks/world/natgeo-editorial/_tokens.css +0 -377
- package/docs/design/mocks/world/natgeo-editorial/hall.html +0 -649
- package/docs/design/mocks/world/natgeo-editorial/landing-b.html +0 -743
- package/docs/design/mocks/world/natgeo-editorial/landing.html +0 -475
- package/docs/design/mocks/world/natgeo-editorial/status.html +0 -437
- package/docs/design/mocks/world/organic-cards/_tokens.css +0 -462
- package/docs/design/mocks/world/organic-cards/hall.html +0 -625
- package/docs/design/mocks/world/organic-cards/landing-b.html +0 -736
- package/docs/design/mocks/world/organic-cards/landing.html +0 -420
- package/docs/design/mocks/world/organic-cards/status.html +0 -458
- package/docs/design/mocks/world/world-sheet.html +0 -156
|
@@ -267,12 +267,12 @@ const REPOS_COOKIE = 'gds_oauth_repos';
|
|
|
267
267
|
const HANDSHAKE_COOKIE_MS = 30 * 60 * 1000;
|
|
268
268
|
|
|
269
269
|
// The apex domain the instance is served under, read from the instance branding
|
|
270
|
-
// pack's cookieDomain (".
|
|
270
|
+
// pack's cookieDomain (".example.com" → "example.com",
|
|
271
271
|
// ".cloudbongos.com" → "cloudbongos.com"). The session + OAuth-state cookies are
|
|
272
272
|
// scoped to ".<apex>" so a single sign-in is shared across every subdomain
|
|
273
273
|
// (apex, builders., status.) — the enabler for the dedicated builders subdomain
|
|
274
274
|
// (#726). MUST come from config, not a hardcoded literal: a hardcoded
|
|
275
|
-
// "
|
|
275
|
+
// "example.com" made every OTHER instance (e.g. cloudbongos.com) set a
|
|
276
276
|
// cookie for the wrong domain, which the browser drops → the OAuth state cookie
|
|
277
277
|
// never persisted → "state mismatch / the gate is closed" (ADR 0062 §3/§5: domains
|
|
278
278
|
// woven into auth, config swaps must not widen cookie scope). Each instance scopes
|
|
@@ -296,7 +296,7 @@ function reqHost(req) {
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
// The production-facing hosts that share one sign-in. Deliberately enumerated
|
|
299
|
-
// (not "any *.
|
|
299
|
+
// (not "any *.example.com") so staging.example.com — a SEPARATE
|
|
300
300
|
// process + DB on :3001 — keeps a host-only cookie and never bleeds an
|
|
301
301
|
// apex-scoped token into prod (or vice versa).
|
|
302
302
|
// When COOKIE_APEX is "" (localhost / no configured cookieDomain) the set is
|
|
@@ -309,7 +309,7 @@ const APEX_COOKIE_HOSTS = new Set(
|
|
|
309
309
|
|
|
310
310
|
// Cookie attributes that vary by host. On the production-facing hosts the
|
|
311
311
|
// session cookie is scoped to the apex so it crosses subdomains (the enabler
|
|
312
|
-
// for builders.
|
|
312
|
+
// for builders.example.com, #726); everywhere else (localhost, staging)
|
|
313
313
|
// it stays host-only. Returns a spread-able object.
|
|
314
314
|
function sessionCookieDomainOpts(req) {
|
|
315
315
|
if (APEX_COOKIE_HOSTS.has(reqHost(req))) {
|
|
@@ -320,7 +320,7 @@ function sessionCookieDomainOpts(req) {
|
|
|
320
320
|
|
|
321
321
|
// Validate a caller-supplied post-sign-in return target. Accept ONLY:
|
|
322
322
|
// - a relative path beginning with a single '/' (not '//', no backslash), or
|
|
323
|
-
// - an absolute https URL whose host is the apex or a *.
|
|
323
|
+
// - an absolute https URL whose host is the apex or a *.example.com sub.
|
|
324
324
|
// Anything else (other origins, javascript:, protocol-relative) → null, so the
|
|
325
325
|
// caller falls back to the safe default. This is an open-redirect guard.
|
|
326
326
|
function safeReturnTo(raw) {
|
|
@@ -355,7 +355,7 @@ function recoveryStartHref(req) {
|
|
|
355
355
|
|
|
356
356
|
// The canonical sign-in origin, read from the instance branding pack (oauthOrigin,
|
|
357
357
|
// else publicOrigin) so a non-OTB Cloud Bongos instance pins its OAuth redirects to
|
|
358
|
-
// ITS OWN apex instead of a hardcoded "
|
|
358
|
+
// ITS OWN apex instead of a hardcoded "example.com" (ADR 0062 §3/§5; the
|
|
359
359
|
// branding contract). The fixed GDS_PUBLIC_ORIGIN env knob still wins for
|
|
360
360
|
// dev/staging (auth.js's documented FIXED env name, not a <PREFIX>_ one). Resolved
|
|
361
361
|
// once at module load; falls back to '' if branding fails, which leaves the env knob
|
|
@@ -535,7 +535,7 @@ module.exports = function buildAuthRouter() {
|
|
|
535
535
|
const state = crypto.randomBytes(16).toString('hex');
|
|
536
536
|
// redirect_uri is PINNED to the canonical origin (#734) — never derived from
|
|
537
537
|
// the request host. GitHub only knows the apex callback, so a sign-in begun
|
|
538
|
-
// on builders.
|
|
538
|
+
// on builders.example.com must still hand GitHub the apex redirect_uri
|
|
539
539
|
// or GitHub rejects it. Same posture the Discord flow already uses.
|
|
540
540
|
const redirectUri = `${WEB_REDIRECT_ORIGIN}/api/gds/auth/web/callback`;
|
|
541
541
|
res.cookie(STATE_COOKIE, state, {
|
|
@@ -551,7 +551,7 @@ module.exports = function buildAuthRouter() {
|
|
|
551
551
|
// the page gates); otherwise fall back to the Referer (the page that kicked
|
|
552
552
|
// off sign-in), so the dozens of client 401 redirects land a builder back
|
|
553
553
|
// where they were with no per-call-site change. Both go through the
|
|
554
|
-
// open-redirect allowlist (relative path or *.
|
|
554
|
+
// open-redirect allowlist (relative path or *.example.com https).
|
|
555
555
|
const returnTo = safeReturnTo(req.query.return) || safeReturnTo(req.headers.referer);
|
|
556
556
|
if (returnTo) {
|
|
557
557
|
res.cookie(RETURN_COOKIE, returnTo, {
|
|
@@ -676,7 +676,7 @@ module.exports = function buildAuthRouter() {
|
|
|
676
676
|
path: '/',
|
|
677
677
|
expires: expiresAt,
|
|
678
678
|
// Apex-scoped on the production hosts so one sign-in spans
|
|
679
|
-
//
|
|
679
|
+
// example.com + builders. + status. (#726); host-only elsewhere.
|
|
680
680
|
...sessionCookieDomainOpts(req),
|
|
681
681
|
});
|
|
682
682
|
// A browser arriving with an older session name must not keep it: two
|
|
@@ -805,7 +805,7 @@ module.exports = function buildAuthRouter() {
|
|
|
805
805
|
secure: req.secure || req.headers['x-forwarded-proto'] === 'https',
|
|
806
806
|
maxAge: 10 * 60 * 1000,
|
|
807
807
|
// Apex-scoped (#734 parity) so the state survives the host switch: the
|
|
808
|
-
// link may start on builders.
|
|
808
|
+
// link may start on builders.example.com but DISCORD_REDIRECT_URI
|
|
809
809
|
// always lands the callback on the apex. A host-only cookie would be
|
|
810
810
|
// dropped on the hop and the state check would fail ("Link handshake
|
|
811
811
|
// expired"). Mirrors the GitHub web-flow state cookie above.
|
|
@@ -1036,7 +1036,7 @@ module.exports = function buildAuthRouter() {
|
|
|
1036
1036
|
const cookieToken = sessionCookieToken(req);
|
|
1037
1037
|
if (!cookieToken) {
|
|
1038
1038
|
// Name the instance's own sign-in host (from the branding pack), not a
|
|
1039
|
-
// hardcoded "
|
|
1039
|
+
// hardcoded "example.com" (ADR 0062 §3/§5).
|
|
1040
1040
|
const signInHost = configuredOAuthOrigin().replace(/^https?:\/\//, '').split('/')[0] || 'the sign-in page';
|
|
1041
1041
|
return res.fail('cookie_required', { status: 403, message: `This endpoint mints CLI bearers from an authenticated browser session. Sign in at ${signInHost} first; bearer-only requests are rejected so a leaked CLI token cannot mint replacements for itself.` });
|
|
1042
1042
|
}
|
|
@@ -35,7 +35,7 @@ const SECURITY_DOC_CONTENT_MAX = 2_000_000;
|
|
|
35
35
|
|
|
36
36
|
// Where the full ADR is expected to live on the droplet. Configurable so dev /
|
|
37
37
|
// staging can point at a different file; the recommended prod path is
|
|
38
|
-
// /etc/
|
|
38
|
+
// /etc/example/security-adr.md (chmod 600, owned by the deploy user).
|
|
39
39
|
function adrPath() {
|
|
40
40
|
return (resolveEnv('SECURITY_ADR_PATH') || '').trim() || null;
|
|
41
41
|
}
|
package/src/bongos/routes.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// Three logical groups:
|
|
12
12
|
// - /auth/* — GitHub Device Flow (CLI) + Authorization Code (web)
|
|
13
13
|
// - /me, /tasks/*, /claims/*, /cost/*, /credits/*, ... — authenticated CLI/web ops
|
|
14
|
-
// - /public/* — read-only, no auth, for status.
|
|
14
|
+
// - /public/* — read-only, no auth, for status.example.com
|
|
15
15
|
//
|
|
16
16
|
// All JSON. Same Express app as the game server.
|
|
17
17
|
|
package/src/branding.js
CHANGED
|
@@ -186,7 +186,7 @@ const branding = () => (_cached ||= loadBranding());
|
|
|
186
186
|
|
|
187
187
|
// The HTTP User-Agent the GDS sends on outbound API calls (GitHub, Discord),
|
|
188
188
|
// derived from the instance branding pack so a non-OTB Cloud Bongos instance
|
|
189
|
-
// identifies itself with ITS OWN name rather than a hardcoded "
|
|
189
|
+
// identifies itself with ITS OWN name rather than a hardcoded "example-gds"
|
|
190
190
|
// (ADR 0062 §3/§5; the branding contract). Source order: the repo name (the
|
|
191
191
|
// GitHub binding these calls actually target), else the env prefix, else a neutral
|
|
192
192
|
// fallback. Falls back hard if branding fails so an outbound call never crashes.
|
package/src/devbox-downloads.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// GET /downloads/devbox/latest.json → { version, assets: { target: url } }
|
|
11
11
|
// GET /downloads/devbox/<target> → 302 to the release asset
|
|
12
12
|
//
|
|
13
|
-
// so every URL a builder ever sees or shares stays on
|
|
13
|
+
// so every URL a builder ever sees or shares stays on example.com, and a
|
|
14
14
|
// release bump is a one-file config change that rides the normal ship flow.
|
|
15
15
|
// Pure (config in, urls out) so tests need no server.
|
|
16
16
|
|
package/src/instance-config.js
CHANGED
|
@@ -73,9 +73,9 @@ function envPrefix(b = safeBrand()) {
|
|
|
73
73
|
// The default API base the CLI targets when there is no <PREFIX>_API_BASE env
|
|
74
74
|
// override AND no logged-in session api_base — i.e. the last-resort fallback.
|
|
75
75
|
// Resolves to THIS instance's own publicOrigin from the branding pack (OTB →
|
|
76
|
-
// https://
|
|
76
|
+
// https://example.com, vanilla → http://localhost:3000), so the portable
|
|
77
77
|
// core never hard-codes another instance's URL (task 1002201 — pre-ADR-0139 the
|
|
78
|
-
// literal 'https://
|
|
78
|
+
// literal 'https://example.com' was baked into cli-lib, sending every
|
|
79
79
|
// unconfigured consumer at the founder's game box). Single source of truth: every
|
|
80
80
|
// CLI base resolution routes through here. Last-resort http://localhost:3000 when
|
|
81
81
|
// branding can't be read at all — a local dev origin, never a foreign prod, so an
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.727'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
// No module in this tree names a FOREIGN origin today — every module here was
|
|
8
8
|
// built in this repo. Some name THIS instance as their origin (e.g. the
|
|
9
|
-
// provisioning module's "
|
|
9
|
+
// provisioning module's "Example World (the first Cloud Bongos instance…)"),
|
|
10
10
|
// which is still own-built. The split (core-loaded) side lights up only once a
|
|
11
11
|
// module is actually pulled back in from the shared catalog under a different
|
|
12
12
|
// project's name (ADR 0107 §8 pipeline, tasks 1770/1772).
|
|
@@ -17,7 +17,7 @@ function deriveProvenance(origin, ownProjectName) {
|
|
|
17
17
|
const own = ownProjectName && String(ownProjectName).trim().toLowerCase();
|
|
18
18
|
// "names this instance itself" (ADR 0107 §9) — an origin that IS, or leads
|
|
19
19
|
// with, the own project name is own-built (a self-referential origin like
|
|
20
|
-
// "
|
|
20
|
+
// "Example World (the first Cloud Bongos instance)" counts). Only an origin
|
|
21
21
|
// naming a DIFFERENT project is core-loaded from the shared catalog. (An
|
|
22
22
|
// exact `===` was too brittle: it was written when no module set `origin`,
|
|
23
23
|
// so it never met a real, descriptive value — task 1776 re-land.)
|
package/tests/art_stage.mjs
CHANGED
|
@@ -82,13 +82,13 @@ await ta('main: no sandbox here → rebuilds, reports no sandbox, not surfaced',
|
|
|
82
82
|
await ta('main: box context → restarts, waits healthy, prints the sandbox URL', async () => {
|
|
83
83
|
let restarted = false;
|
|
84
84
|
const deps = baseDeps({
|
|
85
|
-
resolvePreviewContext: () => ({ kind: 'box', url: 'https://sandbox-x.
|
|
85
|
+
resolvePreviewContext: () => ({ kind: 'box', url: 'https://sandbox-x.example.com', healthUrl: 'http://127.0.0.1:3000/', restart: () => { restarted = true; } }),
|
|
86
86
|
});
|
|
87
87
|
const r = await art.main(['--tiles', 'container_a'], deps);
|
|
88
88
|
assert.equal(r.ok, true);
|
|
89
89
|
assert.equal(r.surfaced, true);
|
|
90
90
|
assert.equal(restarted, true);
|
|
91
|
-
assert.ok(deps.logs.some((l) => l.includes('https://sandbox-x.
|
|
91
|
+
assert.ok(deps.logs.some((l) => l.includes('https://sandbox-x.example.com')));
|
|
92
92
|
assert.ok(deps.logs.some((l) => /container_a/.test(l)), 'affected tiles printed');
|
|
93
93
|
});
|
|
94
94
|
|
|
@@ -49,7 +49,7 @@ const t = async (name, fn) => {
|
|
|
49
49
|
catch (e) { fail++; console.log(`FAIL ${name}\n ${e.stack || e.message}`); }
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
const LINKED = { builder_id: 1, github_login: '
|
|
52
|
+
const LINKED = { builder_id: 1, github_login: 'example-owner', rank: 'archon', status: 'active', anthropic_user_id: 'uuid-real' };
|
|
53
53
|
|
|
54
54
|
await t('linked builder + DIFFERENT header (2nd Claude account) → next() called, NOT 403 (pin removed)', async () => {
|
|
55
55
|
nextSession = LINKED;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// tests/auth_duplicate_cookie.mjs — regression for the duplicate gds_session
|
|
2
2
|
// cookie 401 (#851). A browser can carry TWO gds_session cookies: a stale
|
|
3
|
-
// host-only one (set on
|
|
3
|
+
// host-only one (set on example.com before the #726/#734 apex-scoping
|
|
4
4
|
// rollout) AND the current apex-scoped one. The old extractToken() returned the
|
|
5
5
|
// FIRST value only, so a stale duplicate could shadow the live session and 401
|
|
6
6
|
// the builder (hit on the Discord link callback for builder 25). extractTokens()
|
|
@@ -21,7 +21,7 @@ const { renderAuthErrorPage } = authMod;
|
|
|
21
21
|
const KEYS = ['titleSuffix', 'epigraph', 'heading', 'subtitle', 'ctaLabel'];
|
|
22
22
|
// Instance-specific terms that must never appear in the NEUTRAL/vanilla copy —
|
|
23
23
|
// exactly what was leaking onto cloudbongos.com before this fix.
|
|
24
|
-
const OTB_LORE = [/archon/i, /\bgate\b/i, /chronicles/i, /
|
|
24
|
+
const OTB_LORE = [/archon/i, /\bgate\b/i, /chronicles/i, /example/i, /\bboats\b/i, /drachma/i];
|
|
25
25
|
|
|
26
26
|
// A vanilla boot has no instance config → the neutral starter alone (the
|
|
27
27
|
// fail-to-vanilla path), exactly as a fresh `node src/platform-server.js` would.
|
|
@@ -59,7 +59,7 @@ await t('inactive builder with a live session → 403 builder_inactive, next() N
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
await t('active builder → next() called, req.builder populated (incl. status)', async () => {
|
|
62
|
-
nextSession = { builder_id: 1, github_login: '
|
|
62
|
+
nextSession = { builder_id: 1, github_login: 'example-owner', rank: 'archon', status: 'active' };
|
|
63
63
|
const req = { headers: { authorization: 'Bearer abc123' } };
|
|
64
64
|
const res = fakeRes();
|
|
65
65
|
let nexted = false;
|
package/tests/backup_cli.mjs
CHANGED
|
@@ -46,7 +46,7 @@ function extractTimestamp(filename, dbName) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
t('extractTimestamp: well-formed filename', () => {
|
|
49
|
-
const ts = extractTimestamp('
|
|
49
|
+
const ts = extractTimestamp('example-2026-06-29T040001Z.sql.gz', 'example');
|
|
50
50
|
assert.equal(ts, '2026-06-29T040001Z');
|
|
51
51
|
});
|
|
52
52
|
t('extractTimestamp: different db name', () => {
|
|
@@ -58,7 +58,7 @@ t('extractTimestamp: different db name', () => {
|
|
|
58
58
|
// These verify the shape the CLI backup.js expects from GET /backup/status.
|
|
59
59
|
|
|
60
60
|
t('status: latest_dump null when no dumps', () => {
|
|
61
|
-
const response = { backup_dir: '/var/backups/gds', db_name: '
|
|
61
|
+
const response = { backup_dir: '/var/backups/gds', db_name: 'example', latest_dump: null, trigger_running: false };
|
|
62
62
|
assert.equal(response.latest_dump, null);
|
|
63
63
|
assert.equal(response.trigger_running, false);
|
|
64
64
|
});
|
|
@@ -66,9 +66,9 @@ t('status: latest_dump null when no dumps', () => {
|
|
|
66
66
|
t('status: latest_dump populated when dumps exist', () => {
|
|
67
67
|
const response = {
|
|
68
68
|
backup_dir: '/var/backups/gds',
|
|
69
|
-
db_name: '
|
|
69
|
+
db_name: 'example',
|
|
70
70
|
latest_dump: {
|
|
71
|
-
filename: '
|
|
71
|
+
filename: 'example-2026-06-29T040001Z.sql.gz',
|
|
72
72
|
timestamp: '2026-06-29T040001Z',
|
|
73
73
|
size_bytes: 614400,
|
|
74
74
|
count: 7,
|
|
@@ -6,7 +6,7 @@ import { test } from 'node:test';
|
|
|
6
6
|
import crypto from 'node:crypto';
|
|
7
7
|
import lib from '../scripts/gds/box-connect-lib.js';
|
|
8
8
|
|
|
9
|
-
const HOST = 'box-alice.dev.
|
|
9
|
+
const HOST = 'box-alice.dev.example.com';
|
|
10
10
|
|
|
11
11
|
test('rankAtLeast gates --local at Metic+', () => {
|
|
12
12
|
assert.equal(lib.rankAtLeast('metic', 'metic'), true);
|
|
@@ -69,7 +69,7 @@ test('buildKnownHostsUpsert drops the stale line for the host, keeps others, app
|
|
|
69
69
|
// ACCUMULATED: the client kept verifying against the first, now-wrong entry
|
|
70
70
|
// ("Host denied (verification failed)") on a box that had just re-reported keys.
|
|
71
71
|
test('buildKnownHostsUpsert drops a host,ip entry for this box (the accumulation bug)', () => {
|
|
72
|
-
const existing = `${HOST},
|
|
72
|
+
const existing = `${HOST},192.0.2.30 ssh-ed25519 STALE\n`;
|
|
73
73
|
const next = lib.buildKnownHostsUpsert(existing, HOST, 'ssh-ed25519 AAAANEW');
|
|
74
74
|
assert.ok(!next.includes('STALE'), 'a `host,ip` field-1 names this host and must be replaced');
|
|
75
75
|
assert.ok(next.includes(`${HOST} ssh-ed25519 AAAANEW`));
|
|
@@ -23,7 +23,7 @@ function t(name, fn) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const CFG = {
|
|
26
|
-
releases_repo: '
|
|
26
|
+
releases_repo: 'example-owner/example-bongos',
|
|
27
27
|
version: '1.2.3',
|
|
28
28
|
assets: {
|
|
29
29
|
'mac-arm64': 'bongos-1.2.3-darwin-arm64',
|
|
@@ -39,7 +39,7 @@ t('maps a known target to the versioned release-asset URL', () => {
|
|
|
39
39
|
const d = buildBongosDownloads(CFG);
|
|
40
40
|
assert.equal(
|
|
41
41
|
d.redirectFor('mac-arm64'),
|
|
42
|
-
'https://github.com/
|
|
42
|
+
'https://github.com/example-owner/example-bongos/releases/download/v1.2.3/bongos-1.2.3-darwin-arm64'
|
|
43
43
|
);
|
|
44
44
|
});
|
|
45
45
|
|
package/tests/bongos_login.mjs
CHANGED
|
@@ -17,11 +17,11 @@ const { normalizeInstanceBase, pollDecision } = require('../scripts/gds/login.js
|
|
|
17
17
|
const LOGIN_JS = path.join(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'), 'scripts', 'gds', 'login.js');
|
|
18
18
|
|
|
19
19
|
test('normalizeInstanceBase adds https, strips trailing slashes + a pasted /api/gds', () => {
|
|
20
|
-
assert.equal(normalizeInstanceBase('
|
|
21
|
-
assert.equal(normalizeInstanceBase('https://
|
|
22
|
-
assert.equal(normalizeInstanceBase('https://
|
|
23
|
-
assert.equal(normalizeInstanceBase('https://
|
|
24
|
-
assert.equal(normalizeInstanceBase('https://
|
|
20
|
+
assert.equal(normalizeInstanceBase('example.com'), 'https://example.com');
|
|
21
|
+
assert.equal(normalizeInstanceBase('https://example.com/'), 'https://example.com');
|
|
22
|
+
assert.equal(normalizeInstanceBase('https://example.com///'), 'https://example.com');
|
|
23
|
+
assert.equal(normalizeInstanceBase('https://example.com/api/gds'), 'https://example.com');
|
|
24
|
+
assert.equal(normalizeInstanceBase('https://example.com/api/gds/'), 'https://example.com');
|
|
25
25
|
assert.equal(normalizeInstanceBase('http://localhost:3000'), 'http://localhost:3000');
|
|
26
26
|
});
|
|
27
27
|
|
|
@@ -138,8 +138,8 @@ t('installed managed-settings.json at the override dir, keyed off the session lo
|
|
|
138
138
|
const f = path.join(settingsDir, 'managed-settings.json');
|
|
139
139
|
assert.ok(fs.existsSync(f), 'managed-settings.json not written');
|
|
140
140
|
const s = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
141
|
-
assert.equal(s.sshConfigs[0].sshHost, 'root@box-e2etester.dev.
|
|
142
|
-
assert.deepEqual(s.sshHostAllowlist, ['*.dev.
|
|
141
|
+
assert.equal(s.sshConfigs[0].sshHost, 'root@box-e2etester.dev.example.com');
|
|
142
|
+
assert.deepEqual(s.sshHostAllowlist, ['*.dev.example.com']);
|
|
143
143
|
});
|
|
144
144
|
|
|
145
145
|
t('did NOT leak the GDS token into stdout/stderr', () => {
|
|
@@ -71,8 +71,8 @@ function runHook(scenario, extraEnv = {}, retries = '4') {
|
|
|
71
71
|
...process.env,
|
|
72
72
|
PATH: `${tmp}:${process.env.PATH}`,
|
|
73
73
|
CLOUDFLARE_API_TOKEN: 'faketoken',
|
|
74
|
-
CLOUDFLARE_ZONE: '
|
|
75
|
-
BOX_HOSTNAME: 'box-test.dev.
|
|
74
|
+
CLOUDFLARE_ZONE: 'example.com',
|
|
75
|
+
BOX_HOSTNAME: 'box-test.dev.example.com',
|
|
76
76
|
BOX_DNS_BACKOFF_BASE: '0', // no real sleeps in tests
|
|
77
77
|
BOX_DNS_RETRIES: retries,
|
|
78
78
|
FAKE_CF_SCENARIO: scenario,
|
|
@@ -87,7 +87,7 @@ function runHook(scenario, extraEnv = {}, retries = '4') {
|
|
|
87
87
|
{
|
|
88
88
|
const r = runHook('retry_zone', { BOX_IP: '1.2.3.4' });
|
|
89
89
|
assert.equal(r.status, 0, `retry should self-heal to exit 0; got ${r.status} :: ${r.out}`);
|
|
90
|
-
assert.match(r.out, /updated A box-test\.dev\.
|
|
90
|
+
assert.match(r.out, /updated A box-test\.dev\.example\.com -> 1\.2\.3\.4/, r.out);
|
|
91
91
|
const attempts = parseInt(fs.readFileSync(counterFile, 'utf8'));
|
|
92
92
|
assert.ok(attempts >= 2, `zone lookup should have been retried (>=2 attempts), got ${attempts}`);
|
|
93
93
|
}
|
|
@@ -115,19 +115,19 @@ function runHook(scenario, extraEnv = {}, retries = '4') {
|
|
|
115
115
|
|
|
116
116
|
const hit = runHook('delete_hit', { BOX_DNS_DELETE: '1' });
|
|
117
117
|
assert.equal(hit.status, 0, `delete of a present record exits 0; got ${hit.status} :: ${hit.out}`);
|
|
118
|
-
assert.match(hit.out, /deleted A box-test\.dev\.
|
|
118
|
+
assert.match(hit.out, /deleted A box-test\.dev\.example\.com/, hit.out);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// ── Part B: runDnsHook / runDnsHookBlackhole contract + the idea-309 repoint ────
|
|
122
122
|
{
|
|
123
123
|
const box = require('../scripts/gds/box.js');
|
|
124
|
-
const upOk = box.runDnsHook('box-x.dev.
|
|
124
|
+
const upOk = box.runDnsHook('box-x.dev.example.com', '1.2.3.4', 'exit 0');
|
|
125
125
|
assert.equal(upOk.ok, true, 'runDnsHook returns ok:true when the hook succeeds');
|
|
126
126
|
|
|
127
|
-
const upFail = box.runDnsHook('box-x.dev.
|
|
127
|
+
const upFail = box.runDnsHook('box-x.dev.example.com', '1.2.3.4', 'exit 1');
|
|
128
128
|
assert.equal(upFail.ok, false, 'runDnsHook returns ok:false when the hook FAILS (no silent swallow)');
|
|
129
129
|
|
|
130
|
-
const noIp = box.runDnsHook('box-x.dev.
|
|
130
|
+
const noIp = box.runDnsHook('box-x.dev.example.com', null, 'exit 1');
|
|
131
131
|
assert.equal(noIp.ok, true, 'runDnsHook is a no-op (ok:true skipped) when ip is absent');
|
|
132
132
|
|
|
133
133
|
// idea 309: on deprovision the box lifecycle REPOINTS the record to the
|
|
@@ -137,15 +137,15 @@ function runHook(scenario, extraEnv = {}, retries = '4') {
|
|
|
137
137
|
const probe = path.join(tmp, 'blackhole-ip');
|
|
138
138
|
process.env.PROBE_FILE = probe;
|
|
139
139
|
const captureHook = `node -e 'require("fs").writeFileSync(process.env.PROBE_FILE, process.env.BOX_IP || "MISSING")'`;
|
|
140
|
-
const bhOk = box.runDnsHookBlackhole('box-x.dev.
|
|
140
|
+
const bhOk = box.runDnsHookBlackhole('box-x.dev.example.com', captureHook);
|
|
141
141
|
assert.equal(bhOk.ok, true, 'runDnsHookBlackhole returns ok:true when the hook succeeds');
|
|
142
142
|
assert.equal(fs.readFileSync(probe, 'utf8'), '192.0.2.1', 'runDnsHookBlackhole must REPOINT to the 192.0.2.1 sentinel (not delete)');
|
|
143
143
|
delete process.env.PROBE_FILE;
|
|
144
144
|
|
|
145
|
-
const bhFail = box.runDnsHookBlackhole('box-x.dev.
|
|
145
|
+
const bhFail = box.runDnsHookBlackhole('box-x.dev.example.com', 'exit 1');
|
|
146
146
|
assert.equal(bhFail.ok, false, 'runDnsHookBlackhole returns ok:false when the hook FAILS (best-effort, but still reports)');
|
|
147
147
|
|
|
148
|
-
const bhNoHook = box.runDnsHookBlackhole('box-x.dev.
|
|
148
|
+
const bhNoHook = box.runDnsHookBlackhole('box-x.dev.example.com', null);
|
|
149
149
|
assert.equal(bhNoHook.ok, true, 'runDnsHookBlackhole is a no-op (ok:true skipped) when no hook is configured');
|
|
150
150
|
}
|
|
151
151
|
|
package/tests/box_host_keys.mjs
CHANGED
|
@@ -47,7 +47,7 @@ const RSA = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/FqVQUVRAmcDHRBeEZZwuW0xgsAP
|
|
|
47
47
|
|
|
48
48
|
// 4) a hostname prefix (3 fields) → refused, not silently trimmed
|
|
49
49
|
{
|
|
50
|
-
const r = validateHostKeyLines(`box-x.dev.
|
|
50
|
+
const r = validateHostKeyLines(`box-x.dev.example.com ${ED}`);
|
|
51
51
|
assert.equal(r.ok, false);
|
|
52
52
|
assert.equal(r.error, 'expect_type_and_key_only');
|
|
53
53
|
}
|
package/tests/box_onboard.mjs
CHANGED
|
@@ -131,28 +131,28 @@ console.log('managed-settings:');
|
|
|
131
131
|
t('buildManagedSettings: shape matches the template; allowlist pins the passed sshZone', () => {
|
|
132
132
|
// world + sshZone are supplied by the caller from the branding pack (task 1705);
|
|
133
133
|
// the renderer is pure and bakes exactly what it's given.
|
|
134
|
-
const s = o.buildManagedSettings({ login: 'alice', hostname: 'box-alice.dev.
|
|
134
|
+
const s = o.buildManagedSettings({ login: 'alice', hostname: 'box-alice.dev.example.com', world: 'Example', sshZone: 'dev.example.com' });
|
|
135
135
|
assert.equal(s.sshConfigs[0].id, 'otb-alice');
|
|
136
|
-
assert.equal(s.sshConfigs[0].name, '
|
|
137
|
-
assert.equal(s.sshConfigs[0].sshHost, 'root@box-alice.dev.
|
|
136
|
+
assert.equal(s.sshConfigs[0].name, 'Example Dev Box');
|
|
137
|
+
assert.equal(s.sshConfigs[0].sshHost, 'root@box-alice.dev.example.com');
|
|
138
138
|
assert.equal(s.sshConfigs[0].startDirectory, '~/workspace');
|
|
139
|
-
assert.deepEqual(s.sshHostAllowlist, ['*.dev.
|
|
139
|
+
assert.deepEqual(s.sshHostAllowlist, ['*.dev.example.com']);
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
t('buildManagedSettings: bad login / hostname throw coded errors (no injection into the JSON)', () => {
|
|
143
|
-
assert.throws(() => o.buildManagedSettings({ login: 'a/b', hostname: 'x.dev.
|
|
143
|
+
assert.throws(() => o.buildManagedSettings({ login: 'a/b', hostname: 'x.dev.example.com' }), /BAD_LOGIN|invalid login/);
|
|
144
144
|
assert.throws(() => o.buildManagedSettings({ login: 'alice', hostname: 'evil"\nhost' }), /BAD_HOSTNAME|invalid hostname/);
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
console.log('connect guide:');
|
|
148
148
|
|
|
149
149
|
t('renderConnectGuide: bakes the apiBase into the bootstrap URLs', () => {
|
|
150
|
-
const g = o.renderConnectGuide({ apiBase: 'https://staging.
|
|
151
|
-
assert.ok(g.includes('https://staging.
|
|
150
|
+
const g = o.renderConnectGuide({ apiBase: 'https://staging.example.com' });
|
|
151
|
+
assert.ok(g.includes('https://staging.example.com/api/gds/box/connect.sh'));
|
|
152
152
|
assert.ok(g.includes('/api/gds/box/connect.ps1'));
|
|
153
153
|
// the supplied apiBase is baked verbatim (the caller PINS the origin — it is never
|
|
154
154
|
// a hardcoded host brand, task 1705)
|
|
155
|
-
assert.ok(o.renderConnectGuide({ apiBase: 'https://
|
|
155
|
+
assert.ok(o.renderConnectGuide({ apiBase: 'https://example.com' }).includes('https://example.com/api/gds/box/connect.sh'));
|
|
156
156
|
});
|
|
157
157
|
|
|
158
158
|
console.log('DB query shapes (fake pool):');
|
|
@@ -297,7 +297,7 @@ await ta('GET /box/managed-settings: → 200 JSON keyed off the session login',
|
|
|
297
297
|
assert.equal(r.status, 200);
|
|
298
298
|
const b = await r.json();
|
|
299
299
|
assert.equal(b.sshConfigs[0].id, 'otb-alice');
|
|
300
|
-
assert.equal(b.sshConfigs[0].sshHost, 'root@box-alice.dev.
|
|
300
|
+
assert.equal(b.sshConfigs[0].sshHost, 'root@box-alice.dev.example.com');
|
|
301
301
|
});
|
|
302
302
|
|
|
303
303
|
await ta('GET /box/connect.sh: PUBLIC, no auth → 200; bakes the PINNED origin and IGNORES a spoofed Host (blocker #2/#5)', async () => {
|
|
@@ -85,7 +85,7 @@ await t('box session + allowBoxScope (middleware ran) → next() called, req.bui
|
|
|
85
85
|
|
|
86
86
|
// ---- cli / web sessions are unaffected by the box gate -----------------------
|
|
87
87
|
await t('cli session on a non-box route → unaffected, next() called', async () => {
|
|
88
|
-
nextSession = { builder_id: 1, github_login: '
|
|
88
|
+
nextSession = { builder_id: 1, github_login: 'example-owner', rank: 'archon', status: 'active', source: 'cli' };
|
|
89
89
|
const req = { headers: { authorization: 'Bearer clitoken' } }; // no allowBoxScope
|
|
90
90
|
const res = fakeRes();
|
|
91
91
|
let nexted = false;
|
|
@@ -24,9 +24,9 @@ const test = (name, fn) => {
|
|
|
24
24
|
catch (e) { fail++; console.log(`FAIL ${name}\n ${e.message}`); }
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
const LOGIN = '
|
|
27
|
+
const LOGIN = 'example-owner';
|
|
28
28
|
// The dev-box apex is INJECTED rather than taken from the live instance branding
|
|
29
|
-
// (task 1002470). Two reasons: the neutral core has no
|
|
29
|
+
// (task 1002470). Two reasons: the neutral core has no example.com apex, so the
|
|
30
30
|
// three allow-cases could not pass; and every reject-case below was passing for the
|
|
31
31
|
// WRONG reason — an apex mismatch, not the rule it names. With a pinned apex,
|
|
32
32
|
// 'rejects another builder's term- host' really does prove the login binding.
|
|
@@ -34,10 +34,10 @@ const APEX = 'apex.example';
|
|
|
34
34
|
|
|
35
35
|
// ---- allowed: the two legitimate tunnel shapes -------------------------------
|
|
36
36
|
test('allows the named-tunnel apex host bound to the caller login', () => {
|
|
37
|
-
assert.equal(isSafeTerminalUrl(`https://term-
|
|
37
|
+
assert.equal(isSafeTerminalUrl(`https://term-example-owner.${APEX}`, LOGIN, APEX), true);
|
|
38
38
|
});
|
|
39
39
|
test(`allows the named-tunnel dev-zone host (term-<login>.dev.${APEX})`, () => {
|
|
40
|
-
assert.equal(isSafeTerminalUrl(`https://term-
|
|
40
|
+
assert.equal(isSafeTerminalUrl(`https://term-example-owner.dev.${APEX}/`, LOGIN, APEX), true);
|
|
41
41
|
});
|
|
42
42
|
test('allows a Cloudflare quick-tunnel host (*.trycloudflare.com)', () => {
|
|
43
43
|
assert.equal(isSafeTerminalUrl('https://random-words-1234.trycloudflare.com', LOGIN, APEX), true);
|
|
@@ -66,10 +66,10 @@ for (const bad of [
|
|
|
66
66
|
|
|
67
67
|
// ---- rejected: wrong scheme / shape / cross-login ----------------------------
|
|
68
68
|
test('rejects http (must be https)', () => {
|
|
69
|
-
assert.equal(isSafeTerminalUrl(`http://term-
|
|
69
|
+
assert.equal(isSafeTerminalUrl(`http://term-example-owner.${APEX}`, LOGIN, APEX), false);
|
|
70
70
|
});
|
|
71
71
|
test('rejects embedded credentials (user:pass@host)', () => {
|
|
72
|
-
assert.equal(isSafeTerminalUrl(`https://user:pass@term-
|
|
72
|
+
assert.equal(isSafeTerminalUrl(`https://user:pass@term-example-owner.${APEX}`, LOGIN, APEX), false);
|
|
73
73
|
});
|
|
74
74
|
test('rejects another builder\'s term- host (login binding)', () => {
|
|
75
75
|
assert.equal(isSafeTerminalUrl(`https://term-someoneelse.${APEX}`, LOGIN, APEX), false);
|
|
@@ -89,7 +89,7 @@ test('rejects garbage / unparseable', () => {
|
|
|
89
89
|
assert.equal(isSafeTerminalUrl(null, LOGIN, APEX), false);
|
|
90
90
|
});
|
|
91
91
|
test('rejects when login is empty (cannot bind the term- form)', () => {
|
|
92
|
-
assert.equal(isSafeTerminalUrl(`https://term-
|
|
92
|
+
assert.equal(isSafeTerminalUrl(`https://term-example-owner.${APEX}`, '', APEX), false);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
console.log(`\n${pass} passed, ${fail} failed`);
|
package/tests/boxes.mjs
CHANGED
|
@@ -898,10 +898,10 @@ t('#903: rendered cloud-init user-data stays under DigitalOcean\'s 64KB cap', ()
|
|
|
898
898
|
.split('__CLOUDFLARED_SERVICE_B64__').join(gz('cloudflared.service'))
|
|
899
899
|
.split('__CLOUDFLARED_RUN_B64__').join(gz('cloudflared-run.sh'))
|
|
900
900
|
.split('__CLOSE_BOX_B64__').join(gz('close-box.sh'))
|
|
901
|
-
.split('__REPO_URL__').join('git@github.com:owner/
|
|
901
|
+
.split('__REPO_URL__').join('git@github.com:owner/example.git')
|
|
902
902
|
.split('__BUILDER_AUTHORIZED_KEYS_B64__').join(Buffer.from('ssh-ed25519 AAAA...k1\nssh-ed25519 AAAA...k2\n').toString('base64'))
|
|
903
|
-
.split('__BOX_ENV_B64__').join(Buffer.from('BOX_TUNNEL_TOKEN=' + 'x'.repeat(200) + '\nBOX_TERMINAL_HOSTNAME=term-x.
|
|
904
|
-
.split('__GDS_SESSION_B64__').join(Buffer.from(JSON.stringify({ token: 'x'.repeat(64), builder: { id: 1, github_login: 'x', rank: 'archon' }, api_base: 'https://
|
|
903
|
+
.split('__BOX_ENV_B64__').join(Buffer.from('BOX_TUNNEL_TOKEN=' + 'x'.repeat(200) + '\nBOX_TERMINAL_HOSTNAME=term-x.example.com\nBOX_TERMINAL_CREDENTIAL=' + 'a'.repeat(48) + '\nBOX_PREVIEW_HOSTNAME=sandbox-x.example.com\n').toString('base64'))
|
|
904
|
+
.split('__GDS_SESSION_B64__').join(Buffer.from(JSON.stringify({ token: 'x'.repeat(64), builder: { id: 1, github_login: 'x', rank: 'archon' }, api_base: 'https://example.com' })).toString('base64'));
|
|
905
905
|
const bytes = Buffer.byteLength(out, 'utf8');
|
|
906
906
|
assert.ok(bytes < 64 * 1024, `rendered cloud-init is ${bytes}B; must stay under DO's 65536B cap`);
|
|
907
907
|
});
|
|
@@ -1193,13 +1193,13 @@ t('planDropletDrift: a droplet a live box owns is in sync (no drift)', () => {
|
|
|
1193
1193
|
t('planDropletDrift: ZOMBIE — droplet exists but its box row is destroyed (the bug)', () => {
|
|
1194
1194
|
// The exact task-1289 shape: deprovision marked the row destroyed + nulled
|
|
1195
1195
|
// droplet_id, but the droplet was never actually deleted.
|
|
1196
|
-
const droplets = [{ id: 222, name: 'otb-box-
|
|
1197
|
-
const boxRows = [{ builder_id: 3, github_login: '
|
|
1196
|
+
const droplets = [{ id: 222, name: 'otb-box-example-owner', tags: ['otb-builder-box', 'builder:example-owner'], created_at: '2026-05-01T00:00:00Z' }];
|
|
1197
|
+
const boxRows = [{ builder_id: 3, github_login: 'example-owner', state: 'destroyed', droplet_id: null }];
|
|
1198
1198
|
const { zombies, ghosts } = boxes.planDropletDrift({ droplets, boxRows, nowMs: NOW });
|
|
1199
1199
|
assert.equal(ghosts.length, 0);
|
|
1200
1200
|
assert.equal(zombies.length, 1);
|
|
1201
1201
|
assert.equal(zombies[0].dropletId, '222');
|
|
1202
|
-
assert.equal(zombies[0].login, '
|
|
1202
|
+
assert.equal(zombies[0].login, 'example-owner');
|
|
1203
1203
|
assert.equal(zombies[0].builderId, 3, 'maps the zombie back to its builder for edge teardown');
|
|
1204
1204
|
});
|
|
1205
1205
|
|
|
@@ -1383,8 +1383,8 @@ function sweepHarness({ idleRows, parkThrows = null }) {
|
|
|
1383
1383
|
|
|
1384
1384
|
await ta('cmdSweepIdle: an idle box is PARKED — snapshot retained, no snapshot delete', async () => {
|
|
1385
1385
|
const NOW_ROW = {
|
|
1386
|
-
id: 3, builder_id: 3, state: 'active', droplet_id: '591743808', droplet_name: 'box-
|
|
1387
|
-
github_login: '
|
|
1386
|
+
id: 3, builder_id: 3, state: 'active', droplet_id: '591743808', droplet_name: 'box-example-owner',
|
|
1387
|
+
github_login: 'example-owner', last_activity_at: new Date(Date.now() - 5 * 3600_000).toISOString(),
|
|
1388
1388
|
claude_active: false, snapshot_id: null,
|
|
1389
1389
|
};
|
|
1390
1390
|
const h = sweepHarness({ idleRows: [NOW_ROW] });
|
|
@@ -1414,7 +1414,7 @@ await ta('cmdSweepIdle: an idle box is PARKED — snapshot retained, no snapshot
|
|
|
1414
1414
|
|
|
1415
1415
|
await ta('cmdSweepIdle: dry-run reports "would park" and touches nothing', async () => {
|
|
1416
1416
|
const row = {
|
|
1417
|
-
id: 3, builder_id: 3, state: 'active', droplet_id: '591743808', github_login: '
|
|
1417
|
+
id: 3, builder_id: 3, state: 'active', droplet_id: '591743808', github_login: 'example-owner',
|
|
1418
1418
|
last_activity_at: new Date(Date.now() - 5 * 3600_000).toISOString(), claude_active: false,
|
|
1419
1419
|
};
|
|
1420
1420
|
const h = sweepHarness({ idleRows: [row] });
|
package/tests/branding.mjs
CHANGED
|
@@ -21,7 +21,7 @@ const NEUTRAL = {
|
|
|
21
21
|
|
|
22
22
|
// A GENERIC sample instance pack — exercises the resolver's override/merge/precedence logic
|
|
23
23
|
// without baking any real instance's identity into the (published) test. (task 1002219 / audit H5:
|
|
24
|
-
// swept the former customer-1 sample —
|
|
24
|
+
// swept the former customer-1 sample — Example World / example / Hermeslines — to neutral.)
|
|
25
25
|
const SAMPLE = {
|
|
26
26
|
identity: { productName: 'Acme Rockets', worldName: 'Acme', company: { name: 'Acme Corp', site: 'https://acme.example' } },
|
|
27
27
|
llmProjectName: 'Acme Rockets',
|
|
@@ -95,7 +95,7 @@ test('deepMerge does not mutate its inputs', () => {
|
|
|
95
95
|
test('the committed neutral pack loads + resolves to the neutral identity (the trunk carries no instance branding.json)', () => {
|
|
96
96
|
// The neutral trunk ships config/branding.neutral.json only — no config/branding.json — so
|
|
97
97
|
// loadBranding resolves to the plain Cloud Bongos identity (task 1002219 / audit H5: this used to
|
|
98
|
-
// assert the customer-1 OTB identity, stale since the
|
|
98
|
+
// assert the customer-1 OTB identity, stale since the example→cloudbongos rebrand, ADR 0139/0140).
|
|
99
99
|
const b = loadBranding({ env: {} });
|
|
100
100
|
assert.equal(b.identity.productName, 'Cloud Bongos');
|
|
101
101
|
assert.equal(b.identity.worldName, 'Cloud Bongos');
|
package/tests/bump_version.mjs
CHANGED
|
@@ -146,7 +146,7 @@ test('a HUMAN release commit never matches — skipping it would swallow its pub
|
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
test('work-merge subjects, junk, and empties never match', () => {
|
|
149
|
-
assert.equal(isReleaseCommit('Merge pull request #30 from
|
|
149
|
+
assert.equal(isReleaseCommit('Merge pull request #30 from example-owner/claude/cloudbongos-npm-registry-b9a57f'), false);
|
|
150
150
|
assert.equal(isReleaseCommit('fix(ship): protection-aware deploy mode'), false);
|
|
151
151
|
assert.equal(isReleaseCommit(''), false);
|
|
152
152
|
assert.equal(isReleaseCommit(null), false);
|
|
@@ -190,7 +190,7 @@ test('publish.yml wires the terminator: guard step present, every armed step gat
|
|
|
190
190
|
test('laneReleasedVersions reads the versions this lane announced', () => {
|
|
191
191
|
const subjects = [
|
|
192
192
|
'release: core 1.19.368 — CI auto-patch carrier (publish-on-merge, ADR 0161)',
|
|
193
|
-
'Merge pull request #385 from
|
|
193
|
+
'Merge pull request #385 from example-owner/some-branch',
|
|
194
194
|
'release: core 1.19.367 — CI auto-patch carrier (publish-on-merge, ADR 0161)',
|
|
195
195
|
'feat(security): something unrelated',
|
|
196
196
|
];
|