@bongos/core 1.19.726 → 1.19.728
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 +177 -492
- 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/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_package.mjs +40 -1
- 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/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
|
];
|