@bongos/core 1.19.368 → 1.19.369

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.
@@ -0,0 +1,280 @@
1
+ // tests/platform_visibility_gate.mjs — the project's member door (task 1003043).
2
+ //
3
+ // A gate that is wrong in either direction is worse than none: too open and a
4
+ // "members only" project is readable by anyone; too closed and it locks out the
5
+ // sign-in that admits the members, the health probe the deploy waits on, or the
6
+ // manifest the platform's runner reads to learn whether the setting took. Two
7
+ // layers here:
8
+ //
9
+ // 1. The middleware alone, with the mode and the session lookup injected —
10
+ // both modes, every refusal shape, fail-closed, the inactive-builder rule,
11
+ // the private-cache mark on what a member is shown.
12
+ // 2. The REAL booted app with the branding pack set to 'gated' BEFORE the
13
+ // first require (branding() memoizes for the process lifetime, which is the
14
+ // same reason a live project applies the setting by restarting): proves
15
+ // the gate is mounted early enough to cover surfaces registered elsewhere,
16
+ // and that the exempt ones still answer. The 'public' boot is every other
17
+ // boot test in this suite (tests/platform_boot.mjs, tests/stealth_gate.mjs
18
+ // run with the default pack and expect the surfaces to answer a stranger).
19
+ //
20
+ // Run: node --test tests/platform_visibility_gate.mjs
21
+
22
+ import assert from 'node:assert/strict';
23
+ import { test, before, after } from 'node:test';
24
+ import http from 'node:http';
25
+ import fs from 'node:fs';
26
+ import os from 'node:os';
27
+ import path from 'node:path';
28
+ import { createRequire } from 'node:module';
29
+ import { platformVisibilityGate, isGated, isExempt, mode, signInHref, privateCacheControl } from '../src/bongos/platform-visibility-gate.js';
30
+
31
+ const require = createRequire(import.meta.url);
32
+
33
+ // set BEFORE the app (and branding) is first required
34
+ const brandingFile = path.join(os.tmpdir(), `bongos-pvg-${process.pid}.json`);
35
+ fs.writeFileSync(brandingFile, JSON.stringify({
36
+ identity: { productName: 'Spike Harbor' },
37
+ project: { platformVisibility: 'gated', joinability: 'apply' },
38
+ }));
39
+ process.env.GDS_BRANDING_FILE = brandingFile;
40
+
41
+ const platformMod = require('../src/platform-server.js');
42
+
43
+ // ── 1. the middleware alone ──────────────────────────────────────────────────
44
+
45
+ function fakeReq({ path: p = '/', method = 'GET', headers = {} } = {}) {
46
+ return { path: p, originalUrl: p, method, headers: { host: 'spike.test', ...headers }, secure: false };
47
+ }
48
+ function fakeRes() {
49
+ const r = { statusCode: 200, headers: {}, body: undefined, failed: null, headersSent: false, wrote: null };
50
+ r.set = (k, v) => { r.headers[k.toLowerCase()] = v; return r; };
51
+ r.setHeader = (k, v) => { r.headers[k.toLowerCase()] = v; };
52
+ r.getHeader = (k) => r.headers[k.toLowerCase()];
53
+ r.writeHead = (code) => { r.wrote = code; r.headersSent = true; return r; };
54
+ r.status = (c) => { r.statusCode = c; return r; };
55
+ r.type = () => r;
56
+ r.send = (b) => { r.body = b; return r; };
57
+ r.json = (b) => { r.body = b; return r; };
58
+ r.fail = (code, status) => { r.failed = { code, status }; r.statusCode = status; return r; };
59
+ return r;
60
+ }
61
+ async function run(mw, req) {
62
+ const res = fakeRes();
63
+ let nexted = false;
64
+ mw(req, res, () => { nexted = true; });
65
+ // the middleware answers on a microtask after the (fake) lookup resolves
66
+ await new Promise((r) => setImmediate(r));
67
+ return { res, nexted };
68
+ }
69
+
70
+ const member = { builder_id: 7, status: 'active' };
71
+ const gatedWith = (session) => platformVisibilityGate({ isGated: () => true, resolveSession: async () => session });
72
+
73
+ test('public mode: the gate is inert — nothing is looked up, everything passes', async () => {
74
+ let looked = 0;
75
+ const mw = platformVisibilityGate({ isGated: () => false, resolveSession: async () => { looked += 1; return null; } });
76
+ for (const p of ['/', '/projects', '/builders', '/api/bongos/public/progress', '/status.css']) {
77
+ const { nexted } = await run(mw, fakeReq({ path: p }));
78
+ assert.equal(nexted, true, `${p} passes in public mode`);
79
+ }
80
+ assert.equal(looked, 0, 'a public project never pays a session lookup for the gate');
81
+ });
82
+
83
+ test('gated mode: a stranger meets the door on a page, with the way in on it', async () => {
84
+ const { res, nexted } = await run(gatedWith(null), fakeReq({ path: '/projects?view=map' }));
85
+ assert.equal(nexted, false);
86
+ assert.equal(res.statusCode, 401, 'a refused knock is a 401, not a 200 or a redirect');
87
+ assert.match(res.body, /Sign in/, 'the door carries the way in');
88
+ assert.match(res.body, /href="\/api\/gds\/auth\/web\/start\?return=%2Fprojects%3Fview%3Dmap"/,
89
+ 'the sign-in start returns the member to the page they knocked on — RELATIVE, which safeReturnTo honours everywhere');
90
+ assert.match(res.body, /Spike Harbor/, 'it names the project');
91
+ assert.match(res.body, /noindex/, 'a members-only project is not indexed');
92
+ assert.equal(res.headers['cache-control'], 'no-store');
93
+ });
94
+
95
+ test('the door leaks nothing of the product: no stylesheet, no branding bundle, no script', async () => {
96
+ const { res } = await run(gatedWith(null), fakeReq({ path: '/' }));
97
+ assert.doesNotMatch(res.body, /cosmos\.css/);
98
+ assert.doesNotMatch(res.body, /__BRANDING__/);
99
+ assert.doesNotMatch(res.body, /<script/);
100
+ });
101
+
102
+ test('gated mode: a stranger on the API gets the 401 envelope requireBuilder uses — whatever the spelling', async () => {
103
+ for (const p of ['/api/bongos/public/progress', '/API/bongos/public/progress']) {
104
+ const { res, nexted } = await run(gatedWith(null), fakeReq({ path: p }));
105
+ assert.equal(nexted, false, p);
106
+ assert.deepEqual(res.failed, { code: 'unauthenticated', status: 401 }, `${p} gets the envelope, not the door`);
107
+ }
108
+ });
109
+
110
+ test('gated mode: a member passes everywhere, and what they are shown leaves private — with its freshness kept', async () => {
111
+ for (const p of ['/', '/projects', '/builders', '/api/bongos/public/progress', '/api/bongos/tasks/1']) {
112
+ const { res, nexted } = await run(gatedWith(member), fakeReq({ path: p }));
113
+ assert.equal(nexted, true, `${p} opens to a member`);
114
+ // the route behind sets its own public max-age; the gate changes the SCOPE on
115
+ // the way out, not the freshness — the member's browser may keep it, an edge may not
116
+ res.setHeader('Cache-Control', 'public, max-age=3600');
117
+ res.setHeader('Vary', 'Accept-Encoding');
118
+ res.writeHead(200);
119
+ assert.equal(res.headers['cache-control'], 'private, max-age=3600', `${p}: a member's answer must never sit in a shared cache`);
120
+ assert.equal(res.headers['vary'], 'Accept-Encoding, Cookie');
121
+ assert.equal(res.wrote, 200, 'the original writeHead still runs');
122
+ }
123
+ });
124
+
125
+ test('the private rewrite keeps the route’s freshness and drops only what a shared cache would honour', () => {
126
+ assert.equal(privateCacheControl('public, max-age=3600'), 'private, max-age=3600', 'the browser keeps it, the edge does not');
127
+ assert.equal(privateCacheControl('public, s-maxage=600, max-age=60'), 'private, max-age=60', 's-maxage is a shared-cache directive — gone');
128
+ assert.equal(privateCacheControl('max-age=0, must-revalidate'), 'private, max-age=0, must-revalidate');
129
+ assert.equal(privateCacheControl('private, max-age=30'), 'private, max-age=30', 'already private stays as it was');
130
+ assert.equal(privateCacheControl('no-store'), 'no-store', 'a route that forbade caching is not loosened');
131
+ assert.equal(privateCacheControl(undefined), 'private, no-cache', 'no policy named → revalidate, never share');
132
+ });
133
+
134
+ test('a branding pack that cannot be read fails CLOSED — the gate treats the project as gated', () => {
135
+ assert.equal(mode(() => { throw new Error('pack unreadable'); }), 'gated');
136
+ assert.equal(mode(() => ({ project: { platformVisibility: 'public' } })), 'public');
137
+ assert.equal(mode(() => ({})), 'public', 'a pack with no project block is today’s behaviour');
138
+ });
139
+
140
+ test('gated mode: a deactivated builder keeps no door (the task 871 rule), an unknown status does', async () => {
141
+ const gone = await run(gatedWith({ builder_id: 7, status: 'inactive' }), fakeReq({ path: '/api/bongos/public/progress' }));
142
+ assert.deepEqual(gone.res.failed, { code: 'builder_inactive', status: 403 });
143
+ const page = await run(gatedWith({ builder_id: 7, status: 'inactive' }), fakeReq({ path: '/builders' }));
144
+ assert.equal(page.res.statusCode, 401, 'a page refusal is the door either way');
145
+ const unknown = await run(gatedWith({ builder_id: 7 }), fakeReq({ path: '/builders' }));
146
+ assert.equal(unknown.nexted, true, 'deny only on the KNOWN inactive status — never lock an active builder out');
147
+ });
148
+
149
+ test('gated mode: a session lookup that throws fails CLOSED', async () => {
150
+ const mw = platformVisibilityGate({ isGated: () => true, resolveSession: async () => { throw new Error('db down'); } });
151
+ const page = await run(mw, fakeReq({ path: '/' }));
152
+ assert.equal(page.nexted, false);
153
+ assert.equal(page.res.statusCode, 401);
154
+ const api = await run(mw, fakeReq({ path: '/api/bongos/public/progress' }));
155
+ assert.deepEqual(api.res.failed, { code: 'unauthenticated', status: 401 });
156
+ });
157
+
158
+ test('the exempt list: the door, the manifest, the probes, the downloads — on every API prefix — and nothing wider', () => {
159
+ for (const p of [
160
+ '/api/gds/auth/web/start', '/api/bongos/auth/web/callback', '/api/gds/auth/device/start', '/api/gds/auth',
161
+ '/api/bongos/v1/auth/device/start', '/api/bongos/v1/instance', '/api/bongos/v1/healthz',
162
+ '/api/gds/instance', '/api/bongos/instance', '/api/gds/healthz',
163
+ '/api/bongos/provisioning/tls-check', '/api/gds/provisioning/health',
164
+ '/healthz', '/version', '/metrics', '/__stealth',
165
+ '/downloads/bongos/install.sh', '/downloads/devbox/latest.json',
166
+ // Express routes case-insensitively and with a trailing slash — the list sees what the router sees
167
+ '/API/GDS/INSTANCE', '/api/gds/instance/', '/HEALTHZ', '/api/bongos/auth/',
168
+ ]) assert.equal(isExempt(p), true, `${p} must answer a stranger`);
169
+ for (const p of [
170
+ '/', '/projects', '/builders', '/builders/hall.css', '/docs', '/lib/bongos-client.js',
171
+ '/api/bongos/public/progress', '/api/gds/public/leaderboard', '/api/bongos/instances', '/api/gds/authors',
172
+ '/api/bongos/v1/public/progress', '/api/gds/instance/anything', '/instance', '/healthzz', '/downloads',
173
+ '/api/other/instance', '/api/bongos/provisioning/instances',
174
+ ]) assert.equal(isExempt(p), false, `${p} must be gated`);
175
+ });
176
+
177
+ test('gated mode: an exempt path is never looked up, and a CORS preflight passes', async () => {
178
+ let looked = 0;
179
+ const mw = platformVisibilityGate({ isGated: () => true, resolveSession: async () => { looked += 1; return null; } });
180
+ for (const p of ['/healthz', '/api/gds/instance', '/api/bongos/v1/auth/device/start', '/downloads/bongos/install.sh']) {
181
+ const { nexted } = await run(mw, fakeReq({ path: p }));
182
+ assert.equal(nexted, true, `${p} passes`);
183
+ }
184
+ const pre = await run(mw, fakeReq({ path: '/api/bongos/public/progress', method: 'OPTIONS' }));
185
+ assert.equal(pre.nexted, true, 'a preflight carries no identity and admits nothing');
186
+ assert.equal(looked, 0, 'the probes and the door cost no session lookup');
187
+ });
188
+
189
+ test('the sign-in return is the knocked-on page for GET/HEAD only, relative, and never a foreign host', () => {
190
+ assert.equal(signInHref(fakeReq({ path: '/builders/roadmap' })), '/api/gds/auth/web/start?return=%2Fbuilders%2Froadmap');
191
+ assert.equal(signInHref(fakeReq({ path: '/builders/roadmap', method: 'POST' })), '/api/gds/auth/web/start', 'a write has nothing safe to replay');
192
+ assert.equal(signInHref(fakeReq({ path: '//evil.example/x' })), '/api/gds/auth/web/start', 'a protocol-relative path is dropped');
193
+ assert.equal(signInHref(fakeReq({ path: '/' })), '/api/gds/auth/web/start', 'the front page needs no return');
194
+ });
195
+
196
+ // ── 2. the real booted app, pack = gated ─────────────────────────────────────
197
+
198
+ let server, port;
199
+ before(async () => {
200
+ server = http.createServer(platformMod.buildPlatformApp());
201
+ await new Promise((r) => server.listen(0, '127.0.0.1', r));
202
+ port = server.address().port;
203
+ });
204
+ after(async () => {
205
+ await new Promise((r) => server.close(r));
206
+ delete process.env.GDS_BRANDING_FILE;
207
+ try { fs.unlinkSync(brandingFile); } catch { /* already gone */ }
208
+ });
209
+
210
+ function request(pathname, { method = 'GET', host = '127.0.0.1', cookie } = {}) {
211
+ return new Promise((resolve, reject) => {
212
+ const req = http.request({
213
+ host: '127.0.0.1', port, path: pathname, method,
214
+ headers: { host, ...(cookie ? { cookie } : {}) },
215
+ }, (res) => {
216
+ let data = '';
217
+ res.on('data', (c) => { data += c; });
218
+ res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: data }));
219
+ });
220
+ req.on('error', reject);
221
+ req.end();
222
+ });
223
+ }
224
+
225
+ test('booted gated: the pack the gate reads says so', () => {
226
+ assert.equal(mode(), 'gated');
227
+ assert.equal(isGated(), true);
228
+ });
229
+
230
+ test('booted gated: every human surface meets the door, not the product — assets included', async () => {
231
+ for (const p of ['/', '/projects', '/create', '/builders', '/docs', '/lib/bongos-client.js', '/favicon.ico']) {
232
+ const res = await request(p);
233
+ assert.equal(res.status, 401, `${p} must be gated`);
234
+ assert.match(res.body, /Sign in/, `${p} shows the door`);
235
+ assert.doesNotMatch(res.body, /id="projectsList"|__BRANDING__|window\.bongos/, `${p} must not render the product`);
236
+ }
237
+ });
238
+
239
+ test('booted gated: the public read API refuses a stranger with the API envelope, on every prefix', async () => {
240
+ for (const p of ['/api/bongos/public/progress', '/api/gds/public/progress', '/api/bongos/v1/public/progress']) {
241
+ const res = await request(p);
242
+ assert.equal(res.status, 401, p);
243
+ const j = JSON.parse(res.body);
244
+ assert.equal(j.error && j.error.code, 'unauthenticated', `${p}: the ADR 0116 envelope, as requireBuilder emits it`);
245
+ }
246
+ });
247
+
248
+ test('booted gated: the door, the manifest, the probes and the downloads still answer a stranger', async () => {
249
+ for (const p of ['/healthz', '/version']) {
250
+ const res = await request(p);
251
+ assert.equal(res.status, 200, `${p} must answer a stranger`);
252
+ }
253
+ // the manifest: what a stranger reads to learn how to sign in, and what the
254
+ // platform's runner reads to learn whether the setting was consumed — on the
255
+ // pinned contract prefix too (ADR 0117)
256
+ for (const p of ['/api/bongos/instance', '/api/bongos/v1/instance', '/api/gds/instance']) {
257
+ const m = await request(p);
258
+ assert.equal(m.status, 200, `${p}: the manifest stays public`);
259
+ const j = JSON.parse(m.body);
260
+ assert.equal(j.brand.project.platformVisibility, 'gated', 'and it carries the policy itself');
261
+ }
262
+ // the sign-in start must be reachable: it reaches the auth router (which may
263
+ // refuse for its own reasons — a missing OAuth app — but never with the door)
264
+ for (const p of ['/api/bongos/auth/web/start', '/api/bongos/v1/auth/web/start']) {
265
+ const s = await request(p);
266
+ assert.notEqual(s.status, 401, `${p}: the door must be openable`);
267
+ assert.doesNotMatch(s.body, /Sign in to continue/, 'the auth surface never renders the door');
268
+ }
269
+ // a cookie-less `curl … | sh` must not receive an HTML door
270
+ const dl = await request('/downloads/bongos/install.sh');
271
+ assert.notEqual(dl.status, 401, 'the installer is not a project surface');
272
+ assert.doesNotMatch(dl.body, /Sign in to continue/);
273
+ });
274
+
275
+ test('booted gated: a bogus session cookie is not a member', async () => {
276
+ // resolves to no session (or fails closed if there is no database here) —
277
+ // either way the stranger stays outside
278
+ const res = await request('/projects', { cookie: 'gds_session=notARealToken' });
279
+ assert.equal(res.status, 401);
280
+ });
@@ -1121,3 +1121,96 @@ test('a teardown does not silence the address — the copy says what the runner
1121
1121
  'the honest word, and the accurate one — a revive re-uses exactly this hostname');
