@bongos/core 1.19.211 → 1.19.213
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 +21 -16
- package/modules/platform-identity/routes/sso.js +9 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/projects.html +8 -0
- package/src/bongos/routes/auth.js +10 -3
- package/src/module-api.js +5 -1
- package/tests/auth_device_start_status.mjs +101 -0
- package/tests/projects_hub.mjs +15 -0
- package/tests/public_landing_projects.mjs +9 -0
- package/tests/setup_device_flow_fallback.mjs +8 -2
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
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.213",
|
|
6
|
+
"core_contract": "1.19.213",
|
|
7
|
+
"source_commit": "7cd1ca35002e12c66a3937a55bf8c9e6e69fd231",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-17T23:
|
|
9
|
+
"built_at": "2026-08-17T23:35:49.388Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 310,
|
|
13
13
|
"agent_docs_stubbed": 21,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1302,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1633,
|
|
20
|
+
"tree_sha256": "08e3fcc476f1d9209616049d1d79c946c56394953d7891706e2e41f8e3b28067",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -3892,7 +3892,7 @@
|
|
|
3892
3892
|
{
|
|
3893
3893
|
"path": "modules/platform-identity/routes/sso.js",
|
|
3894
3894
|
"mode": "0000644",
|
|
3895
|
-
"sha256": "
|
|
3895
|
+
"sha256": "72796558274b45faec2b1a63e332100061a6ab0c690dadc42a54f1f39cb0c68e"
|
|
3896
3896
|
},
|
|
3897
3897
|
{
|
|
3898
3898
|
"path": "modules/platform-identity/tests/platform-identity.mjs",
|
|
@@ -4282,12 +4282,12 @@
|
|
|
4282
4282
|
{
|
|
4283
4283
|
"path": "package-lock.json",
|
|
4284
4284
|
"mode": "0000644",
|
|
4285
|
-
"sha256": "
|
|
4285
|
+
"sha256": "b9113d8027c9a36b9d9923e3a850c655a0dfb57ce59633ba81042f1116127882"
|
|
4286
4286
|
},
|
|
4287
4287
|
{
|
|
4288
4288
|
"path": "package.json",
|
|
4289
4289
|
"mode": "0000644",
|
|
4290
|
-
"sha256": "
|
|
4290
|
+
"sha256": "27122dc34fcd4dbe96f345a2cdf6045447a4a94a20217e43f52c803e25576e02"
|
|
4291
4291
|
},
|
|
4292
4292
|
{
|
|
4293
4293
|
"path": "public-docs/index.html",
|
|
@@ -4547,7 +4547,7 @@
|
|
|
4547
4547
|
{
|
|
4548
4548
|
"path": "public-landing/projects.html",
|
|
4549
4549
|
"mode": "0000644",
|
|
4550
|
-
"sha256": "
|
|
4550
|
+
"sha256": "603ec55ce4de6477cf5d0602e5963a096799a404b2324f12c62104023e9e0e4b"
|
|
4551
4551
|
},
|
|
4552
4552
|
{
|
|
4553
4553
|
"path": "public-landing/world/assets/clouds/cloud-island-large.png",
|
|
@@ -5792,7 +5792,7 @@
|
|
|
5792
5792
|
{
|
|
5793
5793
|
"path": "src/bongos/routes/auth.js",
|
|
5794
5794
|
"mode": "0000644",
|
|
5795
|
-
"sha256": "
|
|
5795
|
+
"sha256": "c5688194f21600a64ced6b27aa5c12b4f580db786ffcf85a151422635269aa40"
|
|
5796
5796
|
},
|
|
5797
5797
|
{
|
|
5798
5798
|
"path": "src/bongos/routes/backup.js",
|
|
@@ -5902,7 +5902,7 @@
|
|
|
5902
5902
|
{
|
|
5903
5903
|
"path": "src/module-api.js",
|
|
5904
5904
|
"mode": "0000644",
|
|
5905
|
-
"sha256": "
|
|
5905
|
+
"sha256": "c59f5553c52308967b71da155eab8249d7c8687a37184626b4c4b03ccf2d9fc7"
|
|
5906
5906
|
},
|
|
5907
5907
|
{
|
|
5908
5908
|
"path": "src/module-loader/catalog.js",
|
|
@@ -6074,6 +6074,11 @@
|
|
|
6074
6074
|
"mode": "0000644",
|
|
6075
6075
|
"sha256": "0be7221de2aa3969a54dc856a9fa1548959abd18b1a6c55e67b6ca5030e7ac1d"
|
|
6076
6076
|
},
|
|
6077
|
+
{
|
|
6078
|
+
"path": "tests/auth_device_start_status.mjs",
|
|
6079
|
+
"mode": "0000644",
|
|
6080
|
+
"sha256": "059fededeef783635945496d3183387c1c6407e84fdddf67d14e0d14a48e9dc9"
|
|
6081
|
+
},
|
|
6077
6082
|
{
|
|
6078
6083
|
"path": "tests/auth_duplicate_cookie.mjs",
|
|
6079
6084
|
"mode": "0000644",
|
|
@@ -7527,7 +7532,7 @@
|
|
|
7527
7532
|
{
|
|
7528
7533
|
"path": "tests/projects_hub.mjs",
|
|
7529
7534
|
"mode": "0000644",
|
|
7530
|
-
"sha256": "
|
|
7535
|
+
"sha256": "0471ced4ef3d16b5df6a373c04b13c1e83f2c9b2a9590734e4f02f95e195c45a"
|
|
7531
7536
|
},
|
|
7532
7537
|
{
|
|
7533
7538
|
"path": "tests/provision.mjs",
|
|
@@ -7577,7 +7582,7 @@
|
|
|
7577
7582
|
{
|
|
7578
7583
|
"path": "tests/public_landing_projects.mjs",
|
|
7579
7584
|
"mode": "0000644",
|
|
7580
|
-
"sha256": "
|
|
7585
|
+
"sha256": "c0e2e85c84f75503438c1951b72a94cf1c74a7cd0c27b774c2dc4d46d99d6cf8"
|
|
7581
7586
|
},
|
|
7582
7587
|
{
|
|
7583
7588
|
"path": "tests/public_mirror_export.mjs",
|
|
@@ -7842,7 +7847,7 @@
|
|
|
7842
7847
|
{
|
|
7843
7848
|
"path": "tests/setup_device_flow_fallback.mjs",
|
|
7844
7849
|
"mode": "0000644",
|
|
7845
|
-
"sha256": "
|
|
7850
|
+
"sha256": "0a7c2f4a0ee050573040a88c6a246c773e7b13074115df4b4d715ab99c903a9c"
|
|
7846
7851
|
},
|
|
7847
7852
|
{
|
|
7848
7853
|
"path": "tests/setup_smoke.mjs",
|
|
@@ -303,7 +303,10 @@ module.exports = function ssoRoutes() {
|
|
|
303
303
|
res.json(out);
|
|
304
304
|
} catch (err) {
|
|
305
305
|
console.error('[platform-identity] /sso/device/start', err);
|
|
306
|
-
|
|
306
|
+
// 424, not 502: Cloudflare replaces origin 5xx bodies with its own page, so a
|
|
307
|
+
// proxied upstream refusal must ride a 4xx to keep its JSON diagnosis readable
|
|
308
|
+
// externally (finding W2, task 1003052 — the core device/start twin).
|
|
309
|
+
res.fail('device_start_failed', { status: 424, message: String(err.message || 'error').slice(0, 200) });
|
|
307
310
|
}
|
|
308
311
|
});
|
|
309
312
|
|
|
@@ -343,7 +346,11 @@ module.exports = function ssoRoutes() {
|
|
|
343
346
|
res.json({ state: 'ok', assertion });
|
|
344
347
|
} catch (err) {
|
|
345
348
|
console.error('[platform-identity] /sso/device/poll', err);
|
|
346
|
-
|
|
349
|
+
// 424, matching the core device/poll (finding W2, task 1003052): this catch
|
|
350
|
+
// proxies the hub's GitHub poll refusals, and a 5xx body never survives
|
|
351
|
+
// Cloudflare. (The !out.client_id branch above keeps its 500: an internal
|
|
352
|
+
// invariant breach, not an upstream proxy.)
|
|
353
|
+
res.fail('device_poll_failed', { status: 424, message: 'internal error' });
|
|
347
354
|
}
|
|
348
355
|
});
|
|
349
356
|
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.213",
|
|
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.213",
|
|
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.213",
|
|
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",
|
|
@@ -626,6 +626,14 @@ h1[tabindex="-1"]:focus{outline:none;}
|
|
|
626
626
|
<div class="card" id="mRepoVisibility">
|
|
627
627
|
<h2 style="font-family:var(--font-display);font-size:19px;margin-bottom:12px;">Repository visibility</h2>
|
|
628
628
|
<div id="mRepoVis"><p class="finePrint">Checking the repository on GitHub…</p></div>
|
|
629
|
+
<!-- the limitation itself, one click away (grade follow-up task 1003077):
|
|
630
|
+
the refusal above states it; this fold explains it and links the
|
|
631
|
+
tracked fix. The hall link is apex-only — a child instance keeps the
|
|
632
|
+
explanation, not another platform's task tracker. -->
|
|
633
|
+
<details class="finePrint" id="mRepoVisWhy">
|
|
634
|
+
<summary>Why can’t it be private yet?</summary>
|
|
635
|
+
<p>The platform deploys a project by reading its code from GitHub with a shared read-only credential — and private repositories don’t admit it. A private repository’s next deploy would simply fail, so the switch stays off until each project gets its own deploy credential. <!--apex-only:start-->That work is tracked as <a href="{{buildersOrigin}}#/task/1002735" target="_blank" rel="noopener">task 1002735</a> in the builders’ hall.<!--apex-only:end--></p>
|
|
636
|
+
</details>
|
|
629
637
|
</div>
|
|
630
638
|
|
|
631
639
|
<div class="card" id="mDelete">
|
|
@@ -320,9 +320,13 @@ module.exports = function buildAuthRouter() {
|
|
|
320
320
|
// Surface the underlying reason (task 2126). A thrown device-flow start is almost
|
|
321
321
|
// always a GitHub OAuth-App misconfig (device_flow_disabled / bad client) — safe,
|
|
322
322
|
// non-secret, and actionable — so return it instead of an opaque "internal error".
|
|
323
|
-
// 502: the failure
|
|
323
|
+
// 424 (failed dependency), NOT 502: the failure IS upstream (GitHub), but
|
|
324
|
+
// Cloudflare replaces an origin 5xx body with its own bare error page, so the
|
|
325
|
+
// diagnosis never reached an external caller — the walk's single most likely
|
|
326
|
+
// fresh-instance auth failure could only be read via curl on the box itself
|
|
327
|
+
// (finding W2, task 1003052). A 4xx passes the proxy with the JSON intact.
|
|
324
328
|
const reason = String(err && err.message ? err.message : 'internal error').slice(0, 300);
|
|
325
|
-
res.fail('device_start_failed', { status:
|
|
329
|
+
res.fail('device_start_failed', { status: 424, message: reason });
|
|
326
330
|
}
|
|
327
331
|
});
|
|
328
332
|
|
|
@@ -349,7 +353,10 @@ module.exports = function buildAuthRouter() {
|
|
|
349
353
|
return res.status(403).json({ state: 'not_approved', error: 'not_approved' });
|
|
350
354
|
}
|
|
351
355
|
console.error('[gds] device/poll', err);
|
|
352
|
-
|
|
356
|
+
// 424 for the same reason as device/start above (finding W2, task 1003052):
|
|
357
|
+
// this catch proxies GitHub's poll refusals too, and a 5xx body never
|
|
358
|
+
// survives Cloudflare — the JSON envelope (its code at minimum) must.
|
|
359
|
+
res.fail('device_poll_failed', { status: 424, message: 'internal error' });
|
|
353
360
|
}
|
|
354
361
|
});
|
|
355
362
|
|
package/src/module-api.js
CHANGED
|
@@ -849,8 +849,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
849
849
|
// landed since 1.19.209 with no explicit bump. run 32078135414. (task 1002620)
|
|
850
850
|
// 1.19.211 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
851
851
|
// landed since 1.19.210 with no explicit bump. run 32079512489. (task 1002620)
|
|
852
|
+
// 1.19.212 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
853
|
+
// landed since 1.19.211 with no explicit bump. run 32080157462. (task 1002620)
|
|
854
|
+
// 1.19.213 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
855
|
+
// landed since 1.19.212 with no explicit bump. run 32081051255. (task 1002620)
|
|
852
856
|
// ---------------------------------------------------------------------------
|
|
853
|
-
const CORE_VERSION = '1.19.
|
|
857
|
+
const CORE_VERSION = '1.19.213'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
854
858
|
|
|
855
859
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
856
860
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// tests/auth_device_start_status.mjs — a proxied upstream refusal must SURVIVE
|
|
2
|
+
// Cloudflare (walk finding W2, task 1003052).
|
|
3
|
+
//
|
|
4
|
+
// The repro this pins: GitHub refuses POST /auth/device/start (e.g. Device Flow
|
|
5
|
+
// disabled on the app) and the route used to proxy that as HTTP 502 with a JSON
|
|
6
|
+
// diagnosis in the body — but Cloudflare REPLACES origin 5xx bodies with its own
|
|
7
|
+
// bare "error code: 502" page, so an external caller got zero information; the
|
|
8
|
+
// real message was only readable via curl on the box itself. The fix is the
|
|
9
|
+
// status-code choice: a 4xx (424 failed dependency) passes the proxy with the
|
|
10
|
+
// JSON envelope intact.
|
|
11
|
+
//
|
|
12
|
+
// DB-free + network-free: auth.startDeviceFlow / authConfigured are stubbed on
|
|
13
|
+
// the cached auth module (the github_token_store.mjs pattern).
|
|
14
|
+
//
|
|
15
|
+
// THE SWEEP (task 1003052's done-when requires it recorded): upstream-refusal
|
|
16
|
+
// proxies found on the auth surface and how each was dispositioned —
|
|
17
|
+
// • device_start_failed (core routes/auth.js + the platform-identity SSO
|
|
18
|
+
// twin) — FIXED, 502 → 424 (the W2 repro itself).
|
|
19
|
+
// • device_poll_failed catch blocks (both files) — FIXED, 500 → 424: the
|
|
20
|
+
// poll leg proxies GitHub refusals the same way.
|
|
21
|
+
// • sso.js `!out.client_id` → device_poll_failed 500 — KEPT 500: an internal
|
|
22
|
+
// invariant breach, not an upstream proxy.
|
|
23
|
+
// • routes/github.js github_error 502 ×4 (repo list/create + visibility
|
|
24
|
+
// read/flip) — NOTED, not fixed: same eaten-body class, but the consuming
|
|
25
|
+
// UI already renders generic copy on any failure; candidates for the same
|
|
26
|
+
// 4xx treatment.
|
|
27
|
+
// • platform-identity my-projects.js join_relay_failed 502 — NOTED: same class.
|
|
28
|
+
// • auth_not_configured / idp_not_configured 503s — NOTED: config-state
|
|
29
|
+
// answers, not upstream proxies, but their bodies are equally eaten by
|
|
30
|
+
// Cloudflare (the blocker-1000105 class) if external diagnosability ever
|
|
31
|
+
// matters there.
|
|
32
|
+
// • api-errors.js catalogs bad_gateway=502 — any route leaning on it
|
|
33
|
+
// inherits the hazard.
|
|
34
|
+
|
|
35
|
+
import { strict as assert } from 'node:assert';
|
|
36
|
+
import { test } from 'node:test';
|
|
37
|
+
import { createRequire } from 'node:module';
|
|
38
|
+
import fs from 'node:fs';
|
|
39
|
+
import path from 'node:path';
|
|
40
|
+
import { fileURLToPath } from 'node:url';
|
|
41
|
+
|
|
42
|
+
const require = createRequire(import.meta.url);
|
|
43
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
44
|
+
const auth = require(path.join(ROOT, 'src/bongos/auth.js'));
|
|
45
|
+
const buildAuthRouter = require(path.join(ROOT, 'src/bongos/routes/auth.js'));
|
|
46
|
+
const { attachFail } = require(path.join(ROOT, 'src/bongos/middleware/error-envelope.js'));
|
|
47
|
+
|
|
48
|
+
function fakeRes() {
|
|
49
|
+
const res = { _status: 200, _body: null };
|
|
50
|
+
res.status = (c) => { res._status = c; return res; };
|
|
51
|
+
res.json = (b) => { res._body = b; return res; };
|
|
52
|
+
attachFail({}, res, () => {});
|
|
53
|
+
return res;
|
|
54
|
+
}
|
|
55
|
+
function finalHandler(router, method, pth) {
|
|
56
|
+
const layer = router.stack.find((l) => l.route && l.route.path === pth && l.route.methods[method]);
|
|
57
|
+
const stack = layer.route.stack;
|
|
58
|
+
return stack[stack.length - 1].handle;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test('an upstream device-flow refusal answers 424 with the diagnosis intact — never a Cloudflare-eaten 5xx', async () => {
|
|
62
|
+
const orig = { configured: auth.authConfigured, start: auth.startDeviceFlow };
|
|
63
|
+
auth.authConfigured = () => true;
|
|
64
|
+
auth.startDeviceFlow = async () => {
|
|
65
|
+
throw new Error('GitHub rejected device-flow start: device_flow_disabled — terminal sign-in needs Device Flow enabled on the app.');
|
|
66
|
+
};
|
|
67
|
+
try {
|
|
68
|
+
const handler = finalHandler(buildAuthRouter(), 'post', '/auth/device/start');
|
|
69
|
+
const res = fakeRes();
|
|
70
|
+
await handler({}, res);
|
|
71
|
+
assert.equal(res._status, 424, 'a 4xx passes Cloudflare with its body; a 5xx does not');
|
|
72
|
+
assert.ok(res._status < 500, 'the whole 5xx range is the Cloudflare-intercepted range');
|
|
73
|
+
assert.equal(res._body.error.code, 'device_start_failed');
|
|
74
|
+
assert.match(res._body.error.message, /device_flow_disabled/,
|
|
75
|
+
'the upstream reason travels in the body — this is the diagnosis W2 lost');
|
|
76
|
+
} finally {
|
|
77
|
+
auth.authConfigured = orig.configured;
|
|
78
|
+
auth.startDeviceFlow = orig.start;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('the sweep holds: no device-flow refusal site anywhere proxies a 5xx from a catch', () => {
|
|
83
|
+
// The same refusal codes are minted in two places (the core routes and the
|
|
84
|
+
// platform-identity SSO hub twins); a consumer must never see two statuses.
|
|
85
|
+
// The one sanctioned 500 is sso.js's `!out.client_id` internal-invariant
|
|
86
|
+
// branch — recognizable because it is NOT inside a catch's console.error run.
|
|
87
|
+
for (const file of [
|
|
88
|
+
'src/bongos/routes/auth.js',
|
|
89
|
+
'modules/platform-identity/routes/sso.js',
|
|
90
|
+
]) {
|
|
91
|
+
const src = fs.readFileSync(path.join(ROOT, file), 'utf8');
|
|
92
|
+
for (const m of src.matchAll(/device_start_failed',\s*\{\s*status:\s*(\d+)/g)) {
|
|
93
|
+
assert.ok(Number(m[1]) < 500,
|
|
94
|
+
`${file} proxies device_start_failed as ${m[1]} — Cloudflare eats 5xx bodies (W2)`);
|
|
95
|
+
}
|
|
96
|
+
for (const m of src.matchAll(/console\.error\([^\n]*device\/poll[^\n]*\);[\s\S]{0,400}?device_poll_failed',\s*\{\s*status:\s*(\d+)/g)) {
|
|
97
|
+
assert.ok(Number(m[1]) < 500,
|
|
98
|
+
`${file}'s device/poll catch proxies device_poll_failed as ${m[1]} — same W2 class`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
package/tests/projects_hub.mjs
CHANGED
|
@@ -1028,6 +1028,21 @@ test('the reconnect door asks for repository access and returns to this project'
|
|
|
1028
1028
|
'and returns to the exact project being managed');
|
|
1029
1029
|
});
|
|
1030
1030
|
|
|
1031
|
+
test('the refusal carries a LINK to the limitation, never prose alone (task 1003077)', () => {
|
|
1032
|
+
assert.match(HUB, /id="mRepoVisWhy"/, 'the why-fold is real markup beside the section body');
|
|
1033
|
+
const fold = (HUB.match(/<details[^>]*id="mRepoVisWhy"[\s\S]*?<\/details>/) || [''])[0];
|
|
1034
|
+
assert.ok(fold, 'the fold must be extractable');
|
|
1035
|
+
assert.match(fold, /shared read-only credential/, 'the WHY is stated in plain words');
|
|
1036
|
+
assert.match(fold, /\{\{buildersOrigin\}\}#\/task\/1002735/,
|
|
1037
|
+
'the tracked fix is a real link, hall-origin server-stamped (never client-filled)');
|
|
1038
|
+
// apex-only: a child instance keeps the explanation, not another platform's tracker
|
|
1039
|
+
const at = HUB.indexOf('{{buildersOrigin}}#/task/1002735');
|
|
1040
|
+
const before = HUB.slice(0, at);
|
|
1041
|
+
const opens = (before.match(/<!--apex-only:start-->/g) || []).length;
|
|
1042
|
+
const closes = (before.match(/<!--apex-only:end-->/g) || []).length;
|
|
1043
|
+
assert.equal(opens - closes, 1, 'the task link sits inside an apex-only sentinel pair');
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1031
1046
|
test('making a repo public asks first — exposure is never one accidental click', () => {
|
|
1032
1047
|
const fn = (SCRIPT.match(/function askRepoPublic\(\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
1033
1048
|
assert.ok(fn, 'askRepoPublic() must be extractable');
|
|
@@ -129,6 +129,15 @@ test('the repo-visibility section is served live, with the private direction ref
|
|
|
129
129
|
assert.doesNotMatch(body, /visibility:\s*'private'/,
|
|
130
130
|
'no served code path may send the private direction');
|
|
131
131
|
assert.match(body, /repos=1/, 'the reconnect door asks for repository access');
|
|
132
|
+
// the limitation is one click away (task 1003077). This DB-free serve is the
|
|
133
|
+
// STANDALONE shape, where apex-only spans are stripped — so the explanation
|
|
134
|
+
// must survive while the cloudbongos task link must NOT (a child instance
|
|
135
|
+
// never shows another platform's tracker). The link itself is pinned inside
|
|
136
|
+
// its sentinels by tests/projects_hub.mjs.
|
|
137
|
+
assert.match(body, /id="mRepoVisWhy"/, 'the why-fold is served');
|
|
138
|
+
assert.match(body, /shared read-only credential/, 'the explanation survives the strip');
|
|
139
|
+
assert.doesNotMatch(body, /#\/task\/1002735/,
|
|
140
|
+
'the apex-only task link is stripped from a standalone serve');
|
|
132
141
|
});
|
|
133
142
|
|
|
134
143
|
test('the empty state points a new owner at the wizard view — never at a hall', async () => {
|
|
@@ -51,10 +51,12 @@ const ta = async (name, fn) => {
|
|
|
51
51
|
|
|
52
52
|
console.log('isDeviceFlowDisabled — the refusal detector:');
|
|
53
53
|
|
|
54
|
-
// The REAL shape: routes/auth.js res.fail('device_start_failed', {status:
|
|
54
|
+
// The REAL shape: routes/auth.js res.fail('device_start_failed', {status:424,
|
|
55
55
|
// message: 'GitHub rejected device-flow start: device_flow_disabled — …'}).
|
|
56
|
+
// (424 since task 1003052 — a 502's body was eaten by Cloudflare; the detector
|
|
57
|
+
// matches on TEXT, so both the new 424 and an older server's 502 register.)
|
|
56
58
|
const REAL_REFUSAL = {
|
|
57
|
-
ok: false, status:
|
|
59
|
+
ok: false, status: 424,
|
|
58
60
|
data: { error: { code: 'device_start_failed', message: 'GitHub rejected device-flow start: device_flow_disabled — terminal sign-in needs Device Flow enabled on the app: open https://github.com/settings/apps, open your app, check "Enable Device Flow", and save. Web sign-in is unaffected.' } },
|
|
59
61
|
};
|
|
60
62
|
|
|
@@ -62,6 +64,10 @@ t('matches the real device_start_failed envelope carrying device_flow_disabled',
|
|
|
62
64
|
assert.equal(setup.isDeviceFlowDisabled(REAL_REFUSAL), true);
|
|
63
65
|
});
|
|
64
66
|
|
|
67
|
+
t('still matches an older server\'s 502 shape — the CLI talks to many core versions', () => {
|
|
68
|
+
assert.equal(setup.isDeviceFlowDisabled({ ...REAL_REFUSAL, status: 502 }), true);
|
|
69
|
+
});
|
|
70
|
+
|
|
65
71
|
t('matches a legacy flat error shape too', () => {
|
|
66
72
|
assert.equal(setup.isDeviceFlowDisabled({ ok: false, status: 502, data: { error: 'device_start_failed', message: 'x device_flow_disabled y' } }), true);
|
|
67
73
|
});
|