@bongos/core 1.24.45 → 1.24.46
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 +140 -105
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +8 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/adr/0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md +15 -0
- package/docs/api/openapi.json +152 -3
- package/docs/api-reference.md +4 -2
- package/docs/copy-inventory.md +1102 -1102
- package/docs/copy-registry.json +1538 -1538
- package/docs/module-api-changelog.md +2 -0
- package/docs/onboarding/diagrams/assertions.json +2 -2
- package/docs/page-inventory.json +11 -0
- package/docs/page-readings.json +724 -724
- package/migrations/core_295_runner_logins.sql +50 -0
- package/modules/government/session-scopes.json +5 -0
- package/modules/hall-ui/public/ai-studio.states.json +2 -0
- package/modules/hall-ui/public/atlas.states.json +2 -0
- package/modules/hall-ui/public/board-room.states.json +2 -0
- package/modules/hall-ui/public/city.states.json +2 -0
- package/modules/hall-ui/public/collab.states.json +2 -0
- package/modules/hall-ui/public/commands.states.json +2 -0
- package/modules/hall-ui/public/copy-desk.states.json +2 -0
- package/modules/hall-ui/public/deploy.states.json +2 -0
- package/modules/hall-ui/public/goal-map.states.json +2 -0
- package/modules/hall-ui/public/goals.css +1 -0
- package/modules/hall-ui/public/goals.states.json +2 -0
- package/modules/hall-ui/public/government.states.json +2 -0
- package/modules/hall-ui/public/idea.states.json +2 -0
- package/modules/hall-ui/public/ideas.states.json +2 -0
- package/modules/hall-ui/public/index-founding.states.json +2 -0
- package/modules/hall-ui/public/index-genesis-decide.states.json +2 -0
- package/modules/hall-ui/public/index-genesis-demo.states.json +2 -0
- package/modules/hall-ui/public/index-genesis.states.json +2 -0
- package/modules/hall-ui/public/index.states.json +5 -0
- package/modules/hall-ui/public/market-watch-item.states.json +2 -0
- package/modules/hall-ui/public/market-watch-rules.states.json +2 -0
- package/modules/hall-ui/public/market-watch.states.json +2 -0
- package/modules/hall-ui/public/modules.states.json +2 -0
- package/modules/hall-ui/public/money.states.json +2 -0
- package/modules/hall-ui/public/my-agents.states.json +2 -0
- package/modules/hall-ui/public/notes-panel.css +1 -1
- package/modules/hall-ui/public/primer.states.json +2 -0
- package/modules/hall-ui/public/profile.states.json +2 -0
- package/modules/hall-ui/public/roster.states.json +2 -0
- package/modules/hall-ui/public/settings.states.json +2 -0
- package/modules/hall-ui/public/street-card.states.json +2 -0
- package/modules/hall-ui/public/street.states.json +2 -0
- package/modules/hall-ui/public/studio.states.json +2 -0
- package/modules/hall-ui/public/style.css +27 -24
- package/modules/hall-ui/public/task.states.json +2 -0
- package/modules/hall-ui/public/thinking.states.json +2 -0
- package/modules/hall-ui/public/tweak-editor.states.json +2 -0
- package/modules/hall-ui/public/watch.states.json +2 -0
- package/modules/hall-ui/public/welcome-boot.js +6 -2
- package/modules/hall-ui/public/work-founding.states.json +2 -0
- package/modules/hall-ui/public/work.states.json +3 -1
- package/modules/lifecycle/db-claims.js +6 -3
- package/modules/lifecycle/routes/claims.js +10 -0
- package/modules/lifecycle/routes/tasks.js +1 -1
- package/modules/lifecycle/runner-self-guard.js +19 -10
- package/modules/platform-identity/platform-identity.js +1 -1
- package/modules/platform-identity/tests/platform-identity.mjs +13 -2
- package/modules/provisioning/core-upgrade-authz.js +8 -0
- package/modules/public-landing/public/account.html +1 -0
- package/modules/public-landing/public/community.html +1 -0
- package/modules/public-landing/public/connections.html +1 -0
- package/modules/public-landing/public/contact.html +1 -0
- package/modules/public-landing/public/guild.html +1 -0
- package/modules/public-landing/public/index.html +1 -0
- package/modules/public-landing/public/invitations.html +1 -0
- package/modules/public-landing/public/privacy.html +1 -0
- package/modules/public-landing/public/projects.html +1 -0
- package/modules/public-landing/public/terms.html +1 -0
- package/modules/raise/views/gate.html +1 -0
- package/modules/raise/views/nda.html +1 -0
- package/modules/raise/views/page.html +1 -0
- package/modules/status-ui/public/index.html +1 -0
- package/modules/ui-design/kit/README.md +1 -1
- package/modules/ui-design/kit/fixtures/versions.json +1 -0
- package/modules/ui-design/kit/lib.js +48 -3
- package/modules/ui-design/kit/probe.js +1 -0
- package/modules/ui-design/kit/render.js +2 -0
- package/modules/ui-design/kit/serve.js +1 -0
- package/modules/ui-design/module.json +1 -1
- package/modules/ui-design/public/tokens.css +125 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +14 -0
- package/scripts/bongos/fitness.js +1 -1
- package/scripts/bongos/run-unit-tests.js +3 -0
- package/src/bongos/auth.js +25 -4
- package/src/bongos/route-rank-check.js +5 -0
- package/src/bongos/routes/auth.js +48 -0
- package/src/bongos/runner-login.js +151 -0
- package/src/bongos/serve-internal.js +6 -1
- package/src/module-api.js +1 -1
- package/tests/autobongos_runner_login.mjs +296 -0
- package/tests/autobongos_runner_login_db.mjs +105 -0
- package/tests/design_tokens_shared.mjs +183 -0
- package/tests/government_session_scope.mjs +2 -1
- package/tests/hall_reading_world.mjs +5 -1
- package/tests/hall_tokens.mjs +22 -3
- package/tests/ui_design_kit.mjs +30 -0
- package/tests/welcome_boot_browser.mjs +4 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_": "Watch — the attention board behind requireGovernmentPage. The SEALED state (a viewer below the gate) is a real render, not an error page, so it is a state here. Written with the re-skin (task 1003307) so this page is MEASURED at three widths in both modes rather than looked at once.",
|
|
3
3
|
"page": "/builders/watch",
|
|
4
|
+
"_storage": "The hall asks a first-visit question (the session mode picker, craft-mode.js) over every page; seeding \"already asked\" renders the page a returning builder sees (task 1005080).",
|
|
5
|
+
"storage": { "session": { "hall-craft-asked": "1" } },
|
|
4
6
|
"surface": "hall-ui",
|
|
5
7
|
"stub": {
|
|
6
8
|
"prefix": "/builders/"
|
|
@@ -166,14 +166,18 @@
|
|
|
166
166
|
|
|
167
167
|
var root = null, timers = [], returnFocus = null, shown = false, leaving = false;
|
|
168
168
|
|
|
169
|
+
function bannerTag(builder) { return builder ? 'div' : 'header'; }
|
|
169
170
|
function overlayHtml(builder, now, when) {
|
|
170
171
|
return '<div class="wb__art" aria-hidden="true"><div class="wb__pan">' +
|
|
171
172
|
'<img class="wb__img" alt="" src="' + artFor(when) + '" decoding="async">' +
|
|
172
173
|
'<div class="wb__fx" id="wb-fx"></div>' +
|
|
173
174
|
'</div></div>' +
|
|
174
175
|
'<div class="wb__cloud wb__cloud--a" aria-hidden="true"></div><div class="wb__cloud wb__cloud--b" aria-hidden="true"></div>' +
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
// For a visitor the welcome IS the page (the shell draws no header for them), so its logo
|
|
177
|
+
// banner is the page's one <header>; over a signed-in hall the shell's header stands, and the
|
|
178
|
+
// banner stays a plain block (task 1005080).
|
|
179
|
+
'<' + bannerTag(builder) + ' class="wb__banner">' + logoHtml(window.__BRANDING__) +
|
|
180
|
+
'<span class="wb__version" id="wb-version" hidden></span></' + bannerTag(builder) + '>' +
|
|
177
181
|
'<div class="wb__veil" aria-hidden="true"></div>' +
|
|
178
182
|
'<button class="wb__skip" type="button" data-wb-skip>Skip</button>' +
|
|
179
183
|
'<a class="wb__built" href="' + BUILT_ON.href + '" target="_blank" rel="noopener">Built on <b>' + BUILT_ON.label + '</b></a>' +
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_": "Project founding mode (task 1004422) on an ordinary hall page: the founding band above the topbar and today's nav items asleep, rendered from a /me that says founding (kit fixture me-founding.json, the real server shaper's output at stage strategize). The page itself is work.states.json's; only the identity differs. The no-founding case is every other hall states file.",
|
|
3
3
|
"page": "/builders/work",
|
|
4
|
+
"_storage": "The hall asks a first-visit question (the session mode picker, craft-mode.js) over every page; seeding \"already asked\" renders the page a returning builder sees (task 1005080).",
|
|
5
|
+
"storage": { "session": { "hall-craft-asked": "1" } },
|
|
4
6
|
"surface": "hall-ui",
|
|
5
7
|
"stub": {
|
|
6
8
|
"prefix": "/builders/",
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_": "The hall's Work Board through modules/ui-design/kit/render.js (task 1003304). Written with the re-skin so the board's four tabs and its selection state are measured at 1440/390/320 in both modes, not just looked at. The populated look is the hall-preview harness (scripts/hall-preview/server.js); this file is the FLOORS.",
|
|
3
3
|
"page": "/builders/work",
|
|
4
|
+
"_storage": "The hall asks a first-visit question (the session mode picker, craft-mode.js) over every page; seeding \"already asked\" renders the page a returning builder sees (task 1005080).",
|
|
5
|
+
"storage": { "session": { "hall-craft-asked": "1" } },
|
|
4
6
|
"surface": "hall-ui",
|
|
5
7
|
"stub": { "prefix": "/builders/" },
|
|
6
8
|
"modeQuery": false,
|
|
7
9
|
"_feeds": "The kit's stub carries a small fixture set, not the hall's task feeds, so the board renders its EMPTY and SKELETON states here — which is the point: an empty queue is a state the floors must hold in. The feed 404s are declared, never hidden.",
|
|
8
|
-
"ignoreRequests": ["/api/(gds|bongos)/me$", "/api/(gds|bongos)/me/", "/api/(gds|bongos)/tasks", "/api/(gds|bongos)/claims", "/api/(gds|bongos)/goals", "/api/(gds|bongos)/inbox/", "/api/(gds|bongos)/versions"],
|
|
10
|
+
"ignoreRequests": ["/api/(gds|bongos)/live", "/api/(gds|bongos)/me$", "/api/(gds|bongos)/me/", "/api/(gds|bongos)/tasks", "/api/(gds|bongos)/claims", "/api/(gds|bongos)/goals", "/api/(gds|bongos)/inbox/", "/api/(gds|bongos)/versions"],
|
|
9
11
|
"ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b404\\b", "Unauthorized", "Not Found", "load failed"],
|
|
10
12
|
"states": {
|
|
11
13
|
"ready": { "auth": true, "expect": { "visible": ["#claim-scroll"], "hidden": ["#wip-scroll", "#backlog-scroll", "#history-scroll"] } },
|
|
@@ -108,7 +108,7 @@ function isRebaseGateExempt(task) {
|
|
|
108
108
|
return Boolean(task) && task.kind === 'infra' && Number(task.priority) === 1;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSessionId, enforceOverlap = false, autoPickup = false }) {
|
|
111
|
+
async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSessionId, enforceOverlap = false, autoPickup = false, autonomous = false }) {
|
|
112
112
|
const heldPermissions = await claimPermissionsFor(builderId); // BV1.R106 — before the tx opens
|
|
113
113
|
// ADR 0371 D5: a marked claim needs the project's switch and cap, read before the
|
|
114
114
|
// tx like the permissions above; the COUNT runs inside it, under the lock.
|
|
@@ -433,9 +433,12 @@ async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSess
|
|
|
433
433
|
|
|
434
434
|
// The column is named only on a marked claim, so an ordinary claim's insert is
|
|
435
435
|
// byte-identical to before core_275.
|
|
436
|
+
// `autonomous` (core_295, task 1004547) is the server's mark that an Autobongos
|
|
437
|
+
// runner's own login made this claim — set by the claims route from the SESSION,
|
|
438
|
+
// never from the body. Like auto_pickup, the column is named only when true.
|
|
436
439
|
const insertClaim = await client.query(
|
|
437
|
-
`INSERT INTO claims (task_id, builder_id, worktree_name, requested_worktree_name, head_sha, reserved_migration_number, creator_session_id${autoPickup ? ', auto_pickup' : ''})
|
|
438
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7${autoPickup ? ', TRUE' : ''})
|
|
440
|
+
`INSERT INTO claims (task_id, builder_id, worktree_name, requested_worktree_name, head_sha, reserved_migration_number, creator_session_id${autoPickup ? ', auto_pickup' : ''}${autonomous ? ', autonomous' : ''})
|
|
441
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7${autoPickup ? ', TRUE' : ''}${autonomous ? ', TRUE' : ''})
|
|
439
442
|
RETURNING id, task_id, builder_id, worktree_name, requested_worktree_name, claimed_at, head_sha, reserved_migration_number`,
|
|
440
443
|
[taskId, builderId, finalWorktreeName, requestedWorktreeName, headSha ?? null, reservedMigrationNumber, creatorSessionId ?? null]
|
|
441
444
|
);
|
|
@@ -305,6 +305,10 @@ module.exports = function buildClaimsRouter() {
|
|
|
305
305
|
creatorSessionId: resolveCreatorSessionId(bindSession, req.gdsSession?.session_id),
|
|
306
306
|
enforceOverlap: enforceTouches === true,
|
|
307
307
|
autoPickup: autoPickup === true,
|
|
308
|
+
// task 1004547: an Autobongos runner's own login marks its claim
|
|
309
|
+
// autonomous. Read from the SESSION (auth.requireBuilder), never the body,
|
|
310
|
+
// so a worker cannot claim its way out of the runner-only walls.
|
|
311
|
+
autonomous: !!(req.builder && req.builder.runner),
|
|
308
312
|
});
|
|
309
313
|
const { just_unlocked: justUnlocked, overlap_warning: overlapWarning, sibling_overlap_warning: siblingOverlapWarning, page_notice: pageNotice, ...claim } = claimResult;
|
|
310
314
|
const task = await db.getTask(Number(taskId));
|
|
@@ -476,6 +480,12 @@ module.exports = function buildClaimsRouter() {
|
|
|
476
480
|
if (taskIds.length === 0) {
|
|
477
481
|
return res.fail('task_ids_required', 400);
|
|
478
482
|
}
|
|
483
|
+
// task 1004547: a batch claim binds only its first row to the session, so it
|
|
484
|
+
// cannot carry the autonomous mark row by row. A runner works one task per
|
|
485
|
+
// worker and never batch-claims, so its own login is refused here outright.
|
|
486
|
+
if (req.builder && req.builder.runner) {
|
|
487
|
+
return res.fail('runner_batch_refused', { status: 403, message: 'An Autobongos runner claims one task at a time. Use POST /claims.' });
|
|
488
|
+
}
|
|
479
489
|
if (taskIds.length > 20) {
|
|
480
490
|
// Safety cap. The optimizer doesn't return more than a handful at a time,
|
|
481
491
|
// and unlimited batches let one slow transaction lock up the table.
|
|
@@ -1184,7 +1184,7 @@ module.exports = function buildTasksRouter() {
|
|
|
1184
1184
|
`Merging triggers .github/workflows/deploy-prod.yml -> ${branding().domains.publicOrigin}. ` +
|
|
1185
1185
|
prMergeSentence(held);
|
|
1186
1186
|
const pr = await githubPush.openOrFindPullRequest({ branch, title, body });
|
|
1187
|
-
const prReads = sharedPrReads(); for (const [guard, refusalFor] of [[autoPickupFiles, autoPickupFiles.autoPickupFileRefusal], [runnerSelfGuard, runnerSelfGuard.runnerSelfEditRefusal]]) { const refusal = await refusalFor({ taskId: id, prNumber: pr.number, branch }, { getPullRequestFiles: prReads.getPullRequestFiles }); if (refusal) { await githubPrClose.closePullRequest({ number: pr.number }).catch(() => null); return res.fail(refusal.code, 409, { ...refusal, message: guard.refusalMessage(refusal) }); } } // the PR's own files: an auto-pickup round stays on its page (ADR 0371 D8, task 1004678); a runner never changes its own files (task 1004546)
|
|
1187
|
+
const prReads = sharedPrReads(); for (const [guard, refusalFor] of [[autoPickupFiles, autoPickupFiles.autoPickupFileRefusal], [runnerSelfGuard, runnerSelfGuard.runnerSelfEditRefusal]]) { const refusal = await refusalFor({ taskId: id, prNumber: pr.number, branch, publisherIsRunner: !!(req.builder && req.builder.runner) }, { getPullRequestFiles: prReads.getPullRequestFiles }); if (refusal) { await githubPrClose.closePullRequest({ number: pr.number }).catch(() => null); return res.fail(refusal.code, 409, { ...refusal, message: guard.refusalMessage(refusal) }); } } // the PR's own files: an auto-pickup round stays on its page (ADR 0371 D8, task 1004678); a runner never changes its own files (task 1004546), whoever's claim it is when a runner login publishes (task 1004547)
|
|
1188
1188
|
// Best-effort by contract (pr-assign.js never rejects); the catch is for a future
|
|
1189
1189
|
// edit that forgets — an assignment must not be able to fail a pushed ship.
|
|
1190
1190
|
const assignment = await prAssign.assignPullRequest({ number: pr.number, login: builderLogin })
|
|
@@ -26,11 +26,18 @@
|
|
|
26
26
|
// paths the registry has always protected, and one that does not is treated as
|
|
27
27
|
// unreadable, which refuses.
|
|
28
28
|
//
|
|
29
|
-
// WHICH
|
|
30
|
-
//
|
|
31
|
-
// runner's
|
|
32
|
-
//
|
|
33
|
-
//
|
|
29
|
+
// WHICH PUBLISH IS A RUNNER'S (task 1004547). Any one of three says so:
|
|
30
|
+
// - the claim row's `autonomous` mark, which the claims route sets from the
|
|
31
|
+
// claiming SESSION (a runner's own login, src/bongos/runner-login.js) and which
|
|
32
|
+
// no request body can set or clear. This is the reliable one: a worker that
|
|
33
|
+
// claims under a hand-picked worktree name is still recognised;
|
|
34
|
+
// - the publishing session is itself a runner's (`publisherIsRunner`), whoever
|
|
35
|
+
// made the claim;
|
|
36
|
+
// - the claim's worktree name (runner-self.js RUNNER_TREE_RE), kept for runners
|
|
37
|
+
// still running under their builder's CLI session until they are re-set-up.
|
|
38
|
+
// If the claim read FAILS, the branch name stands in (the runner's branch is its
|
|
39
|
+
// worktree name), so a database hiccup does not switch the wall off for a runner
|
|
40
|
+
// while an ordinary publish still never depends on the read.
|
|
34
41
|
//
|
|
35
42
|
// FAILS CLOSED on the files: a read that throws, a list at GitHub's 300-file cap,
|
|
36
43
|
// or a matcher that fails its check all refuse. An unattended change that cannot
|
|
@@ -49,7 +56,7 @@ const runnerSelf = require('./runner-self');
|
|
|
49
56
|
// resolved it. Both names are read because the server may store a disambiguated
|
|
50
57
|
// worktree_name beside the one the runner asked for.
|
|
51
58
|
const CLAIM_SQL = `
|
|
52
|
-
SELECT c.worktree_name, c.requested_worktree_name
|
|
59
|
+
SELECT c.worktree_name, c.requested_worktree_name, c.autonomous
|
|
53
60
|
FROM claims c WHERE c.task_id = $1
|
|
54
61
|
ORDER BY c.claimed_at DESC, c.id DESC LIMIT 1`;
|
|
55
62
|
|
|
@@ -66,9 +73,11 @@ async function readLatestClaim(taskId, exec = api.pool) {
|
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
function isRunnerPublish(claim, branch) {
|
|
76
|
+
function isRunnerPublish(claim, branch, publisherIsRunner = false) {
|
|
77
|
+
if (publisherIsRunner === true) return true;
|
|
70
78
|
if (claim === CLAIM_UNREADABLE) return runnerSelf.isRunnerTree(branch);
|
|
71
|
-
return !!claim && (
|
|
79
|
+
return !!claim && (claim.autonomous === true
|
|
80
|
+
|| runnerSelf.isRunnerTree(claim.worktree_name) || runnerSelf.isRunnerTree(claim.requested_worktree_name));
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
// Paths the registry has protected since ADR 0043; a matcher that misses either
|
|
@@ -86,9 +95,9 @@ const unreadable = () => ({ code: 'self_modification_unreadable', files: [], pro
|
|
|
86
95
|
// The route's one call. Returns null (publish as usual) or the refusal
|
|
87
96
|
// { code, files, protected }. `deps` is the test seam, and the route passes a
|
|
88
97
|
// shared getPullRequestFiles so this and the auto-pickup check read GitHub once.
|
|
89
|
-
async function runnerSelfEditRefusal({ taskId, prNumber, branch = null }, deps = {}) {
|
|
98
|
+
async function runnerSelfEditRefusal({ taskId, prNumber, branch = null, publisherIsRunner = false }, deps = {}) {
|
|
90
99
|
const claim = await (deps.readLatestClaim || readLatestClaim)(taskId);
|
|
91
|
-
if (!isRunnerPublish(claim, branch)) return null;
|
|
100
|
+
if (!isRunnerPublish(claim, branch, publisherIsRunner)) return null;
|
|
92
101
|
const match = verifiedMatcher(deps.matchProtected || ((files) => api.permissionPathCheck.matchProtected(files)));
|
|
93
102
|
if (!match) return unreadable();
|
|
94
103
|
let rows;
|
|
@@ -96,7 +96,7 @@ function clientSecretValid(client, presentedSecret) {
|
|
|
96
96
|
// FEDERATION_SOURCES mirrors modules/government/session-scopes.json — a module may
|
|
97
97
|
// not read another module's files, and the doorway exports no lookup for it — and
|
|
98
98
|
// tests/platform-identity.mjs pins the two lists equal, so a source declared or
|
|
99
|
-
// retired there without the same edit here fails the build instead of drifting.
|
|
99
|
+
// retired there without the same edit here fails the build instead of drifting ('runner' is left out: task 1004547).
|
|
100
100
|
const FEDERATION_SOURCES = Object.freeze(['cli', 'web', null]);
|
|
101
101
|
function sessionUsableForFederation(session) {
|
|
102
102
|
if (!session) return { ok: false, reason: 'no_session' };
|
|
@@ -130,12 +130,23 @@ t('a session of an undeclared source (a leftover dev-box token) may NOT federate
|
|
|
130
130
|
}
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
// Sources the registry declares that must NOT federate. An Autobongos runner's own
|
|
134
|
+
// login (task 1004547) is a machine working for its builder on ONE project; it has no
|
|
135
|
+
// business signing that person in to other projects through the hub.
|
|
136
|
+
const NEVER_FEDERATES = ['runner'];
|
|
137
|
+
|
|
138
|
+
t('FEDERATION_SOURCES is exactly the kernel session-source registry, less the machine logins', () => {
|
|
134
139
|
// platform-identity may not read the government module, so it mirrors the list;
|
|
135
140
|
// this pin is what stops the copy drifting from modules/government/session-scopes.json.
|
|
136
141
|
const reg = JSON.parse(readFileSync(new URL('../../government/session-scopes.json', import.meta.url), 'utf8'));
|
|
137
142
|
const declared = reg.scopes.map((r) => (r.source === undefined ? null : r.source));
|
|
138
|
-
assert.
|
|
143
|
+
for (const s of NEVER_FEDERATES) assert.ok(declared.includes(s), `${s} is still declared, so the exclusion is still needed`);
|
|
144
|
+
assert.deepEqual([...P.FEDERATION_SOURCES].sort(), declared.filter((s) => !NEVER_FEDERATES.includes(s)).sort());
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
t('an Autobongos runner login may NOT federate', () => {
|
|
148
|
+
const r = P.sessionUsableForFederation({ github_id: 1, status: 'active', source: 'runner' });
|
|
149
|
+
assert.deepEqual(r, { ok: false, reason: 'session_scope' });
|
|
139
150
|
});
|
|
140
151
|
|
|
141
152
|
t('a service principal (system_role set) may NOT federate', () => {
|
|
@@ -54,6 +54,14 @@ function ownsUpgradableProject(inst, builder) {
|
|
|
54
54
|
function requireOwnProjectOrPin({ pinGate, provisioning, pool, log }) {
|
|
55
55
|
return async function ownProjectOrPinGate(req, res, next) {
|
|
56
56
|
const raw = req.params ? req.params.id : undefined;
|
|
57
|
+
// An Autobongos runner's own login never takes the owner door (task 1004547):
|
|
58
|
+
// an unattended machine does not move a live version, even its owner's. It falls
|
|
59
|
+
// to the permission gate, which refuses it, because a runner session holds no
|
|
60
|
+
// system permission (src/bongos/auth.js resolveHeldPermissions).
|
|
61
|
+
if (req.builder && req.builder.runner) {
|
|
62
|
+
req.coreUpgradeAuthority = 'pin';
|
|
63
|
+
return pinGate(req, res, next);
|
|
64
|
+
}
|
|
57
65
|
if (typeof raw === 'string' && ID_RE.test(raw)) {
|
|
58
66
|
try {
|
|
59
67
|
const inst = await provisioning.getInstanceById(pool, Number(raw));
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
20
20
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
21
21
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
22
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
22
23
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
23
24
|
<!-- Dark only, like every apex page (ADR 0204): data-mode="dark" is fixed on <html>. -->
|
|
24
25
|
<style>
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
37
37
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
38
38
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
39
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
39
40
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
40
41
|
<!-- Dark only, like every apex page (ADR 0204): data-mode="dark" is fixed on <html>. -->
|
|
41
42
|
<style>
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
23
23
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
24
24
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
25
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
25
26
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
26
27
|
<!-- Dark only, like every apex page (ADR 0204): data-mode="dark" is fixed on <html>. -->
|
|
27
28
|
<style>
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
17
17
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
18
18
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
19
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
19
20
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
20
21
|
<!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
|
|
21
22
|
<style>
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
30
30
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
31
31
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
32
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
32
33
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
33
34
|
<!-- Dark only, like every apex page (ADR 0204): data-mode="dark" is fixed on <html>. -->
|
|
34
35
|
<style>
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
<!-- The world's token layer. Root-absolute so the server's ?v= content stamp
|
|
42
42
|
applies (a relative href is never stamped, and a four-hour browser cache
|
|
43
43
|
then styles new markup with an old sheet). -->
|
|
44
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
44
45
|
<link rel="stylesheet" href="/assets/world.css">
|
|
45
46
|
<style>
|
|
46
47
|
/* ── PAGE-LOCAL SHEET ────────────────────────────────────────────────────
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
24
24
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
25
25
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
26
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
26
27
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
27
28
|
<!-- Dark only, like every apex page (ADR 0204): data-mode="dark" is fixed on <html>. -->
|
|
28
29
|
<style>
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
15
15
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
16
16
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
17
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
17
18
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
18
19
|
<!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
|
|
19
20
|
<style>
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
space sky both modes ride on, light/dark governing the cards. The server
|
|
20
20
|
re-injects the branding :root AFTER stylesheets, so the pack stays
|
|
21
21
|
authoritative. Design contract: docs/design/projects-surfaces-redesign-direction.md -->
|
|
22
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
22
23
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
23
24
|
<!-- The apex is dark only (task 1003336, ADR 0204): data-mode="dark" is fixed on
|
|
24
25
|
<html> above — no pre-paint script, no stored choice, no OS preference. Light
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
15
15
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
16
16
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
17
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
17
18
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
18
19
|
<!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
|
|
19
20
|
<style>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
11
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
12
12
|
<script src="{{base}}/assets/mode.js"></script>
|
|
13
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
13
14
|
<link rel="stylesheet" href="{{base}}/assets/raise.css">
|
|
14
15
|
</head>
|
|
15
16
|
<body class="gate-body">
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
11
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
12
12
|
<script src="{{base}}/assets/mode.js"></script>
|
|
13
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
13
14
|
<link rel="stylesheet" href="{{base}}/assets/raise.css">
|
|
14
15
|
</head>
|
|
15
16
|
<body>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
11
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
12
12
|
<script src="{{base}}/assets/mode.js"></script>
|
|
13
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
13
14
|
<link rel="stylesheet" href="{{base}}/assets/raise.css?v={{assetVersion}}">
|
|
14
15
|
<script src="{{base}}/assets/raise.js?v={{assetVersion}}" defer></script>
|
|
15
16
|
{{editorAssets}}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
11
|
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;700&family=EB+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&display=swap" rel="stylesheet">
|
|
12
|
+
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
12
13
|
<link rel="stylesheet" href="/style.css?v=2026-06-12-wip3">
|
|
13
14
|
</head>
|
|
14
15
|
<body>
|
|
@@ -14,7 +14,7 @@ A **look** is a branding pack from the module's style library (`modules/ui-desig
|
|
|
14
14
|
|
|
15
15
|
Two of the floors are about fit ([task 1004203](https://cloudbongos.com/builders#/task/1004203), [ADR 0329](../../../docs/adr/0329-the-rendered-fit-check-is-a-kit-floor-and-an-advisory-ship-check-row.md)): `text-fit` (text past its box, SVG text past its card) and `sibling-overlap` (text-bearing siblings on top of each other). `scripts/bongos/render-check.js` runs just those two over the pages a branch changed, and `/ship-check` shows them as an advisory row.
|
|
16
16
|
|
|
17
|
-
A surface describes itself in two JSON files beside it — `<page>.states.json` and `<page>.probes.json` — in the vocabularies `lib.js` defines and validates. The full loop, the file shapes and the failure table: [`docs/recipes/ui-look-before-you-ship.md`](../../../docs/recipes/ui-look-before-you-ship.md). Exit codes everywhere: 0 clean · 1 problems · 2 a broken file or usage · 3 no browser.
|
|
17
|
+
A surface describes itself in two JSON files beside it — `<page>.states.json` and `<page>.probes.json` — in the vocabularies `lib.js` defines and validates. A page served at the surface root (the hall's Home, `/builders/`) reads `index.states.json`. A file (or one state) may carry `"storage": { "session": {…}, "local": {…} }`, written into the browser before the page's own scripts run, so a first-visit question renders as a returning visitor sees it: every hall file seeds `hall-craft-asked` so the mode picker stays out of the shots ([task 1005080](https://cloudbongos.com/builders#/task/1005080)). A hall state that needs real data (a goal's detail, the Settings sections) passes against the hall-preview harness with `--base-in`/`--base-out`, one render at a time. The full loop, the file shapes and the failure table: [`docs/recipes/ui-look-before-you-ship.md`](../../../docs/recipes/ui-look-before-you-ship.md). Exit codes everywhere: 0 clean · 1 problems · 2 a broken file or usage · 3 no browser.
|
|
18
18
|
|
|
19
19
|
## The anti-pattern detector (`tells.js`, and the rendered twins in `lib.js`)
|
|
20
20
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"versions":[{"id":"BONGOS-V1","name":"Cloud Bongos — platform (V1)","status":"shipped","scope_doc_path":null,"started_at":"2026-06-01T00:00:00.000Z","shipped_at":"2026-09-01T00:00:00.000Z","done_when":"The platform builds itself."},{"id":"BONGOS-V2","name":"The ten working areas","status":"building","scope_doc_path":null,"started_at":"2026-09-01T00:00:00.000Z","shipped_at":null,"done_when":"Every working area has a goal."}],"page":{"limit":50,"offset":0,"total":2}}
|
|
@@ -79,7 +79,16 @@ function resolveSurfaceDir(surface, root = REPO_ROOT) {
|
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
81
81
|
// The states/probes file for a page lives BESIDE the surface: <dir>/<page>.states.json.
|
|
82
|
-
function sidecarPath(surfaceDir, urlPath, kind) {
|
|
82
|
+
function sidecarPath(surfaceDir, urlPath, kind) {
|
|
83
|
+
const named = path.join(surfaceDir, `${pageName(urlPath)}.${kind}.json`);
|
|
84
|
+
// A page served at the surface ROOT ("/builders/") has no last segment of its own — the
|
|
85
|
+
// segment is the surface's prefix — so its sidecar is index.<kind>.json (task 1005080).
|
|
86
|
+
if (/\/$/.test(String(urlPath || '').split('?')[0]) && !fs.existsSync(named)) {
|
|
87
|
+
const index = path.join(surfaceDir, `index.${kind}.json`);
|
|
88
|
+
if (fs.existsSync(index)) return index;
|
|
89
|
+
}
|
|
90
|
+
return named;
|
|
91
|
+
}
|
|
83
92
|
function readJson(file) { return JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
84
93
|
|
|
85
94
|
// ── the action vocabulary (states + probes) ───────────────────────────────────
|
|
@@ -332,6 +341,37 @@ function reducedMotionProblems({ running = [], frames = 0 } = {}) {
|
|
|
332
341
|
// floor and not proof, and a reviewer verifies each tell in context (task 1003329, ADR 0223).
|
|
333
342
|
function craftTells(facts) { return (facts && facts.tells ? facts.tells : []).map((t) => `tell: ${t}`); }
|
|
334
343
|
|
|
344
|
+
// ── storage seeded before a page's own scripts run (task 1005080) ─────────────
|
|
345
|
+
// { "storage"?: { "session"?: { key: "value" }, "local"?: { key: "value" } } } on a states or
|
|
346
|
+
// probes file — and the same object on one state — is written into the browser's storage by
|
|
347
|
+
// an init script, so a page that asks a one-time question on first visit (the hall's mode
|
|
348
|
+
// picker) renders as a returning visitor sees it. A state that MEANS the first visit leaves
|
|
349
|
+
// it out. A state's own storage is merged over the file's.
|
|
350
|
+
function validateStorage(st, at) {
|
|
351
|
+
if (st === undefined) return [];
|
|
352
|
+
if (!st || typeof st !== 'object' || Array.isArray(st)) return [`${at}: an object { session?, local? }`];
|
|
353
|
+
const errors = [];
|
|
354
|
+
for (const k of Object.keys(st)) {
|
|
355
|
+
if (k !== 'session' && k !== 'local') { errors.push(`${at}.${k}: unknown store (session | local)`); continue; }
|
|
356
|
+
const m = st[k];
|
|
357
|
+
if (!m || typeof m !== 'object' || Array.isArray(m) || !Object.values(m).every((v) => typeof v === 'string')) errors.push(`${at}.${k}: an object of string → string`);
|
|
358
|
+
}
|
|
359
|
+
return errors;
|
|
360
|
+
}
|
|
361
|
+
function mergeStorage(...layers) {
|
|
362
|
+
const out = { session: {}, local: {} };
|
|
363
|
+
for (const s of layers) if (s) for (const k of ['session', 'local']) Object.assign(out[k], s[k] || {});
|
|
364
|
+
return out;
|
|
365
|
+
}
|
|
366
|
+
async function seedStorage(ctx, ...layers) {
|
|
367
|
+
const st = mergeStorage(...layers);
|
|
368
|
+
if (!Object.keys(st.session).length && !Object.keys(st.local).length) return;
|
|
369
|
+
await ctx.addInitScript((s) => {
|
|
370
|
+
try { for (const [k, v] of Object.entries(s.session)) window.sessionStorage.setItem(k, v); } catch (_) { /* storage refused */ }
|
|
371
|
+
try { for (const [k, v] of Object.entries(s.local)) window.localStorage.setItem(k, v); } catch (_) { /* storage refused */ }
|
|
372
|
+
}, st);
|
|
373
|
+
}
|
|
374
|
+
|
|
335
375
|
// ── the states file ───────────────────────────────────────────────────────────
|
|
336
376
|
// { "page": "/projects", "surface"?: "public-landing" (a module key), "stub"?: { prefix?, child? },
|
|
337
377
|
// "modes"?: ["dark" | "light", …] (the modes the page renders; default both — a page that pins
|
|
@@ -339,7 +379,8 @@ function craftTells(facts) { return (facts && facts.tells ? facts.tells : []).ma
|
|
|
339
379
|
// stub.fixtures?: { "<fixture name>": "<other fixture file>" } (serve another file under a name — an empty list, a
|
|
340
380
|
// failure shape — for the states in THIS file; task 1003345),
|
|
341
381
|
// "ignoreControls"?: [selectors], "states": { name: { url?, auth?, actions?, expect? } },
|
|
342
|
-
// "reducedMotion"?: { auth?, url?, actions? }
|
|
382
|
+
// "reducedMotion"?: { auth?, url?, actions? },
|
|
383
|
+
// "storage"?: { session?, local? } (seeded before load; a state may carry its own — see seedStorage) }
|
|
343
384
|
function validateStatesFile(json) {
|
|
344
385
|
if (!json || typeof json !== 'object' || Array.isArray(json)) return { ok: false, errors: ['a states file is a JSON object'] };
|
|
345
386
|
const errors = [];
|
|
@@ -349,6 +390,7 @@ function validateStatesFile(json) {
|
|
|
349
390
|
if (json.modeQuery !== undefined && json.modeQuery !== false && typeof json.modeQuery !== 'string') errors.push('modeQuery: the query param the page reads for its colour mode (default "mode"), or false');
|
|
350
391
|
if (json.modes !== undefined && !(Array.isArray(json.modes) && json.modes.length && json.modes.every((m) => MODES.includes(m)))) errors.push('modes: a non-empty array of "dark" | "light" — the modes the page renders (default both); a page that pins one mode declares it here');
|
|
351
392
|
for (const k of ['ignoreRequests', 'ignoreConsole', 'ignoreControls']) if (json[k] !== undefined && !(Array.isArray(json[k]) && json[k].every((s) => typeof s === 'string'))) errors.push(`${k}: an array of strings`);
|
|
393
|
+
errors.push(...validateStorage(json.storage, 'storage'));
|
|
352
394
|
const states = json.states;
|
|
353
395
|
if (!states || typeof states !== 'object' || Array.isArray(states) || !Object.keys(states).length) errors.push('states: required — a non-empty object of name → { url?, auth?, actions?, expect? }');
|
|
354
396
|
else {
|
|
@@ -359,6 +401,7 @@ function validateStatesFile(json) {
|
|
|
359
401
|
if (s.auth !== undefined && typeof s.auth !== 'boolean') errors.push(`states.${name}.auth: true (signed-in stub) or false`);
|
|
360
402
|
errors.push(...validateActions(s.actions, `states.${name}.actions`));
|
|
361
403
|
errors.push(...validateExpect(s.expect, `states.${name}.expect`));
|
|
404
|
+
errors.push(...validateStorage(s.storage, `states.${name}.storage`));
|
|
362
405
|
}
|
|
363
406
|
}
|
|
364
407
|
if (json.reducedMotion !== undefined) {
|
|
@@ -386,6 +429,7 @@ function validateProbesFile(json) {
|
|
|
386
429
|
const errors = [];
|
|
387
430
|
if (typeof json.page !== 'string' || !json.page.startsWith('/')) errors.push('page: required — the root-relative path the probes open');
|
|
388
431
|
errors.push(...validateStub(json.stub, 'stub'));
|
|
432
|
+
errors.push(...validateStorage(json.storage, 'storage'));
|
|
389
433
|
if (!Array.isArray(json.probes) || !json.probes.length) errors.push('probes: required — a non-empty array');
|
|
390
434
|
else json.probes.forEach((pr, i) => {
|
|
391
435
|
const at = `probes[${i}]`;
|
|
@@ -396,6 +440,7 @@ function validateProbesFile(json) {
|
|
|
396
440
|
if (pr.url !== undefined && (typeof pr.url !== 'string' || !pr.url.startsWith('/'))) errors.push(`${at}.url: a root-relative path`);
|
|
397
441
|
if (pr.continue !== undefined && typeof pr.continue !== 'boolean') errors.push(`${at}.continue: boolean (reuse the previous probe's page)`);
|
|
398
442
|
if (pr.continue && i === 0) errors.push(`${at}.continue: the first probe has nothing to continue`);
|
|
443
|
+
errors.push(...validateStorage(pr.storage, `${at}.storage`));
|
|
399
444
|
errors.push(...validateActions(pr.steps, `${at}.steps`));
|
|
400
445
|
if (!pr.expect || typeof pr.expect !== 'object' || !Object.keys(pr.expect).length) errors.push(`${at}.expect: required — a probe with nothing to expect proves nothing`);
|
|
401
446
|
else errors.push(...validateExpect(pr.expect, `${at}.expect`));
|
|
@@ -527,7 +572,7 @@ module.exports = {
|
|
|
527
572
|
REPO_ROOT, KIT_DIR, STYLES_DIR, EXIT, FLOORS, SIZES, DEFAULT_SIZES, MODES, CHANNELS, SETTLE_MS, ACTIONS,
|
|
528
573
|
argOf, hasFlag, listArg, pageName, normalizePageArg, resolveSurfaceDir, sidecarPath, readJson,
|
|
529
574
|
runActions, factsInPage, expectProblems, auditFloors, reducedMotionProblems, craftTells,
|
|
530
|
-
validateStatesFile, validateProbesFile, NoBrowserError, launchBrowser, attachCollectors, withMode,
|
|
575
|
+
validateStatesFile, validateProbesFile, mergeStorage, seedStorage, NoBrowserError, launchBrowser, attachCollectors, withMode,
|
|
531
576
|
freePort, startStub, parseColor, composite, luminance, contrastRatio, instancePack, packColors,
|
|
532
577
|
listStyles, resolveStyle, styleBrandingFile,
|
|
533
578
|
};
|
|
@@ -72,6 +72,7 @@ async function main() {
|
|
|
72
72
|
if (ctx) await ctx.close();
|
|
73
73
|
const [W, H] = lib.SIZES[pr.width || 1440];
|
|
74
74
|
ctx = await browser.newContext({ viewport: { width: W, height: H }, deviceScaleFactor: 1 });
|
|
75
|
+
await lib.seedStorage(ctx, json.storage, pr.storage);
|
|
75
76
|
page = await ctx.newPage();
|
|
76
77
|
errors = [];
|
|
77
78
|
page.on('pageerror', (e) => errors.push(String(e.message).split('\n')[0]));
|
|
@@ -97,6 +97,7 @@ async function main() {
|
|
|
97
97
|
const st = json.states[name];
|
|
98
98
|
const [W, H] = lib.SIZES[w];
|
|
99
99
|
const ctx = await browser.newContext({ viewport: { width: W, height: H }, deviceScaleFactor: 1, colorScheme: mode });
|
|
100
|
+
await lib.seedStorage(ctx, json.storage, st.storage);
|
|
100
101
|
const page = await ctx.newPage();
|
|
101
102
|
const logs = lib.attachCollectors(page, collectorOpts);
|
|
102
103
|
const base = st.auth ? baseIn : baseOut;
|
|
@@ -121,6 +122,7 @@ async function main() {
|
|
|
121
122
|
const rm = json.reducedMotion || {};
|
|
122
123
|
for (const mode of lib.hasFlag(args, '--no-reduced-motion') ? [] : modes) {
|
|
123
124
|
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, colorScheme: mode, reducedMotion: 'reduce' });
|
|
125
|
+
await lib.seedStorage(ctx, json.storage, rm.storage);
|
|
124
126
|
const page = await ctx.newPage();
|
|
125
127
|
await page.goto((rm.auth === false ? baseOut : baseIn) + lib.withMode(rm.url || json.page, mode, json.modeQuery), { waitUntil: 'load' });
|
|
126
128
|
await page.waitForTimeout(lib.SETTLE_MS);
|
|
@@ -106,6 +106,7 @@ const FEEDS = {
|
|
|
106
106
|
'/public/cost-summary': 'cost-summary',
|
|
107
107
|
'/public/recent-shipped': 'recent-shipped',
|
|
108
108
|
'/public/leaderboard': 'leaderboard',
|
|
109
|
+
'/public/versions': 'versions', // the roadmap and goals pages' version dates (task 1005080)
|
|
109
110
|
'/projects/featured': 'featured',
|
|
110
111
|
};
|
|
111
112
|
// /founding/builders (task 1004424) answers a never-founded project's empty list by default,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"key": "ui-design",
|
|
3
3
|
"title": "UI design (the design contract, its adapters and sync, the /design playbook, the kit, the styles)",
|
|
4
|
-
"description": "The platform capability that lets any builder on any instance create and design good-looking, functional UIs in line with that instance's world (scoped with the owner 2026-08-28; task 1003324, ADR 0197). It ships METHOD and never a world: the neutral design contract of ADR 0081 with its tool adapters (Claude Design, Figma), their sync CLIs and the validator; the /design playbook for ui-discipline work; the look-before-you-ship kit (kit/) and the style library (styles/). The thirteen design-style skills that lived in skills/ are the opt-in design-styles module since task 1004470; /design reaches them when that module is on. The instance's world — its branding pack, config/design-tokens.*, config/design-sources.json and its DESIGN.md — stays in config/ and at the repo root as the host boundary this module reads and never owns (ADR 0062 §7). Cloud Bongos's own front door is this module's first customer, not its owner. default-on because every instance has a face. No routes, no migrations, no port:
|
|
4
|
+
"description": "The platform capability that lets any builder on any instance create and design good-looking, functional UIs in line with that instance's world (scoped with the owner 2026-08-28; task 1003324, ADR 0197). It ships METHOD and never a world: the neutral design contract of ADR 0081 with its tool adapters (Claude Design, Figma), their sync CLIs and the validator; the /design playbook for ui-discipline work; the look-before-you-ship kit (kit/) and the style library (styles/). The thirteen design-style skills that lived in skills/ are the opt-in design-styles module since task 1004470; /design reaches them when that module is on. The instance's world — its branding pack, config/design-tokens.*, config/design-sources.json and its DESIGN.md — stays in config/ and at the repo root as the host boundary this module reads and never owns (ADR 0062 §7). Cloud Bongos's own front door is this module's first customer, not its owner. default-on because every instance has a face. No routes, no migrations, no port. Since task 1005081 it also ships public/tokens.css, the shared token sheet (the scales: type, spacing, corners, shadows, motion, stacking) every surface loads at /modules/ui-design/public/tokens.css, so disabling it now also withdraws that sheet and the hall's scale aliases lose their values.",
|
|
5
5
|
"version": "1.0.0",
|
|
6
6
|
"coreVersion": "^1.19.0",
|
|
7
7
|
"category": { "parent": "pages" },
|