@bongos/core 1.17.3 → 1.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +212 -332
- package/.devcontainer/healthcheck.sh +0 -0
- package/.devcontainer/init-firewall.sh +0 -0
- package/.devcontainer/post-create.sh +0 -0
- package/GOVERNANCE.md +1 -1
- package/README.md +26 -22
- package/bin/bongos.js +0 -0
- package/config/branding.neutral.json +5 -1
- package/docker-entrypoint.sh +0 -0
- package/docs/adr/0142-project-context-is-prose-only.md +41 -0
- package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
- package/docs/adr/README.md +6 -0
- package/docs/api/openapi.json +1630 -632
- package/docs/api-reference.md +50 -15
- package/docs/file-map.md +0 -18
- package/docs/onboarding/diagrams/05-versioning.mmd +4 -6
- package/docs/onboarding/diagrams/assertions.json +5 -9
- package/docs/recipes/packaging-the-core.md +13 -0
- package/docs/recipes/upgrading-the-core.md +5 -2
- package/migrations/039_newcomer_friendly_column.sql +5 -0
- package/migrations/102_remove_box_first_provision_approval.sql +4 -0
- package/migrations/core_192_github_oauth_tokens.sql +33 -0
- package/modules/hall-ui/public/builders.js +37 -0
- package/modules/hall-ui/public/goals-page.js +98 -176
- package/modules/hall-ui/public/goals.css +183 -0
- package/modules/hall-ui/public/goals.html +35 -261
- package/modules/hall-ui/public/goals.js +7 -2
- package/modules/hall-ui/public/hall-widgets.js +12 -4
- package/modules/hall-ui/public/my-projects.html +78 -0
- package/modules/hall-ui/public/my-projects.js +142 -0
- package/modules/hall-ui/public/scouting.html +45 -0
- package/modules/hall-ui/public/scouting.js +73 -0
- package/modules/hall-ui/public/settings.css +355 -0
- package/modules/hall-ui/public/settings.html +86 -414
- package/modules/hall-ui/public/settings.js +96 -35
- package/modules/hall-ui/public/style.css +31 -0
- package/modules/hall-ui/public/watch.css +184 -0
- package/modules/hall-ui/public/watch.html +111 -285
- package/modules/hall-ui/public/watch.js +59 -21
- package/modules/status-ui/public/explore.html +54 -0
- package/modules/status-ui/public/explore.js +67 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/create.html +292 -19
- package/public-landing/index.html +11 -76
- package/scripts/cron/style_guide_audit.sh +0 -0
- package/scripts/gds/api.js +0 -0
- package/scripts/gds/architect-audit.js +0 -0
- package/scripts/gds/audit-authorship.sh +0 -0
- package/scripts/gds/audit-replay.sh +0 -0
- package/scripts/gds/box.js +0 -0
- package/scripts/gds/capture.js +0 -0
- package/scripts/gds/cli-lib.js +11 -5
- package/scripts/gds/conductor-dispatch.js +0 -0
- package/scripts/gds/coreB-carve-proof.sh +0 -0
- package/scripts/gds/db-backup-nightly.sh +0 -0
- package/scripts/gds/db-backup-offsite.sh +0 -0
- package/scripts/gds/doctor.js +0 -0
- package/scripts/gds/grade-smoke.js +0 -0
- package/scripts/gds/hooks/pre-push.js +0 -0
- package/scripts/gds/install-git-hooks.js +0 -0
- package/scripts/gds/lint-migrations.js +102 -0
- package/scripts/gds/login.js +42 -10
- package/scripts/gds/main-audit.js +0 -0
- package/scripts/gds/mirror-redact.js +5 -0
- package/scripts/gds/oauth-secret.js +0 -0
- package/scripts/gds/onboarding-analyze.js +0 -0
- package/scripts/gds/onboarding-ship.js +0 -0
- package/scripts/gds/package-core.js +263 -4
- package/scripts/gds/paste-token.js +0 -0
- package/scripts/gds/portability-smoke-art.sh +0 -0
- package/scripts/gds/portability-smoke-memory.sh +0 -0
- package/scripts/gds/provision.js +270 -11
- package/scripts/gds/publish-manifest.js +15 -0
- package/scripts/gds/push-main.js +0 -0
- package/scripts/gds/render-ideas.js +0 -0
- package/scripts/gds/run-unit-tests.js +38 -14
- package/scripts/gds/security-baseline-check.sh +0 -0
- package/scripts/gds/session.js +0 -0
- package/scripts/gds/setup.js +41 -0
- package/scripts/gds/ship.js +126 -32
- package/scripts/gds/smoke-batch-claim.sh +0 -0
- package/scripts/gds/smoke-batch-release.sh +0 -0
- package/scripts/gds/smoke-box.sh +0 -0
- package/scripts/gds/smoke-claim-consistency.sh +0 -0
- package/scripts/gds/smoke-dependencies.sh +0 -0
- package/scripts/gds/smoke-write.sh +0 -0
- package/scripts/gds/upgrade.js +147 -5
- package/scripts/hall-preview/server.js +18 -1
- package/scripts/migrate.sh +10 -0
- package/scripts/migrate_cost_ledger.sh +0 -0
- package/scripts/render-diagrams.sh +0 -0
- package/scripts/security/dep-audit.js +0 -0
- package/scripts/setup-builder.js +0 -0
- package/scripts/setup-builder.sh +0 -0
- package/scripts/snapshots/maybe-render-on-deploy.sh +0 -0
- package/src/bongos/auth.js +362 -5
- package/src/bongos/db.js +66 -0
- package/src/bongos/github-repos.js +93 -0
- package/src/bongos/routes/auth.js +93 -11
- package/src/bongos/routes/github.js +90 -0
- package/src/bongos/routes/instance.js +15 -3
- package/src/bongos/routes.js +2 -0
- package/src/branding.js +17 -2
- package/src/instance-config.js +16 -0
- package/src/module-api.js +48 -1
- package/tests/branding.mjs +29 -23
- package/tests/github_repos_helpers.mjs +91 -0
- package/tests/github_token_store.mjs +121 -0
- package/tests/idp_device_exchange.mjs +105 -0
- package/tests/idp_federation.mjs +100 -0
- package/tests/migrations_forward_safe.mjs +86 -0
- package/tests/module_api.mjs +1 -1
- package/tests/package_core.mjs +246 -1
- package/tests/provision.mjs +226 -0
- package/tests/pruned_lockfile_integrity.mjs +79 -0
- package/tests/ship_ci_deploy.mjs +60 -0
- package/tests/upgrade.mjs +254 -5
- package/.gitignore +0 -91
- package/scripts/hall-preview/fixtures/access-requests.json +0 -1
- package/scripts/hall-preview/fixtures/achievements.json +0 -76
- package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
- package/scripts/hall-preview/fixtures/audit-log.json +0 -1
- package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -1
- package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
- package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
- package/scripts/hall-preview/fixtures/builders__roster.json +0 -1
- package/scripts/hall-preview/fixtures/goals.json +0 -502
- package/scripts/hall-preview/fixtures/goals__25.json +0 -96
- package/scripts/hall-preview/fixtures/goals__26.json +0 -67
- package/scripts/hall-preview/fixtures/goals__3.json +0 -103
- package/scripts/hall-preview/fixtures/goals__35.json +0 -49
- package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
- package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
- package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
- package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
- package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -1
- package/scripts/hall-preview/fixtures/inbox.json +0 -330
- package/scripts/hall-preview/fixtures/instance.json +0 -124
- package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
- package/scripts/hall-preview/fixtures/me.json +0 -476
- package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
- package/scripts/hall-preview/fixtures/override-requests.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
- package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
- package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
- package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
- package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
- package/scripts/hall-preview/fixtures/public__grades.json +0 -50
- package/scripts/hall-preview/fixtures/public__progress.json +0 -124
- package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
- package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
- package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
- package/scripts/hall-preview/fixtures/security__docs.json +0 -1
- package/scripts/hall-preview/fixtures/security__reports.json +0 -1
- package/scripts/hall-preview/fixtures/sessions__search.json +0 -1
- package/scripts/hall-preview/fixtures/tasks.json +0 -2420
- package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
- package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
- package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
- package/scripts/hall-preview/fixtures/versions.json +0 -89
|
@@ -45,20 +45,17 @@ body{
|
|
|
45
45
|
.stage{
|
|
46
46
|
position:relative;z-index:1;min-height:100vh;
|
|
47
47
|
display:flex;flex-direction:column;align-items:center;justify-content:center;
|
|
48
|
-
text-align:center;padding:48px 24px
|
|
48
|
+
text-align:center;padding:48px 24px 64px;gap:8px;
|
|
49
49
|
}
|
|
50
50
|
/* a soft white glow behind the mascot so a white- or transparent-bg PNG blends */
|
|
51
51
|
.hero-mascot{
|
|
52
52
|
position:relative;width:min(540px,86vw);margin-bottom:6px;
|
|
53
|
-
/* cap by height on short viewports so the hall CTA + sign-in hint always clear
|
|
54
|
-
the fixed footer (task 2094); on tall desktops the width cap wins, unchanged */
|
|
55
|
-
max-height:50vh;display:flex;justify-content:center;
|
|
56
53
|
}
|
|
57
54
|
.hero-mascot::before{
|
|
58
55
|
content:"";position:absolute;inset:-6% -10%;z-index:-1;border-radius:50%;
|
|
59
56
|
background:radial-gradient(ellipse at center, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.7) 45%, rgba(255,255,255,0) 72%);
|
|
60
57
|
}
|
|
61
|
-
.hero-mascot img{display:block;width:100%;height:auto;
|
|
58
|
+
.hero-mascot img{display:block;width:100%;height:auto;}
|
|
62
59
|
/* fallback shown if buddha.png is not present yet */
|
|
63
60
|
.mascot-fallback{
|
|
64
61
|
width:min(540px,86vw);aspect-ratio:1.6/1;border-radius:28px;
|
|
@@ -80,27 +77,9 @@ body{
|
|
|
80
77
|
border:none;border-radius:999px;padding:15px 32px;text-decoration:none;
|
|
81
78
|
display:inline-flex;align-items:center;gap:9px;
|
|
82
79
|
background:var(--accent);color:#fff;box-shadow:0 12px 28px -10px var(--accent);
|
|
83
|
-
transition:transform .12s ease, box-shadow .12s ease
|
|
80
|
+
transition:transform .12s ease, box-shadow .12s ease;
|
|
84
81
|
}
|
|
85
82
|
.btn:hover{transform:translateY(-1px);box-shadow:0 16px 34px -10px var(--accent);}
|
|
86
|
-
.btn.ghost{background:rgba(255,255,255,0.75);color:var(--ink);box-shadow:0 6px 18px -8px rgba(42,31,23,0.25);}
|
|
87
|
-
.btn.ghost:hover{box-shadow:0 10px 24px -8px rgba(42,31,23,0.3);}
|
|
88
|
-
/* signed-out: the create door is visible but resting — it hints, and clicking it
|
|
89
|
-
starts the GitHub sign-in (returning straight into /create). */
|
|
90
|
-
.btn.resting{background:#eeddc8;color:#a08a74;box-shadow:none;}
|
|
91
|
-
.btn.resting:hover{background:#ecd6ba;transform:none;box-shadow:none;}
|
|
92
|
-
.cta-row{display:flex;gap:14px;flex-wrap:wrap;justify-content:center;}
|
|
93
|
-
.signin-hint{font-size:13.5px;font-weight:600;color:var(--ink-soft);margin-top:14px;min-height:19px;}
|
|
94
|
-
.signin-hint a{color:var(--accent-deep);text-decoration:none;font-weight:700;}
|
|
95
|
-
.signin-hint a:hover{text-decoration:underline;}
|
|
96
|
-
.who{
|
|
97
|
-
position:fixed;top:16px;right:20px;z-index:2;display:flex;align-items:center;gap:9px;
|
|
98
|
-
font-size:14px;font-weight:600;color:var(--ink-soft);min-height:30px;
|
|
99
|
-
}
|
|
100
|
-
.who img{width:28px;height:28px;border-radius:50%;border:2px solid #fff;box-shadow:0 2px 8px rgba(42,31,23,0.14);}
|
|
101
|
-
.who a{color:var(--accent-deep);text-decoration:none;font-weight:700;}
|
|
102
|
-
.who a:hover{text-decoration:underline;}
|
|
103
|
-
.who .who-act{padding-left:10px;margin-left:2px;border-left:1px solid rgba(42,31,23,0.18);}
|
|
104
83
|
.foot{
|
|
105
84
|
position:fixed;bottom:18px;left:0;right:0;z-index:1;text-align:center;
|
|
106
85
|
color:var(--ink-soft);font-size:13px;opacity:0.8;
|
|
@@ -119,68 +98,24 @@ body{
|
|
|
119
98
|
</div>
|
|
120
99
|
<div class="wordmark" data-brand-product>cloud bongos</div>
|
|
121
100
|
<p class="tagline" data-brand-tagline>Building software is laying down a beat.</p>
|
|
122
|
-
<
|
|
123
|
-
<a class="btn" id="hallBtn" data-link-hall href="https://builders.cloudbongos.com">Enter the Hall →</a>
|
|
124
|
-
</div>
|
|
125
|
-
<p class="signin-hint" id="signinHint" aria-live="polite"></p>
|
|
101
|
+
<a class="btn" data-link-hall href="https://builders.cloudbongos.com">Enter the Hall →</a>
|
|
126
102
|
</main>
|
|
127
|
-
<div class="who" id="who" aria-live="polite"></div>
|
|
128
103
|
|
|
129
104
|
<div class="foot">A non-profit, capture-resistant commons. Built by agents, on Cloud Bongos.</div>
|
|
130
105
|
|
|
131
106
|
<script>
|
|
132
107
|
/* Brand-pack fill (R57). The server injects window.__BRANDING__ synchronously
|
|
133
|
-
ahead of this script (src/
|
|
108
|
+
ahead of this script (src/gds/serve-internal.js); the baked markup above is
|
|
134
109
|
the no-JS / standalone fallback. A differently-branded game-less instance
|
|
135
110
|
overrides the product name, tagline, and Hall link from its own config. */
|
|
136
111
|
(function () {
|
|
137
112
|
var b = (typeof window !== 'undefined' && window.__BRANDING__) || null;
|
|
138
|
-
if (b)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/* Membership-aware hall access (goal 35, task 2094). This is a PROJECT's own
|
|
147
|
-
landing page. Signed in as an admitted builder here → "Enter the Hall".
|
|
148
|
-
Otherwise → "Request access" (this project's own hall shows the request-
|
|
149
|
-
access card), with a quiet "already a builder? sign in" for returning
|
|
150
|
-
builders. Sign-in is itself gated to approved builders (auth.js), so a
|
|
151
|
-
newcomer who tries it is routed to request access anyway. The identity +
|
|
152
|
-
action live top-right. Degrades to the request-access visual when the API
|
|
153
|
-
is unreachable (static preview / standalone viewing). */
|
|
154
|
-
var hallHref = (b && b.domains && b.domains.buildersOrigin) || 'https://builders.cloudbongos.com';
|
|
155
|
-
var signInHref = '/api/bongos/auth/web/start?return=' + encodeURIComponent('/');
|
|
156
|
-
function esc(s) {
|
|
157
|
-
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
|
158
|
-
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
var hallBtn = document.getElementById('hallBtn');
|
|
162
|
-
var signinHint = document.getElementById('signinHint');
|
|
163
|
-
var who = document.getElementById('who');
|
|
164
|
-
|
|
165
|
-
function showSignedOut() {
|
|
166
|
-
if (hallBtn) { hallBtn.textContent = 'Request access'; hallBtn.setAttribute('href', hallHref); }
|
|
167
|
-
if (signinHint) signinHint.innerHTML = 'Already a builder here? <a href="' + signInHref + '">Sign in with GitHub</a>.';
|
|
168
|
-
if (who) who.innerHTML = '<a href="' + signInHref + '">Sign in with GitHub</a>';
|
|
169
|
-
}
|
|
170
|
-
function showMember(me) {
|
|
171
|
-
if (hallBtn) { hallBtn.textContent = 'Enter the Hall →'; hallBtn.setAttribute('href', hallHref); }
|
|
172
|
-
if (signinHint) signinHint.textContent = '';
|
|
173
|
-
if (who) who.innerHTML =
|
|
174
|
-
(me.avatar_url ? '<img src="' + esc(me.avatar_url) + '" alt="">' : '') +
|
|
175
|
-
'<span>' + esc(me.github_login || 'signed in') + '</span>' +
|
|
176
|
-
'<a class="who-act" href="' + hallHref + '">Enter hall →</a>';
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
showSignedOut(); // default until /me resolves (and the no-JS request-access intent)
|
|
180
|
-
fetch('/api/bongos/me', { credentials: 'same-origin' })
|
|
181
|
-
.then(function (res) { if (!res.ok) throw new Error(String(res.status)); return res.json(); })
|
|
182
|
-
.then(function (data) { showMember(data.builder || data.me || data); })
|
|
183
|
-
.catch(function () { /* signed out / not a member / unreachable: request-access visual stands */ });
|
|
113
|
+
if (!b) return;
|
|
114
|
+
var id = b.identity || {}, copy = b.copy || {}, dom = b.domains || {};
|
|
115
|
+
function set(sel, val){ if(!val) return; document.querySelectorAll(sel).forEach(function(n){ n.textContent = val; }); }
|
|
116
|
+
if (id.productName) set('[data-brand-product]', id.productName.toLowerCase());
|
|
117
|
+
if (copy.tagline) set('[data-brand-tagline]', copy.tagline);
|
|
118
|
+
if (dom.buildersOrigin) document.querySelectorAll('[data-link-hall]').forEach(function(n){ n.setAttribute('href', dom.buildersOrigin); });
|
|
184
119
|
})();
|
|
185
120
|
</script>
|
|
186
121
|
</body>
|
|
File without changes
|
package/scripts/gds/api.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/scripts/gds/box.js
CHANGED
|
File without changes
|
package/scripts/gds/capture.js
CHANGED
|
File without changes
|
package/scripts/gds/cli-lib.js
CHANGED
|
@@ -27,10 +27,16 @@ const SESSION_READ_PATHS = [
|
|
|
27
27
|
...ic.configReadPaths('gds-session.json'),
|
|
28
28
|
...ic.configReadPaths('pms-session.json'),
|
|
29
29
|
];
|
|
30
|
-
//
|
|
30
|
+
// The last-resort default API base — THIS instance's own publicOrigin from the
|
|
31
|
+
// branding pack (task 1002201). One source of truth in instance-config so no CLI
|
|
32
|
+
// script hard-codes another instance's URL; pre-ADR-0139 the literal
|
|
33
|
+
// 'https://amazonprimea.com' was baked in at four sites here, so an unconfigured
|
|
34
|
+
// consumer targeted the founder's (game-only) box. Override with <PREFIX>_API_BASE
|
|
35
|
+
// for local dev / staging:
|
|
31
36
|
// CLOUDBONGOS_API_BASE=http://localhost:3000 node scripts/gds/start.js
|
|
32
37
|
// Resolves the configured prefix first, then legacy GDS_/OTB_/PMS_API_BASE.
|
|
33
|
-
const
|
|
38
|
+
const DEFAULT_API_BASE = ic.defaultApiBase();
|
|
39
|
+
const API_BASE = ic.resolveEnv('API_BASE') || DEFAULT_API_BASE;
|
|
34
40
|
|
|
35
41
|
async function readSessionFile(p) {
|
|
36
42
|
try {
|
|
@@ -102,7 +108,7 @@ function readSessionToken() {
|
|
|
102
108
|
if (!j || !j.token) return null;
|
|
103
109
|
return {
|
|
104
110
|
token: j.token,
|
|
105
|
-
apiBase: ic.resolveEnv('API_BASE') || j.api_base ||
|
|
111
|
+
apiBase: ic.resolveEnv('API_BASE') || j.api_base || DEFAULT_API_BASE,
|
|
106
112
|
};
|
|
107
113
|
}
|
|
108
114
|
|
|
@@ -278,7 +284,7 @@ async function apiCall(method, urlPath, body = null, opts = {}) {
|
|
|
278
284
|
// (local dev / staging override), then the logged-in instance, then prod. OTB
|
|
279
285
|
// sessions already store api_base='https://amazonprimea.com', so this is a no-op
|
|
280
286
|
// for the OTB instance and only bites once someone logs in to a different one.
|
|
281
|
-
const base = ic.resolveEnv('API_BASE') || (session && session.api_base) ||
|
|
287
|
+
const base = ic.resolveEnv('API_BASE') || (session && session.api_base) || DEFAULT_API_BASE;
|
|
282
288
|
const headers = {
|
|
283
289
|
'Content-Type': 'application/json',
|
|
284
290
|
'User-Agent': 'amazonprimea-gds-cli/1.0',
|
|
@@ -349,7 +355,7 @@ async function cliClient(opts = {}) {
|
|
|
349
355
|
printInstanceMismatchAndExit(mm);
|
|
350
356
|
}
|
|
351
357
|
}
|
|
352
|
-
const base = ic.resolveEnv('API_BASE') || (session && session.api_base) ||
|
|
358
|
+
const base = ic.resolveEnv('API_BASE') || (session && session.api_base) || DEFAULT_API_BASE;
|
|
353
359
|
const token = (session && session.token) || null;
|
|
354
360
|
const key = `${base}|${token || ''}|${allowUnauthenticated}|${allowBoxScope}`;
|
|
355
361
|
if (_clientCache.has(key)) return _clientCache.get(key);
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/scripts/gds/doctor.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// scripts/gds/lint-migrations.js — core-migration FORWARD-SAFETY guard (task 1002223 / audit H10,
|
|
4
|
+
// finding F6).
|
|
5
|
+
//
|
|
6
|
+
// `bongos upgrade` auto-rollback reverts the CODE pin but DELIBERATELY NOT the database
|
|
7
|
+
// (upgrade.js: "the DB is deliberately NOT rolled back … core migrations are additive + forward-
|
|
8
|
+
// safe"). That safety rested on a convention nothing checked. A core migration is FORWARD-SAFE when
|
|
9
|
+
// the PREVIOUS code version still runs against the post-migration schema — so a code-only rollback
|
|
10
|
+
// never strands old code on a schema it can't read. This lints core migrations for operations that
|
|
11
|
+
// BREAK that property and fails unless the migration explicitly acknowledges the exception with a
|
|
12
|
+
// -- forward-unsafe: <reason>
|
|
13
|
+
// pragma (a deliberate, documented decision — e.g. a historical migration, or one paired with a
|
|
14
|
+
// two-step deploy).
|
|
15
|
+
//
|
|
16
|
+
// SAFE (never flagged): CREATE TABLE/INDEX/…, ADD COLUMN, INSERT/UPDATE, and DROP-then-CREATE of an
|
|
17
|
+
// INDEX / TRIGGER / VIEW / FUNCTION / POLICY (the recreate-in-place idempotency pattern) — none of
|
|
18
|
+
// these remove state the old code depends on.
|
|
19
|
+
// UNSAFE (flagged): DROP TABLE, DROP COLUMN, RENAME COLUMN, ALTER TABLE … RENAME TO, ALTER COLUMN …
|
|
20
|
+
// TYPE, SET NOT NULL — each removes / renames / retypes existing state old code reads.
|
|
21
|
+
//
|
|
22
|
+
// Usage:
|
|
23
|
+
// node scripts/gds/lint-migrations.js # STRICT: exit 1 on any unacknowledged unsafe migration
|
|
24
|
+
// node scripts/gds/lint-migrations.js --warn # report only, always exit 0 (migrate.sh uses this)
|
|
25
|
+
// node scripts/gds/lint-migrations.js --dir <path> # scan a specific migrations dir (default: <repo>/migrations)
|
|
26
|
+
|
|
27
|
+
const fs = require('node:fs');
|
|
28
|
+
const path = require('node:path');
|
|
29
|
+
|
|
30
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
31
|
+
|
|
32
|
+
// The acknowledgement pragma. Lives in a SQL comment; matched on the raw file text.
|
|
33
|
+
const ACK_RE = /^\s*--\s*forward-unsafe\s*:\s*(.+)$/im;
|
|
34
|
+
|
|
35
|
+
// Genuinely forward-unsafe statement shapes. Ordered; a file matching ANY is unsafe.
|
|
36
|
+
const UNSAFE = [
|
|
37
|
+
['drop-table', /\bdrop\s+table\b/i],
|
|
38
|
+
['drop-column', /\bdrop\s+column\b/i],
|
|
39
|
+
['rename-column', /\brename\s+column\b/i],
|
|
40
|
+
['rename-table', /\balter\s+table\s+[^\s;]+\s+rename\s+to\b/i],
|
|
41
|
+
['alter-column-type', /\balter\s+column\b[^;]*\btype\b/i],
|
|
42
|
+
['set-not-null', /\bset\s+not\s+null\b/i],
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// Strip block + line comments so a pattern that appears only in prose/comments never counts as an
|
|
46
|
+
// operation. (The ACK pragma is detected separately, on the raw text, since it IS a comment.)
|
|
47
|
+
function stripSqlComments(sql) {
|
|
48
|
+
return String(sql).replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/--[^\n]*/g, ' ');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Classify one migration's SQL: { unsafe: [names], acknowledged: bool, ackReason }.
|
|
52
|
+
function scanMigrationSql(sql) {
|
|
53
|
+
const code = stripSqlComments(sql);
|
|
54
|
+
const unsafe = [];
|
|
55
|
+
for (const [name, re] of UNSAFE) if (re.test(code)) unsafe.push(name);
|
|
56
|
+
const ack = String(sql).match(ACK_RE);
|
|
57
|
+
return { unsafe, acknowledged: !!ack, ackReason: ack ? ack[1].trim() : null };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Scan a migrations directory. Returns { violations, acknowledged } — each entry { file, unsafe, ackReason }.
|
|
61
|
+
// `violations` = unsafe WITHOUT the pragma (fail-closed); `acknowledged` = unsafe WITH it (documented).
|
|
62
|
+
function scanMigrationsDir(dir, fsImpl = fs) {
|
|
63
|
+
let files;
|
|
64
|
+
try { files = fsImpl.readdirSync(dir).filter((f) => f.endsWith('.sql')); }
|
|
65
|
+
catch { return { violations: [], acknowledged: [], scanned: 0 }; }
|
|
66
|
+
const violations = [];
|
|
67
|
+
const acknowledged = [];
|
|
68
|
+
for (const f of files.sort()) {
|
|
69
|
+
const r = scanMigrationSql(fsImpl.readFileSync(path.join(dir, f), 'utf8'));
|
|
70
|
+
if (r.unsafe.length === 0) continue;
|
|
71
|
+
(r.acknowledged ? acknowledged : violations).push({ file: f, unsafe: r.unsafe, ackReason: r.ackReason });
|
|
72
|
+
}
|
|
73
|
+
return { violations, acknowledged, scanned: files.length };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function arg(name, fallback = null) {
|
|
77
|
+
const i = process.argv.indexOf(name);
|
|
78
|
+
return i >= 0 && i + 1 < process.argv.length ? process.argv[i + 1] : fallback;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function main() {
|
|
82
|
+
const warnOnly = process.argv.includes('--warn');
|
|
83
|
+
const dir = path.resolve(arg('--dir', path.join(REPO_ROOT, 'migrations')));
|
|
84
|
+
const { violations, acknowledged, scanned } = scanMigrationsDir(dir);
|
|
85
|
+
|
|
86
|
+
for (const a of acknowledged) {
|
|
87
|
+
console.error(`[migrations] forward-unsafe (acknowledged): ${a.file} [${a.unsafe.join(', ')}] — ${a.ackReason}`);
|
|
88
|
+
}
|
|
89
|
+
if (violations.length === 0) {
|
|
90
|
+
console.error(`[migrations] forward-safety: OK — ${scanned} core migration(s) scanned, ${acknowledged.length} acknowledged exception(s), 0 unacknowledged.`);
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
console.error(`[migrations] forward-safety: ${warnOnly ? 'WARNING' : 'FAILED'} — ${violations.length} core migration(s) contain a forward-UNSAFE operation with no acknowledgement:`);
|
|
94
|
+
for (const v of violations) console.error(` ${v.file} [${v.unsafe.join(', ')}]`);
|
|
95
|
+
console.error(' A code-only `bongos upgrade` rollback does NOT revert the DB, so old code would run on this schema.');
|
|
96
|
+
console.error(' If intentional, add a top-of-file SQL comment: -- forward-unsafe: <why it is safe here / the two-step plan>');
|
|
97
|
+
return warnOnly ? 0 : 1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { scanMigrationSql, scanMigrationsDir, stripSqlComments, UNSAFE, ACK_RE };
|
|
101
|
+
|
|
102
|
+
if (require.main === module) process.exit(main());
|
package/scripts/gds/login.js
CHANGED
|
@@ -80,12 +80,26 @@ function prompt(q) {
|
|
|
80
80
|
|
|
81
81
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
82
82
|
|
|
83
|
-
// Run the
|
|
83
|
+
// Run the device flow and return one of:
|
|
84
84
|
// { state: 'ok', token, builder } | { state: 'not_approved' } | { state: 'expired' }
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
//
|
|
86
|
+
// Two modes (ADR 0141 §1). Direct: run GitHub device flow against the instance's
|
|
87
|
+
// own app. Federated (idp = { origin, client_id } from the instance manifest): run
|
|
88
|
+
// the device flow against the HUB, then exchange the hub-signed assertion at the
|
|
89
|
+
// instance's /auth/device/exchange — a hosted instance has no GitHub app of its own.
|
|
90
|
+
// The instance's OWN admission gate runs at poll (direct) or exchange (federated),
|
|
91
|
+
// so the stranger path is identical for both.
|
|
92
|
+
async function runDeviceFlow(base, { log, idp = null }) {
|
|
93
|
+
const startUrl = idp ? `${idp.origin}/api/gds/sso/device/start` : `${base}/api/gds/auth/device/start`;
|
|
94
|
+
const start = await fetchJson(startUrl, {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
// Federated: the hub resolves + binds the client from the USER-TYPED origin
|
|
97
|
+
// (`base`), never a client_id we supply — so this CLI can only ever mint an
|
|
98
|
+
// assertion scoped to the instance the user actually typed (ADR 0141 §1).
|
|
99
|
+
...(idp ? { body: JSON.stringify({ origin: base }) } : {}),
|
|
100
|
+
});
|
|
101
|
+
if (start.status === 503 || (start.data && (start.data.error === 'auth_not_configured' || (start.data.error && start.data.error.code === 'idp_signing_unconfigured')))) {
|
|
102
|
+
throw new Error('that instance has no sign-in configured yet — ask its Archon to set it up.');
|
|
89
103
|
}
|
|
90
104
|
if (!start.ok || !start.data || !start.data.device_code) {
|
|
91
105
|
throw new Error(`could not start sign-in (HTTP ${start.status}).`);
|
|
@@ -103,15 +117,29 @@ async function runDeviceFlow(base, { log }) {
|
|
|
103
117
|
openBrowser(verification_uri);
|
|
104
118
|
log(' Waiting for you to approve in the browser…');
|
|
105
119
|
|
|
120
|
+
const pollUrl = idp ? `${idp.origin}/api/gds/sso/device/poll` : `${base}/api/gds/auth/device/poll`;
|
|
106
121
|
const deadline = Date.now() + expires * 1000;
|
|
107
122
|
while (Date.now() < deadline) {
|
|
108
123
|
await sleep(interval * 1000);
|
|
109
|
-
const poll = await fetchJson(
|
|
124
|
+
const poll = await fetchJson(pollUrl, {
|
|
110
125
|
method: 'POST',
|
|
126
|
+
// The hub uses the client BOUND to device_code at start; the CLI names no
|
|
127
|
+
// audience at poll either (ADR 0141 §1).
|
|
111
128
|
body: JSON.stringify({ device_code }),
|
|
112
129
|
});
|
|
113
130
|
const st = poll.data && poll.data.state;
|
|
114
|
-
if (st === 'ok')
|
|
131
|
+
if (st === 'ok') {
|
|
132
|
+
if (!idp) return { state: 'ok', token: poll.data.token, builder: poll.data.builder };
|
|
133
|
+
// Federated: exchange the hub assertion at the instance for a real session.
|
|
134
|
+
const ex = await fetchJson(`${base}/api/gds/auth/device/exchange`, {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
body: JSON.stringify({ assertion: poll.data.assertion }),
|
|
137
|
+
});
|
|
138
|
+
const exState = ex.data && ex.data.state;
|
|
139
|
+
if (exState === 'not_approved' || (ex.status === 403 && exState !== 'pending')) return { state: 'not_approved' };
|
|
140
|
+
if (!ex.ok || !ex.data || !ex.data.token) throw new Error(`sign-in exchange failed (HTTP ${ex.status}).`);
|
|
141
|
+
return { state: 'ok', token: ex.data.token, builder: ex.data.builder };
|
|
142
|
+
}
|
|
115
143
|
if (st === 'not_approved' || (poll.status === 403 && st !== 'pending')) return { state: 'not_approved' };
|
|
116
144
|
if (st === 'denied') throw new Error('sign-in was denied in the browser.');
|
|
117
145
|
if (st === 'expired') return { state: 'expired' };
|
|
@@ -146,10 +174,14 @@ async function main() {
|
|
|
146
174
|
}
|
|
147
175
|
const instanceName = man.data.name || base;
|
|
148
176
|
const admission = (man.data.admission && man.data.admission.policy) || 'invite-only';
|
|
149
|
-
|
|
177
|
+
// Federation (ADR 0141 §1): a hosted instance advertises auth.idp = { origin,
|
|
178
|
+
// client_id } — sign-in then runs through the Cloud Bongos hub, not a local
|
|
179
|
+
// GitHub app. null → the direct-GitHub path, unchanged.
|
|
180
|
+
const idp = (man.data.auth && man.data.auth.idp) || null;
|
|
181
|
+
log(` Signing in to ${instanceName}${admission === 'invite-only' ? ' (invite-only)' : ''}${idp ? ' — via Cloud Bongos platform sign-in' : ''}`);
|
|
150
182
|
|
|
151
183
|
// 2. Device flow.
|
|
152
|
-
let flow = await runDeviceFlow(base, { log });
|
|
184
|
+
let flow = await runDeviceFlow(base, { log, idp });
|
|
153
185
|
|
|
154
186
|
// 3. Stranger path: not admitted → file the request, poll until an Archon 👍s,
|
|
155
187
|
// then finish sign-in with a fresh device flow (the first code is spent).
|
|
@@ -193,7 +225,7 @@ async function main() {
|
|
|
193
225
|
}
|
|
194
226
|
process.stdout.write('.');
|
|
195
227
|
}
|
|
196
|
-
flow = await runDeviceFlow(base, { log });
|
|
228
|
+
flow = await runDeviceFlow(base, { log, idp });
|
|
197
229
|
}
|
|
198
230
|
|
|
199
231
|
if (flow.state !== 'ok') {
|
|
File without changes
|
|
@@ -185,6 +185,11 @@ function deriveInstanceLiterals(branding = {}, extra = {}) {
|
|
|
185
185
|
add(tokens, company.name);
|
|
186
186
|
{ const label = registrableLabel(company.site); if (label) add(tokens, label); }
|
|
187
187
|
|
|
188
|
+
// `extra` also carries a caller-supplied FIXED FLOOR: the package-core neutral-floor gate
|
|
189
|
+
// (task 1002217 / audit H4) injects prior-instance tokens/owners here so published DOCS de-brand
|
|
190
|
+
// against them and the fail-closed gate fires EVEN WITH NO branding.json. Those floor literals
|
|
191
|
+
// live with the CALLER (package-core), never hardcoded in this file — so this redactor's self-leak
|
|
192
|
+
// invariant (tests/mirror_redact.mjs) keeps holding while the floor is still enforced.
|
|
188
193
|
for (const t of extra.tokens || []) add(tokens, t);
|
|
189
194
|
for (const o of extra.owners || []) add(owners, o);
|
|
190
195
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|