@bongos/core 1.19.352 → 1.19.354
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 +19 -19
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/api/openapi.json +4 -2
- package/modules/provisioning/provisioning.js +13 -1
- package/modules/provisioning/routes/provisioning.js +4 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/projects.html +12 -0
- package/scripts/gds/provision-repo.js +14 -7
- package/scripts/gds/provision-units.js +6 -6
- package/scripts/gds/provision.js +4 -4
- package/src/module-api.js +5 -1
- package/tests/projects_hub_pre_uat.mjs +14 -0
- package/tests/provision.mjs +26 -0
- package/tests/provisioning_settings.mjs +27 -0
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.354",
|
|
6
|
+
"core_contract": "1.19.354",
|
|
7
|
+
"source_commit": "b3914da0290b60c5eb6ce4f3c7c32b6f9ae07e1f",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-25T13:
|
|
9
|
+
"built_at": "2026-08-25T13:13:49.790Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 329,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 1857,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "4765f579b8f7f2e758fb27b63db2088b1dab283f54ada499fcaff84c0c86d920",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -367,7 +367,7 @@
|
|
|
367
367
|
{
|
|
368
368
|
"path": "clients/bongos-client/index.d.ts",
|
|
369
369
|
"mode": "0000644",
|
|
370
|
-
"sha256": "
|
|
370
|
+
"sha256": "f32214ef80ab2e05d032e35da07b84e7300a490001dd39a7f7d0ff1997053836"
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"path": "clients/bongos-client/index.mjs",
|
|
@@ -1452,7 +1452,7 @@
|
|
|
1452
1452
|
{
|
|
1453
1453
|
"path": "docs/api/openapi.json",
|
|
1454
1454
|
"mode": "0000644",
|
|
1455
|
-
"sha256": "
|
|
1455
|
+
"sha256": "03a8b40f27b4ad5bd8219d4a755eb18c7b925feee465c6d97d3cb1b8c372651f"
|
|
1456
1456
|
},
|
|
1457
1457
|
{
|
|
1458
1458
|
"path": "docs/architecture.md",
|
|
@@ -4477,7 +4477,7 @@
|
|
|
4477
4477
|
{
|
|
4478
4478
|
"path": "modules/provisioning/provisioning.js",
|
|
4479
4479
|
"mode": "0000644",
|
|
4480
|
-
"sha256": "
|
|
4480
|
+
"sha256": "ff156c4c0252240026a5ce98547efa76d585d6f0fd88c5b0b95c5598901865ab"
|
|
4481
4481
|
},
|
|
4482
4482
|
{
|
|
4483
4483
|
"path": "modules/provisioning/rate-limit.js",
|
|
@@ -4487,7 +4487,7 @@
|
|
|
4487
4487
|
{
|
|
4488
4488
|
"path": "modules/provisioning/routes/provisioning.js",
|
|
4489
4489
|
"mode": "0000644",
|
|
4490
|
-
"sha256": "
|
|
4490
|
+
"sha256": "026234f2c2c212a7ecc3808380bd3ea0cca81e2efc39c619968c505099830e5c"
|
|
4491
4491
|
},
|
|
4492
4492
|
{
|
|
4493
4493
|
"path": "modules/provisioning/tests/liveness.mjs",
|
|
@@ -4787,12 +4787,12 @@
|
|
|
4787
4787
|
{
|
|
4788
4788
|
"path": "package-lock.json",
|
|
4789
4789
|
"mode": "0000644",
|
|
4790
|
-
"sha256": "
|
|
4790
|
+
"sha256": "0e7087b6c93131c62fbe40e9b626df9e4a4ec2719b1de4fc48e428d00a3a7e7b"
|
|
4791
4791
|
},
|
|
4792
4792
|
{
|
|
4793
4793
|
"path": "package.json",
|
|
4794
4794
|
"mode": "0000644",
|
|
4795
|
-
"sha256": "
|
|
4795
|
+
"sha256": "9ef59eab6d6debaa99f34c64cc37bf26ccd2db1dcf08fa89682eb02590da29ca"
|
|
4796
4796
|
},
|
|
4797
4797
|
{
|
|
4798
4798
|
"path": "public-docs/index.html",
|
|
@@ -5052,7 +5052,7 @@
|
|
|
5052
5052
|
{
|
|
5053
5053
|
"path": "public-landing/projects.html",
|
|
5054
5054
|
"mode": "0000644",
|
|
5055
|
-
"sha256": "
|
|
5055
|
+
"sha256": "42ddc3ecf96a31919f0e700f2cd0092d8225858c78ef4d231e4b3ac301b5b318"
|
|
5056
5056
|
},
|
|
5057
5057
|
{
|
|
5058
5058
|
"path": "public-landing/world/assets/clouds/cloud-island-large.png",
|
|
@@ -5797,17 +5797,17 @@
|
|
|
5797
5797
|
{
|
|
5798
5798
|
"path": "scripts/gds/provision-repo.js",
|
|
5799
5799
|
"mode": "0000644",
|
|
5800
|
-
"sha256": "
|
|
5800
|
+
"sha256": "c87cccdd63ac6fa6d38771c3ad178e5e919f8bee62ad769b9d127819f84e2750"
|
|
5801
5801
|
},
|
|
5802
5802
|
{
|
|
5803
5803
|
"path": "scripts/gds/provision-units.js",
|
|
5804
5804
|
"mode": "0000644",
|
|
5805
|
-
"sha256": "
|
|
5805
|
+
"sha256": "e8a44143883403c2ff8e20107250d63f6d19a14050279e8d675480582e07c555"
|
|
5806
5806
|
},
|
|
5807
5807
|
{
|
|
5808
5808
|
"path": "scripts/gds/provision.js",
|
|
5809
5809
|
"mode": "0000644",
|
|
5810
|
-
"sha256": "
|
|
5810
|
+
"sha256": "5b4f5edce415dd46db830104eb196de678214730d736ebf3ae26a7671b718c77"
|
|
5811
5811
|
},
|
|
5812
5812
|
{
|
|
5813
5813
|
"path": "scripts/gds/publish-credential-check.js",
|
|
@@ -6652,7 +6652,7 @@
|
|
|
6652
6652
|
{
|
|
6653
6653
|
"path": "src/module-api.js",
|
|
6654
6654
|
"mode": "0000644",
|
|
6655
|
-
"sha256": "
|
|
6655
|
+
"sha256": "534d2a2ac4a67829ae4589161a0900ee08b1312d512ef18fb4a136419b465f0d"
|
|
6656
6656
|
},
|
|
6657
6657
|
{
|
|
6658
6658
|
"path": "src/module-loader/catalog.js",
|
|
@@ -8542,7 +8542,7 @@
|
|
|
8542
8542
|
{
|
|
8543
8543
|
"path": "tests/projects_hub_pre_uat.mjs",
|
|
8544
8544
|
"mode": "0000644",
|
|
8545
|
-
"sha256": "
|
|
8545
|
+
"sha256": "919caac313f78c8c88e7ecca1a49ff2f1da53e443acda434f0648a9b5215f24d"
|
|
8546
8546
|
},
|
|
8547
8547
|
{
|
|
8548
8548
|
"path": "tests/promote_goal_id.mjs",
|
|
@@ -8557,7 +8557,7 @@
|
|
|
8557
8557
|
{
|
|
8558
8558
|
"path": "tests/provision.mjs",
|
|
8559
8559
|
"mode": "0000644",
|
|
8560
|
-
"sha256": "
|
|
8560
|
+
"sha256": "0fc6c6626b43458c2e065322bec5a2ecd87e50a44abd2838d81ff0f0b6abeae7"
|
|
8561
8561
|
},
|
|
8562
8562
|
{
|
|
8563
8563
|
"path": "tests/provisioning_default_domain.mjs",
|
|
@@ -8582,7 +8582,7 @@
|
|
|
8582
8582
|
{
|
|
8583
8583
|
"path": "tests/provisioning_settings.mjs",
|
|
8584
8584
|
"mode": "0000644",
|
|
8585
|
-
"sha256": "
|
|
8585
|
+
"sha256": "7b6aa958f626054746e5272113bb314a118073507d5baed65f52cafdc25f7085"
|
|
8586
8586
|
},
|
|
8587
8587
|
{
|
|
8588
8588
|
"path": "tests/provisioning_settings_env.mjs",
|
|
@@ -81,7 +81,7 @@ export interface GetProjectsResponse { projects: unknown }
|
|
|
81
81
|
export interface GetProvisioningFleetResponse { instances: unknown }
|
|
82
82
|
export interface GetProvisioningGithubAppStatusResponse { ok: boolean; status: unknown; ready: unknown; terminal: unknown; domain: unknown; instance_status: unknown; app_slug: unknown }
|
|
83
83
|
export interface GetProvisioningHealthResponse { module: unknown; ok: boolean; coreVersion: unknown }
|
|
84
|
-
export interface GetProvisioningInstancesIdResponse { instance: unknown; open_intent: unknown }
|
|
84
|
+
export interface GetProvisioningInstancesIdResponse { instance: unknown; open_intent: unknown; github_app: unknown }
|
|
85
85
|
export interface GetProvisioningInstancesIdSettingsResponse { ok: boolean; settings: unknown; options: unknown }
|
|
86
86
|
export interface GetProvisioningInstancesResponse { instances: unknown }
|
|
87
87
|
export interface GetProvisioningOnboardPlanResponse { mode: unknown; steps: unknown }
|
package/docs/api/openapi.json
CHANGED
|
@@ -14994,11 +14994,13 @@
|
|
|
14994
14994
|
"type": "object",
|
|
14995
14995
|
"properties": {
|
|
14996
14996
|
"instance": {},
|
|
14997
|
-
"open_intent": {}
|
|
14997
|
+
"open_intent": {},
|
|
14998
|
+
"github_app": {}
|
|
14998
14999
|
},
|
|
14999
15000
|
"required": [
|
|
15000
15001
|
"instance",
|
|
15001
|
-
"open_intent"
|
|
15002
|
+
"open_intent",
|
|
15003
|
+
"github_app"
|
|
15002
15004
|
]
|
|
15003
15005
|
},
|
|
15004
15006
|
"GetProvisioningInstancesIdSettingsResponse": {
|
|
@@ -232,6 +232,18 @@ function publicInstance(row) {
|
|
|
232
232
|
};
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
// The GitHub-App manifest leg's truth for an instance, as the read surface carries
|
|
236
|
+
// it (task 1003235): the latest attempt's lifecycle status + the App's public slug.
|
|
237
|
+
// Same vocabulary as GET /github-app/status ('pending' | 'code_received' |
|
|
238
|
+
// 'converting' | 'done' | 'error' | 'expired'), so the wizard can seed its
|
|
239
|
+
// `live.manifest` from a plain instance read after a reload, a second tab, or
|
|
240
|
+
// another device — no sessionStorage bridge needed. null = no attempt ever made.
|
|
241
|
+
// PURE: an allow-list projection of the manifest row, never the row itself.
|
|
242
|
+
function publicGithubApp(manifestRow) {
|
|
243
|
+
if (!manifestRow || !manifestRow.status) return null;
|
|
244
|
+
return { status: String(manifestRow.status), app_slug: manifestRow.app_slug || null };
|
|
245
|
+
}
|
|
246
|
+
|
|
235
247
|
// The instance's EFFECTIVE settings: the defaults overlaid with the stored,
|
|
236
248
|
// on-vocabulary keys. An off-vocabulary or unknown stored value falls back to
|
|
237
249
|
// its default rather than leaking into the read surface. PURE.
|
|
@@ -812,7 +824,7 @@ module.exports = {
|
|
|
812
824
|
isValidSlug, costEstimateUsd, publicInstance, normalizeHostname,
|
|
813
825
|
defaultDomainForSlug, isReservedHostname, effectiveSettings,
|
|
814
826
|
// pure — GitHub App Manifest flow (task 2080)
|
|
815
|
-
githubAppName, buildGithubAppManifest, manifestPostUrl,
|
|
827
|
+
githubAppName, buildGithubAppManifest, manifestPostUrl, publicGithubApp,
|
|
816
828
|
// data access
|
|
817
829
|
isProvisionedHostname,
|
|
818
830
|
getInstanceById, getInstanceBySlug, getInstanceByDomain, listInstancesForOwner, listAllInstances,
|
|
@@ -619,9 +619,13 @@ module.exports = function provisioningRoutes() {
|
|
|
619
619
|
return res.fail('instance_not_found', 404);
|
|
620
620
|
}
|
|
621
621
|
const open = await provisioning.getOpenIntent(pool, id);
|
|
622
|
+
// The GitHub-App leg's truth rides the same read (task 1003235), so the done
|
|
623
|
+
// panel recovers it from server state after a reload — not from sessionStorage.
|
|
624
|
+
const manifest = await provisioning.getLatestOAuthManifestForInstance(pool, id);
|
|
622
625
|
res.json({
|
|
623
626
|
instance: provisioning.publicInstance(inst),
|
|
624
627
|
open_intent: open ? { action: open.action, state: open.state } : null,
|
|
628
|
+
github_app: provisioning.publicGithubApp(manifest),
|
|
625
629
|
});
|
|
626
630
|
} catch (err) {
|
|
627
631
|
log.error('[provisioning] GET /provisioning/instances/:id', 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.354",
|
|
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.354",
|
|
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.354",
|
|
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",
|
|
@@ -3555,6 +3555,18 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3555
3555
|
var inst = data.instance;
|
|
3556
3556
|
if (inst) inst.open_intent = data.open_intent || null; /* F22: the strip reads intents */
|
|
3557
3557
|
if (inst) watchFlow = inst.torn_down_at ? 'revive' : 'standup';
|
|
3558
|
+
/* server-derived manifest truth (task 1003235): a reload, a second tab
|
|
3559
|
+
or another device re-learns the GitHub-App leg from this same read.
|
|
3560
|
+
A live manifest poll owns live.manifest while it runs, so the seed
|
|
3561
|
+
yields to it. */
|
|
3562
|
+
var ga = data.github_app;
|
|
3563
|
+
if (ga && ga.status && !manifestPollTimer) {
|
|
3564
|
+
var seeded = ga.status === 'done' ? 'done' : ga.status;
|
|
3565
|
+
var seedChanged = seeded !== live.manifest || (!!ga.app_slug && ga.app_slug !== live.appSlug);
|
|
3566
|
+
if (ga.app_slug) live.appSlug = ga.app_slug;
|
|
3567
|
+
live.manifest = seeded;
|
|
3568
|
+
if (seedChanged) { rememberManifest(undefined, live.manifest, live.appSlug || undefined); renderSteps(); }
|
|
3569
|
+
}
|
|
3558
3570
|
/* the checklist reflects live state (task 1002694): a status transition —
|
|
3559
3571
|
e.g. the scaffold landing — re-renders the steps (change-only, so the
|
|
3560
3572
|
5s tick never clobbers an open dropdown or mid-flight button) */
|
|
@@ -323,6 +323,18 @@ function alreadyScaffolded(root, mode, { fsImpl = fs } = {}) {
|
|
|
323
323
|
//
|
|
324
324
|
// Idempotent (see alreadyScaffolded) and dry-run safe (every mutation goes through the
|
|
325
325
|
// injected exec/writeFile). ADR 0108 / 0121 / 0125.
|
|
326
|
+
// The signed-in builder who owns the instance — the founding firstAdmin. Best-effort:
|
|
327
|
+
// no db, no row, or a query error → null, and callers fall back to the repo owner in
|
|
328
|
+
// target_ref. Shared by the scaffold's branding.firstAdmin and web.env's
|
|
329
|
+
// <prefix>_FIRST_ADMIN (task 1003240) so the two can never disagree.
|
|
330
|
+
async function ownerLoginOf(inst, db) {
|
|
331
|
+
if (!inst || !inst.owner_builder_id || !db || typeof db.query !== 'function') return null;
|
|
332
|
+
try {
|
|
333
|
+
const r = await db.query('SELECT github_login FROM builders WHERE id = $1', [inst.owner_builder_id]);
|
|
334
|
+
return r && r.rows && r.rows[0] && r.rows[0].github_login ? r.rows[0].github_login : null;
|
|
335
|
+
} catch { return null; }
|
|
336
|
+
}
|
|
337
|
+
|
|
326
338
|
async function scaffoldStandaloneRepo(inst, deps) {
|
|
327
339
|
// The scaffold BUILDS + pushes the instance's repo (git clone, package-core,
|
|
328
340
|
// `bongos init`, git push) — control-plane work that needs the packaging tools +
|
|
@@ -352,13 +364,7 @@ async function scaffoldStandaloneRepo(inst, deps) {
|
|
|
352
364
|
: ` [scaffold] ${repo.owner}/${repo.name} → ${root} (clone → materialize → pin core ${coreV} → commit → push)`);
|
|
353
365
|
|
|
354
366
|
// Owner login = the founding firstAdmin. Best-effort; the spec falls back to the repo owner.
|
|
355
|
-
|
|
356
|
-
if (inst.owner_builder_id && db) {
|
|
357
|
-
try {
|
|
358
|
-
const r = await db.query('SELECT github_login FROM builders WHERE id = $1', [inst.owner_builder_id]);
|
|
359
|
-
ownerLogin = r && r.rows && r.rows[0] ? r.rows[0].github_login : null;
|
|
360
|
-
} catch { /* fall back to the repo owner */ }
|
|
361
|
-
}
|
|
367
|
+
const ownerLogin = await ownerLoginOf(inst, db);
|
|
362
368
|
|
|
363
369
|
// Resolve the instance owner's OAuth token BEFORE any git network op.
|
|
364
370
|
//
|
|
@@ -820,6 +826,7 @@ module.exports = {
|
|
|
820
826
|
CORE_PKG_DIR,
|
|
821
827
|
INSTANCE_GITIGNORE,
|
|
822
828
|
alreadyScaffolded,
|
|
829
|
+
ownerLoginOf,
|
|
823
830
|
asPostgres,
|
|
824
831
|
buildCorePinRefreshCommit,
|
|
825
832
|
dbCreateCmd,
|
|
@@ -153,7 +153,7 @@ function provisionedEnvPrefix() {
|
|
|
153
153
|
// the hall on this host + OAuth uses the real callback origin — without them the pack
|
|
154
154
|
// stays at neutral localhost and the hall never loads (task 1972). No domain yet
|
|
155
155
|
// (bring-your-own deferred, ADR 0111 §4) ⇒ origins stay neutral until one is set.
|
|
156
|
-
function webEnvBody(inst, { clientId = '', clientSecret = '', appId = null, appPem = null, federation = null } = {}) {
|
|
156
|
+
function webEnvBody(inst, { clientId = '', clientSecret = '', appId = null, appPem = null, federation = null, ownerLogin = null } = {}) {
|
|
157
157
|
const prefix = provisionedEnvPrefix(), { settingsEnvLines } = require(path.join(REPO_ROOT, 'modules/provisioning/provisioning')); // eslint-disable-line global-require
|
|
158
158
|
const origin = inst.domain ? `https://${inst.domain}` : '';
|
|
159
159
|
const originLines = origin
|
|
@@ -191,12 +191,12 @@ ${prefix}_OAUTH_ORIGIN=${origin}
|
|
|
191
191
|
// demo had every origin override but no FIRST_ADMIN, so the owner could not be seated).
|
|
192
192
|
// Derived from the instance repo owner (target_ref = "<owner>/<repo>"), the SAME source
|
|
193
193
|
// instanceInitSpec uses for the scaffold's branding.firstAdmin — so this env override and
|
|
194
|
-
// the scaffolded config/branding.json agree
|
|
195
|
-
//
|
|
194
|
+
// the scaffolded config/branding.json agree — and, like the spec, PREFERS the signed-in
|
|
195
|
+
// builder's login when the caller resolved one (ownerLoginOf). An org-owned repo's
|
|
196
|
+
// owner is the org, which is not a sign-in identity: seating it seats nobody (task 1003240).
|
|
196
197
|
const ownerRepo = parseTargetRef(inst.target_ref);
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
: '';
|
|
198
|
+
const firstAdmin = ownerLogin || (ownerRepo && ownerRepo.owner) || null;
|
|
199
|
+
const firstAdminLine = firstAdmin ? `${prefix}_FIRST_ADMIN=${firstAdmin}\n` : '';
|
|
200
200
|
// Cross-site identity federation block (ADR 0141 §4, task #1002109). Present only for
|
|
201
201
|
// a HOSTED (federated) instance: flips auth.idp ON via the env override branding.js
|
|
202
202
|
// reads (idpEnabled), and carries the hub origin, this instance's per-client secret
|
package/scripts/gds/provision.js
CHANGED
|
@@ -50,7 +50,7 @@ const cp = require('node:child_process');
|
|
|
50
50
|
const crypto = require('node:crypto');
|
|
51
51
|
|
|
52
52
|
const { CONFIG, MANIFEST_UA, MANIFEST_VERIFY_INTERVAL_MS, MANIFEST_VERIFY_TRIES, MAX_INTENT_ATTEMPTS, REPO_ROOT, coreVersionSafe, hasFlag, loadDeps, oauthSecret, provisionerBotEmail } = require('./provision-config.js');
|
|
53
|
-
const { APP_USER, alreadyScaffolded, buildCorePinRefreshCommit, dbCreateCmd, dbName, deployKeyPath, deployKeyTitle, ensurePrivateRepoAccess, installedCorePackDir, installedCoreTarball, instanceInitSpec, instanceRepoRemote, migrateCmd, onboardMode, parseTargetRef, planCorePinRefresh, refreshStandaloneCorePin, resolveOwnerGithubToken, resolveVendorableCoreTarball, safeVersionLabel, scaffoldStandaloneRepo, seedFirstVersionCmd, standaloneInstallCmd, standaloneMigrateCmd, standalonePullCmd, standaloneRegenDocsCmd, standaloneRoot } = require('./provision-repo.js');
|
|
53
|
+
const { APP_USER, alreadyScaffolded, buildCorePinRefreshCommit, dbCreateCmd, dbName, deployKeyPath, deployKeyTitle, ensurePrivateRepoAccess, installedCorePackDir, installedCoreTarball, instanceInitSpec, instanceRepoRemote, migrateCmd, onboardMode, ownerLoginOf, parseTargetRef, planCorePinRefresh, refreshStandaloneCorePin, resolveOwnerGithubToken, resolveVendorableCoreTarball, safeVersionLabel, scaffoldStandaloneRepo, seedFirstVersionCmd, standaloneInstallCmd, standaloneMigrateCmd, standalonePullCmd, standaloneRegenDocsCmd, standaloneRoot } = require('./provision-repo.js');
|
|
54
54
|
const { backupScriptPath, backupService, backupServicePath, backupTimer, backupTimerPath, backupUnitName, serviceUnit, serviceUnitPath, webEnvBody, webEnvPath } = require('./provision-units.js');
|
|
55
55
|
const { caddyBlock, caddySnippetPath, checkDnsTokenScope, classifyZoneScope, dnsUpsertEnv, federateInstance, federationHubOrigin, healthzCmd, shouldFederate, zoneForDomain } = require('./provision-net.js');
|
|
56
56
|
|
|
@@ -381,7 +381,7 @@ async function provisionInstance(inst, deps) {
|
|
|
381
381
|
|
|
382
382
|
// ── step 3: config + per-instance secrets ─────────────────────────────────
|
|
383
383
|
log(' [config] per-instance web.env');
|
|
384
|
-
if (local) boxWriteFile(webEnvPath(inst), webEnvBody(inst, { federation }), { mode: 0o600, sudo: true });
|
|
384
|
+
if (local) boxWriteFile(webEnvPath(inst), webEnvBody(inst, { federation, ownerLogin: await ownerLoginOf(inst, deps.db) }), { mode: 0o600, sudo: true });
|
|
385
385
|
else log(' (baked into cloud-init)');
|
|
386
386
|
|
|
387
387
|
// ── step 4: systemd unit + enable ─────────────────────────────────────────
|
|
@@ -588,7 +588,7 @@ async function domainAttachInstance(inst, deps) {
|
|
|
588
588
|
: CONFIG.publicIp;
|
|
589
589
|
|
|
590
590
|
log(' [config] re-write per-instance web.env (origins now carry the domain)');
|
|
591
|
-
boxWriteFile(webEnvPath(inst), webEnvBody(inst, { federation: null }), { mode: 0o600, sudo: true });
|
|
591
|
+
boxWriteFile(webEnvPath(inst), webEnvBody(inst, { federation: null, ownerLogin: await ownerLoginOf(inst, deps.db) }), { mode: 0o600, sudo: true });
|
|
592
592
|
log(' [service] restart to pick up the new origins');
|
|
593
593
|
boxExec(`${sudoP}systemctl restart ${inst.slug}`);
|
|
594
594
|
|
|
@@ -790,7 +790,7 @@ async function placeManifestCreds(inst, mrow, deps) {
|
|
|
790
790
|
// writeFile logs only a byte count; the value is base64/temp-file placed).
|
|
791
791
|
log(` [oauth-manifest] place creds in ${webEnvPath(inst)} + restart ${inst.slug}`);
|
|
792
792
|
boxWriteFile(webEnvPath(inst), webEnvBody(inst, {
|
|
793
|
-
clientId: creds.clientId, clientSecret: creds.clientSecret,
|
|
793
|
+
clientId: creds.clientId, clientSecret: creds.clientSecret, ownerLogin: await ownerLoginOf(inst, deps.db),
|
|
794
794
|
// The App id + private key double as the ADR 0055 publish credential (task
|
|
795
795
|
// 1003049); older Apps (pre-permission manifests) return a pem too — placing
|
|
796
796
|
// it is harmless and heals once the owner grants the permissions + installs.
|
package/src/module-api.js
CHANGED
|
@@ -1134,8 +1134,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
1134
1134
|
// landed since 1.19.350 with no explicit bump. run 32850120472. (task 1002620)
|
|
1135
1135
|
// 1.19.352 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1136
1136
|
// landed since 1.19.351 with no explicit bump. run 32850767673. (task 1002620)
|
|
1137
|
+
// 1.19.353 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1138
|
+
// landed since 1.19.352 with no explicit bump. run 32851392269. (task 1002620)
|
|
1139
|
+
// 1.19.354 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1140
|
+
// landed since 1.19.353 with no explicit bump. run 32852045058. (task 1002620)
|
|
1137
1141
|
// ---------------------------------------------------------------------------
|
|
1138
|
-
const CORE_VERSION = '1.19.
|
|
1142
|
+
const CORE_VERSION = '1.19.354'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
1139
1143
|
|
|
1140
1144
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
1141
1145
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -484,3 +484,17 @@ test('control nits: orb cursor, caret under reduced motion, mode label, home lab
|
|
|
484
484
|
assert.match(HUB, /\.again\{[^}]*padding:6px 2px;min-height:24px;/);
|
|
485
485
|
assert.match(HUB, /\nsummary\{min-height:24px;padding:3px 0;\}/, 'every fold is a 24px target; the disclosure marker stays');
|
|
486
486
|
});
|
|
487
|
+
|
|
488
|
+
/* ── server-derived manifest truth (task 1003235) ── */
|
|
489
|
+
test('the done panel re-learns the GitHub-App leg from the instance read, yielding to a live manifest poll', () => {
|
|
490
|
+
/* the seed lives in startProgress's nested tick(), past where fn() stops — read the script */
|
|
491
|
+
const sp = SCRIPT.slice(SCRIPT.indexOf('function startProgress('));
|
|
492
|
+
assert.match(sp, /var ga = data\.github_app;\n\s*if \(ga && ga\.status && !manifestPollTimer\) \{/, 'seeded from the same 5s read; a running manifest poll owns live.manifest');
|
|
493
|
+
assert.match(sp, /var seeded = ga\.status === 'done' \? 'done' : ga\.status;/, 'the status route’s vocabulary, verbatim');
|
|
494
|
+
assert.match(sp, /if \(ga\.app_slug\) live\.appSlug = ga\.app_slug;\n\s*live\.manifest = seeded;/);
|
|
495
|
+
assert.match(sp, /if \(seedChanged\) \{ rememberManifest\(undefined, live\.manifest, live\.appSlug \|\| undefined\); renderSteps\(\); \}/, 'change-only: an identical tick neither rewrites the bridge nor re-renders');
|
|
496
|
+
const seedAt = sp.indexOf('var ga = data.github_app;');
|
|
497
|
+
const statusAt = sp.indexOf("if (inst && inst.status !== live.instStatus)");
|
|
498
|
+
assert.ok(seedAt > 0 && seedAt < statusAt, 'the manifest seed lands before the status re-render, so one renderSteps() paints both');
|
|
499
|
+
assert.match(fn('enter'), /if \(created\.manifest\) \{ live\.manifest = created\.manifest; live\.appSlug = created\.app_slug \|\| null; \}/, 'the sessionStorage bridge stays as the pre-first-tick seed');
|
|
500
|
+
});
|
package/tests/provision.mjs
CHANGED
|
@@ -238,6 +238,17 @@ t('webEnvBody: the repo target_ref seats the founding owner via <prefix>_FIRST_A
|
|
|
238
238
|
assert.doesNotMatch(P.webEnvBody(coTenant), /_FIRST_ADMIN=/);
|
|
239
239
|
});
|
|
240
240
|
|
|
241
|
+
t('webEnvBody: a resolved owner login wins over the repo owner — an org-owned repo seats the signed-in builder (task 1003240)', () => {
|
|
242
|
+
// The org that owns an adopted repo is not a sign-in identity; seating it seats nobody.
|
|
243
|
+
// Same preference instanceInitSpec already applies to the scaffold's branding.firstAdmin.
|
|
244
|
+
const inst = { ...coTenant, hosting_shape: 'standalone', domain: 'mercury.example.com', target_ref: 'acme-org/mercury' };
|
|
245
|
+
const withLogin = P.webEnvBody(inst, { ownerLogin: 'octocat' });
|
|
246
|
+
assert.match(withLogin, new RegExp(`${reEsc(NEUTRAL_PREFIX)}_FIRST_ADMIN=octocat`));
|
|
247
|
+
assert.doesNotMatch(withLogin, /_FIRST_ADMIN=acme-org/);
|
|
248
|
+
// no resolved login → the repo owner stays the fallback (a personal repo's owner IS the builder)
|
|
249
|
+
assert.match(P.webEnvBody(inst), new RegExp(`${reEsc(NEUTRAL_PREFIX)}_FIRST_ADMIN=acme-org`));
|
|
250
|
+
});
|
|
251
|
+
|
|
241
252
|
t('caddyBlock: a domain → on-demand-TLS reverse_proxy to the local port; no domain → a comment', () => {
|
|
242
253
|
const withDomain = P.caddyBlock({ ...coTenant, domain: 'mercury.example.com' });
|
|
243
254
|
assert.match(withDomain, /mercury\.example\.com \{/);
|
|
@@ -408,6 +419,21 @@ await ta('provisionInstance(standalone): the backup unit ExecStart points at the
|
|
|
408
419
|
assert.match(svc, /Environment=GDS_BACKUP_DB=mercury/);
|
|
409
420
|
});
|
|
410
421
|
|
|
422
|
+
await ta('provisionInstance(standalone): web.env seats the signed-in builder, not the org that owns the repo (task 1003240)', async () => {
|
|
423
|
+
const { deps } = recorder();
|
|
424
|
+
const bodies = {};
|
|
425
|
+
deps.writeFile = (p, body) => { bodies[p] = body; return { ok: true }; };
|
|
426
|
+
// the builders lookup answers with the signed-in owner; every other query stays empty
|
|
427
|
+
deps.db = { query: async (sql) => (/FROM builders/.test(String(sql)) ? { rows: [{ github_login: 'octocat' }] } : { rows: [] }) };
|
|
428
|
+
const inst = { ...coTenant, hosting_shape: 'standalone', target_ref: 'acme-org/mercury', owner_builder_id: 7 };
|
|
429
|
+
const r = await P.provisionInstance(inst, deps);
|
|
430
|
+
assert.equal(r.ok, true);
|
|
431
|
+
const envPath = Object.keys(bodies).find((p) => p.endsWith('/web.env'));
|
|
432
|
+
assert.ok(envPath, 'wrote web.env');
|
|
433
|
+
assert.match(bodies[envPath], new RegExp(`${reEsc(NEUTRAL_PREFIX)}_FIRST_ADMIN=octocat`));
|
|
434
|
+
assert.doesNotMatch(bodies[envPath], /_FIRST_ADMIN=acme-org/);
|
|
435
|
+
});
|
|
436
|
+
|
|
411
437
|
await ta('provisionInstance(standalone) SCAFFOLDS its repo, then syncs (pull + npm ci) + migrates from the instance root', async () => {
|
|
412
438
|
const { deps, cmds, writes } = recorder();
|
|
413
439
|
const r = await P.provisionInstance({ ...coTenant, hosting_shape: 'standalone', target_ref: 'lars589/mercury' }, deps);
|
|
@@ -42,6 +42,8 @@ provisioning.updateInstanceSettings = async (_db, id, patch) => {
|
|
|
42
42
|
};
|
|
43
43
|
provisioning.recordEvent = async (_db, e) => { events.push(e); };
|
|
44
44
|
provisioning.getOpenIntent = async () => null;
|
|
45
|
+
let manifest = null; // the latest manifest attempt the read surfaces (task 1003235)
|
|
46
|
+
provisioning.getLatestOAuthManifestForInstance = async () => manifest;
|
|
45
47
|
|
|
46
48
|
const api = require('../src/module-api.js');
|
|
47
49
|
let builder = { id: 7, github_login: 'owner', rank: 'thetes' };
|
|
@@ -106,6 +108,31 @@ await ta('owner reads their own instance', async () => {
|
|
|
106
108
|
assert.equal(r.body.instance.slug, 'spike');
|
|
107
109
|
});
|
|
108
110
|
|
|
111
|
+
await ta('the read carries the GitHub-App leg\'s truth — github_app {status, app_slug} from the latest manifest attempt (task 1003235)', async () => {
|
|
112
|
+
reset();
|
|
113
|
+
manifest = { id: 3, instance_id: 18, status: 'done', app_slug: 'spike-bongos', github_code: 'never-surfaced', state: 'never-surfaced' };
|
|
114
|
+
const r = await get('/provisioning/instances/18');
|
|
115
|
+
assert.equal(r.status, 200, r.raw);
|
|
116
|
+
assert.deepEqual(r.body.github_app, { status: 'done', app_slug: 'spike-bongos' }, 'an allow-list projection — the code and the state token never ride the read');
|
|
117
|
+
assert.equal(r.body.instance.slug, 'spike', 'the instance projection is untouched');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await ta('no manifest attempt yet → github_app is null, not absent (a reload can tell "never started" from "unknown")', async () => {
|
|
121
|
+
reset();
|
|
122
|
+
manifest = null;
|
|
123
|
+
const r = await get('/provisioning/instances/18');
|
|
124
|
+
assert.equal(r.status, 200, r.raw);
|
|
125
|
+
assert.ok('github_app' in r.body, 'the key is always present');
|
|
126
|
+
assert.equal(r.body.github_app, null);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await ta('publicGithubApp is a pure allow-list projection with the status route\'s vocabulary', async () => {
|
|
130
|
+
assert.equal(provisioning.publicGithubApp(null), null);
|
|
131
|
+
assert.equal(provisioning.publicGithubApp({ app_slug: 'x' }), null, 'a row with no status is no truth');
|
|
132
|
+
assert.deepEqual(provisioning.publicGithubApp({ status: 'pending', github_code: 'secret' }), { status: 'pending', app_slug: null });
|
|
133
|
+
assert.deepEqual(provisioning.publicGithubApp({ status: 'code_received', app_slug: 'mercury-bongos' }), { status: 'code_received', app_slug: 'mercury-bongos' });
|
|
134
|
+
});
|
|
135
|
+
|
|
109
136
|
await ta('ANOTHER builder\'s GET → 404 (no existence leak)', async () => {
|
|
110
137
|
reset({ as: { id: 8, github_login: 'stranger', rank: 'thetes' } });
|
|
111
138
|
const r = await get('/provisioning/instances/18');
|