@bongos/core 1.19.82 → 1.19.84
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 +57 -22
- package/docs/adr/0055-server-mediated-branch-publish.md +4 -0
- package/docs/design/mocks/component-sheet.html +428 -0
- package/docs/design/mocks/projects-page-mock.html +167 -0
- package/docs/design/projects-surfaces-redesign-direction.md +3 -2
- package/modules/lifecycle/github-push.js +82 -0
- package/modules/provisioning/migrations/provisioning_006_liveness.sql +30 -0
- package/modules/provisioning/module.json +2 -1
- package/modules/provisioning/onboard-plan.js +20 -4
- package/modules/provisioning/pollers/liveness-sweep.js +266 -0
- package/modules/provisioning/provisioning.js +43 -0
- package/modules/provisioning/routes/provisioning.js +6 -2
- package/modules/provisioning/tests/liveness.mjs +420 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +311 -0
- package/public-landing/create.html +253 -125
- package/public-landing/gate/index.html +3 -0
- package/public-landing/gate/space.css +11 -1
- package/public-landing/projects.html +298 -213
- package/scripts/gds/publish-credential-check.js +63 -0
- package/src/module-api.js +5 -1
- package/tests/publish_branch_route.mjs +69 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gds/publish-credential-check.js — can the publish credential SEE Actions?
|
|
3
|
+
// (task 1002733, promoted from idea 1000705.)
|
|
4
|
+
//
|
|
5
|
+
// Run ON THE HOST, where GITHUB_PUSH_TOKEN / the GitHub App env lives:
|
|
6
|
+
// node scripts/gds/publish-credential-check.js
|
|
7
|
+
//
|
|
8
|
+
// WHY. On 2026-08-12 task 1002695 stranded at 'confirmed' across four reconciler
|
|
9
|
+
// sweeps: its PR was MERGED with green checks, but the server's deploy probe kept
|
|
10
|
+
// answering 'pending' because the credential could not read the Actions API. That
|
|
11
|
+
// blindness was invisible — the only way to find it was to notice a strand and
|
|
12
|
+
// hand-run getPublishStatus against the live server. This turns a silent
|
|
13
|
+
// misconfiguration into a one-command answer.
|
|
14
|
+
//
|
|
15
|
+
// Task 1002724 stopped the blindness from stranding work (a blind probe no longer
|
|
16
|
+
// vetoes a git-history-proven land). This tells you whether you are relying on that
|
|
17
|
+
// fallback instead of a real deploy gate.
|
|
18
|
+
//
|
|
19
|
+
// Exit codes: 0 = credential can read Actions (gate is real, or the workflow is
|
|
20
|
+
// legitimately absent), 1 = BLIND (remediation printed), 2 = nothing to check.
|
|
21
|
+
|
|
22
|
+
const githubPush = require('../../modules/lifecycle/github-push');
|
|
23
|
+
const { cliExit } = require('./cli-lib');
|
|
24
|
+
|
|
25
|
+
const C = process.stdout.isTTY
|
|
26
|
+
? { g: '\x1b[32m', y: '\x1b[33m', r: '\x1b[31m', b: '\x1b[1m', x: '\x1b[0m' }
|
|
27
|
+
: { g: '', y: '', r: '', b: '', x: '' };
|
|
28
|
+
|
|
29
|
+
(async () => {
|
|
30
|
+
if (!githubPush.isConfigured()) {
|
|
31
|
+
console.log(`${C.y}!${C.x} No publish credential configured on this machine.`);
|
|
32
|
+
console.log(' This check belongs on the server/host that holds GITHUB_PUSH_TOKEN or the GitHub App env.');
|
|
33
|
+
return cliExit(2);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let probe;
|
|
37
|
+
try {
|
|
38
|
+
probe = await githubPush.probeDeployVisibility();
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.log(`${C.r}✖${C.x} probe failed: ${e && e.message}`);
|
|
41
|
+
return cliExit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const http = probe.http ? ` (HTTP ${probe.http})` : '';
|
|
45
|
+
switch (probe.state) {
|
|
46
|
+
case 'readable':
|
|
47
|
+
console.log(`${C.g}✓${C.x} ${C.b}Actions readable${C.x}${http} — ${probe.detail}`);
|
|
48
|
+
return cliExit(0);
|
|
49
|
+
case 'readable_no_workflow':
|
|
50
|
+
console.log(`${C.g}✓${C.x} ${C.b}Actions readable${C.x}${http} — ${probe.detail}`);
|
|
51
|
+
return cliExit(0);
|
|
52
|
+
case 'blind':
|
|
53
|
+
console.log(`${C.r}✖${C.x} ${C.b}BLIND${C.x}${http} — ${probe.detail}`);
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(` ${C.b}Fix:${C.x} ${probe.remediation}`);
|
|
56
|
+
console.log(' Until then the reconciler falls back to the git-history land proof (task 1002724),');
|
|
57
|
+
console.log(' so work still lands — but the deploy gate is not actually being enforced.');
|
|
58
|
+
return cliExit(1);
|
|
59
|
+
default:
|
|
60
|
+
console.log(`${C.y}!${C.x} ${probe.state}${http} — ${probe.detail}`);
|
|
61
|
+
return cliExit(2);
|
|
62
|
+
}
|
|
63
|
+
})();
|
package/src/module-api.js
CHANGED
|
@@ -591,8 +591,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
591
591
|
// landed since 1.19.80 with no explicit bump. run 31649705242. (task 1002620)
|
|
592
592
|
// 1.19.82 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
593
593
|
// landed since 1.19.81 with no explicit bump. run 31650108283. (task 1002620)
|
|
594
|
+
// 1.19.83 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
595
|
+
// landed since 1.19.82 with no explicit bump. run 31651046759. (task 1002620)
|
|
596
|
+
// 1.19.84 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
597
|
+
// landed since 1.19.83 with no explicit bump. run 31651217970. (task 1002620)
|
|
594
598
|
// ---------------------------------------------------------------------------
|
|
595
|
-
const CORE_VERSION = '1.19.
|
|
599
|
+
const CORE_VERSION = '1.19.84'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
596
600
|
|
|
597
601
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
598
602
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -570,5 +570,74 @@ await t('landConfirmed: unconfigured deploy counts as landed, but ONLY when merg
|
|
|
570
570
|
assert.equal(gp.landConfirmed({ merged: true, deploy_status: 'none' }), false, 'none ≠ unconfigured — no PR at all is not a land');
|
|
571
571
|
});
|
|
572
572
|
|
|
573
|
+
// ===========================================================================
|
|
574
|
+
// task 1002733 — probeDeployVisibility: is the publish credential BLIND?
|
|
575
|
+
//
|
|
576
|
+
// On a private repo GitHub deliberately blurs "not there" and "not yours" — both
|
|
577
|
+
// can surface as 404 — so one call cannot tell "this repo has no deploy-prod.yml"
|
|
578
|
+
// (fine) from "this credential cannot read Actions" (the silent misconfiguration
|
|
579
|
+
// that stranded task 1002695 across 4 sweeps). The CONTROL call is listing the
|
|
580
|
+
// repo's workflows: if that works, a 404 on the specific workflow is genuine.
|
|
581
|
+
// ===========================================================================
|
|
582
|
+
|
|
583
|
+
await t('probeDeployVisibility: workflows list 403 → blind, with remediation', async () => {
|
|
584
|
+
const fetchImpl = async (url) => {
|
|
585
|
+
if (url.includes('/actions/workflows?')) return jsonRes(403, { message: 'Resource not accessible' });
|
|
586
|
+
throw new Error('must not probe the specific workflow once the list is refused: ' + url);
|
|
587
|
+
};
|
|
588
|
+
const p = await gp.probeDeployVisibility({ token: 'TT', repoInfo: REPO, fetchImpl });
|
|
589
|
+
assert.equal(p.state, 'blind');
|
|
590
|
+
assert.equal(p.http, 403);
|
|
591
|
+
assert.match(p.remediation, /Actions: read/i);
|
|
592
|
+
// The remediation must name the credential that is ACTUALLY live rather than
|
|
593
|
+
// guessing: resolveToken prefers the App and falls back to GITHUB_PUSH_TOKEN, and an
|
|
594
|
+
// instance runs on exactly one of them. Pointing at the wrong one sends the operator
|
|
595
|
+
// to widen permissions on a credential that is not in play (cloudbongos.com is on
|
|
596
|
+
// the PAT, despite ADR 0055's history saying the App replaced it — task 1002733).
|
|
597
|
+
assert.match(
|
|
598
|
+
p.remediation, /GITHUB_APP_\*|GITHUB_PUSH_TOKEN/,
|
|
599
|
+
'must identify which credential resolveToken will actually use'
|
|
600
|
+
);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
await t('probeDeployVisibility: list OK + runs 404 → readable_no_workflow (NOT blind)', async () => {
|
|
604
|
+
const fetchImpl = async (url) => {
|
|
605
|
+
if (url.includes('/actions/workflows?')) return jsonRes(200, { workflows: [{ id: 1, path: '.github/workflows/unit.yml' }] });
|
|
606
|
+
if (url.includes('deploy-prod.yml/runs')) return jsonRes(404, { message: 'Not Found' });
|
|
607
|
+
throw new Error('unexpected ' + url);
|
|
608
|
+
};
|
|
609
|
+
const p = await gp.probeDeployVisibility({ token: 'TT', repoInfo: REPO, fetchImpl });
|
|
610
|
+
assert.equal(p.state, 'readable_no_workflow', 'a 404 behind a readable list means the workflow is genuinely absent');
|
|
611
|
+
assert.equal(p.remediation, null, 'nothing to fix — this is the ADR 0108 pin-bump shape');
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
await t('probeDeployVisibility: list OK + runs OK → readable (the deploy gate is real)', async () => {
|
|
615
|
+
const fetchImpl = async (url) => {
|
|
616
|
+
if (url.includes('/actions/workflows?')) return jsonRes(200, { workflows: [] });
|
|
617
|
+
if (url.includes('deploy-prod.yml/runs')) return jsonRes(200, { workflow_runs: [] });
|
|
618
|
+
throw new Error('unexpected ' + url);
|
|
619
|
+
};
|
|
620
|
+
const p = await gp.probeDeployVisibility({ token: 'TT', repoInfo: REPO, fetchImpl });
|
|
621
|
+
assert.equal(p.state, 'readable');
|
|
622
|
+
assert.equal(p.remediation, null);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
await t('probeDeployVisibility: list OK but runs 403 → still blind', async () => {
|
|
626
|
+
const fetchImpl = async (url) => {
|
|
627
|
+
if (url.includes('/actions/workflows?')) return jsonRes(200, { workflows: [] });
|
|
628
|
+
if (url.includes('deploy-prod.yml/runs')) return jsonRes(403, { message: 'nope' });
|
|
629
|
+
throw new Error('unexpected ' + url);
|
|
630
|
+
};
|
|
631
|
+
const p = await gp.probeDeployVisibility({ token: 'TT', repoInfo: REPO, fetchImpl });
|
|
632
|
+
assert.equal(p.state, 'blind');
|
|
633
|
+
assert.equal(p.http, 403);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
await t('probeDeployVisibility: no credential → no_credential, not a false alarm', async () => {
|
|
637
|
+
const p = await gp.probeDeployVisibility({ token: null, repoInfo: REPO, fetchImpl: async () => { throw new Error('must not fetch'); } });
|
|
638
|
+
assert.equal(p.state, 'no_credential');
|
|
639
|
+
assert.equal(p.remediation, null);
|
|
640
|
+
});
|
|
641
|
+
|
|
573
642
|
console.log(`\n${pass} passed, ${fail} failed`);
|
|
574
643
|
process.exit(fail === 0 ? 0 : 1);
|