@bongos/core 1.19.726 → 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 +176 -491
- 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 +2 -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/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/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/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
package/tests/provision.mjs
CHANGED
|
@@ -154,7 +154,7 @@ t('buildCorePinRefreshCommit (the commit step): git sequence, token-URL push, an
|
|
|
154
154
|
`git -C /srv/bongos/spike-harbor config user.email "${P.provisionerBotEmail()}"`,
|
|
155
155
|
]);
|
|
156
156
|
assert.equal(P.provisionerBotEmail({ PROVISION_BOT_EMAIL: 'ops@acme.test' }), 'ops@acme.test', 'env override wins');
|
|
157
|
-
assert.doesNotMatch(P.provisionerBotEmail({}), /
|
|
157
|
+
assert.doesNotMatch(P.provisionerBotEmail({}), /example/i, 'never a founder-host address');
|
|
158
158
|
assert.equal(withTok.add, 'git -C /srv/bongos/spike-harbor add -A');
|
|
159
159
|
assert.match(withTok.commit, /@bongos\/core 1\.19\.2 -> 1\.19\.196 \(task 1003050\)/);
|
|
160
160
|
assert.match(withTok.push, /store --file=\/tmp\/cred.*x-access-token@github\.com\/jaxri\/spike-harbor\.git HEAD:main/);
|
|
@@ -179,7 +179,7 @@ t('backup units: per-instance names + the DB set on the unit (fixes the task-221
|
|
|
179
179
|
assert.equal(P.backupServicePath(coTenant), '/etc/systemd/system/mercury-db-backup.service');
|
|
180
180
|
assert.equal(P.backupTimerPath(coTenant), '/etc/systemd/system/mercury-db-backup.timer');
|
|
181
181
|
const svc = P.backupService(coTenant);
|
|
182
|
-
// the DB is pinned on the unit → no fallback to the nonexistent
|
|
182
|
+
// the DB is pinned on the unit → no fallback to the nonexistent example default
|
|
183
183
|
assert.match(svc, /Environment=GDS_BACKUP_DB=mercury/);
|
|
184
184
|
assert.match(svc, /Environment=GDS_BACKUP_DIR=\/var\/backups\/gds/);
|
|
185
185
|
assert.match(svc, new RegExp(`User=${reEsc('lars')}`), 'runs as the app user (mirrors serviceUnit)');
|
|
@@ -189,7 +189,7 @@ t('backup units: per-instance names + the DB set on the unit (fixes the task-221
|
|
|
189
189
|
assert.match(timer, /Unit=mercury-db-backup\.service/, 'the timer drives the per-instance service');
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
-
t('backupScriptPath resolves the CONSUMER LAYOUT, never the old /home/lars/
|
|
192
|
+
t('backupScriptPath resolves the CONSUMER LAYOUT, never the old /home/lars/example monorepo path', () => {
|
|
193
193
|
// standalone → the script inside the instance's OWN pinned @bongos/core
|
|
194
194
|
const standalone = { ...coTenant, hosting_shape: 'standalone' };
|
|
195
195
|
const sPath = P.backupScriptPath(standalone);
|
|
@@ -198,8 +198,8 @@ t('backupScriptPath resolves the CONSUMER LAYOUT, never the old /home/lars/amazo
|
|
|
198
198
|
// co-tenant → this control-plane checkout's scripts/gds (mirrors serviceUnit's REPO_ROOT split)
|
|
199
199
|
assert.match(P.backupScriptPath(coTenant), /\/scripts\/gds\/db-backup-nightly\.sh$/);
|
|
200
200
|
// the regression this whole task fixes: NEVER the hardcoded old monorepo path
|
|
201
|
-
assert.doesNotMatch(P.backupService(standalone), /\/home\/lars\/
|
|
202
|
-
assert.doesNotMatch(P.backupService(coTenant), /\/home\/lars\/
|
|
201
|
+
assert.doesNotMatch(P.backupService(standalone), /\/home\/lars\/example/);
|
|
202
|
+
assert.doesNotMatch(P.backupService(coTenant), /\/home\/lars\/example/);
|
|
203
203
|
});
|
|
204
204
|
|
|
205
205
|
t('webEnvBody: OAuth creds use the LOADED pack\'s envPrefix (not the slug) + brand/port/db', () => {
|
|
@@ -336,7 +336,7 @@ t('dnsUpsertEnv: hostname + target IP + derived zone; an explicit zone wins', ()
|
|
|
336
336
|
t('dnsUpsertEnv: BOX_PROXIED defaults off (co-tenant on-demand ACME); proxied:true opts in (standalone behind the wildcard origin cert, task 2077)', () => {
|
|
337
337
|
assert.equal(P.dnsUpsertEnv({ domain: 'mercury.example.com' }, { publicIp: '203.0.113.5' }).BOX_PROXIED, '0');
|
|
338
338
|
assert.equal(P.dnsUpsertEnv({ domain: 'mercury.example.com' }, { publicIp: '203.0.113.5', proxied: false }).BOX_PROXIED, '0');
|
|
339
|
-
assert.equal(P.dnsUpsertEnv({ domain: 'demo.cloudbongos.com' }, { publicIp: '
|
|
339
|
+
assert.equal(P.dnsUpsertEnv({ domain: 'demo.cloudbongos.com' }, { publicIp: '198.51.100.20', proxied: true }).BOX_PROXIED, '1');
|
|
340
340
|
});
|
|
341
341
|
|
|
342
342
|
t('healthzCmd hits the instance port /healthz', () => {
|
|
@@ -460,7 +460,7 @@ await ta('provisionInstance(standalone): the backup unit ExecStart points at the
|
|
|
460
460
|
// capture the unit BODIES, not just paths, so we can assert the ExecStart path
|
|
461
461
|
const bodies = {};
|
|
462
462
|
deps.writeFile = (p, body) => { writes.push(p); bodies[p] = body; return { ok: true }; };
|
|
463
|
-
const inst = { ...coTenant, hosting_shape: 'standalone', target_ref: '
|
|
463
|
+
const inst = { ...coTenant, hosting_shape: 'standalone', target_ref: 'example-owner/mercury' };
|
|
464
464
|
await P.provisionInstance(inst, deps);
|
|
465
465
|
const svc = bodies[P.backupServicePath(inst)];
|
|
466
466
|
assert.ok(svc, 'wrote the backup service unit');
|
|
@@ -486,12 +486,12 @@ await ta('provisionInstance(standalone): web.env seats the signed-in builder, no
|
|
|
486
486
|
|
|
487
487
|
await ta('provisionInstance(standalone) SCAFFOLDS its repo, then syncs (pull + npm ci) + migrates from the instance root', async () => {
|
|
488
488
|
const { deps, cmds, writes } = recorder();
|
|
489
|
-
const r = await P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: '
|
|
489
|
+
const r = await P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: 'example-owner/mercury' }, deps);
|
|
490
490
|
assert.equal(r.ok, true);
|
|
491
491
|
const joined = cmds.join('\n');
|
|
492
492
|
// scaffold leg (task 2055): git init → package-core → init --vendor-core → push
|
|
493
493
|
assert.match(joined, /git -C \S+ init -q/, 'inits the instance working tree (empty repo — nothing to clone)');
|
|
494
|
-
assert.match(joined, /git -C \S+ remote add origin git@github-deploy:
|
|
494
|
+
assert.match(joined, /git -C \S+ remote add origin git@github-deploy:example-owner\/mercury\.git/, 'sets the instance repo as origin');
|
|
495
495
|
assert.match(joined, /node scripts\/gds\/package-core\.js/, 'builds the core tarball');
|
|
496
496
|
assert.match(joined, /init\.js --from .* --dir .* --no-seed --force --vendor-core /, 'materializes + file:-pins the core');
|
|
497
497
|
assert.match(joined, /git -C .* push -u origin main/, 'pushes the scaffold');
|
|
@@ -518,27 +518,27 @@ await ta('provisionInstance(standalone) SCAFFOLDS its repo, then syncs (pull + n
|
|
|
518
518
|
|
|
519
519
|
// ── standalone scaffold helpers + guards (task 2055) ──────────────────────────
|
|
520
520
|
t('parseTargetRef: owner/repo, SSH + HTTPS URLs, .git suffix; garbage → null', () => {
|
|
521
|
-
assert.deepEqual(P.parseTargetRef('
|
|
522
|
-
assert.deepEqual(P.parseTargetRef('git@github-deploy:
|
|
523
|
-
assert.deepEqual(P.parseTargetRef('https://github.com/
|
|
521
|
+
assert.deepEqual(P.parseTargetRef('example-owner/demo'), { owner: 'example-owner', name: 'demo' });
|
|
522
|
+
assert.deepEqual(P.parseTargetRef('git@github-deploy:example-owner/demo.git'), { owner: 'example-owner', name: 'demo' });
|
|
523
|
+
assert.deepEqual(P.parseTargetRef('https://github.com/example-owner/demo.git'), { owner: 'example-owner', name: 'demo' });
|
|
524
524
|
assert.equal(P.parseTargetRef('garbage'), null);
|
|
525
525
|
assert.equal(P.parseTargetRef(''), null);
|
|
526
526
|
assert.equal(P.parseTargetRef(null), null);
|
|
527
527
|
});
|
|
528
528
|
|
|
529
529
|
t('instanceRepoRemote: reuses the SSH deploy alias (no secret in the URL); alias overridable', () => {
|
|
530
|
-
assert.equal(P.instanceRepoRemote({ owner: '
|
|
530
|
+
assert.equal(P.instanceRepoRemote({ owner: 'example-owner', name: 'demo' }), 'git@github-deploy:example-owner/demo.git');
|
|
531
531
|
assert.equal(P.instanceRepoRemote({ owner: 'o', name: 'r' }, { sshAlias: 'gh-alt' }), 'git@gh-alt:o/r.git');
|
|
532
532
|
});
|
|
533
533
|
|
|
534
534
|
t('instanceInitSpec: builds a VALID greenfield init spec from the row + owner login', () => {
|
|
535
535
|
const spec = P.instanceInitSpec(
|
|
536
|
-
{ slug: 'demo', target_ref: '
|
|
537
|
-
{ ownerLogin: '
|
|
536
|
+
{ slug: 'demo', target_ref: 'example-owner/demo', domain: 'demo.cloudbongos.com', port: 3003 },
|
|
537
|
+
{ ownerLogin: 'example-owner' },
|
|
538
538
|
);
|
|
539
539
|
assert.equal(spec.identity.productName, 'demo');
|
|
540
|
-
assert.deepEqual(spec.repo, { owner: '
|
|
541
|
-
assert.equal(spec.firstAdmin, '
|
|
540
|
+
assert.deepEqual(spec.repo, { owner: 'example-owner', name: 'demo' });
|
|
541
|
+
assert.equal(spec.firstAdmin, 'example-owner');
|
|
542
542
|
assert.equal(spec.domains.publicOrigin, 'https://demo.cloudbongos.com');
|
|
543
543
|
// it must satisfy init.validateSpec (greenfield) — the scaffold feeds it to `bongos init`
|
|
544
544
|
const init = require('../scripts/gds/init.js');
|
|
@@ -566,7 +566,7 @@ await ta('scaffoldStandaloneRepo: idempotent — a repo that already carries pac
|
|
|
566
566
|
mkdirSync(join(tmp, 'demo'), { recursive: true });
|
|
567
567
|
writeFileSync(join(tmp, 'demo', 'package.json'), '{}\n'); // sign of a completed scaffold
|
|
568
568
|
const { deps, cmds } = recorder({ apply: true });
|
|
569
|
-
const res = await P.scaffoldStandaloneRepo({ slug: 'demo', hosting_shape: 'standalone', target_ref: '
|
|
569
|
+
const res = await P.scaffoldStandaloneRepo({ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo' }, deps);
|
|
570
570
|
assert.equal(res.already, true);
|
|
571
571
|
assert.equal(cmds.length, 0, 'no init/build/push when already scaffolded');
|
|
572
572
|
} finally {
|
|
@@ -589,7 +589,7 @@ async function scaffoldWithEnv(env) {
|
|
|
589
589
|
// the scaffold rebuilds via package-core.js (task 1002275). A consumer-instance root
|
|
590
590
|
// (a real pin) takes the reuse path — covered by its own test below.
|
|
591
591
|
await P.scaffoldStandaloneRepo(
|
|
592
|
-
{ slug: 'demo', hosting_shape: 'standalone', target_ref: '
|
|
592
|
+
{ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo' },
|
|
593
593
|
{ ...deps, env, instanceRoot: tmp },
|
|
594
594
|
);
|
|
595
595
|
return cmds.join('\n');
|
|
@@ -670,13 +670,13 @@ await ta('scaffoldStandaloneRepo: on a consumer control plane it REUSES the pinn
|
|
|
670
670
|
try {
|
|
671
671
|
const { deps, cmds } = recorder({ apply: true });
|
|
672
672
|
await P.scaffoldStandaloneRepo(
|
|
673
|
-
{ slug: 'demo', hosting_shape: 'standalone', target_ref: '
|
|
673
|
+
{ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo' },
|
|
674
674
|
{ ...deps, instanceRoot: cp },
|
|
675
675
|
);
|
|
676
676
|
const joined = cmds.join('\n');
|
|
677
677
|
assert.ok(!/package-core\.js/.test(joined), 'does NOT rebuild the core from source (the empty-set abort path)');
|
|
678
678
|
assert.match(joined, new RegExp('init\\.js --from .* --no-seed --force --vendor-core ' + reEsc(pinned)), 'feeds the PINNED tarball to init --vendor-core');
|
|
679
|
-
assert.match(joined, /git -C \S+ remote add origin git@github-deploy:
|
|
679
|
+
assert.match(joined, /git -C \S+ remote add origin git@github-deploy:example-owner\/demo\.git/, 'still targets the repo as origin');
|
|
680
680
|
assert.match(joined, /push /, 'still pushes the scaffold');
|
|
681
681
|
} finally {
|
|
682
682
|
P.CONFIG.standaloneBase = origBase;
|
|
@@ -738,7 +738,7 @@ await ta('scaffoldStandaloneRepo: a REGISTRY-pinned consumer control plane PACKS
|
|
|
738
738
|
try {
|
|
739
739
|
const { deps, cmds } = recorder({ apply: true });
|
|
740
740
|
await P.scaffoldStandaloneRepo(
|
|
741
|
-
{ slug: 'demo', hosting_shape: 'standalone', target_ref: '
|
|
741
|
+
{ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo' },
|
|
742
742
|
{ ...deps, instanceRoot: cp },
|
|
743
743
|
);
|
|
744
744
|
const joined = cmds.join('\n');
|
|
@@ -771,14 +771,14 @@ await ta('scaffoldStandaloneRepo: pushes via the owner OAuth token (not SSH), to
|
|
|
771
771
|
deps.writeFile = (p, body, opts) => { writes.push({ p, body, opts }); return { ok: true }; };
|
|
772
772
|
deps.getGithubToken = async () => ({ token: 'gho_SECRET_TOKEN_XYZ', scope: 'public_repo' });
|
|
773
773
|
await P.scaffoldStandaloneRepo(
|
|
774
|
-
{ slug: 'demo', hosting_shape: 'standalone', target_ref: '
|
|
774
|
+
{ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', owner_builder_id: 7 },
|
|
775
775
|
{ ...deps, instanceRoot: base },
|
|
776
776
|
);
|
|
777
777
|
const joined = cmds.join('\n');
|
|
778
778
|
// SECURITY: the token must NEVER appear in a command string (makeExec logs commands)
|
|
779
779
|
assert.ok(!joined.includes('gho_SECRET_TOKEN_XYZ'), 'token never appears in any command string');
|
|
780
780
|
// pushes over HTTPS via the credential-store helper to HEAD:main, NOT the SSH origin
|
|
781
|
-
assert.match(joined, /credential\.helper='store --file=[^']*bongos-gitcred-demo[^']*' push https:\/\/x-access-token@github\.com\/
|
|
781
|
+
assert.match(joined, /credential\.helper='store --file=[^']*bongos-gitcred-demo[^']*' push https:\/\/x-access-token@github\.com\/example-owner\/demo\.git HEAD:main/, 'pushes via the token credential-store helper');
|
|
782
782
|
assert.ok(!/push -u origin main/.test(joined), 'does NOT use the SSH origin push when a token is available');
|
|
783
783
|
// the credential file is written chmod 600, carries the token (content, NOT logged), then removed
|
|
784
784
|
const cred = writes.find((w) => /bongos-gitcred-demo/.test(w.p));
|
|
@@ -799,7 +799,7 @@ await ta('scaffoldStandaloneRepo: no owner token → falls back to the SSH origi
|
|
|
799
799
|
try {
|
|
800
800
|
const { deps, cmds } = recorder({ apply: true }); // default getGithubToken → null
|
|
801
801
|
await P.scaffoldStandaloneRepo(
|
|
802
|
-
{ slug: 'demo', hosting_shape: 'standalone', target_ref: '
|
|
802
|
+
{ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', owner_builder_id: 7 },
|
|
803
803
|
{ ...deps, instanceRoot: base },
|
|
804
804
|
);
|
|
805
805
|
const joined = cmds.join('\n');
|
|
@@ -869,7 +869,7 @@ async function scaffoldInMode(mode, { token = null, seedRoot = null } = {}) {
|
|
|
869
869
|
deps.writeFile = (p, body, opts) => { writes.push({ p, body, opts }); return { ok: true }; };
|
|
870
870
|
if (token) deps.getGithubToken = async () => ({ token, scope: 'read:user,repo' });
|
|
871
871
|
await P.scaffoldStandaloneRepo(
|
|
872
|
-
{ slug: 'demo', hosting_shape: 'standalone', target_ref: '
|
|
872
|
+
{ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', owner_builder_id: 7, onboard_mode: mode },
|
|
873
873
|
{ ...deps, instanceRoot: base },
|
|
874
874
|
);
|
|
875
875
|
return { joined: cmds.join('\n'), cmds, writes, root: join(base, 'demo') };
|
|
@@ -881,7 +881,7 @@ async function scaffoldInMode(mode, { token = null, seedRoot = null } = {}) {
|
|
|
881
881
|
|
|
882
882
|
await ta('scaffoldStandaloneRepo(adopt): CLONES the existing repo instead of git-init-ing a fresh tree', async () => {
|
|
883
883
|
const { joined } = await scaffoldInMode('adopt', { token: 'gho_TOK' });
|
|
884
|
-
assert.match(joined, /clone https:\/\/x-access-token@github\.com\/
|
|
884
|
+
assert.match(joined, /clone https:\/\/x-access-token@github\.com\/example-owner\/demo\.git/, 'clones over the token URL (covers a PRIVATE repo the shared deploy key cannot read)');
|
|
885
885
|
assert.ok(!/git -C \S+ init -q/.test(joined), 'never git-inits a fresh tree on the adopt path — that is what produced the unrelated history');
|
|
886
886
|
assert.ok(!/checkout -B main/.test(joined), 'does not force-create the branch over the cloned one');
|
|
887
887
|
assert.match(joined, /init\.js --from .* --no-seed --adopt --accept /, 'materializes via init --adopt --accept (layering, accept-gated)');
|
|
@@ -889,7 +889,7 @@ await ta('scaffoldStandaloneRepo(adopt): CLONES the existing repo instead of git
|
|
|
889
889
|
// (`git pull --ff-only origin main`, and the box timer's `git reset --hard origin/main`)
|
|
890
890
|
// reads origin, and the one-shot token URL cannot authenticate once its credential file
|
|
891
891
|
// is deleted.
|
|
892
|
-
assert.match(joined, /git -C \S+ remote set-url origin git@github-deploy:
|
|
892
|
+
assert.match(joined, /git -C \S+ remote set-url origin git@github-deploy:example-owner\/demo\.git/, 'normalizes origin to the SSH remote after cloning');
|
|
893
893
|
});
|
|
894
894
|
|
|
895
895
|
// THE SAFETY PROPERTY. Since ADR 0155 this credential can write to EVERY repo the owner
|
|
@@ -900,7 +900,7 @@ await ta('scaffoldStandaloneRepo(adopt): NEVER force-pushes and never passes --f
|
|
|
900
900
|
assert.ok(!/--force/.test(joined), 'no --force anywhere in the adopt argv');
|
|
901
901
|
assert.ok(!/ -f\b/.test(joined.replace(/rm -f /g, '')), 'no -f force shorthand (rm -f of the credential file excepted)');
|
|
902
902
|
assert.ok(!/\+refs|\+HEAD|:\+/.test(joined), 'no leading-+ refspec (the other way to force a push)');
|
|
903
|
-
assert.match(joined, /push https:\/\/x-access-token@github\.com\/
|
|
903
|
+
assert.match(joined, /push https:\/\/x-access-token@github\.com\/example-owner\/demo\.git HEAD:main/, 'a plain fast-forward push');
|
|
904
904
|
});
|
|
905
905
|
|
|
906
906
|
await ta('scaffoldStandaloneRepo(greenfield): unchanged — git init + --force, no clone', async () => {
|
|
@@ -914,7 +914,7 @@ await ta('scaffoldStandaloneRepo(greenfield): unchanged — git init + --force,
|
|
|
914
914
|
|
|
915
915
|
await ta('scaffoldStandaloneRepo(adopt): no owner token → clones over the SSH remote (no credential helper)', async () => {
|
|
916
916
|
const { joined } = await scaffoldInMode('adopt'); // recorder default getGithubToken → null
|
|
917
|
-
assert.match(joined, /clone git@github-deploy:
|
|
917
|
+
assert.match(joined, /clone git@github-deploy:example-owner\/demo\.git/, 'falls back to the SSH remote');
|
|
918
918
|
assert.ok(!/credential\.helper=/.test(joined), 'no credential helper without a token');
|
|
919
919
|
assert.match(joined, /push -u origin main/, 'and the SSH origin push');
|
|
920
920
|
});
|
|
@@ -944,7 +944,7 @@ await ta('scaffoldStandaloneRepo(adopt): a default branch that is not `main` fai
|
|
|
944
944
|
deps.exec = (cmd) => ({ ok: true, stdout: /rev-parse --abbrev-ref HEAD/.test(cmd) ? 'master\n' : '' });
|
|
945
945
|
await assert.rejects(
|
|
946
946
|
() => P.scaffoldStandaloneRepo(
|
|
947
|
-
{ slug: 'demo', hosting_shape: 'standalone', target_ref: '
|
|
947
|
+
{ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', owner_builder_id: 7, onboard_mode: 'adopt' },
|
|
948
948
|
{ ...deps, instanceRoot: base },
|
|
949
949
|
),
|
|
950
950
|
/default branch is "master", not "main"/,
|
|
@@ -969,7 +969,7 @@ await ta('scaffoldStandaloneRepo(adopt): the credential file is removed even whe
|
|
|
969
969
|
};
|
|
970
970
|
await assert.rejects(
|
|
971
971
|
() => P.scaffoldStandaloneRepo(
|
|
972
|
-
{ slug: 'demo', hosting_shape: 'standalone', target_ref: '
|
|
972
|
+
{ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', owner_builder_id: 7, onboard_mode: 'adopt' },
|
|
973
973
|
{ ...deps, instanceRoot: base },
|
|
974
974
|
),
|
|
975
975
|
/non-fast-forward/,
|
|
@@ -1475,11 +1475,11 @@ console.log('\nremote orchestration over SSH (task 2065):');
|
|
|
1475
1475
|
|
|
1476
1476
|
t('makeExec(remoteHost): a step is ssh-wrapped and cd\'s to the remote checkout (dry-run)', () => {
|
|
1477
1477
|
const logs = [];
|
|
1478
|
-
const run = P.makeExec({ apply: false, log: (m) => logs.push(m), remoteHost: 'lars@10.0.0.9', remoteCwd: '/home/lars/
|
|
1478
|
+
const run = P.makeExec({ apply: false, log: (m) => logs.push(m), remoteHost: 'lars@10.0.0.9', remoteCwd: '/home/lars/example', sshOpts: '-o BatchMode=yes' });
|
|
1479
1479
|
run('createdb mercury');
|
|
1480
1480
|
const line = logs.join('\n');
|
|
1481
1481
|
assert.match(line, /\[ssh lars@10\.0\.0\.9\]/, 'shows the ssh target');
|
|
1482
|
-
assert.match(line, /cd \/home\/lars\/
|
|
1482
|
+
assert.match(line, /cd \/home\/lars\/example && createdb mercury/, 'cd to the remote checkout, then the command');
|
|
1483
1483
|
});
|
|
1484
1484
|
|
|
1485
1485
|
t('makeExec(remoteHost): explicit cwd overrides the default; env is composed INTO the remote script (crosses the ssh hop)', () => {
|
|
@@ -1544,8 +1544,8 @@ await ta('provisionInstance(co-tenant, remote): box steps use the box `exec`, bu
|
|
|
1544
1544
|
await ta('provisionInstance(standalone, remote): EVERY box step runs control-plane-local — nothing crosses to the co-hosting box; the A record points at the control plane (task 2074)', async () => {
|
|
1545
1545
|
const savedPub = P.CONFIG.publicIp, savedCp = P.CONFIG.controlPlaneIp, savedHook = P.CONFIG.dnsHook, savedRemote = P.CONFIG.remoteHost;
|
|
1546
1546
|
P.CONFIG.remoteHost = 'cohost'; // remote-exec mode: co-tenant steps WOULD go to the co-hosting box
|
|
1547
|
-
P.CONFIG.publicIp = '
|
|
1548
|
-
P.CONFIG.controlPlaneIp = '
|
|
1547
|
+
P.CONFIG.publicIp = '192.0.2.10'; // the co-hosting box's IP (where co-tenants run)
|
|
1548
|
+
P.CONFIG.controlPlaneIp = '198.51.100.20'; // the control plane's OWN IP (where a standalone runs)
|
|
1549
1549
|
P.CONFIG.dnsHook = '/opt/infra/box-dns-cloudflare.sh';
|
|
1550
1550
|
try {
|
|
1551
1551
|
const box = [], control = [], boxWrites = [], controlWrites = [], dnsEnvs = [];
|
|
@@ -1560,7 +1560,7 @@ await ta('provisionInstance(standalone, remote): EVERY box step runs control-pla
|
|
|
1560
1560
|
doApi: null,
|
|
1561
1561
|
checkDnsTokenScope: async () => ({ reachable: true, zone: 'cloudbongos.com' }),
|
|
1562
1562
|
};
|
|
1563
|
-
await P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: '
|
|
1563
|
+
await P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: 'example-owner/mercury', domain: 'mercury.cloudbongos.com' }, deps);
|
|
1564
1564
|
// The bug (task 2074): these landed on the REMOTE box, which never had the scaffolded
|
|
1565
1565
|
// dir. They must ALL be on the control-plane executor now.
|
|
1566
1566
|
for (const re of [/git pull --ff-only/, /npm ci/, /createdb mercury/, /migrate\.sh/, /systemctl enable --now/, /systemctl reload caddy/, /healthz/]) {
|
|
@@ -1579,7 +1579,7 @@ await ta('provisionInstance(standalone, remote): EVERY box step runs control-pla
|
|
|
1579
1579
|
// DNS: the A record points at the CONTROL PLANE's IP (not the co-hosting box's publicIp).
|
|
1580
1580
|
const dns = dnsEnvs.find((e) => e.cmd === '/opt/infra/box-dns-cloudflare.sh');
|
|
1581
1581
|
assert.ok(dns, 'DNS hook ran on the control executor');
|
|
1582
|
-
assert.equal(dns.env.BOX_IP, '
|
|
1582
|
+
assert.equal(dns.env.BOX_IP, '198.51.100.20', 'A record → the control-plane IP, NOT the co-hosting box');
|
|
1583
1583
|
assert.equal(dns.env.BOX_PROXIED, '1', 'standalone A record is PROXIED (behind the wildcard origin cert; task 2077) — publicly-trusted cert + hidden IP');
|
|
1584
1584
|
} finally { P.CONFIG.publicIp = savedPub; P.CONFIG.controlPlaneIp = savedCp; P.CONFIG.dnsHook = savedHook; P.CONFIG.remoteHost = savedRemote; }
|
|
1585
1585
|
});
|
|
@@ -1587,7 +1587,7 @@ await ta('provisionInstance(standalone, remote): EVERY box step runs control-pla
|
|
|
1587
1587
|
await ta('provisionInstance(standalone, remote, --apply): FAILS LOUD when the control-plane IP is unset (never silently points the domain at the co-hosting box)', async () => {
|
|
1588
1588
|
const savedPub = P.CONFIG.publicIp, savedCp = P.CONFIG.controlPlaneIp, savedHook = P.CONFIG.dnsHook, savedRemote = P.CONFIG.remoteHost;
|
|
1589
1589
|
P.CONFIG.remoteHost = 'cohost'; // split-box mode
|
|
1590
|
-
P.CONFIG.publicIp = '
|
|
1590
|
+
P.CONFIG.publicIp = '192.0.2.10'; // co-hosting box — must NOT be used for a standalone A record
|
|
1591
1591
|
P.CONFIG.controlPlaneIp = null; // operator forgot to set it
|
|
1592
1592
|
P.CONFIG.dnsHook = '/opt/infra/box-dns-cloudflare.sh';
|
|
1593
1593
|
try {
|
|
@@ -1600,7 +1600,7 @@ await ta('provisionInstance(standalone, remote, --apply): FAILS LOUD when the co
|
|
|
1600
1600
|
checkDnsTokenScope: async () => ({ reachable: true, zone: 'cloudbongos.com' }),
|
|
1601
1601
|
};
|
|
1602
1602
|
await assert.rejects(
|
|
1603
|
-
() => P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: '
|
|
1603
|
+
() => P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: 'example-owner/mercury', domain: 'mercury.cloudbongos.com' }, deps),
|
|
1604
1604
|
/PROVISION_CONTROL_PLANE_IP/,
|
|
1605
1605
|
'must demand the control-plane IP, not fall back to the co-hosting box',
|
|
1606
1606
|
);
|
|
@@ -1792,7 +1792,7 @@ await ta('provisionInstance(standalone, hub-configured): still NOT federated —
|
|
|
1792
1792
|
deps.controlWriteFile = (p, body) => { bodies[p] = body; return { ok: true }; };
|
|
1793
1793
|
deps.writeFile = (p, body) => { bodies[p] = body; return { ok: true }; };
|
|
1794
1794
|
deps.platformIdentity = pi; deps.hubKeys = hubKeysOn;
|
|
1795
|
-
await P.provisionInstance({ ...coTenantWithDomain, hosting_shape: 'standalone', target_ref: '
|
|
1795
|
+
await P.provisionInstance({ ...coTenantWithDomain, hosting_shape: 'standalone', target_ref: 'example-owner/mercury' }, deps);
|
|
1796
1796
|
const env = bodies[P.webEnvPath({ ...coTenantWithDomain, hosting_shape: 'standalone' })];
|
|
1797
1797
|
assert.ok(env, 'wrote web.env');
|
|
1798
1798
|
assert.doesNotMatch(env, /_AUTH_IDP=/, 'standalone is the self-hosted path — no federation');
|
|
@@ -2283,7 +2283,7 @@ function repoAuthDeps({ apply = true, priv = true, token = 'gho_TOK', keys = [],
|
|
|
2283
2283
|
deps.fsImpl = { existsSync: () => existing, readFileSync: () => `${pub}\n` };
|
|
2284
2284
|
return { deps, cmds, apiCalls };
|
|
2285
2285
|
}
|
|
2286
|
-
const privInst = { id: 9, slug: 'demo', hosting_shape: 'standalone', status: 'requested', owner_builder_id: 7, target_ref: '
|
|
2286
|
+
const privInst = { id: 9, slug: 'demo', hosting_shape: 'standalone', status: 'requested', owner_builder_id: 7, target_ref: 'example-owner/demo' };
|
|
2287
2287
|
|
|
2288
2288
|
await ta('ensurePrivateRepoAccess(private): registers a READ-ONLY key and wires origin + core.sshCommand into the checkout', async () => {
|
|
2289
2289
|
const { deps, cmds, apiCalls } = repoAuthDeps({ existing: false });
|
|
@@ -2300,7 +2300,7 @@ await ta('ensurePrivateRepoAccess(private): registers a READ-ONLY key and wires
|
|
|
2300
2300
|
assert.equal(add[1], 'cloudbongos-deploy-demo', 'titled per-instance so teardown can revoke by title');
|
|
2301
2301
|
assert.equal(add[3], true, 'read_only: a leaked box key must never be able to write the owner\'s repo');
|
|
2302
2302
|
// origin re-pointed at PLAIN github.com (the shared alias would resolve the wrong key)…
|
|
2303
|
-
assert.match(joined, /git -C \S+\/demo remote set-url origin git@github\.com:
|
|
2303
|
+
assert.match(joined, /git -C \S+\/demo remote set-url origin git@github\.com:example-owner\/demo\.git/);
|
|
2304
2304
|
// …and core.sshCommand pinned to the key IN THE REPO CONFIG: survives `git reset --hard`,
|
|
2305
2305
|
// applies to every future git op here, and -F \/dev\/null keeps ~/.ssh/config identities out.
|
|
2306
2306
|
assert.match(joined, /git -C \S+\/demo config core\.sshCommand "ssh -F \/dev\/null -i \S+\/\.git\/cloudbongos-deploy-key -o IdentitiesOnly=yes/);
|
|
@@ -2326,14 +2326,14 @@ await ta('ensurePrivateRepoAccess(flipped public): UN-wires — origin back to t
|
|
|
2326
2326
|
assert.equal(r.unwired, true);
|
|
2327
2327
|
assert.deepEqual(apiCalls.find((c) => c[0] === 'del'), ['del', 11], 'revokes the now-orphaned key');
|
|
2328
2328
|
const joined = cmds.join('\n');
|
|
2329
|
-
assert.match(joined, /remote set-url origin git@github-deploy:
|
|
2329
|
+
assert.match(joined, /remote set-url origin git@github-deploy:example-owner\/demo\.git/, 'origin returns to the shared alias');
|
|
2330
2330
|
assert.match(joined, /config --unset core\.sshCommand/);
|
|
2331
2331
|
assert.match(joined, /rm -f \S+\/\.git\/cloudbongos-deploy-key \S+\/\.git\/cloudbongos-deploy-key\.pub/);
|
|
2332
2332
|
});
|
|
2333
2333
|
|
|
2334
2334
|
await ta('ensurePrivateRepoAccess(private, non-admin token): refuses UP FRONT with the fix, not a raw 403 mid-standup', async () => {
|
|
2335
2335
|
const { deps, cmds, apiCalls } = repoAuthDeps({ existing: false });
|
|
2336
|
-
deps.githubRepos.getUserRepo = async () => ({ full_name: '
|
|
2336
|
+
deps.githubRepos.getUserRepo = async () => ({ full_name: 'example-owner/demo', private: true, admin: false });
|
|
2337
2337
|
await assert.rejects(() => P.ensurePrivateRepoAccess(privInst, deps), /repository access is not admin: .*ADMIN/);
|
|
2338
2338
|
assert.ok(!cmds.some((c) => /ssh-keygen/.test(c)), 'no key minted for a credential that cannot register it');
|
|
2339
2339
|
assert.ok(!apiCalls.some((c) => c[0] === 'add'));
|
|
@@ -2357,7 +2357,7 @@ await ta('ensurePrivateRepoAccess: 422 key_in_use (repo changed under a slug-key
|
|
|
2357
2357
|
await ta('ensurePrivateRepoAccess: a transient GitHub failure surfaces as a plain sentence, never raw jargon on the owner card', async () => {
|
|
2358
2358
|
const { deps } = repoAuthDeps({ existing: true });
|
|
2359
2359
|
deps.githubRepos.listDeployKeys = async () => { const e = new Error('GitHub deploy-key list failed (502)'); e.code = 'github_error'; throw e; };
|
|
2360
|
-
await assert.rejects(() => P.ensurePrivateRepoAccess(privInst, deps), /deploy-key setup failed for
|
|
2360
|
+
await assert.rejects(() => P.ensurePrivateRepoAccess(privInst, deps), /deploy-key setup failed for example-owner\/demo: .*try again/);
|
|
2361
2361
|
});
|
|
2362
2362
|
|
|
2363
2363
|
await ta('ensurePrivateRepoAccess(no token): skips without touching git or GitHub (task 1002909 owns the request-time refusal)', async () => {
|
|
@@ -2378,7 +2378,7 @@ await ta('ensurePrivateRepoAccess: an already-registered CURRENT key re-asserts
|
|
|
2378
2378
|
assert.ok(!apiCalls.some((c) => c[0] === 'del'), 'no deletion');
|
|
2379
2379
|
assert.ok(!cmds.some((c) => /ssh-keygen/.test(c)), 'no new keypair');
|
|
2380
2380
|
// the wiring is still re-asserted (a re-provision heals a hand-edited remote)
|
|
2381
|
-
assert.ok(cmds.some((c) => /remote set-url origin git@github\.com:
|
|
2381
|
+
assert.ok(cmds.some((c) => /remote set-url origin git@github\.com:example-owner\/demo\.git/.test(c)));
|
|
2382
2382
|
assert.ok(cmds.some((c) => /config core\.sshCommand/.test(c)));
|
|
2383
2383
|
});
|
|
2384
2384
|
|
|
@@ -2398,7 +2398,7 @@ await ta('ensurePrivateRepoAccess: repo_not_found with a token → a PLAIN error
|
|
|
2398
2398
|
deps.githubRepos.getUserRepo = async () => { const e = new Error('GitHub answered 404 for that repository'); e.code = 'repo_not_found'; throw e; };
|
|
2399
2399
|
await assert.rejects(
|
|
2400
2400
|
() => P.ensurePrivateRepoAccess(privInst, deps),
|
|
2401
|
-
/repository not found: .*
|
|
2401
|
+
/repository not found: .*example-owner\/demo.*credential/i,
|
|
2402
2402
|
);
|
|
2403
2403
|
});
|
|
2404
2404
|
|
|
@@ -2415,10 +2415,10 @@ await ta('ensurePrivateRepoAccess(dry-run): fully OFFLINE — no token read, no
|
|
|
2415
2415
|
|
|
2416
2416
|
await ta('provisionInstance(standalone): the private-repo wiring lands BETWEEN the scaffold and the step-2 pull', async () => {
|
|
2417
2417
|
const { deps, cmds } = repoAuthDeps({ existing: false });
|
|
2418
|
-
const r = await P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: '
|
|
2418
|
+
const r = await P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: 'example-owner/mercury' }, deps);
|
|
2419
2419
|
assert.equal(r.ok, true);
|
|
2420
2420
|
const idx = (re) => cmds.findIndex((c) => re.test(c));
|
|
2421
|
-
assert.ok(idx(/remote set-url origin git@github\.com:
|
|
2421
|
+
assert.ok(idx(/remote set-url origin git@github\.com:example-owner\/mercury\.git/) >= 0, 'wired the private origin');
|
|
2422
2422
|
assert.ok(idx(/remote set-url origin git@github\.com:/) < idx(/git pull --ff-only origin main/),
|
|
2423
2423
|
'the pull that failed on prod (mercury, 2026-08-17) now runs AFTER the key wiring');
|
|
2424
2424
|
});
|
|
@@ -81,7 +81,7 @@ console.log('\nprovisioningOrigin override (task 2101 — customer App carries t
|
|
|
81
81
|
// so mothershipOrigin() must prefer it over publicOrigin. mothershipOrigin() calls
|
|
82
82
|
// api.branding() (module-api captured the fn ref at require-time), so override THAT.
|
|
83
83
|
const prevBranding = api.branding;
|
|
84
|
-
api.branding = () => ({ ...realBranding(), identity: { productName: 'Cloud Bongos' }, domains: { publicOrigin: 'https://
|
|
84
|
+
api.branding = () => ({ ...realBranding(), identity: { productName: 'Cloud Bongos' }, domains: { publicOrigin: 'https://example.com', provisioningOrigin: 'https://get.cloudbongos.com' } });
|
|
85
85
|
const res = await fetch(`${base}/provisioning/instances/42/github-app`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' });
|
|
86
86
|
const j = await res.json();
|
|
87
87
|
t('redirect_url uses provisioningOrigin, not the game publicOrigin', j.manifest && j.manifest.redirect_url === 'https://get.cloudbongos.com/api/bongos/provisioning/github-app/callback', j.manifest && j.manifest.redirect_url);
|
|
@@ -52,7 +52,7 @@ function recordingDb(returnRow = {}) {
|
|
|
52
52
|
await ta('createInstance: onboardMode "adopt" is persisted', async () => {
|
|
53
53
|
const db = recordingDb();
|
|
54
54
|
await provisioning.createInstance(db, {
|
|
55
|
-
ownerBuilderId: 7, slug: 'demo', targetRef: '
|
|
55
|
+
ownerBuilderId: 7, slug: 'demo', targetRef: 'example-owner/demo',
|
|
56
56
|
hostingShape: 'standalone', onboardMode: 'adopt',
|
|
57
57
|
});
|
|
58
58
|
const insert = db.calls.find((c) => /INSERT INTO provisioning_instances/i.test(c.text));
|
|
@@ -150,7 +150,7 @@ function post(path, body) {
|
|
|
150
150
|
await ta('POST /instances: onboard_mode=adopt is threaded through to createInstance', async () => {
|
|
151
151
|
lastCreateArgs = null;
|
|
152
152
|
const res = await post('/provisioning/instances', {
|
|
153
|
-
slug: 'demo', hosting_shape: 'standalone', target_ref: '
|
|
153
|
+
slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', onboard_mode: 'adopt',
|
|
154
154
|
});
|
|
155
155
|
assert.equal(res.status, 201, `expected 201, got ${res.status} ${res.raw}`);
|
|
156
156
|
assert.equal(lastCreateArgs && lastCreateArgs.onboardMode, 'adopt');
|
|
@@ -1187,12 +1187,12 @@ test('1915 the heal path never runs when the module lacks refreshStaleGenerated
|
|
|
1187
1187
|
// (once — per-tick would be spam), and name the RESOLVED owner/repo so a WRONG
|
|
1188
1188
|
// repo bind is visible without owner access.
|
|
1189
1189
|
// ---------------------------------------------------------------------------
|
|
1190
|
-
const REPO_OK = { owner: '
|
|
1190
|
+
const REPO_OK = { owner: 'example-owner', repo: 'cloud-bongos', branch: 'main' };
|
|
1191
1191
|
const REPO_ERR = { error: 'no_repo_info', detail: 'git remote origin not parseable' };
|
|
1192
1192
|
|
|
1193
1193
|
test('1002461 describeTarget names the resolved owner/repo and the credential state', () => {
|
|
1194
1194
|
const s = reconciler.describeTarget({ configured: true, repo: REPO_OK });
|
|
1195
|
-
assert.match(s, /
|
|
1195
|
+
assert.match(s, /example-owner\/cloud-bongos/, 'the resolved repo must be visible');
|
|
1196
1196
|
assert.match(s, /main/);
|
|
1197
1197
|
assert.match(s, /credential present/i);
|
|
1198
1198
|
});
|
|
@@ -1200,7 +1200,7 @@ test('1002461 describeTarget names the resolved owner/repo and the credential st
|
|
|
1200
1200
|
test('1002461 describeTarget shouts when there is no credential', () => {
|
|
1201
1201
|
const s = reconciler.describeTarget({ configured: false, repo: REPO_OK });
|
|
1202
1202
|
assert.match(s, /NO PUSH CREDENTIAL/, 'the missing credential must be unmissable');
|
|
1203
|
-
assert.match(s, /
|
|
1203
|
+
assert.match(s, /example-owner\/cloud-bongos/, 'still names the repo — a wrong bind and a missing credential are separate faults');
|
|
1204
1204
|
});
|
|
1205
1205
|
|
|
1206
1206
|
test('1002461 describeTarget reports an unresolved repo instead of pretending', () => {
|
|
@@ -1218,7 +1218,7 @@ test('1002461 an unconfigured sweep WARNs — the skip is no longer silent', asy
|
|
|
1218
1218
|
assert.equal(r.skipped, 'unconfigured', 'still a no-op — this task only adds the log');
|
|
1219
1219
|
assert.equal(deps._calls.errors.length, 1, 'the skip must say so');
|
|
1220
1220
|
assert.match(deps._calls.errors[0], /NO PUSH CREDENTIAL/);
|
|
1221
|
-
assert.match(deps._calls.errors[0], /
|
|
1221
|
+
assert.match(deps._calls.errors[0], /example-owner\/cloud-bongos/, 'names the repo it would have used');
|
|
1222
1222
|
});
|
|
1223
1223
|
|
|
1224
1224
|
test('1002461 the unconfigured WARN fires ONCE, not once per tick (no log spam)', async () => {
|
package/tests/sandbox_stage.mjs
CHANGED
|
@@ -40,7 +40,7 @@ t('isDevBox: true only when box.env exists', () => {
|
|
|
40
40
|
|
|
41
41
|
// ---------- readPreviewHostname ----------
|
|
42
42
|
t('readPreviewHostname: plain, quoted, export-prefixed, missing, empty', () => {
|
|
43
|
-
assert.equal(sbx.readPreviewHostname('BOX_PREVIEW_HOSTNAME=sandbox-somebuilder.
|
|
43
|
+
assert.equal(sbx.readPreviewHostname('BOX_PREVIEW_HOSTNAME=sandbox-somebuilder.example.com\n'), 'sandbox-somebuilder.example.com');
|
|
44
44
|
assert.equal(sbx.readPreviewHostname('A=1\nBOX_PREVIEW_HOSTNAME="sandbox-x.example.com"\nB=2'), 'sandbox-x.example.com');
|
|
45
45
|
assert.equal(sbx.readPreviewHostname("export BOX_PREVIEW_HOSTNAME='sandbox-y.example.com'"), 'sandbox-y.example.com');
|
|
46
46
|
assert.equal(sbx.readPreviewHostname('BOX_TERMINAL_HOSTNAME=term-x.example.com'), null);
|
|
@@ -97,7 +97,7 @@ function positives() {
|
|
|
97
97
|
// by the "token": assignment + the hex entropy rule, not by any prefix match.
|
|
98
98
|
const sessTok = randStr(110, 64, HEX);
|
|
99
99
|
const sessionJson =
|
|
100
|
-
'{\n "token": "' + sessTok + '",\n "builder": "
|
|
100
|
+
'{\n "token": "' + sessTok + '",\n "builder": "example-owner",\n "rank": "archon"\n}';
|
|
101
101
|
// The same real token as it appears on the wire: a bare-64-hex cookie value +
|
|
102
102
|
// an Authorization: Bearer header (the two ways it leaves a CLI session).
|
|
103
103
|
const wireTok = randStr(115, 64, HEX);
|
|
@@ -130,7 +130,7 @@ function positives() {
|
|
|
130
130
|
{ name: 'PEM private key body', secret: pemBody, context: `key:\n${pem}\n(end)` },
|
|
131
131
|
{ name: 'gds-session.json token (real bare-64-hex shape)', secret: sessTok, context: `$ cat ~/.config/otb/gds-session.json\n${sessionJson}` },
|
|
132
132
|
{ name: 'real GDS token on the wire (cookie)', secret: wireTok, context: `Cookie: gds_session=${wireTok}; Path=/` },
|
|
133
|
-
{ name: 'real GDS token on the wire (Bearer header)', secret: wireTok, context: `curl -H "Authorization: Bearer ${wireTok}" https://
|
|
133
|
+
{ name: 'real GDS token on the wire (Bearer header)', secret: wireTok, context: `curl -H "Authorization: Bearer ${wireTok}" https://example.com/api/gds/me` },
|
|
134
134
|
{ name: 'env GITHUB_TOKEN value', secret: envGithub.split('=')[1], context: envGithub },
|
|
135
135
|
{ name: 'env access_token value', secret: envAccess.split('=')[1], context: envAccess },
|
|
136
136
|
{ name: 'JSON client_secret value', secret: envClient.match(/"client_secret": "([^"]+)"/)[1], context: envClient },
|
|
@@ -178,7 +178,7 @@ function negatives() {
|
|
|
178
178
|
'internationalization is a long english word',
|
|
179
179
|
'see docs/adr/0022-secrets-policy.md for the policy',
|
|
180
180
|
'merge-mode swept the confirmed queue and deployed',
|
|
181
|
-
'the vendor sells physical and digital goods in
|
|
181
|
+
'the vendor sells physical and digital goods in Example',
|
|
182
182
|
'P0: kill the grader bypass and rotate the token soon', // "token" but no value after a separator
|
|
183
183
|
'git log shows acf3f3c bcb0794 282a155 fc8f4a5', // short SHAs (< 20)
|
|
184
184
|
];
|
|
@@ -165,7 +165,7 @@ if (!client) {
|
|
|
165
165
|
try {
|
|
166
166
|
await client.query('BEGIN');
|
|
167
167
|
await client.query(`SET LOCAL pg_trgm.word_similarity_threshold = ${search.TRGM_THRESHOLD}`);
|
|
168
|
-
// builder 1 exists (
|
|
168
|
+
// builder 1 exists (example-owner); seed a protected (metic) chunk, a public one,
|
|
169
169
|
// and a chunk owned by builder 1 — all matching q='enforcement'.
|
|
170
170
|
await client.query(`INSERT INTO search_chunks (source_kind, source_ref, chunk_index, heading_path, title, body, min_rank, owner_builder_id, content_hash) VALUES
|
|
171
171
|
('doc','__iso__/protected.md',0,'p','PROT','trust boundary permission core enforcement','metic',NULL,'i1'),
|
|
@@ -502,7 +502,7 @@ test('a failed/empty subagent spawn digests to 0 tokens despite 1 assistant turn
|
|
|
502
502
|
// assistant_turns>=1 guard let these into the corpus (#698); total_tokens===0
|
|
503
503
|
// is what now excludes them — in the backfill AND server-side at POST.
|
|
504
504
|
const spawn = [
|
|
505
|
-
{ type: 'user', message: { content: 'You are the **Hacker** — a specialist adversarial subagent for the
|
|
505
|
+
{ type: 'user', message: { content: 'You are the **Hacker** — a specialist adversarial subagent for the Example World project.' } },
|
|
506
506
|
{
|
|
507
507
|
type: 'assistant',
|
|
508
508
|
message: {
|
package/tests/ship_card.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import fs from 'node:fs';
|
|
|
15
15
|
const require = createRequire(import.meta.url);
|
|
16
16
|
const card = require('../modules/lifecycle/ship-card.js');
|
|
17
17
|
|
|
18
|
-
const BRAND = { productName: '
|
|
18
|
+
const BRAND = { productName: 'Example World', currency: 'drachmae' };
|
|
19
19
|
const row = (m, k) => { const r = m.rows.find((x) => x.k === k); return r ? r.v : undefined; };
|
|
20
20
|
|
|
21
21
|
const shippedInput = {
|
|
@@ -230,13 +230,13 @@ test('review: warning tone, link action to the sandbox URL', () => {
|
|
|
230
230
|
const m = card.buildCardModel({
|
|
231
231
|
stage: 'review', taskId: 1271, title: 'Add model preference', brand: BRAND,
|
|
232
232
|
valueSummary: 'Pick the subagent model.', fileCount: 6, added: 240, removed: 15,
|
|
233
|
-
sandboxUrl: 'https://sandbox-
|
|
233
|
+
sandboxUrl: 'https://sandbox-example-owner.example.com',
|
|
234
234
|
});
|
|
235
235
|
assert.equal(m.cfg.tone, 'warning');
|
|
236
236
|
assert.equal(m.action.kind, 'link');
|
|
237
|
-
assert.equal(m.action.url, 'https://sandbox-
|
|
237
|
+
assert.equal(m.action.url, 'https://sandbox-example-owner.example.com');
|
|
238
238
|
const html = card.renderCardHtml(m);
|
|
239
|
-
assert.match(html, /href="https:\/\/sandbox-
|
|
239
|
+
assert.match(html, /href="https:\/\/sandbox-example-owner\.example\.com"/);
|
|
240
240
|
assert.match(html, /Open in sandbox/);
|
|
241
241
|
assert.match(html, /staged on your sandbox/);
|
|
242
242
|
});
|
|
@@ -244,7 +244,7 @@ test('review: warning tone, link action to the sandbox URL', () => {
|
|
|
244
244
|
// ---- monospace ----
|
|
245
245
|
test('monospace: rows + brand + currency + indent', () => {
|
|
246
246
|
const out = card.renderMonospaceCard(card.buildCardModel(shippedInput));
|
|
247
|
-
assert.match(out, /
|
|
247
|
+
assert.match(out, /Example World · task #1266/);
|
|
248
248
|
assert.match(out, /Shipped/);
|
|
249
249
|
assert.match(out, /\+112\/−37/);
|
|
250
250
|
assert.match(out, /12,936 drachmae/);
|
|
@@ -289,7 +289,7 @@ test('emitCard with widgetsEnabled:false: monospace only, no HTML, off-marker',
|
|
|
289
289
|
const file = card.emitCard(card.buildCardModel(shippedInput), 99998, { log: (s) => lines.push(s), widgetsEnabled: false });
|
|
290
290
|
const out = lines.join('\n');
|
|
291
291
|
assert.equal(file, null, 'no HTML file written when widgets are off');
|
|
292
|
-
assert.match(out, /
|
|
292
|
+
assert.match(out, /Example World · task #1266/); // the monospace card still printed
|
|
293
293
|
assert.ok(!out.includes('[otb-card-html]'), 'no [otb-card-html] marker emitted');
|
|
294
294
|
assert.match(out, /widgets are off/); // off-marker tells the skill not to call show_widget
|
|
295
295
|
});
|
package/tests/ship_ci_deploy.mjs
CHANGED
|
@@ -133,7 +133,7 @@ test('resolveDeployTarget: blank/whitespace config values are treated as unset',
|
|
|
133
133
|
|
|
134
134
|
// ---- 1c. resolveInstanceBase precedence + the "no hardcoded host" guarantee (task 1002232) ----
|
|
135
135
|
// The ci-land /version + /healthz probes, the ci PR-body deploy note, and the
|
|
136
|
-
// manual-confirm curl guidance all baked in `https://
|
|
136
|
+
// manual-confirm curl guidance all baked in `https://example.com`, so a
|
|
137
137
|
// ci-land / manual-confirm on a migrated instance pointed at the WRONG box. The
|
|
138
138
|
// helper derives the origin: env → session api_base → branding publicOrigin → null.
|
|
139
139
|
// Every case injects env/sessionApiBase/branding so the machine's own env + session
|
|
@@ -60,7 +60,7 @@ test('single-host instance: the configured buildersOrigin host IS the hall host'
|
|
|
60
60
|
[
|
|
61
61
|
'emersonian-circles.cloudbongos.com',
|
|
62
62
|
'builders.anything.com',
|
|
63
|
-
'
|
|
63
|
+
'example.com',
|
|
64
64
|
'status.emersonian-circles.cloudbongos.com',
|
|
65
65
|
'evil.example',
|
|
66
66
|
'',
|
|
@@ -71,7 +71,7 @@ test('single-host instance: the configured buildersOrigin host IS the hall host'
|
|
|
71
71
|
// Back-compat: a builders.<apex> host still matches via startsWith("builders.").
|
|
72
72
|
assert.equal(v['builders.anything.com'], true);
|
|
73
73
|
// A DIFFERENT apex is NOT the hall host, so its /builders redirect still fires.
|
|
74
|
-
assert.equal(v['
|
|
74
|
+
assert.equal(v['example.com'], false);
|
|
75
75
|
// The status subdomain of the single host is a status host, not the hall.
|
|
76
76
|
assert.equal(v['status.emersonian-circles.cloudbongos.com'], false);
|
|
77
77
|
// Unrelated / empty hosts are never the hall.
|
|
@@ -83,16 +83,16 @@ test('conventional two-host instance: the apex stays a NON-builders host (no reg
|
|
|
83
83
|
const v = buildersHostVerdicts(
|
|
84
84
|
{
|
|
85
85
|
domains: {
|
|
86
|
-
publicOrigin: 'https://
|
|
87
|
-
buildersOrigin: 'https://builders.
|
|
88
|
-
statusOrigin: 'https://status.
|
|
86
|
+
publicOrigin: 'https://example.com',
|
|
87
|
+
buildersOrigin: 'https://builders.example.com',
|
|
88
|
+
statusOrigin: 'https://status.example.com',
|
|
89
89
|
},
|
|
90
90
|
},
|
|
91
|
-
['builders.
|
|
91
|
+
['builders.example.com', 'example.com', 'status.example.com'],
|
|
92
92
|
);
|
|
93
93
|
// The dedicated subdomain is the hall (both the startsWith and the config branch agree).
|
|
94
|
-
assert.equal(v['builders.
|
|
94
|
+
assert.equal(v['builders.example.com'], true);
|
|
95
95
|
// The apex is NOT the hall — /builders must still 301 to the subdomain here.
|
|
96
|
-
assert.equal(v['
|
|
97
|
-
assert.equal(v['status.
|
|
96
|
+
assert.equal(v['example.com'], false);
|
|
97
|
+
assert.equal(v['status.example.com'], false);
|
|
98
98
|
});
|
|
@@ -31,7 +31,7 @@ await t('isLocalTarget: localhost / 127.0.0.1 / ::1 / 0.0.0.0 are local', () =>
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
await t('isLocalTarget: prod + LAN hosts are NOT local (remote stays a real failure)', () => {
|
|
34
|
-
for (const h of ['
|
|
34
|
+
for (const h of ['example.com', 'status.example.com', '10.0.0.5', 'example.com']) {
|
|
35
35
|
assert.equal(isLocalTarget(h), false, `${h} should be remote`);
|
|
36
36
|
}
|
|
37
37
|
});
|