@bongos/core 1.21.92 → 1.21.94
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 +86 -56
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +4 -2
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0363-who-decides-what-is-a-table-changed-only-by-amendment.md +1 -0
- package/docs/api/openapi.json +38 -7
- package/docs/api-reference.md +4 -3
- package/docs/copy-inventory.md +354 -354
- package/docs/copy-registry.json +420 -420
- package/docs/design/project-startup-direction.md +7 -0
- package/docs/module-api-changelog.md +9 -0
- package/docs/page-readings.json +963 -992
- package/modules/autonomy/routes/autonomy.js +14 -5
- package/modules/government/decision-board.js +13 -3
- package/modules/government/decision-rules.js +61 -13
- package/modules/lifecycle/routes/done-when.js +2 -2
- package/modules/lifecycle/routes/gate-approvals.js +1 -1
- package/modules/lifecycle/routes/goals.js +4 -4
- package/modules/lifecycle/routes/project-settings.js +2 -2
- package/modules/lifecycle/routes/tasks.js +1 -1
- package/modules/lifecycle/routes/versions.js +1 -1
- package/modules/npm-release/routes/release.js +1 -0
- package/modules/onboarding/routes/access-requests.js +3 -3
- package/modules/provisioning/github-setup.js +129 -0
- package/modules/provisioning/module.json +2 -1
- package/modules/provisioning/provisioning.js +3 -0
- package/modules/provisioning/routes/callback-page.js +66 -1
- package/modules/provisioning/routes/core-upgrade.js +11 -0
- package/modules/provisioning/routes/github-setup.js +113 -0
- package/modules/provisioning/routes/provisioning.js +11 -45
- package/modules/public-landing/public/projects.html +105 -57
- package/modules/render-deploy/routes/act.js +1 -1
- package/modules/security/routes/reports.js +3 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/fitness.js +7 -0
- package/scripts/gds/oauth-secret.js +4 -2
- package/scripts/gds/provision-github-setup.js +148 -0
- package/scripts/gds/provision.js +19 -5
- package/src/bongos/act-alone.js +84 -0
- package/src/bongos/routes/builders.js +6 -1
- package/src/module-api.js +7 -1
- package/tests/autonomy_fence_priority.mjs +3 -1
- package/tests/github_one_trip.mjs +297 -0
- package/tests/government_act_alone_buildings.mjs +430 -0
- package/tests/module_api.mjs +1 -0
- package/tests/npm_release_release.mjs +2 -1
- package/tests/projects_hub_dns_ready.mjs +2 -1
- package/tests/projects_hub_pre_uat.mjs +17 -9
- package/tests/provision.mjs +51 -9
- package/tests/provisioning_github_app_routes.mjs +94 -8
- package/tests/ship_mode.mjs +2 -1
- package/tests/wizard_github_feedback.mjs +8 -5
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// modules/provisioning/routes/github-setup.js — the GitHub leg's two PUBLIC, state-keyed
|
|
2
|
+
// routes: the progress poll the project page and the callback page read, and the setup_url
|
|
3
|
+
// GitHub sends the founder to after installing the app (task 1004649 — "Connect GitHub in
|
|
4
|
+
// one trip"; ADR 0133 is the manifest design).
|
|
5
|
+
//
|
|
6
|
+
// Its own routes file (declared in module.json) because routes/provisioning.js sits at the
|
|
7
|
+
// 1500-line size ratchet; the status poll moved here from it unchanged in key and contract.
|
|
8
|
+
// Both routes are public by the same model as the manifest callback: the unguessable,
|
|
9
|
+
// single-use `state` minted for the signed-in owner is the only key, and neither route
|
|
10
|
+
// reveals a secret or changes authority. The checklist they report is derived in
|
|
11
|
+
// ../github-setup.js from what the control-plane runner has VERIFIED.
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const express = require('express');
|
|
15
|
+
const api = require('../../../src/module-api');
|
|
16
|
+
const { pool } = api;
|
|
17
|
+
const provisioning = require('../provisioning');
|
|
18
|
+
const githubSetup = require('../github-setup');
|
|
19
|
+
const { makeAskRateLimiter } = require('../rate-limit');
|
|
20
|
+
const { callbackPage } = require('./callback-page');
|
|
21
|
+
|
|
22
|
+
const log = api.logger('provisioning');
|
|
23
|
+
|
|
24
|
+
// GitHub's installation id is digits; anything else is dropped before it reaches a log line.
|
|
25
|
+
function installationIdOf(q) {
|
|
26
|
+
const s = String((q && q.installation_id) || '');
|
|
27
|
+
return /^\d{1,20}$/.test(s) ? s : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = function buildGithubSetupRouter() {
|
|
31
|
+
const router = express.Router();
|
|
32
|
+
// the page polls every few seconds, the callback page every two — per-IP, in-module
|
|
33
|
+
const statusRateLimit = makeAskRateLimiter({ max: 120 });
|
|
34
|
+
const setupRateLimit = makeAskRateLimiter({ max: 30 });
|
|
35
|
+
|
|
36
|
+
// GET /provisioning/github-app/status?state=<> — the PUBLIC progress read (task 1004548;
|
|
37
|
+
// the checklist since task 1004649). Reveals the manifest lifecycle status, the
|
|
38
|
+
// instance's own (already-public) domain and repo, the App's public slug, and the
|
|
39
|
+
// four-step checklist with the one-trip install link — never a secret. `ready` is true
|
|
40
|
+
// iff sign-in is verified (the auto-open signal). `terminal` now waits for the install
|
|
41
|
+
// too: a finished sign-in with the app not yet installed is not the end of the trip.
|
|
42
|
+
// rank: public — no session, no mutation, no secret; an unknown state is a 404 no-op.
|
|
43
|
+
router.get('/provisioning/github-app/status', statusRateLimit, async (req, res) => {
|
|
44
|
+
try {
|
|
45
|
+
const state = String(req.query.state || '');
|
|
46
|
+
if (!state) return res.status(400).json({ ok: false, error: 'missing_state' });
|
|
47
|
+
const row = await provisioning.getOAuthManifestByState(pool, state);
|
|
48
|
+
if (!row) return res.status(404).json({ ok: false, error: 'unknown_state' });
|
|
49
|
+
const inst = await provisioning.getInstanceById(pool, row.instance_id).catch(() => null);
|
|
50
|
+
// EFFECTIVE status + how far a failure got (task 1004572, manifest-truth.js)
|
|
51
|
+
const { status, failure } = provisioning.publicGithubApp(row);
|
|
52
|
+
const setup = await githubSetup.githubSetupRead(pool, inst, row, status);
|
|
53
|
+
const failed = status === 'error' || status === 'expired';
|
|
54
|
+
return res.json({
|
|
55
|
+
ok: true,
|
|
56
|
+
status,
|
|
57
|
+
failure,
|
|
58
|
+
ready: status === 'done',
|
|
59
|
+
terminal: failed || (setup.checklist.connected && (setup.checklist.device_flow === 'done' || !setup.verifying)),
|
|
60
|
+
domain: inst && inst.domain ? inst.domain : null,
|
|
61
|
+
instance_status: inst ? inst.status : null,
|
|
62
|
+
// The App's public slug (task 1003049) — its GitHub page is public by design.
|
|
63
|
+
app_slug: row.app_slug || null,
|
|
64
|
+
checklist: setup.checklist,
|
|
65
|
+
install_url: setup.install_url,
|
|
66
|
+
repo: setup.repo,
|
|
67
|
+
verifying: setup.verifying,
|
|
68
|
+
});
|
|
69
|
+
} catch (err) {
|
|
70
|
+
log.error('[provisioning] GET /provisioning/github-app/status', err);
|
|
71
|
+
return res.status(500).json({ ok: false, error: 'status_check_failed' });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// GET /provisioning/github-app/setup?installation_id=&setup_action=&state= — the App's
|
|
76
|
+
// setup_url (task 1004649). GitHub sends the founder's BROWSER here after they install
|
|
77
|
+
// the app (or change its repos), carrying the state we put on the install link. GitHub's
|
|
78
|
+
// own docs warn the installation_id can be spoofed, so this route trusts NOTHING it is
|
|
79
|
+
// handed: it records only that an install was REPORTED for the attempt — which makes the
|
|
80
|
+
// runner ask GitHub, with the app's own key, whether the app really is on the repo — and
|
|
81
|
+
// hands the founder back to the project page. A missing or unknown state gets the same
|
|
82
|
+
// way back, without the attempt. rank: public — a browser redirect with no session; no
|
|
83
|
+
// secret revealed, no authority changed, the only write an audit row.
|
|
84
|
+
router.get('/provisioning/github-app/setup', setupRateLimit, async (req, res) => {
|
|
85
|
+
const sendPage = (status, page) => res.status(status).type('html').send(page);
|
|
86
|
+
try {
|
|
87
|
+
const state = String(req.query.state || '');
|
|
88
|
+
const row = /^[0-9a-f]{16,128}$/i.test(state) ? await provisioning.getOAuthManifestByState(pool, state) : null;
|
|
89
|
+
if (!row) {
|
|
90
|
+
return sendPage(200, callbackPage({ ok: true, title: 'Installed on GitHub',
|
|
91
|
+
body: '<p>GitHub has installed the app. Go back to your project’s page: it checks the install itself.</p><p><a class="btn" href="/projects?view=mine">Back to my projects →</a></p>' }));
|
|
92
|
+
}
|
|
93
|
+
const id = installationIdOf(req.query);
|
|
94
|
+
const action = String(req.query.setup_action || '') === 'update' ? 'update' : 'install';
|
|
95
|
+
await provisioning.recordEvent(pool, {
|
|
96
|
+
instanceId: row.instance_id, ownerBuilderId: row.requested_by, event: githubSetup.SETUP_EVENTS.installReported,
|
|
97
|
+
detail: `GitHub returned from the app ${action}${id ? ` (installation ${id}, unverified)` : ''}; awaiting the runner's check`, actor: 'github',
|
|
98
|
+
}).catch(() => {});
|
|
99
|
+
const inst = await provisioning.getInstanceById(pool, row.instance_id).catch(() => null);
|
|
100
|
+
const domain = inst && inst.domain ? inst.domain : null;
|
|
101
|
+
return sendPage(200, domain
|
|
102
|
+
? callbackPage({ ok: true, title: 'Installed on GitHub', state, domain })
|
|
103
|
+
: callbackPage({ ok: true, title: 'Installed on GitHub',
|
|
104
|
+
body: '<p>GitHub has installed the app. We check it on your project’s page.</p><p><a class="btn" href="/projects?view=mine">Back to my projects →</a></p>' }));
|
|
105
|
+
} catch (err) {
|
|
106
|
+
log.error('[provisioning] GET /provisioning/github-app/setup', err);
|
|
107
|
+
return sendPage(500, callbackPage({ ok: false, title: 'Something went wrong',
|
|
108
|
+
body: '<p>We couldn’t record GitHub’s response. Your project’s page checks the install itself.</p><p><a class="btn" href="/projects?view=mine">Back to my projects →</a></p>' }));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return router;
|
|
113
|
+
};
|
|
@@ -62,7 +62,7 @@ const { requireRoomOnBox } = require('../capacity-gate'); // a full box refuse
|
|
|
62
62
|
const { adoptCredentialFault } = require('../credential-preflight'); const { termsAcknowledgement } = require('../terms-ack');
|
|
63
63
|
const catalogBridge = require('../catalog-bridge');
|
|
64
64
|
const capacity = require('../capacity'); // the box's slot ceiling (ADR 0285, task 1003927)
|
|
65
|
-
const { callbackPage } = require('./callback-page');
|
|
65
|
+
const { callbackPage } = require('./callback-page'); const githubSetup = require('../github-setup');
|
|
66
66
|
// the LOUD body validators (task 1003504) — lifted out when this file hit the size ratchet
|
|
67
67
|
const { badProjectDetail, badModuleSelection, badProjectType, screenedOut } = require('./body-validators'); const demo = require('../demo'); const birth = require('../birth');
|
|
68
68
|
const { failFrom } = require('../public-refusal'); // every catch: a declared refusal reaches the owner, anything else stays opaque (task 1004126)
|
|
@@ -171,11 +171,7 @@ module.exports = function provisioningRoutes() {
|
|
|
171
171
|
const router = express.Router();
|
|
172
172
|
// Per-IP limiter for the public tls-check `ask` endpoint (SEC review, task 1945).
|
|
173
173
|
const askRateLimit = makeAskRateLimiter();
|
|
174
|
-
//
|
|
175
|
-
// 2096): the onboarding page polls it every ~3s while it waits, so legitimate volume is
|
|
176
|
-
// higher than tls-check's one-shot ask. 120/min/IP gives comfortable headroom while
|
|
177
|
-
// still shedding a flood; in-memory, own bucket (does not share tls-check's budget).
|
|
178
|
-
const statusRateLimit = makeAskRateLimiter({ max: 120 });
|
|
174
|
+
// (The public status poll and its own limiter moved to ./github-setup.js, task 1004649.)
|
|
179
175
|
// Limiter for the slug-availability check (task 1002898 N10). Signed-in-only, but a
|
|
180
176
|
// session is cheap and the route answers a yes/no about EVERY project slug on the
|
|
181
177
|
// instance — an enumeration oracle if left unthrottled. A wizard debouncing one handle
|
|
@@ -675,6 +671,9 @@ module.exports = function provisioningRoutes() {
|
|
|
675
671
|
instance: ownerInstance(inst),
|
|
676
672
|
open_intent: open ? { action: open.action, state: open.state } : null,
|
|
677
673
|
github_app: provisioning.publicGithubApp(manifest),
|
|
674
|
+
// the four-step checklist from what the runner verified (task 1004649) — the
|
|
675
|
+
// install link rides WITHOUT the state token this read never carries
|
|
676
|
+
github_setup: await githubSetup.githubSetupRead(pool, inst, manifest, manifest ? provisioning.publicGithubApp(manifest).status : null, { withState: false }),
|
|
678
677
|
});
|
|
679
678
|
} catch (err) {
|
|
680
679
|
log.error('[provisioning] GET /provisioning/instances/:id', err);
|
|
@@ -1392,41 +1391,8 @@ module.exports = function provisioningRoutes() {
|
|
|
1392
1391
|
}
|
|
1393
1392
|
});
|
|
1394
1393
|
|
|
1395
|
-
// GET /provisioning/github-app/status
|
|
1396
|
-
//
|
|
1397
|
-
// Same public-by-state model as the callback: the unguessable, single-use `state` token
|
|
1398
|
-
// is the only key, and the response reveals only the manifest lifecycle status + the
|
|
1399
|
-
// instance's own (already-public) domain — never a secret. Per-IP rate-limited because
|
|
1400
|
-
// the page polls it every ~3s. `ready` is true iff the manifest reached 'done' (creds
|
|
1401
|
-
// placed + /healthz verified) — the setup checklist then opens https://<domain>.
|
|
1402
|
-
// rank: public — no session, no mutation, no secret; an unknown state is a 404 no-op.
|
|
1403
|
-
router.get('/provisioning/github-app/status', statusRateLimit, async (req, res) => {
|
|
1404
|
-
try {
|
|
1405
|
-
const state = String(req.query.state || '');
|
|
1406
|
-
if (!state) return res.status(400).json({ ok: false, error: 'missing_state' });
|
|
1407
|
-
const row = await provisioning.getOAuthManifestByState(pool, state);
|
|
1408
|
-
if (!row) return res.status(404).json({ ok: false, error: 'unknown_state' });
|
|
1409
|
-
const inst = await provisioning.getInstanceById(pool, row.instance_id).catch(() => null);
|
|
1410
|
-
// EFFECTIVE status + how far a failure got (task 1004572, manifest-truth.js)
|
|
1411
|
-
const { status, failure } = provisioning.publicGithubApp(row);
|
|
1412
|
-
return res.json({
|
|
1413
|
-
ok: true,
|
|
1414
|
-
status,
|
|
1415
|
-
failure,
|
|
1416
|
-
ready: status === 'done',
|
|
1417
|
-
terminal: status === 'done' || status === 'error' || status === 'expired',
|
|
1418
|
-
domain: inst && inst.domain ? inst.domain : null,
|
|
1419
|
-
instance_status: inst ? inst.status : null,
|
|
1420
|
-
// The App's public slug (task 1003049): lets the wizard render the one-click
|
|
1421
|
-
// install link (github.com/apps/<slug>/installations/new) for the app-install
|
|
1422
|
-
// step. Public info — the App's page is public by design (task 1003048).
|
|
1423
|
-
app_slug: row.app_slug || null,
|
|
1424
|
-
});
|
|
1425
|
-
} catch (err) {
|
|
1426
|
-
log.error('[provisioning] GET /provisioning/github-app/status', err);
|
|
1427
|
-
return res.status(500).json({ ok: false, error: 'status_check_failed' });
|
|
1428
|
-
}
|
|
1429
|
-
});
|
|
1394
|
+
// GET /provisioning/github-app/status + /setup — the state-keyed poll and the App's
|
|
1395
|
+
// setup_url — live in ./github-setup.js (task 1004649).
|
|
1430
1396
|
|
|
1431
1397
|
// GET /provisioning/github-app/callback?code=&state= — GitHub redirects the owner's
|
|
1432
1398
|
// BROWSER here after they approve the App Manifest (task 2080, ADR 0133). It is a
|
|
@@ -1465,11 +1431,11 @@ module.exports = function provisioningRoutes() {
|
|
|
1465
1431
|
}).catch(() => {});
|
|
1466
1432
|
const inst = await provisioning.getInstanceById(pool, row.instance_id).catch(() => null);
|
|
1467
1433
|
const domain = inst && inst.domain ? inst.domain : null;
|
|
1468
|
-
//
|
|
1469
|
-
//
|
|
1470
|
-
//
|
|
1434
|
+
// One trip (task 1004649): the page waits for the runner to name the app, then sends
|
|
1435
|
+
// the founder on to installing it on their repo; GitHub's setup_url brings them back
|
|
1436
|
+
// to the checklist. Falls back to a static success if we have no domain to point at.
|
|
1471
1437
|
return sendPage(200, domain
|
|
1472
|
-
? callbackPage({ ok: true, title: 'GitHub app created', state, domain })
|
|
1438
|
+
? callbackPage({ ok: true, title: 'GitHub app created', state, domain, next: 'install' })
|
|
1473
1439
|
: callbackPage({ ok: true, title: 'GitHub app created',
|
|
1474
1440
|
body: '<p>Nice — GitHub created the app. We’re placing its credentials on your instance now; this finishes on its own within a few minutes.</p><p class="muted">You can close this tab.</p>' }));
|
|
1475
1441
|
} catch (err) {
|
|
@@ -601,6 +601,8 @@ body:has(#view-new:not([hidden])) footer.foot{position:relative;z-index:1;backgr
|
|
|
601
601
|
display:inline-flex;align-items:center;justify-content:center;font-family:var(--font-body);font-size:13px;letter-spacing:0;}
|
|
602
602
|
#panel-done .humanSteps>li.sdone::before{content:"✓";text-transform:none;color:var(--ok);background:color-mix(in srgb,var(--ok) 12%,transparent);border:1px solid var(--ok);}
|
|
603
603
|
#panel-done .humanSteps>li.slocked::before{border:1px solid var(--rule-soft);}
|
|
604
|
+
/* the optional step (command-line sign-in, task 1004649): a dashed ring until it ticks */
|
|
605
|
+
#panel-done .humanSteps>li.soptional:not(.sdone)::before{border-style:dashed;}
|
|
604
606
|
#panel-done .humanSteps b{font-size:15px;line-height:24px;}
|
|
605
607
|
.alive{margin-top:18px;padding-top:18px;border-top:1px solid var(--rule);}
|
|
606
608
|
#view-new .aliveH{margin:0;font-family:var(--font-display);font-size:28px;font-weight:200;letter-spacing:-.02em;line-height:1.2;text-transform:none;color:var(--ink);}
|
|
@@ -6603,7 +6605,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6603
6605
|
return '<a href="' + (instanceId ? '?view=manage&id=' + encodeURIComponent(instanceId) : '?view=mine') +
|
|
6604
6606
|
'">' + label + '</a>';
|
|
6605
6607
|
}
|
|
6606
|
-
var live = { instStatus: null, addrReady: false, manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null };
|
|
6608
|
+
var live = { instStatus: null, addrReady: false, manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null, setup: null, installUrl: null };
|
|
6607
6609
|
var planPromises = {}; /* mode|domain → Promise<steps[]> */
|
|
6608
6610
|
var lastStepsHtml = '';
|
|
6609
6611
|
var lastProgHtml = ''; /* same change-only contract, for the progress strip */
|
|
@@ -6632,6 +6634,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6632
6634
|
(task 1002562) — the owner no longer runs `bongos init --adopt` here. */
|
|
6633
6635
|
function renderStepGithubRepo() {
|
|
6634
6636
|
var landed = live.instStatus === 'active';
|
|
6637
|
+
/* the one-page checklist (task 1004649) shows only the GitHub steps the founder
|
|
6638
|
+
takes; a repo the platform made or adopted needs nothing, so it is not a row */
|
|
6639
|
+
if (state.mode === 'adopt' || state.repoAutoCreated || landed) return '';
|
|
6635
6640
|
var repoLabel = state.owner && state.repo ? '<strong>' + esc(state.owner + '/' + state.repo) + '</strong>' : 'your new repository';
|
|
6636
6641
|
/* Naming a repo without linking it stranded the owner on exactly the path where the
|
|
6637
6642
|
platform did the work (task 1002855). Adopt links the repo the owner picked;
|
|
@@ -6697,27 +6702,34 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6697
6702
|
else window.location.assign(url);
|
|
6698
6703
|
}
|
|
6699
6704
|
|
|
6705
|
+
/* The four GitHub steps of part 03, in the mockup's words and order (task 1004649,
|
|
6706
|
+
docs/design/mocks/day-night-rooms/): Approve the <name> app on GitHub · Install it
|
|
6707
|
+
on your repo · Builders can sign in · Command-line sign-in is on. One click starts
|
|
6708
|
+
ONE trip — approve, straight on to installing, back here — and every tick is what
|
|
6709
|
+
the server verified (live.setup, the status poll's `checklist`). Every value that
|
|
6710
|
+
reaches the markup goes through esc(). */
|
|
6711
|
+
function ghStep(key) { return live.setup && live.setup[key]; }
|
|
6712
|
+
function appTitle() { return 'Approve the ' + esc(state.name || state.slug || 'project') + ' app on GitHub'; }
|
|
6713
|
+
|
|
6700
6714
|
/* oauth-app — the ADR 0133 one-click GitHub-App manifest flow is the primary path
|
|
6701
6715
|
(gap 3 / idea 1000676); locked until a domain exists (the App needs the real
|
|
6702
6716
|
callback host — the server refuses domainless with domain_required). */
|
|
6703
6717
|
function renderStepOauthApp() {
|
|
6704
6718
|
var domain = resolvedDomain();
|
|
6705
6719
|
if (!domain) {
|
|
6706
|
-
return '<li class="slocked" data-onboard-step="oauth-app"><b>
|
|
6720
|
+
return '<li class="slocked" data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>Unlocks once your project has an address — sign-in needs a real domain. Attach one any time from ' + projectPageLink('this project’s own page') + '.</p></li>';
|
|
6721
|
+
}
|
|
6722
|
+
/* GitHub made the app and the platform placed its keys: the slug is recorded only
|
|
6723
|
+
after the placement (the runner), so a known slug outside a failure is the tick —
|
|
6724
|
+
a fact, whatever the venue says about starting a NEW trip */
|
|
6725
|
+
if (live.manifest !== 'error' && live.manifest !== 'expired' && (ghStep('approve') === 'done' || live.appSlug)) {
|
|
6726
|
+
return '<li class="sdone" data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>GitHub made the app, and its keys are on your project.</p></li>';
|
|
6707
6727
|
}
|
|
6708
6728
|
/* honesty about the venue (owner walk, 2026-08-12): GitHub rejects app
|
|
6709
6729
|
manifests whose hook URL isn't publicly reachable, so a locally-hosted
|
|
6710
6730
|
platform can't complete the one-click flow — say it BEFORE the bounce. */
|
|
6711
6731
|
if (!apexBase()) {
|
|
6712
|
-
return '<li class="slocked" data-onboard-step="oauth-app"><b>
|
|
6713
|
-
}
|
|
6714
|
-
if (live.manifest === 'done') {
|
|
6715
|
-
/* the wait ends HERE now, not on the callback page (task 1004548): once the
|
|
6716
|
-
project answers too, the step carries the door itself */
|
|
6717
|
-
var open = live.instStatus === 'active' && live.addrReady;
|
|
6718
|
-
return '<li class="sdone" data-onboard-step="oauth-app"><b>GitHub sign-in is wired</b><p>Credentials placed and verified — sign-in on <code style="font-family:var(--font-mono)">' + esc(domain) + '</code> is live.' +
|
|
6719
|
-
(open ? '' : ' The door below opens once your project is answering.') + '</p>' +
|
|
6720
|
-
(open ? '<a class="ghbtn" href="https://' + esc(domain) + '" target="_blank" rel="noopener">Open ' + esc(domain) + ' →</a>' : '') + '</li>';
|
|
6732
|
+
return '<li class="slocked" data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>This platform is running on a local address, and GitHub refuses app setups it can’t reach over the public internet — so the one-click flow can’t complete here. On a publicly-addressed platform this is one click.</p>' + manualOauthDetails() + '</li>';
|
|
6721
6733
|
}
|
|
6722
6734
|
if (live.manifest === 'error' || live.manifest === 'expired') {
|
|
6723
6735
|
/* a plain sentence first; the raw route text folds under Technical
|
|
@@ -6731,86 +6743,98 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6731
6743
|
else if (live.manifestCode === 'domain_required') snag = 'This project needs an address first — attach one from ' + projectPageLink('its own page') + ', then come back to this step.';
|
|
6732
6744
|
else if (live.manifestFailure === 'sign_in_unconfirmed') {
|
|
6733
6745
|
var appLink = live.appSlug ? '<a href="https://github.com/settings/apps/' + esc(live.appSlug) + '" target="_blank" rel="noopener">your app on GitHub</a>' : 'the app on your GitHub account';
|
|
6734
|
-
return '<li data-onboard-step="oauth-app"><b>
|
|
6746
|
+
return '<li data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>GitHub approved the app and the platform placed its credentials on your project, but your project didn’t confirm that sign-in works. Trying again would make a second app on your GitHub account and is unlikely to fix this — it needs a look from the platform team. <a href="/contact">Contact us</a> and mention ' + appLink + '.</p>' +
|
|
6735
6747
|
progressMarkup(1, 1, 'Stopped: your project didn’t confirm sign-in.', 'isErr', 'signin') +
|
|
6736
6748
|
'<button type="button" class="linklike" data-action="manifest-start">Try again anyway</button></li>';
|
|
6737
6749
|
}
|
|
6738
6750
|
else if (live.manifestFailure === 'finish_failed') snag = 'GitHub approved the app, but the platform couldn’t finish setting up sign-in on your project. The app GitHub made is still on your account — delete it first (GitHub → Settings → Developer settings → GitHub Apps) so you don’t end up with two, then try again.';
|
|
6739
6751
|
else { snag = 'GitHub setup didn’t start. Nothing was changed on GitHub — try again.'; raw = live.manifestMsg; }
|
|
6740
|
-
return '<li data-onboard-step="oauth-app"><b>
|
|
6752
|
+
return '<li data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>' + snag + '</p>' +
|
|
6741
6753
|
(raw ? '<details><summary>Technical details</summary><p>' + esc(raw) + '</p></details>' : '') +
|
|
6742
|
-
'<button type="button" class="ghbtn" data-action="manifest-start">' + GH_ICON + '
|
|
6754
|
+
'<button type="button" class="ghbtn" data-action="manifest-start">' + GH_ICON + 'Connect GitHub ↗</button>' + manualOauthDetails() + '</li>';
|
|
6743
6755
|
}
|
|
6744
6756
|
if (live.manifest === 'code_received' || live.manifest === 'converting') {
|
|
6745
|
-
/* GitHub's half is done — say so, rather than keep telling the
|
|
6746
|
-
to approve an app they just approved */
|
|
6757
|
+
/* GitHub's half of the approval is done — say so, rather than keep telling the
|
|
6758
|
+
founder to approve an app they just approved */
|
|
6747
6759
|
autoOpenArmed = true;
|
|
6748
|
-
return '<li data-onboard-step="oauth-app"><b>
|
|
6749
|
-
progressMarkup(1, 1, 'Placing
|
|
6760
|
+
return '<li data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>Approved on GitHub. The platform is placing the app’s keys on your project, and the GitHub tab goes on to installing it on your repo. This step ticks itself.</p>' +
|
|
6761
|
+
progressMarkup(1, 1, 'Placing the app’s keys on your project…', '', 'signin') + autoOpenTick() + '</li>';
|
|
6750
6762
|
}
|
|
6751
6763
|
if (live.manifest) { /* pending — GitHub holds the manifest, nobody has approved it yet */
|
|
6752
6764
|
autoOpenArmed = true;
|
|
6753
|
-
return '<li data-onboard-step="oauth-app"><b>
|
|
6765
|
+
return '<li data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>Waiting on GitHub — approve the app in the tab that opened. No GitHub tab? It may have opened behind this window or in another one. Once approved, GitHub takes you straight on to installing it on your repo, then back here; every step ticks itself.</p>' +
|
|
6754
6766
|
progressMarkup(0, 0, 'Waiting for your approval on GitHub…', '', 'signin') + autoOpenTick() +
|
|
6755
6767
|
(manifestStale ? '<button type="button" class="linklike" data-action="manifest-start">Didn’t get to GitHub? Start the setup again</button>' : '') + '</li>';
|
|
6756
6768
|
}
|
|
6757
|
-
return '<li data-onboard-step="oauth-app"><b>
|
|
6758
|
-
'<button type="button" class="ghbtn" data-action="manifest-start">' + GH_ICON + '
|
|
6769
|
+
return '<li data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>One trip to GitHub: approve the app, install it on your repo, and you land back here. No keys to copy, and the secret never leaves the server side.</p>' +
|
|
6770
|
+
'<button type="button" class="ghbtn" data-action="manifest-start">' + GH_ICON + 'Connect GitHub ↗</button>' + manualOauthDetails() + '</li>';
|
|
6759
6771
|
}
|
|
6760
6772
|
|
|
6761
|
-
/* device-flow —
|
|
6762
|
-
GitHub creates a manifest-born App with Device Flow OFF and offers no
|
|
6763
|
-
|
|
6764
|
-
the
|
|
6773
|
+
/* device-flow — "Command-line sign-in is on", the OPTIONAL fourth step (task 1004649).
|
|
6774
|
+
GitHub creates a manifest-born App with Device Flow OFF and offers no manifest
|
|
6775
|
+
field and no API to switch it on (the spike in task 1004649), so only the founder's
|
|
6776
|
+
own tick in the app's settings can — and the runner ticks this step when GitHub
|
|
6777
|
+
starts a device sign-in for the app. It never holds the project back: web sign-in
|
|
6778
|
+
works without it, and the CLI falls back to a web token meanwhile. */
|
|
6765
6779
|
function renderStepDeviceFlow() {
|
|
6780
|
+
var title = '<b>Command-line sign-in is on</b>';
|
|
6781
|
+
if (ghStep('device_flow') === 'done') {
|
|
6782
|
+
return '<li class="sdone soptional" data-onboard-step="device-flow">' + title + '<p>Builders can sign in from a terminal too — the <code style="font-family:var(--font-mono)">bongos setup</code> command builders run on their own machine.</p></li>';
|
|
6783
|
+
}
|
|
6766
6784
|
if (live.manifest !== 'done') {
|
|
6767
|
-
return '<li class="slocked" data-onboard-step="device-flow"
|
|
6785
|
+
return '<li class="slocked soptional" data-onboard-step="device-flow">' + title + '<p>Optional — turn it on later. It lets builders sign in from a terminal, with the <code style="font-family:var(--font-mono)">bongos setup</code> command they run on their own machine; web sign-in works without it.</p></li>';
|
|
6768
6786
|
}
|
|
6769
6787
|
var appUrl = live.appSlug ? 'https://github.com/settings/apps/' + esc(live.appSlug) : null;
|
|
6770
|
-
return '<li data-onboard-step="device-flow"
|
|
6788
|
+
return '<li class="soptional" data-onboard-step="device-flow">' + title + '<p>Optional — turn it on later. GitHub leaves it off on new apps and lets only you switch it on: ' +
|
|
6771
6789
|
(appUrl
|
|
6772
|
-
? '
|
|
6790
|
+
? 'open <a href="' + appUrl + '" target="_blank" rel="noopener">your app’s settings</a>, check <strong>Enable Device Flow</strong>, and save. '
|
|
6773
6791
|
: 'GitHub → Settings → Developer settings → GitHub Apps → your app → check <strong>Enable Device Flow</strong> → save. ') +
|
|
6774
|
-
'
|
|
6792
|
+
'We tick this when GitHub has it on. Until then builders sign in on the web, and terminal sign-in falls back to a web token from the hall’s Settings page.</p></li>';
|
|
6775
6793
|
}
|
|
6776
6794
|
|
|
6777
|
-
/* app-install — the ADR 0055 publish credential (task
|
|
6778
|
-
|
|
6779
|
-
|
|
6795
|
+
/* app-install — "Install it on your repo": the ADR 0055 publish credential (task
|
|
6796
|
+
1003049). The same trip carries the founder here straight from the approval (task
|
|
6797
|
+
1004649); this step ticks only once the runner has asked GitHub, with the app's own
|
|
6798
|
+
key, and GitHub said the app is on this repo. The button is the way back for a
|
|
6799
|
+
founder who returned without installing. */
|
|
6780
6800
|
function renderStepAppInstall() {
|
|
6781
|
-
|
|
6782
|
-
|
|
6801
|
+
var title = '<b>Install it on your repo</b>';
|
|
6802
|
+
if (ghStep('install') === 'done') {
|
|
6803
|
+
return '<li class="sdone" data-onboard-step="app-install">' + title + '<p>The app is on ' + (state.owner && state.repo ? esc(state.owner + '/' + state.repo) : 'your repo') + ', so finished work can merge and ship itself.</p></li>';
|
|
6783
6804
|
}
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
'
|
|
6805
|
+
if (!live.appSlug) {
|
|
6806
|
+
return '<li class="slocked" data-onboard-step="app-install">' + title + '<p>Comes straight after the approval, on the same trip: pick your repo and you land back here.</p></li>';
|
|
6807
|
+
}
|
|
6808
|
+
var installUrl = live.installUrl || ('https://github.com/apps/' + encodeURIComponent(live.appSlug) + '/installations/new');
|
|
6809
|
+
return '<li data-onboard-step="app-install">' + title + '<p>Your GitHub tab goes here next. We check with GitHub that the app is on your repo, and this step ticks itself. Came back without installing it?</p>' +
|
|
6810
|
+
'<a class="ghbtn" href="' + esc(installUrl) + '" target="_blank" rel="noopener">' + GH_ICON + 'Install it on your repo ↗</a></li>';
|
|
6790
6811
|
}
|
|
6791
6812
|
|
|
6813
|
+
/* first-sign-in — "Builders can sign in": ticks when the platform has seen sign-in
|
|
6814
|
+
work on the project (the runner's auth_configured check, the manifest 'done'). The
|
|
6815
|
+
door opens only when the project ANSWERS too: a link to an address that does not
|
|
6816
|
+
resolve yet is the worst first impression a founder can get (task 1003232). */
|
|
6792
6817
|
function renderStepFirstSignIn() {
|
|
6793
6818
|
var domain = resolvedDomain();
|
|
6819
|
+
var title = '<b>Builders can sign in</b>';
|
|
6794
6820
|
if (!domain) {
|
|
6795
|
-
return '<li class="slocked" data-onboard-step="first-sign-in"
|
|
6821
|
+
return '<li class="slocked" data-onboard-step="first-sign-in">' + title + '<p>Unlocks with an address — then you open it, sign in with GitHub once, and take the owner’s seat (the founding Archon).</p></li>';
|
|
6796
6822
|
}
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
impression a founder can get of their own project (task 1003232) */
|
|
6800
|
-
if (live.instStatus !== 'active' || !live.addrReady || live.manifest !== 'done') {
|
|
6801
|
-
return '<li class="slocked" data-onboard-step="first-sign-in"><b>First sign-in</b><p>Unlocks once your project is answering and sign-in above is wired. Then you’ll open <code style="font-family:var(--font-mono)">' + esc(domain) + '</code>, sign in with GitHub once, and be seated as the owner — the founding Archon.</p></li>';
|
|
6823
|
+
if (live.manifest !== 'done') {
|
|
6824
|
+
return '<li class="slocked" data-onboard-step="first-sign-in">' + title + '<p>Ticks once the platform has seen sign-in work on <code style="font-family:var(--font-mono)">' + esc(domain) + '</code>.</p></li>';
|
|
6802
6825
|
}
|
|
6803
|
-
|
|
6826
|
+
var open = live.instStatus === 'active' && live.addrReady;
|
|
6827
|
+
return '<li class="sdone" data-onboard-step="first-sign-in">' + title + '<p>Sign-in works on <code style="font-family:var(--font-mono)">' + esc(domain) + '</code>.' +
|
|
6828
|
+
(open ? ' Open it and sign in with GitHub once to take the owner’s seat — the founding Archon.' : ' The door opens here once your project is answering.') + '</p>' +
|
|
6829
|
+
(open ? '<a class="ghbtn" href="https://' + esc(domain) + '" target="_blank" rel="noopener">Open ' + esc(domain) + ' →</a>' : '') + '</li>';
|
|
6804
6830
|
}
|
|
6805
6831
|
|
|
6832
|
+
/* enter-hall — not a row of the GitHub checklist (the "is alive" block below carries
|
|
6833
|
+
the way in); kept only for a project with no address, whose hall is local */
|
|
6806
6834
|
function renderStepEnterHall() {
|
|
6807
6835
|
var domain = resolvedDomain();
|
|
6808
|
-
if (domain
|
|
6809
|
-
|
|
6810
|
-
}
|
|
6811
|
-
return domain
|
|
6812
|
-
? '<li data-onboard-step="enter-hall"><b>Enter your builders hall</b><p>Your project’s home — open <a href="https://' + esc(domain) + '/builders" target="_blank" rel="noopener"><code style="font-family:var(--font-mono)">' + esc(domain) + '/builders</code></a> and start building.</p></li>'
|
|
6813
|
-
: '<li data-onboard-step="enter-hall"><b>Enter your builders hall</b><p>No address yet — run <code style="font-family:var(--font-mono)">bongos dev</code> in your repo checkout to start the local hall on your machine.</p></li>';
|
|
6836
|
+
if (domain) return '';
|
|
6837
|
+
return '<li data-onboard-step="enter-hall"><b>Enter your builders hall</b><p>No address yet — run <code style="font-family:var(--font-mono)">bongos dev</code> in your repo checkout to start the local hall on your machine (on an address it lives at /builders).</p></li>';
|
|
6814
6838
|
}
|
|
6815
6839
|
|
|
6816
6840
|
var STEP_RENDERERS = {
|
|
@@ -6828,9 +6852,13 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6828
6852
|
control-plane wizard cannot reach. Spun out per the direction's §9
|
|
6829
6853
|
delivery-split as task 1002739. */
|
|
6830
6854
|
|
|
6855
|
+
/* the mockup's order: the repo (only when the founder must make it), approve,
|
|
6856
|
+
install, sign-in, command line; a plan step this page has no row for goes last */
|
|
6857
|
+
var STEP_ORDER = ['github-repo', 'oauth-app', 'app-install', 'first-sign-in', 'device-flow', 'enter-hall'];
|
|
6858
|
+
function stepRank(s) { var i = STEP_ORDER.indexOf(s.key); return i < 0 ? STEP_ORDER.length : i; }
|
|
6831
6859
|
function renderSteps() {
|
|
6832
6860
|
fetchPlan().then(function (steps) {
|
|
6833
|
-
var html = steps.filter(function (s) { return s && s.kind === 'human'; }).map(function (s) {
|
|
6861
|
+
var html = steps.filter(function (s) { return s && s.kind === 'human'; }).slice().sort(function (a, b) { return stepRank(a) - stepRank(b); }).map(function (s) {
|
|
6834
6862
|
var r = STEP_RENDERERS[s.key];
|
|
6835
6863
|
return r ? r(s) : '<li data-onboard-step="' + esc(s.key) + '"><b>' + esc(s.label || s.key) + '</b></li>';
|
|
6836
6864
|
}).join('');
|
|
@@ -6991,6 +7019,22 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6991
7019
|
function manifestInFlight() {
|
|
6992
7020
|
return live.manifest === 'pending' || live.manifest === 'code_received' || live.manifest === 'converting';
|
|
6993
7021
|
}
|
|
7022
|
+
/* the checklist the server derived (task 1004649) — change-only; true when it moved.
|
|
7023
|
+
The install link rides only on a state-keyed read; the owner's read keeps the last. */
|
|
7024
|
+
function seedSetup(checklist, installUrl) {
|
|
7025
|
+
if (!checklist || typeof checklist !== 'object') return false;
|
|
7026
|
+
var next = JSON.stringify(checklist), was = live.setup ? JSON.stringify(live.setup) : '';
|
|
7027
|
+
var url = typeof installUrl === 'string' && /^https:\/\/github\.com\/apps\//.test(installUrl) ? installUrl : null;
|
|
7028
|
+
var urlMoved = !!url && url !== live.installUrl && (!live.installUrl || /[?&]state=/.test(url) || !/[?&]state=/.test(live.installUrl));
|
|
7029
|
+
if (next === was && !urlMoved) return false;
|
|
7030
|
+
live.setup = checklist;
|
|
7031
|
+
if (urlMoved) live.installUrl = url;
|
|
7032
|
+
return true;
|
|
7033
|
+
}
|
|
7034
|
+
/* still worth watching after sign-in: the install not verified yet, while the runner checks */
|
|
7035
|
+
function setupOpen() {
|
|
7036
|
+
return !!(live.setup && !live.setup.connected && live.manifest === 'done');
|
|
7037
|
+
}
|
|
6994
7038
|
function noteManifestWait() {
|
|
6995
7039
|
if (live.manifest === 'pending' && !manifestStale && manifestStartedAt && (Date.now() - manifestStartedAt) > 90000) { manifestStale = true; renderSteps(); }
|
|
6996
7040
|
}
|
|
@@ -7006,6 +7050,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7006
7050
|
.then(function (d) {
|
|
7007
7051
|
if (myGen !== manifestGen) return;
|
|
7008
7052
|
if (d.app_slug && d.app_slug !== live.appSlug) { live.appSlug = d.app_slug; rememberManifest(undefined, undefined, d.app_slug); renderSteps(); }
|
|
7053
|
+
if (seedSetup(d.checklist, d.install_url)) renderSteps(); /* the server-verified ticks (task 1004649) */
|
|
7009
7054
|
var next = d.ready ? 'done' : (d.status || 'pending');
|
|
7010
7055
|
var failure = d.failure || null;
|
|
7011
7056
|
if (next !== live.manifest || failure !== live.manifestFailure) { live.manifest = next; live.manifestFailure = failure; rememberManifest(undefined, next); renderSteps(); }
|
|
@@ -7249,6 +7294,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7249
7294
|
if (seedChanged) { rememberManifest(undefined, live.manifest, live.appSlug || undefined); renderSteps(); }
|
|
7250
7295
|
else noteManifestWait();
|
|
7251
7296
|
}
|
|
7297
|
+
/* the server-verified checklist rides the same read (task 1004649) */
|
|
7298
|
+
if (data.github_setup && !manifestPollTimer && seedSetup(data.github_setup.checklist, data.github_setup.install_url)) renderSteps();
|
|
7252
7299
|
/* the checklist reflects live state (task 1002694): a status transition —
|
|
7253
7300
|
e.g. the scaffold landing — re-renders the steps (change-only, so the
|
|
7254
7301
|
5s tick never clobbers an open dropdown or mid-flight button) */
|
|
@@ -7258,7 +7305,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7258
7305
|
var terminal = renderProgress(inst);
|
|
7259
7306
|
/* a GitHub leg still in flight keeps this read alive even once the
|
|
7260
7307
|
project is up — after a reload it is the ONLY thing watching it */
|
|
7261
|
-
if (!terminal || manifestInFlight()) pollTimer = setTimeout(tick, 5000);
|
|
7308
|
+
if (!terminal || manifestInFlight() || setupOpen()) pollTimer = setTimeout(tick, 5000);
|
|
7262
7309
|
else stopPolling();
|
|
7263
7310
|
})
|
|
7264
7311
|
.catch(function (err) {
|
|
@@ -7288,7 +7335,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7288
7335
|
instanceId = null;
|
|
7289
7336
|
watchPending = false; manifestPending = false; manifestState = null;
|
|
7290
7337
|
manifestStartedAt = 0; manifestStale = false;
|
|
7291
|
-
live = { instStatus: null, addrReady: false, manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null };
|
|
7338
|
+
live = { instStatus: null, addrReady: false, manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null, setup: null, installUrl: null };
|
|
7292
7339
|
lastStepsHtml = '';
|
|
7293
7340
|
lastProgHtml = '';
|
|
7294
7341
|
hopReset();
|
|
@@ -7722,7 +7769,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7722
7769
|
var list = $('humanSteps'), st = $('ghState');
|
|
7723
7770
|
if (!list || !st) return;
|
|
7724
7771
|
if (state.step !== 2) { st.textContent = 'opens once your project exists'; return; }
|
|
7725
|
-
|
|
7772
|
+
/* the optional step (command-line sign-in) never holds "connected" back */
|
|
7773
|
+
var items = Array.prototype.slice.call(list.children).filter(function (li) { return !li.classList.contains('soptional'); });
|
|
7726
7774
|
var done = items.filter(function (li) { return li.classList.contains('sdone'); }).length;
|
|
7727
7775
|
st.textContent = items.length && done === items.length ? 'connected ✓'
|
|
7728
7776
|
: manifestInFlight() ? 'waiting on GitHub…'
|
|
@@ -26,7 +26,7 @@ const log = api.logger('render-deploy');
|
|
|
26
26
|
module.exports = function buildRenderDeployActRouter({ makeClient = defaultMakeClient, pool } = {}) {
|
|
27
27
|
const router = express.Router();
|
|
28
28
|
|
|
29
|
-
router.post('/render-deploy/act', api.requireBuilder, api.requirePermission('core.pin.move'), async (req, res) => {
|
|
29
|
+
router.post('/render-deploy/act', api.requireBuilder, api.requirePermission('core.pin.move'), api.requireActAlone('release'), async (req, res) => {
|
|
30
30
|
const db = pool || api.pool;
|
|
31
31
|
// Shape only, and strict: an undeclared field is refused. `confirm` and `expectLiveDeployId`
|
|
32
32
|
// are left to the checks below so each gets its own plain refusal rather than a schema one.
|
|
@@ -217,6 +217,7 @@ module.exports = function buildSecurityReportsRouter() {
|
|
|
217
217
|
'/security/reports/:id/confirm',
|
|
218
218
|
api.requireBuilder,
|
|
219
219
|
api.requirePermission('security.report.adjudicate'),
|
|
220
|
+
api.requireActAlone('disputes'),
|
|
220
221
|
async (req, res) => {
|
|
221
222
|
// drachmae_awarded is OPTIONAL and deliberately UNTYPED here: the handler
|
|
222
223
|
// accepts a number, a stringified number, '' and null alike, and the inline
|
|
@@ -256,6 +257,7 @@ module.exports = function buildSecurityReportsRouter() {
|
|
|
256
257
|
'/security/reports/:id/dispute',
|
|
257
258
|
api.requireBuilder,
|
|
258
259
|
api.requirePermission('security.report.adjudicate'),
|
|
260
|
+
api.requireActAlone('disputes'),
|
|
259
261
|
async (req, res) => {
|
|
260
262
|
const id = parseId(req, res, { code: 'bad_report_id', positiveInt: true });
|
|
261
263
|
if (id === null) return;
|
|
@@ -281,6 +283,7 @@ module.exports = function buildSecurityReportsRouter() {
|
|
|
281
283
|
'/security/reports/:id/fix',
|
|
282
284
|
api.requireBuilder,
|
|
283
285
|
api.requirePermission('security.report.adjudicate'),
|
|
286
|
+
api.requireActAlone('disputes'),
|
|
284
287
|
async (req, res) => {
|
|
285
288
|
const id = parseId(req, res, { code: 'bad_report_id', positiveInt: true });
|
|
286
289
|
if (id === null) return;
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.94",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.94",
|
|
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.21.
|
|
3
|
+
"version": "1.21.94",
|
|
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/release-notes.json
CHANGED
|
@@ -9382,5 +9382,17 @@
|
|
|
9382
9382
|
"id": "1004648",
|
|
9383
9383
|
"text": "Starting a project is now one page instead of six screens. After signing in, a founder sees \"Make something.\" with three parts: their project (name, kind, a sentence and four quick checks), a short summary of how it will be ru"
|
|
9384
9384
|
}
|
|
9385
|
+
],
|
|
9386
|
+
"1.21.93": [
|
|
9387
|
+
{
|
|
9388
|
+
"id": "1004657",
|
|
9389
|
+
"text": "When a project votes that a part of its government is decided by a group, that vote now holds on every page, not only the People page. Before, someone with the right permission could still switch unattended building on, sign o"
|
|
9390
|
+
}
|
|
9391
|
+
],
|
|
9392
|
+
"1.21.94": [
|
|
9393
|
+
{
|
|
9394
|
+
"id": "1004649",
|
|
9395
|
+
"text": "Connecting GitHub to a new project is now one trip. The founder clicks \"Connect GitHub\", approves the project's app on GitHub, and GitHub carries straight on to installing it on their repo (already picked when the repo is publ"
|
|
9396
|
+
}
|
|
9385
9397
|
]
|
|
9386
9398
|
}
|