1122
1122
  assert.match(fn, /repository is untouched/, 'what survives is still stated in both shapes');
1123
1123
  });
1124
+
1125
+ /* ── who can see it (task 1003043): the platform-visibility toggle ───────────
1126
+ Two reads kept apart: the CHOICE is the platform's settings row, and what the
1127
+ project RUNS is read from its own public manifest (brand.project, ADR 0190
1128
+ §9). The save's reply is a receipt — shown verbatim, never painted as the live
1129
+ state — because a change reaches a running project only by a restart. ── */
1130
+
1131
+ // a function's text runs from its declaration to the next declaration (none of
1132
+ // the functions pinned here nest a named function of their own)
1133
+ const fnHub = (name) => {
1134
+ const start = SCRIPT.indexOf(`function ${name}(`);
1135
+ assert.ok(start >= 0, `${name}() must exist`);
1136
+ const rest = SCRIPT.slice(start + 1);
1137
+ const next = rest.search(/\n\s*function \w+\(/);
1138
+ return next < 0 ? rest : rest.slice(0, next);
1139
+ };
1140
+
1141
+ test('the visibility section is live markup with its body slot and a sibling fold (C7)', () => {
1142
+ assert.match(HUB, /id="mVis"/, 'the volatile slot is real markup, not JS-built');
1143
+ assert.match(HUB, /id="mVisWhat"/, 'the what-changes fold ships with the page');
1144
+ const card = (HUB.match(/<div class="card" id="mVisibility">[\s\S]*?\n <\/div>/) || [''])[0];
1145
+ assert.ok(card, 'the visibility card must be extractable');
1146
+ const slotAt = card.indexOf('id="mVis"');
1147
+ const foldAt = card.indexOf('id="mVisWhat"');
1148
+ assert.ok(slotAt > 0 && foldAt > slotAt, 'the fold sits outside the slot it must survive');
1149
+ assert.doesNotMatch(card, /isn’t wired up/, 'the placeholder copy is gone — the section is real now');
1150
+ const fold = (HUB.match(/<details class="finePrint" id="mVisWhat">[\s\S]*?<\/details>/) || [''])[0];
1151
+ assert.match(fold, /door itself stays open/, 'the sign-in door is promised reachable — members can always get in');
1152
+ assert.match(fold, /separate from the repository’s visibility/, 'the two visibilities are named as separate settings (owner decision 2026-08-16)');
1153
+ assert.match(fold, /restarts the project/, 'the restart is said, not hidden (ADR 0190)');
1154
+ });
1155
+
1156
+ test('the choices are the platform’s vocabulary, each in plain words, and the pick is a real radio group', () => {
1157
+ assert.match(SCRIPT, /var VIS_OPTIONS = \{\s*public: \{ label: 'Everyone'/);
1158
+ assert.match(SCRIPT, /gated: \{ label: 'Only signed-in members'/);
1159
+ const render = fnHub('renderVis');
1160
+ assert.match(render, /\(s\.options \|\| \[\]\)\.filter\(function \(v\) \{ return !!VIS_OPTIONS\[v\]; \}\)/,
1161
+ 'only values the page can describe are offered — the server’s options list is the source');
1162
+ assert.match(render, /role="radiogroup"/);
1163
+ assert.match(render, /type="radio" name="mVisPick"/);
1164
+ assert.match(render, /data-vis-save/, 'one Save control');
1165
+ });
1166
+
1167
+ test('the save sends the choice and shows the platform’s receipt verbatim — never the reply as the live state', () => {
1168
+ const save = fnHub('doVisSave');
1169
+ assert.match(save, /method: 'PATCH'/);
1170
+ assert.match(save, /\/settings'/, 'PATCH /provisioning/instances/:id/settings');
1171
+ assert.match(save, /JSON\.stringify\(\{ platform_visibility: value \}\)/, 'the body is the one key this section owns');
1172
+ assert.match(save, /\(data\.push && data\.push\.message\)/, 'push.message is rendered as the server wrote it');
1173
+ assert.doesNotMatch(save, /visRuns\(|mVisRuns/, 'the "runs" line is never written from the save reply');
1174
+ assert.doesNotMatch(save, /data\.saved[^;]*Right now|applied/i, 'a save is a request, not a claim the project runs it');
1175
+ assert.match(save, /e\.code === 'intent_conflict' \? 'projMsg' : 'projMsg err'/,
1176
+ 'a 409 intent_conflict is saved-but-waiting (details.recorded) — shown as the server’s sentence, not as a failure');
1177
+ assert.match(save, /loadManage\(true, true\)/, 'the strip + in-flight line re-read open_intent after a save');
1178
+ });
1179
+
1180
+ test('what the project RUNS comes only from its own manifest, and every answer is said', () => {
1181
+ const runs = fnHub('loadVisRuns');
1182
+ assert.match(runs, /inst\.status !== 'active' \|\| !inst\.domain/, 'only a live project with an address can be asked');
1183
+ assert.match(runs, /'https:\/\/' \+ inst\.domain \+ '\/api\/gds\/instance'/,
1184
+ 'the same public GET /instance the platform’s runner reads after a restart');
1185
+ assert.match(runs, /m\.brand && m\.brand\.project/);
1186
+ assert.match(runs, /doesn’t report this setting yet/, 'an older core is named, not shown as applied');
1187
+ assert.match(runs, /hasn’t taken effect there yet/, 'a saved-but-not-applied value is said');
1188
+ assert.match(runs, /Couldn’t reach the project/, 'an unreachable project is said');
1189
+ assert.match(runs, /if \(!res\.ok\) throw new Error\('http:' \+ res\.status\)/, 'a refusal is not an older core');
1190
+ assert.match(runs, /answered but didn’t share what it’s running/, 'a project that answered-and-refused is said as such');
1191
+ assert.doesNotMatch(runs, /mVisSettings\.value\)\s*\+\s*'<\/b>'|runs as <b>' \+ esc\(VIS_OPTIONS\[want\]/,
1192
+ 'the platform’s stored choice is never painted as what the project runs');
1193
+ });
1194
+
1195
+ test('the in-flight line reads open_intent only, and re-asks the manifest when the push settles', () => {
1196
+ const st = fnHub('renderVisState');
1197
+ assert.match(st, /if \(visPushOpen\(inst\)\)/, 'one predicate for "a push is open", shared with loadVisibility');
1198
+ assert.match(fnHub('visPushOpen'), /open_intent\.action === 'settings-apply'/);
1199
+ assert.match(st, /inst.open_intent.state === 'running'/);
1200
+ assert.match(st, /Until then it keeps its current setting\./, 'a queued push does not claim the new value');
1201
+ assert.doesNotMatch(st, /fetch\(/, 'fetch-free — the minute tick may call it');
1202
+ assert.match(st, /visRuns\(inst\.open_intent\.state === 'running'/, 'the in-flight line goes through visRuns so a re-render keeps it');
1203
+ assert.match(fnHub('loadVisibility'), /if \(!visPushOpen\(inst\)\) loadVisRuns\(inst\);/, 'entering during an open push keeps the intent line, not the stale manifest');
1204
+ assert.match(fnHub('doVisSave'), /if \(data\.push && data\.push\.queued\) mVisPushSeen = true;/, 'a push that settles before the re-read still triggers the manifest read');
1205
+ assert.match(st, /if \(mVisPushSeen\) \{ mVisPushSeen = false; loadVisRuns\(inst\); \}/,
1206
+ 'the manifest is asked again the moment the push is gone — the only source that can say the value took');
1207
+ assert.match(fnHub('renderManage'), /renderVisState\(inst\);/, 'renderManage repaints it on every tick');
1208
+ });
1209
+
1210
+ test('the settings reads fire on entry + tab return only — never on the in-flight tick', () => {
1211
+ assert.match(SCRIPT, /if \(!keepRepo\) loadVisibility\(inst\);/);
1212
+ const load = fnHub('loadVisibility');
1213
+ assert.match(load, /\/settings', \{ credentials: 'same-origin' \}/, 'GET /provisioning/instances/:id/settings');
1214
+ assert.match(load, /res\.status === 401 \? signInAgain\(\)/, 'a lapsed session is said as sign-in, not as an outage');
1215
+ assert.match(load, /var asked = manageId;/, 'a stale answer must never describe a newer project');
1216
+ });
@@ -166,13 +166,15 @@ test('an in-flight project is watched on its own page; a settled one keeps the C
166
166
  assert.match(SCRIPT, /setInterval\(manageWatchTick, 5000\)/);
167
167
  });
168
168
 
169
- test('working sections come before the not-wired ones on manage, and the placeholders state today’s behavior', () => {
169
+ test('working sections come before the not-wired one on manage, and the placeholder states today’s behavior', () => {
170
170
  const repo = HUB.indexOf('id="mRepoVisibility"');
171
171
  const vis = HUB.indexOf('id="mVisibility"');
172
172
  const join = HUB.indexOf('id="mJoinability"');
173
173
  const del = HUB.indexOf('id="mDelete"');
174
174
  assert.ok(repo > 0 && repo < vis && vis < join && join < del, 'repo visibility → who can see it → how people join → delete');
175
- assert.match(HUB, /For now, every project’s pages are open to everyone\./);
175
+ // "who can see it" is live since task 1003043 — its not-yet line is gone
176
+ const visCard = HUB.slice(vis, join);
177
+ assert.doesNotMatch(visCard, /isn’t wired up|For now, every project’s pages are open to everyone/);
176
178
  assert.match(HUB, /For now, people apply to join and you invite them\./);
177
179
  assert.doesNotMatch(HUB, /It lands in one of the next updates/);
178
180
  });