@bongos/core 1.19.654 → 1.19.656
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 +54 -49
- package/.env.local.example +3 -3
- package/config/branding.neutral.json +1 -1
- package/docker-compose.yml +1 -1
- package/docs/branding-contract.md +1 -1
- package/docs/copy-inventory.md +26 -26
- package/docs/copy-registry.json +32 -32
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/self-host.md +3 -3
- package/docs/recipes/standalone-live-docs.md +1 -1
- package/docs/recipes/upgrading-the-core.md +1 -1
- package/modules/dev-box/routes/box.js +4 -2
- package/modules/hall-ui/public/watch.js +38 -14
- package/modules/lifecycle/db-rank-authz.js +30 -10
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/box.js +3 -3
- package/scripts/gds/claude-materialize.js +1 -1
- package/scripts/gds/init.js +2 -2
- package/scripts/gds/module.js +3 -2
- package/scripts/gds/oauth-secret.js +6 -3
- package/scripts/gds/provision-units.js +3 -2
- 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-provisioning-tasks.js +1 -1
- package/scripts/gds/status.js +3 -2
- package/scripts/gds/upgrade.js +3 -3
- package/scripts/hall-preview/server.js +1 -1
- package/src/bongos/routes/auth.js +3 -2
- package/src/bongos/routes/backup.js +8 -4
- package/src/bongos/routes/security.js +2 -1
- package/src/bongos/serve-internal.js +2 -2
- package/src/branding.js +24 -7
- package/src/instance-config.js +74 -13
- package/src/module-api.js +1 -1
- package/src/modules.js +15 -7
- package/tests/claude_materialize.mjs +1 -1
- package/tests/cloud_bongos_pack.mjs +1 -1
- package/tests/government_claim_eligibility.mjs +82 -0
- package/tests/init.mjs +1 -1
- package/tests/instance_config.mjs +98 -2
- package/tests/upgrade.mjs +3 -3
- package/tests/watch_applications_queue.mjs +13 -1
- package/tests/watch_sealed_floor.mjs +241 -0
|
@@ -96,7 +96,19 @@
|
|
|
96
96
|
throw { kind: '401' };
|
|
97
97
|
}
|
|
98
98
|
if (res.status === 403) {
|
|
99
|
-
|
|
99
|
+
// Carry the FLOOR the server actually enforced. requirePermission puts
|
|
100
|
+
// `details.floors` (permission key → catalog floor rank) on every denial
|
|
101
|
+
// for exactly this purpose — see its comment in src/bongos/auth.js, which
|
|
102
|
+
// names hardcoded prose as the failure shape ADR 0157 left behind. This
|
|
103
|
+
// page WAS that shape: every panel said "Archons only" while its route had
|
|
104
|
+
// long since moved to a metic-floored atom.
|
|
105
|
+
//
|
|
106
|
+
// One distinct floor is the panel's floor. A multi-atom gate that denies on
|
|
107
|
+
// keys with DIFFERENT floors has no single honest answer, so the panel names
|
|
108
|
+
// none rather than picking one — the same fail-quiet the old copy did not do.
|
|
109
|
+
const details = res.data && res.data.error && res.data.error.details;
|
|
110
|
+
const ranks = details && details.floors ? [...new Set(Object.values(details.floors))] : [];
|
|
111
|
+
throw { kind: '403', floor: ranks.length === 1 ? ranks[0] : null };
|
|
100
112
|
}
|
|
101
113
|
if (!res.ok) throw new Error(`${path} → ${res.status}`);
|
|
102
114
|
return res.data;
|
|
@@ -152,13 +164,25 @@
|
|
|
152
164
|
// `sealedMsg`; any other failure logs `[watch] <name> failed` and shows `errMsg`.
|
|
153
165
|
// NOTE: loadMarks is the deliberate outlier — its failure state is a plain
|
|
154
166
|
// "empty" message, not "unavailable", so it keeps its own try/catch.
|
|
167
|
+
// sealedText — name the floor the DENIAL reported, never one written here.
|
|
168
|
+
// Each `sealedMsg` below is the SUBJECT clause only ("The roster is not visible
|
|
169
|
+
// at your rank."); the rank sentence in front of it is composed from the 403.
|
|
170
|
+
// That is what keeps this page from telling a builder a permission model the
|
|
171
|
+
// server stopped implementing, which is the whole of task 1003449 — and why the
|
|
172
|
+
// floor is not merely corrected to 'metic' here: a hardcoded 'metic' is the same
|
|
173
|
+
// defect one ADR later. Label via rankLabel so an instance that renames its
|
|
174
|
+
// ranks reads its own words (ADR 0157).
|
|
175
|
+
function sealedText(floor, subject) {
|
|
176
|
+
return floor ? `${rankLabel(floor)} and up. ${subject}` : subject;
|
|
177
|
+
}
|
|
178
|
+
|
|
155
179
|
async function section(target, fetcher, render, { sealedMsg, errMsg, name }) {
|
|
156
180
|
if (!target) return;
|
|
157
181
|
try {
|
|
158
182
|
render(await fetcher(), target);
|
|
159
183
|
} catch (err) {
|
|
160
184
|
if (err && err.kind === '401') return;
|
|
161
|
-
if (err && err.kind === '403') { renderUnavailable(target, sealedMsg); return; }
|
|
185
|
+
if (err && err.kind === '403') { renderUnavailable(target, sealedText(err.floor, sealedMsg)); return; }
|
|
162
186
|
console.error(`[watch] ${name} failed`, err);
|
|
163
187
|
renderUnavailable(target, errMsg);
|
|
164
188
|
}
|
|
@@ -499,7 +523,7 @@
|
|
|
499
523
|
|
|
500
524
|
function loadGate() {
|
|
501
525
|
return section($('#gate-body'), () => fetchJson(`/access-requests?status=${encodeURIComponent(gateView)}`), renderGate, {
|
|
502
|
-
sealedMsg: '
|
|
526
|
+
sealedMsg: 'Access requests are not visible at your rank.',
|
|
503
527
|
errMsg: 'Could not load access requests.',
|
|
504
528
|
name: 'gate',
|
|
505
529
|
});
|
|
@@ -554,7 +578,7 @@
|
|
|
554
578
|
|
|
555
579
|
function loadReports() {
|
|
556
580
|
return section($('#reports-body'), () => fetchJson('/security/reports'), renderReports, {
|
|
557
|
-
sealedMsg: '
|
|
581
|
+
sealedMsg: 'Security reports are not visible at your rank.',
|
|
558
582
|
errMsg: 'Could not load the security reports.',
|
|
559
583
|
name: 'reports',
|
|
560
584
|
});
|
|
@@ -621,7 +645,7 @@
|
|
|
621
645
|
|
|
622
646
|
function loadEngagementDocs() {
|
|
623
647
|
return section($('#engagement-docs-body'), () => fetchJson('/security/docs'), renderEngagementDocs, {
|
|
624
|
-
sealedMsg: '
|
|
648
|
+
sealedMsg: 'Engagement reports are not visible at your rank.',
|
|
625
649
|
errMsg: 'Could not load the engagement reports.',
|
|
626
650
|
name: 'engagement docs',
|
|
627
651
|
});
|
|
@@ -667,7 +691,7 @@
|
|
|
667
691
|
|
|
668
692
|
function loadOverrides() {
|
|
669
693
|
return section($('#overrides-body'), () => fetchJson('/override-requests?status=open'), renderOverrides, {
|
|
670
|
-
sealedMsg: '
|
|
694
|
+
sealedMsg: 'The override queue is not visible at your rank.',
|
|
671
695
|
errMsg: 'Could not load the override queue.',
|
|
672
696
|
name: 'overrides',
|
|
673
697
|
});
|
|
@@ -706,7 +730,7 @@
|
|
|
706
730
|
|
|
707
731
|
function loadAudit() {
|
|
708
732
|
return section($('#audit-body'), () => fetchJson('/audit-log?limit=50'), renderAudit, {
|
|
709
|
-
sealedMsg: '
|
|
733
|
+
sealedMsg: 'The audit log is not visible at your rank.',
|
|
710
734
|
errMsg: 'Could not load the audit log.',
|
|
711
735
|
name: 'audit',
|
|
712
736
|
});
|
|
@@ -826,7 +850,7 @@
|
|
|
826
850
|
|
|
827
851
|
function loadRoster() {
|
|
828
852
|
return section($('#roster-body'), () => fetchJson('/builders/roster'), renderRoster, {
|
|
829
|
-
sealedMsg: '
|
|
853
|
+
sealedMsg: 'The roster is not visible at your rank.',
|
|
830
854
|
errMsg: 'Could not load the roster.',
|
|
831
855
|
name: 'roster',
|
|
832
856
|
});
|
|
@@ -861,7 +885,7 @@
|
|
|
861
885
|
|
|
862
886
|
function loadBoxCosts() {
|
|
863
887
|
return section($('#box-costs-body'), () => fetchJson('/boxes/cost-ledger?limit=100'), renderBoxCosts, {
|
|
864
|
-
sealedMsg: '
|
|
888
|
+
sealedMsg: 'The cost ledger is not visible at your rank.',
|
|
865
889
|
errMsg: 'Could not load the dev-box cost ledger.',
|
|
866
890
|
name: 'box costs',
|
|
867
891
|
});
|
|
@@ -908,7 +932,7 @@
|
|
|
908
932
|
if (data.window_days) target.insertAdjacentHTML('beforeend', foot(`Window: last ${escapeHtml(data.window_days)} days.`));
|
|
909
933
|
} catch (err) {
|
|
910
934
|
if (err && err.kind === '401') return;
|
|
911
|
-
if (err && err.kind === '403') { renderUnavailable(target,
|
|
935
|
+
if (err && err.kind === '403') { renderUnavailable(target, sealedText(err.floor, 'Review data is not visible at your rank.')); return; }
|
|
912
936
|
// 404 / not-yet-built / any other failure → graceful empty state.
|
|
913
937
|
console.error('[watch] marks-by-builder failed', err);
|
|
914
938
|
target.innerHTML = emptyStateHtml('No reviews recorded in this window.');
|
|
@@ -964,7 +988,7 @@
|
|
|
964
988
|
|
|
965
989
|
function loadMasonMarks() {
|
|
966
990
|
return section($('#mason-marks-stats'), () => fetchJson('/public/grades?days=7'), renderMasonMarks, {
|
|
967
|
-
sealedMsg: '
|
|
991
|
+
sealedMsg: 'Review quality data is not visible at your rank.',
|
|
968
992
|
errMsg: 'Could not load review quality data.',
|
|
969
993
|
name: 'public grades',
|
|
970
994
|
});
|
|
@@ -999,7 +1023,7 @@
|
|
|
999
1023
|
|
|
1000
1024
|
function loadSurveyor() {
|
|
1001
1025
|
return section($('#surveyor-stats'), () => fetchJson('/public/estimation-drift'), renderSurveyor, {
|
|
1002
|
-
sealedMsg: '
|
|
1026
|
+
sealedMsg: 'Estimation data is not visible at your rank.',
|
|
1003
1027
|
errMsg: 'Could not load estimation data.',
|
|
1004
1028
|
name: 'estimation drift',
|
|
1005
1029
|
});
|
|
@@ -1177,7 +1201,7 @@
|
|
|
1177
1201
|
|
|
1178
1202
|
function loadLedger() {
|
|
1179
1203
|
return section($('#ledger-stats'), () => fetchJson('/public/repo-health?days=30'), renderLedger, {
|
|
1180
|
-
sealedMsg: '
|
|
1204
|
+
sealedMsg: 'Repo health data is not visible at your rank.',
|
|
1181
1205
|
errMsg: 'Could not load repo health data.',
|
|
1182
1206
|
name: 'repo health',
|
|
1183
1207
|
});
|
|
@@ -1227,7 +1251,7 @@
|
|
|
1227
1251
|
|
|
1228
1252
|
function loadSessions() {
|
|
1229
1253
|
return section($('#sessions-stats'), () => fetchJson('/sessions/search?order=recent&limit=5'), renderSessions, {
|
|
1230
|
-
sealedMsg: '
|
|
1254
|
+
sealedMsg: 'Session data is not visible at your rank.',
|
|
1231
1255
|
errMsg: 'Could not load the session tally.',
|
|
1232
1256
|
name: 'sessions tally',
|
|
1233
1257
|
});
|
|
@@ -80,15 +80,33 @@ function xenosClaimAllowed(rank, newcomerFriendly) {
|
|
|
80
80
|
|
|
81
81
|
// claimPermissionsFor — the builder's effective permission set for the claim
|
|
82
82
|
// decision, read through the `governance` kernel PORT (never by importing the
|
|
83
|
-
// module — the one-way rule, ADR 0083).
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
// • the
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
83
|
+
// module — the one-way rule, ADR 0083).
|
|
84
|
+
//
|
|
85
|
+
// NULL means authority could not be ESTABLISHED, which claimEligibilityAllows
|
|
86
|
+
// reads as "fall back to the incumbent rank rule". Exactly two things mean that:
|
|
87
|
+
// • the governance module is not mounted (no port), so there is no authority
|
|
88
|
+
// system to ask;
|
|
89
|
+
// • the resolver threw, so the answer is unknown rather than empty.
|
|
90
|
+
//
|
|
91
|
+
// AN EMPTY SET IS AN ANSWER, NOT A FAILURE (task 1003816). It used to be folded
|
|
92
|
+
// into the null above, on the stated grounds that governance_002's backfill was
|
|
93
|
+
// run-once and nothing re-assigned a rank-role afterwards — so a builder holding
|
|
94
|
+
// no role row was a live gap rather than a real verdict. That is no longer true,
|
|
95
|
+
// and the three writes that closed it are all shipped:
|
|
96
|
+
// • BV1.R95b rotates the role inside the rank-change transaction, and syncs it
|
|
97
|
+
// on every sign-in (src/bongos/auth-admission.js safeSyncRankRole);
|
|
98
|
+
// • task 1003193 rotates it in-tx on offboard AND reactivation;
|
|
99
|
+
// • governance_005 repaired the rows already written.
|
|
100
|
+
// governance_005 states the intended reading in as many words — a builder with no
|
|
101
|
+
// role row "resolves to the empty set (denied everywhere)" — and
|
|
102
|
+
// syncGovernanceRankRole calls that same outcome "the fail-closed answer". Folding
|
|
103
|
+
// it back into the rank rule is what stopped those three from being true at the
|
|
104
|
+
// claim gate, so the empty set now flows through as a real deny.
|
|
105
|
+
//
|
|
106
|
+
// The residual way to hold no role row is the sign-in sync failing transiently
|
|
107
|
+
// (it is deliberately best-effort so a governance hiccup cannot block a sign-in).
|
|
108
|
+
// Denying the claim is the correct read of that state, and it self-repairs on the
|
|
109
|
+
// next successful sign-in.
|
|
92
110
|
//
|
|
93
111
|
// Called BEFORE the claim transaction opens: authority is a per-request read (ADR
|
|
94
112
|
// 0016 — resolved server-side, uncached), and taking a second pool connection while
|
|
@@ -98,7 +116,9 @@ async function claimPermissionsFor(builderId) {
|
|
|
98
116
|
if (!governance || typeof governance.resolveBuilderPermissions !== 'function') return null;
|
|
99
117
|
try {
|
|
100
118
|
const keys = await governance.resolveBuilderPermissions(builderId);
|
|
101
|
-
|
|
119
|
+
// A non-array is a broken port contract, not a verdict — resolveBuilderPermissions
|
|
120
|
+
// always returns an array. Treat it as "could not establish", never as "denied".
|
|
121
|
+
if (!Array.isArray(keys)) return null;
|
|
102
122
|
return new Set(keys);
|
|
103
123
|
} catch (err) {
|
|
104
124
|
console.error('[gds] claim eligibility: permission resolve failed, falling back to the rank rule', err);
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.656",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.656",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.656",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/scripts/gds/box.js
CHANGED
|
@@ -312,7 +312,7 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
|
|
|
312
312
|
// session-file fallback (box-source-fetch.sh, out-of-repo on the control-plane
|
|
313
313
|
// — see the cross-repo blocker filed alongside this fix). Baking it explicitly
|
|
314
314
|
// closes that gap for every future box regardless of which script forgets it.
|
|
315
|
-
const apiOrigin =
|
|
315
|
+
const apiOrigin = instanceConfig.resolveEnv('PUBLIC_ORIGIN') || instanceConfig.defaultApiBase();
|
|
316
316
|
let boxEnv = buildBoxEnv({ apiOrigin });
|
|
317
317
|
let terminalHostname = null;
|
|
318
318
|
let terminalCredential = null;
|
|
@@ -380,7 +380,7 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
|
|
|
380
380
|
// operator's own CLI session happens to be signed into right now — the two can
|
|
381
381
|
// differ (an operator provisioning boxes across instances) and the box must
|
|
382
382
|
// still call home to itself, not to the operator's other login.
|
|
383
|
-
api_base:
|
|
383
|
+
api_base: instanceConfig.resolveEnv('PUBLIC_ORIGIN') || instanceConfig.defaultApiBase(),
|
|
384
384
|
}, null, 2);
|
|
385
385
|
|
|
386
386
|
const doApi = makeDo(apply);
|
|
@@ -1204,7 +1204,7 @@ async function cmdStatus(pool, boxes) {
|
|
|
1204
1204
|
|
|
1205
1205
|
let APPLY = false;
|
|
1206
1206
|
function actorTag() {
|
|
1207
|
-
const who =
|
|
1207
|
+
const who = instanceConfig.resolveEnv('ACTOR') || process.env.USER || 'cli';
|
|
1208
1208
|
return `cli:${who}`;
|
|
1209
1209
|
}
|
|
1210
1210
|
function fail(msg) { console.error(`✖ ${msg}`); return { ok: false, error: msg }; }
|
|
@@ -121,7 +121,7 @@ function buildRebrandRules(from = {}, to = {}) {
|
|
|
121
121
|
add(`.config/${fc}`, `.config/${tc}`);
|
|
122
122
|
add(`/etc/${fc}`, `/etc/${tc}`);
|
|
123
123
|
add(`[${fc}]`, `[${tc}]`);
|
|
124
|
-
// The instance env-var prefix (e.g. OTB_ →
|
|
124
|
+
// The instance env-var prefix (e.g. OTB_ → BONGOS_), bounded by its underscore.
|
|
125
125
|
add(`${effEnvPrefix(from)}_`, `${effEnvPrefix(to)}_`);
|
|
126
126
|
// task 1002672: the bare User-Agent brand token (e.g. `amazonprimea-gds-cli/1.0`
|
|
127
127
|
// in a hook's outbound fetch). It carries no TLD, so none of the HOST rules above
|
package/scripts/gds/init.js
CHANGED
|
@@ -73,7 +73,7 @@ const path = require('node:path');
|
|
|
73
73
|
const readline = require('node:readline');
|
|
74
74
|
const { apiCall, arg, hasFlag } = require('./cli-lib');
|
|
75
75
|
const { DEFAULT_HIERARCHY } = require('../../src/bongos/hierarchy-config');
|
|
76
|
-
const { resolveCoreRoot } = require('../../src/instance-config'); // ADR 0108 §1: where the core package lives
|
|
76
|
+
const { resolveCoreRoot, FALLBACK_ENV_PREFIX } = require('../../src/instance-config'); // ADR 0108 §1: where the core package lives
|
|
77
77
|
const { materializeClaude } = require('./claude-materialize'); // ADR 0108 §3 (W3): copy .claude/ into the instance
|
|
78
78
|
const { detectStack, formatStackSummary } = require('./adopt-detect'); // ADR 0121 §Decision 4 (task 2043): inspect an existing repo before layering
|
|
79
79
|
const { resolveBacklogTasks, DEFAULT_MAX: BACKLOG_MAX } = require('./adopt-import'); // ADR 0121 §Decision 3 (task 2044): import an existing backlog
|
|
@@ -250,7 +250,7 @@ function buildBrandingConfig(spec) {
|
|
|
250
250
|
repo: { owner: spec.repo.owner, name: spec.repo.name },
|
|
251
251
|
firstAdmin: spec.firstAdmin,
|
|
252
252
|
currency: { label: (spec.currency && spec.currency.label) || 'credits', symbol: (spec.currency && spec.currency.symbol) || '' },
|
|
253
|
-
envPrefix: spec.envPrefix ||
|
|
253
|
+
envPrefix: spec.envPrefix || FALLBACK_ENV_PREFIX,
|
|
254
254
|
// Every instance gets its OWN session/config dir (task 1002626). Without
|
|
255
255
|
// this, instance-config.js falls back to the shared 'cloudbongos' dir, so
|
|
256
256
|
// every un-branded instance on a machine reads/writes the SAME
|
package/scripts/gds/module.js
CHANGED
|
@@ -93,8 +93,9 @@ function readJson(p) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
function envPrefix() {
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
const { FALLBACK_ENV_PREFIX } = require('../../src/instance-config');
|
|
97
|
+
try { return require('../../src/branding').branding().envPrefix || FALLBACK_ENV_PREFIX; }
|
|
98
|
+
catch { return FALLBACK_ENV_PREFIX; }
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
// ---------------------------------------------------------------------------
|
|
@@ -59,12 +59,15 @@ function validateClientId(id) {
|
|
|
59
59
|
|
|
60
60
|
// The env-var prefix the provisioned instance resolves (the neutral pack's envPrefix,
|
|
61
61
|
// mirroring provision.js provisionedEnvPrefix — the creds MUST be written under this
|
|
62
|
-
// prefix or src/branding + auth never read them, task 1972). Fail-soft to
|
|
62
|
+
// prefix or src/branding + auth never read them, task 1972). Fail-soft to the
|
|
63
|
+
// core's own FALLBACK_ENV_PREFIX so this cannot drift from the pack again — it
|
|
64
|
+
// was a hardcoded 'CLOUDBONGOS' until task 1003703 made BONGOS canonical.
|
|
63
65
|
function provisionedEnvPrefix() {
|
|
66
|
+
const { FALLBACK_ENV_PREFIX } = require('../../src/instance-config');
|
|
64
67
|
try {
|
|
65
68
|
const pack = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, PROVISIONED_BRANDING_FILE), 'utf8'));
|
|
66
|
-
return pack.envPrefix ||
|
|
67
|
-
} catch { return
|
|
69
|
+
return pack.envPrefix || FALLBACK_ENV_PREFIX;
|
|
70
|
+
} catch { return FALLBACK_ENV_PREFIX; }
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
function webEnvPath(slug, { etcBase = ETC_BASE } = {}) {
|
|
@@ -152,10 +152,11 @@ const ENV_PREFIX_PACK = 'config/branding.neutral.json';
|
|
|
152
152
|
// was a latent bug: it never matched the neutral pack's CLOUDBONGOS envPrefix, so
|
|
153
153
|
// the OAuth creds were silently unreadable — task 1972.)
|
|
154
154
|
function provisionedEnvPrefix() {
|
|
155
|
+
const { FALLBACK_ENV_PREFIX } = require('../../src/instance-config');
|
|
155
156
|
try {
|
|
156
157
|
const pack = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, ENV_PREFIX_PACK), 'utf8'));
|
|
157
|
-
return pack.envPrefix ||
|
|
158
|
-
} catch { return
|
|
158
|
+
return pack.envPrefix || FALLBACK_ENV_PREFIX;
|
|
159
|
+
} catch { return FALLBACK_ENV_PREFIX; }
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
// The per-instance secrets file body (the standup runbook's /etc/<inst>/web.env).
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Run: node scripts/gds/seed-bongos-coreB-tranche1-tasks.js
|
|
8
8
|
const os = require('os');
|
|
9
9
|
const TOKEN = require(os.homedir() + '/.config/otb/gds-session.json').token;
|
|
10
|
-
const BASE =
|
|
10
|
+
const BASE = require('../../src/instance-config').resolveEnv('BASE') || 'https://amazonprimea.com';
|
|
11
11
|
const H = { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' };
|
|
12
12
|
|
|
13
13
|
// ref → task spec. depends_on_refs are local R## refs resolved after create.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Run: node scripts/gds/seed-bongos-coreB-tranche2-tasks.js
|
|
10
10
|
const os = require('os');
|
|
11
11
|
const TOKEN = require(os.homedir() + '/.config/otb/gds-session.json').token;
|
|
12
|
-
const BASE =
|
|
12
|
+
const BASE = require('../../src/instance-config').resolveEnv('BASE') || 'https://amazonprimea.com';
|
|
13
13
|
const H = { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' };
|
|
14
14
|
|
|
15
15
|
// ref → task spec. deps are local R## refs resolved after create. Clean-slicing
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Run: node scripts/gds/seed-provisioning-tasks.js
|
|
8
8
|
const os = require('os');
|
|
9
9
|
const TOKEN = require(os.homedir() + '/.config/otb/gds-session.json').token;
|
|
10
|
-
const BASE =
|
|
10
|
+
const BASE = require('../../src/instance-config').resolveEnv('BASE') || 'https://amazonprimea.com';
|
|
11
11
|
const H = { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' };
|
|
12
12
|
const GOAL = 26; // Cloud Bongos productization and self-host
|
|
13
13
|
|
package/scripts/gds/status.js
CHANGED
|
@@ -20,12 +20,13 @@
|
|
|
20
20
|
// Exit codes: 0 ok · 1 network/api error · 2 bad invocation · 3 not found.
|
|
21
21
|
|
|
22
22
|
const { API_BASE, cliClient, assertInstanceMatch } = require('./cli-lib');
|
|
23
|
+
const { resolveEnv } = require('../../src/instance-config');
|
|
23
24
|
|
|
24
|
-
// The active internal version; override with
|
|
25
|
+
// The active internal version; override with <PREFIX>_STATUS_DEFAULT_VERSION (or pass
|
|
25
26
|
// --version). Bumped GDS-V4 → BONGOS-V1 (task 1709, ADR 0103): GDS-V4 was
|
|
26
27
|
// consolidated into BONGOS-V1 (the Cloud Bongos platform version) and frozen, so
|
|
27
28
|
// the bare `/status` card should default to the live version, not a sealed one.
|
|
28
|
-
const DEFAULT_VERSION =
|
|
29
|
+
const DEFAULT_VERSION = resolveEnv('STATUS_DEFAULT_VERSION') || 'BONGOS-V1';
|
|
29
30
|
|
|
30
31
|
// Compact, status-first ordering for the "remaining: …" summaries.
|
|
31
32
|
const STATUS_ORDER = ['shipped', 'confirmed', 'completed', 'claimed', 'ready', 'blocked', 'backlog'];
|
package/scripts/gds/upgrade.js
CHANGED
|
@@ -358,7 +358,7 @@ function resolveInstanceDb({ instanceDir }, run = spawnSync) {
|
|
|
358
358
|
const r = run(process.execPath, ['-e', "process.stdout.write(require(process.argv[1]).instanceDbName() || '')", poolPath], {
|
|
359
359
|
cwd: instanceDir,
|
|
360
360
|
encoding: 'utf8',
|
|
361
|
-
env: { ...process.env,
|
|
361
|
+
env: { ...process.env, BONGOS_INSTANCE_ROOT: instanceDir },
|
|
362
362
|
});
|
|
363
363
|
if (!r || r.error || r.status !== 0 || typeof r.stdout !== 'string') return { db: null, ran: false };
|
|
364
364
|
return { db: String(r.stdout).trim() || null, ran: true };
|
|
@@ -377,7 +377,7 @@ function resolveInstanceDb({ instanceDir }, run = spawnSync) {
|
|
|
377
377
|
// (fitness `gen-api-docs --check` + the api_client test backstop it on the next ship).
|
|
378
378
|
// Runs AFTER npm install so the generators come from the NEW core.
|
|
379
379
|
function regenerateApiArtifacts({ instanceDir, coreRoot }, run = spawnSync, fsImpl = fs) {
|
|
380
|
-
const env = { ...process.env,
|
|
380
|
+
const env = { ...process.env, BONGOS_INSTANCE_ROOT: instanceDir };
|
|
381
381
|
const ran = [];
|
|
382
382
|
for (const gen of ['gen-api-docs.js', 'gen-api-client.js']) {
|
|
383
383
|
const script = path.join(coreRoot, 'scripts', 'gds', gen);
|
|
@@ -416,7 +416,7 @@ function regenerateApiArtifacts({ instanceDir, coreRoot }, run = spawnSync, fsIm
|
|
|
416
416
|
// an upgrade is precisely what destroys them (task 1002441).
|
|
417
417
|
const NAV_WHOLE_FILE_GENERATORS = ['gen-session-index.js'];
|
|
418
418
|
function regenerateNavDocs({ instanceDir, coreRoot, generators = NAV_WHOLE_FILE_GENERATORS }, run = spawnSync, fsImpl = fs) {
|
|
419
|
-
const env = { ...process.env,
|
|
419
|
+
const env = { ...process.env, BONGOS_INSTANCE_ROOT: instanceDir };
|
|
420
420
|
const ran = [];
|
|
421
421
|
const failed = [];
|
|
422
422
|
for (const gen of generators) {
|
|
@@ -66,7 +66,7 @@ const MODULES_ON = String(arg('--modules-on') || '').split(',').map((s) => s.tri
|
|
|
66
66
|
// and it is the explicit way to preview the signed-out fallback (task 1003315).
|
|
67
67
|
const FIXTURE_ME = process.argv.includes('--fixture-me');
|
|
68
68
|
const BRAND_FILE = arg('--brand') || 'config/branding.neutral.json';
|
|
69
|
-
process.env.
|
|
69
|
+
process.env.BONGOS_BRANDING_FILE = path.resolve(ROOT, BRAND_FILE);
|
|
70
70
|
|
|
71
71
|
/* eslint-disable import/no-dynamic-require */
|
|
72
72
|
// express comes from this tree's node_modules when installed, else node's
|
|
@@ -40,6 +40,7 @@ const { pairStore } = require('../app-pair');
|
|
|
40
40
|
const { publicOrigin, parseCookie, validateOrRespond } = require('./_helpers');
|
|
41
41
|
const { accountExistenceReadRateLimit } = require('../middleware/rate-limit');
|
|
42
42
|
const { branding } = require('../../branding');
|
|
43
|
+
const { resolveEnv } = require('../../instance-config');
|
|
43
44
|
const seams = require('../../module-seams');
|
|
44
45
|
// task 1003208: structured logging — was console.*. A core file logs through
|
|
45
46
|
// the kernel logger directly; only MODULES go via api.logger (the doorway).
|
|
@@ -373,14 +374,14 @@ function postLoginLanding(brand) {
|
|
|
373
374
|
// the request's x-forwarded-host (spoofable; F2 Hacker finding). Configured via
|
|
374
375
|
// env, falling back to the configured canonical public origin, so an injected host
|
|
375
376
|
// cannot rewrite the redirect and Discord's URI registration is not the SOLE backstop.
|
|
376
|
-
const DISCORD_REDIRECT_ORIGIN =
|
|
377
|
+
const DISCORD_REDIRECT_ORIGIN = resolveEnv('PUBLIC_ORIGIN') || configuredOAuthOrigin();
|
|
377
378
|
const DISCORD_REDIRECT_URI = `${DISCORD_REDIRECT_ORIGIN}/api/gds/auth/discord/callback`;
|
|
378
379
|
|
|
379
380
|
// The GitHub web-OAuth redirect_uri origin (#734). PINNED to the canonical
|
|
380
381
|
// public origin (same env knob as Discord), never the request host — GitHub
|
|
381
382
|
// matches redirect_uri against the app's single registered callback host, so a
|
|
382
383
|
// sign-in started on a builders.* subdomain must still use the apex.
|
|
383
|
-
const WEB_REDIRECT_ORIGIN =
|
|
384
|
+
const WEB_REDIRECT_ORIGIN = resolveEnv('PUBLIC_ORIGIN') || configuredOAuthOrigin();
|
|
384
385
|
|
|
385
386
|
// The ship-time hub push (task 1002803, ADR 0171 amendment R01) lives here
|
|
386
387
|
// because this file already owns the OTHER trigger: the federated sign-in flows
|
|
@@ -22,19 +22,23 @@ const path = require('node:path');
|
|
|
22
22
|
const { execFile } = require('node:child_process');
|
|
23
23
|
const auth = require('../auth');
|
|
24
24
|
const { branding } = require('../../branding');
|
|
25
|
+
const { resolveEnv } = require('../../instance-config');
|
|
25
26
|
const { asyncHandler } = require('./_helpers');
|
|
26
27
|
// task 1003208: structured logging — was console.*. A core file logs through
|
|
27
28
|
// the kernel logger directly; only MODULES go via api.logger (the doorway).
|
|
28
29
|
const log = require('../logger').logger.child({ src: 'backup' });
|
|
29
30
|
|
|
30
31
|
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
|
|
31
|
-
|
|
32
|
+
// These four knobs are set in the droplet's systemd unit, so they resolve through
|
|
33
|
+
// resolveEnv() — <PREFIX>_BACKUP_* first, then the legacy spellings including the
|
|
34
|
+
// GDS_BACKUP_* the deployed unit still exports (task 1003703).
|
|
35
|
+
const BACKUP_DIR = resolveEnv('BACKUP_DIR') || '/var/backups/gds';
|
|
32
36
|
// The DB name comes from the branding pack (branding.db.database), never a
|
|
33
37
|
// hardcoded host brand (ADR 0062 §3) — a non-OTB instance dumps its own DB.
|
|
34
|
-
const DB_NAME =
|
|
38
|
+
const DB_NAME = resolveEnv('BACKUP_DB')
|
|
35
39
|
|| (() => { try { return branding().db && branding().db.database; } catch { return null; } })()
|
|
36
40
|
|| 'gds';
|
|
37
|
-
const BACKUP_SCRIPT =
|
|
41
|
+
const BACKUP_SCRIPT = resolveEnv('BACKUP_SCRIPT')
|
|
38
42
|
|| path.join(REPO_ROOT, 'scripts', 'gds', 'db-backup-nightly.sh');
|
|
39
43
|
|
|
40
44
|
// In-memory guard: true while a trigger-initiated dump is running.
|
|
@@ -42,7 +46,7 @@ let triggerRunning = false;
|
|
|
42
46
|
|
|
43
47
|
// A dump older than this counts as STALE. The nightly timer fires daily, so 36h
|
|
44
48
|
// allows one missed run plus slack before we call it broken.
|
|
45
|
-
const STALE_AFTER_HOURS = Number(
|
|
49
|
+
const STALE_AFTER_HOURS = Number(resolveEnv('BACKUP_STALE_HOURS')) || 36;
|
|
46
50
|
|
|
47
51
|
// parseDumpTimestamp — the dump filename stamp `YYYY-MM-DDTHHMMSSZ` is ALMOST
|
|
48
52
|
// ISO-8601 but omits the time separators, so Date.parse rejects it. Re-insert
|
|
@@ -21,6 +21,7 @@ const fs = require('node:fs/promises');
|
|
|
21
21
|
const auth = require('../auth');
|
|
22
22
|
const db = require('../db');
|
|
23
23
|
const { validateOrRespond } = require('./_helpers');
|
|
24
|
+
const { resolveEnv } = require('../../instance-config');
|
|
24
25
|
|
|
25
26
|
// Slug shape for security_docs (task 991): lowercase kebab, 1–128 chars. Used to
|
|
26
27
|
// validate POST /security/docs and to safely interpolate :slug into the GET.
|
|
@@ -36,7 +37,7 @@ const SECURITY_DOC_CONTENT_MAX = 2_000_000;
|
|
|
36
37
|
// staging can point at a different file; the recommended prod path is
|
|
37
38
|
// /etc/amazonprimea/security-adr.md (chmod 600, owned by the deploy user).
|
|
38
39
|
function adrPath() {
|
|
39
|
-
return (
|
|
40
|
+
return (resolveEnv('SECURITY_ADR_PATH') || '').trim() || null;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
// Read the full ADR off disk. Returns { adr_md, source, fetched_at } on
|
|
@@ -51,7 +51,7 @@ const { stealthGate } = require('./stealth-gate');
|
|
|
51
51
|
const { platformVisibilityGate } = require('./platform-visibility-gate');
|
|
52
52
|
const { clientModules, isModuleEnabled } = require('../modules');
|
|
53
53
|
const { hallWidgetScripts, moduleWebSurfaces } = require('../module-loader/loader');
|
|
54
|
-
const { resolveCoreRoot, resolveDocsRoot, resolveInstanceRoot } = require('../instance-config');
|
|
54
|
+
const { resolveCoreRoot, resolveDocsRoot, resolveInstanceRoot, resolveEnv } = require('../instance-config');
|
|
55
55
|
const { API_PREFIX, LEGACY_API_PREFIXES, API_VERSION, VERSIONED_API_PREFIX, ALL_API_PREFIXES } = require('./api-prefix');
|
|
56
56
|
|
|
57
57
|
// ADR 0108 §1: every use of ROOT below (infra/ installers, modules/, docs/) is
|
|
@@ -640,7 +640,7 @@ function mountInternalSurfaces(app) {
|
|
|
640
640
|
// Pinned public origin for the served installer scripts (never a request header —
|
|
641
641
|
// a spoofed Host can't redirect a `curl | sh`). The fallback comes from the
|
|
642
642
|
// branding pack, not a hardcoded host brand (ADR 0062 §3).
|
|
643
|
-
const BONGOS_PUBLIC_ORIGIN =
|
|
643
|
+
const BONGOS_PUBLIC_ORIGIN = resolveEnv('PUBLIC_ORIGIN') || branding().domains.publicOrigin;
|
|
644
644
|
const readBongosInstallScript = (name) => {
|
|
645
645
|
try {
|
|
646
646
|
return fs.readFileSync(path.join(ROOT, 'infra', name), 'utf8').split('__GDS_API_BASE__').join(BONGOS_PUBLIC_ORIGIN);
|
package/src/branding.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
const fs = require('node:fs');
|
|
20
20
|
const path = require('node:path');
|
|
21
|
-
const { resolveCoreRoot, resolveInstanceRoot } = require('./instance-config');
|
|
21
|
+
const { resolveCoreRoot, resolveInstanceRoot, resolveEnv, FALLBACK_ENV_PREFIX } = require('./instance-config');
|
|
22
22
|
|
|
23
23
|
// ADR 0108 §1: the neutral starter ships WITH the core package; the instance
|
|
24
24
|
// pack is host content. Both resolvers return the repo root today, so this is
|
|
@@ -106,9 +106,15 @@ function resolveBranding({ neutral = {}, instance = {}, env = {} } = {}) {
|
|
|
106
106
|
// neutral/instance inputs — otherwise the deepFreeze below would also freeze
|
|
107
107
|
// the caller's input objects (deepMerge shallow-copies only the top level).
|
|
108
108
|
const resolved = structuredClone(deepMerge(neutral, instance));
|
|
109
|
-
const prefix = resolved.envPrefix ||
|
|
109
|
+
const prefix = resolved.envPrefix || FALLBACK_ENV_PREFIX;
|
|
110
110
|
for (const [suffix, p, coerce] of ENV_OVERRIDES) {
|
|
111
|
-
|
|
111
|
+
// resolveEnv, not a bare env[] read: these overrides are set OUTSIDE the repo
|
|
112
|
+
// (/etc/cloudbongos/web.env, systemd units, the provisioning runner), so when
|
|
113
|
+
// task 1003703 moved the canonical prefix from CLOUDBONGOS_ to BONGOS_ a
|
|
114
|
+
// direct read would have silently stopped honoring every already-deployed
|
|
115
|
+
// CLOUDBONGOS_PUBLIC_ORIGIN — an origin regression with no error, found only
|
|
116
|
+
// by a broken OAuth callback. The legacy spellings warn once each instead.
|
|
117
|
+
const v = resolveEnv(suffix, { env, prefix });
|
|
112
118
|
if (v === undefined || v === '') continue;
|
|
113
119
|
// Most overrides are string-valued (origins, cookie domain) and set verbatim. A
|
|
114
120
|
// 'bool'-tagged override (auth.idp) coerces the env string to a real boolean —
|
|
@@ -139,15 +145,26 @@ function readJson(p) {
|
|
|
139
145
|
// OTB on the apex (server.js, the committed config/branding.json) and a vanilla
|
|
140
146
|
// Cloud Bongos process pointed at its own pack (config/branding.cloudbongos.json)
|
|
141
147
|
// — differentiated only by env, with no divergent clone to maintain. The loader
|
|
142
|
-
// runs before the resolved envPrefix is known, so this is a FIXED env name
|
|
143
|
-
//
|
|
144
|
-
//
|
|
148
|
+
// runs before the resolved envPrefix is known, so this is a FIXED env name, not
|
|
149
|
+
// a <PREFIX>_ one — which is exactly why it cannot go through resolveEnv() and
|
|
150
|
+
// needs the two spellings listed by hand here. A relative path resolves against
|
|
151
|
+
// the repo root.
|
|
152
|
+
//
|
|
153
|
+
// BONGOS_BRANDING_FILE is canonical since task 1003703; GDS_BRANDING_FILE is
|
|
154
|
+
// still read (one deprecation warning per process) because it is set outside the
|
|
155
|
+
// repo — the hall-preview server, CI jobs and side-by-side dev processes all
|
|
156
|
+
// export it. Task 1003706 retires the old spelling.
|
|
145
157
|
function loadBranding({ neutralPath = NEUTRAL_PATH, instancePath, env = process.env } = {}) {
|
|
146
158
|
const neutral = readJson(neutralPath);
|
|
147
159
|
if (!neutral) throw new Error(`branding: neutral starter not found at ${neutralPath}`);
|
|
148
160
|
let ip = instancePath;
|
|
149
161
|
if (ip === undefined) {
|
|
150
|
-
|
|
162
|
+
// prefix pinned to FALLBACK_ENV_PREFIX, not envPrefix(): this read happens
|
|
163
|
+
// BEFORE the pack that would name the prefix can be loaded, so the canonical
|
|
164
|
+
// spelling has to be a fixed one. Same pin, same reason, as
|
|
165
|
+
// resolveInstanceRootExplicit(). The legacy prefixes still resolve, which is
|
|
166
|
+
// what keeps every already-deployed GDS_BRANDING_FILE working.
|
|
167
|
+
const override = resolveEnv('BRANDING_FILE', { env, prefix: FALLBACK_ENV_PREFIX });
|
|
151
168
|
ip = override ? path.resolve(resolveInstanceRoot(), override) : INSTANCE_PATH;
|
|
152
169
|
}
|
|
153
170
|
const instance = readJson(ip) || {};
|