@bongos/core 1.24.26 → 1.24.27
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 +142 -92
- package/docs/adr/0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md +12 -0
- package/docs/copy-inventory.md +67 -48
- package/docs/copy-registry.json +279 -78
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +10 -4
- package/docs/page-readings.json +4335 -4314
- package/docs/recipes/ui-look-before-you-ship.md +1 -0
- package/modules/autonomy/fence.js +16 -1
- package/modules/government/protected-surfaces.json +2 -0
- package/modules/hall-ui/public/ai-studio.html +1 -1
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/city.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/core-move-notice.js +9 -6
- package/modules/hall-ui/public/deploy-handoff.js +203 -0
- package/modules/hall-ui/public/deploy.html +4 -2
- package/modules/hall-ui/public/deploy.js +7 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/market-watch-item.html +1 -1
- package/modules/hall-ui/public/market-watch-rules.html +1 -1
- package/modules/hall-ui/public/market-watch-settings.html +1 -1
- package/modules/hall-ui/public/market-watch.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/my-agents.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings-software-update.js +22 -8
- package/modules/hall-ui/public/settings.html +7 -5
- package/modules/hall-ui/public/shell.js +25 -73
- package/modules/hall-ui/public/street.html +1 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +119 -39
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/update-pill.js +623 -0
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/hall-ui/records/update-pill.md +12 -0
- package/modules/lifecycle/github-push.js +1 -0
- package/modules/lifecycle/routes/tasks.js +2 -2
- package/modules/lifecycle/runner-self-guard.js +122 -0
- package/modules/lifecycle/runner-self.js +154 -0
- package/modules/public-landing/public/account.html +3 -3
- package/modules/public-landing/public/assets/cosmos.css +45 -0
- package/modules/public-landing/public/community.html +4 -4
- package/modules/public-landing/public/connections.html +1 -1
- package/modules/public-landing/public/guild.html +1 -1
- package/modules/public-landing/public/invitations.html +4 -4
- package/modules/public-landing/public/projects.html +26 -26
- package/modules/status-ui/public/index.html +2 -2
- package/modules/status-ui/public/index.states.json +12 -0
- package/modules/status-ui/public/status.js +10 -2
- package/modules/status-ui/public/style.css +57 -0
- package/modules/ui-design/kit/README.md +1 -1
- package/modules/ui-design/kit/serve.js +8 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +14 -0
- package/scripts/bongos/autobongos-loop.js +4 -0
- package/scripts/bongos/autobongos-run.js +21 -4
- package/scripts/bongos/autobongos-tweak-lane.js +5 -3
- package/scripts/bongos/ship-flow.js +20 -0
- package/src/module-api.js +1 -1
- package/tests/autobongos_host_access.mjs +5 -3
- package/tests/autobongos_self_modification.mjs +420 -0
- package/tests/core_update_banner.mjs +6 -92
- package/tests/core_upgrade_owner_door.mjs +4 -56
- package/tests/deploy_handoff.mjs +206 -0
- package/tests/government_protected_surfaces.mjs +15 -0
- package/tests/hub_connections_page.mjs +1 -1
- package/tests/hub_invitations_page.mjs +1 -1
- package/tests/loading_mark.mjs +203 -0
- package/tests/nav_permission_atoms.mjs +3 -2
- package/tests/software_update.mjs +6 -2
- package/tests/update_pill.mjs +431 -0
|
@@ -1347,6 +1347,7 @@ async function getPullRequestFiles({ number }, deps = {}) {
|
|
|
1347
1347
|
for (const f of batch) {
|
|
1348
1348
|
files.push({
|
|
1349
1349
|
path: f.filename,
|
|
1350
|
+
previous_path: f.previous_filename || null, // a rename's old path (task 1004546)
|
|
1350
1351
|
mode: '100644',
|
|
1351
1352
|
deleted: f.status === 'removed',
|
|
1352
1353
|
status: f.status,
|
|
@@ -37,7 +37,7 @@ const { gradeQualityWhere } = require('../db-shared');
|
|
|
37
37
|
const { helperJoinSql } = require('../page-tweak-artist'); // task 1004432: a page tweak's applier as a helper
|
|
38
38
|
const auth = api;
|
|
39
39
|
const db = require('../db');
|
|
40
|
-
const pageTweakReads = require('../page-tweak-reads'); const { HELD_NOT_ARMED, prMergeSentence } = require('../page-tweak-hold'); const autoPickupFiles = require('../autopickup-files'); const githubPrClose = require('../github-pr-close'); // ADR 0341 D7 (tests stub the read); ADR 0371 D8
|
|
40
|
+
const pageTweakReads = require('../page-tweak-reads'); const { HELD_NOT_ARMED, prMergeSentence } = require('../page-tweak-hold'); const autoPickupFiles = require('../autopickup-files'); const githubPrClose = require('../github-pr-close'); const runnerSelfGuard = require('../runner-self-guard'); const { sharedPrReads } = require('../ship-files'); // ADR 0341 D7 (tests stub the read); ADR 0371 D8; task 1004546
|
|
41
41
|
const hierarchy = require('../hierarchy');
|
|
42
42
|
const { suggestCriteria } = require('../criterion-suggest');
|
|
43
43
|
// The orphan-task advisory (task 1002539) — pure, fail-open; see goal-advisory.js.
|
|
@@ -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
|
|
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)
|
|
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 })
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/lifecycle/runner-self-guard.js — the server refuses to publish a
|
|
4
|
+
// runner's change to its own supervisor, safety checks or kill switch, or to any
|
|
5
|
+
// ADR 0043 protected file (task 1004546, ADR 0307 §6).
|
|
6
|
+
//
|
|
7
|
+
// WHY. The runner's own checks (the pre-claim fence and the ship-time diff check)
|
|
8
|
+
// run on the runner's machine, where the worker runs with blanket permissions and
|
|
9
|
+
// could edit or skip them. This one runs on the deployed server, against the pull
|
|
10
|
+
// request's files as GitHub reports them, so nothing on that machine can change it.
|
|
11
|
+
// A runner host holds no push credential (ADR 0055), so publish-branch is its only
|
|
12
|
+
// road to main, and that route asks this file once per publish, beside the
|
|
13
|
+
// auto-pickup files check and in the same shape (autopickup-files.js).
|
|
14
|
+
//
|
|
15
|
+
// TWO LISTS, OVER THE REAL DIFF. The runner's own files (runner-self.js, matched by
|
|
16
|
+
// that file's matcher, required directly) and the ADR 0043 registry (the fence
|
|
17
|
+
// already refuses a DECLARED protected touch; this refuses one the work actually
|
|
18
|
+
// made). The registry is what holds the route line calling this file: tasks.js is
|
|
19
|
+
// on it, so a runner cannot publish its removal.
|
|
20
|
+
//
|
|
21
|
+
// THE REGISTRY MATCHER COMES THROUGH THE DOORWAY. A module reaches core only
|
|
22
|
+
// through src/module-api.js, and task 1004540's grade found that a matcher
|
|
23
|
+
// resolved there could be swapped by a change to that file. So the doorway is on
|
|
24
|
+
// the runner's own list (owner ruling 2026-11-03), and a runner cannot publish a
|
|
25
|
+
// change to it. The matcher is also checked before it is trusted: it must flag two
|
|
26
|
+
// paths the registry has always protected, and one that does not is treated as
|
|
27
|
+
// unreadable, which refuses.
|
|
28
|
+
//
|
|
29
|
+
// WHICH CLAIM IS A RUNNER'S. Its worktree name (runner-self.js RUNNER_TREE_RE),
|
|
30
|
+
// read from the claim row. If that read FAILS, the branch name stands in (the
|
|
31
|
+
// runner's branch is its worktree name), so a database hiccup does not switch the
|
|
32
|
+
// wall off for a runner while an ordinary publish still never depends on the read.
|
|
33
|
+
// Telling a runner's session from its owner's on the server is task 1004547.
|
|
34
|
+
//
|
|
35
|
+
// FAILS CLOSED on the files: a read that throws, a list at GitHub's 300-file cap,
|
|
36
|
+
// or a matcher that fails its check all refuse. An unattended change that cannot
|
|
37
|
+
// be checked goes to a person.
|
|
38
|
+
//
|
|
39
|
+
// Its own small file, on the protected list and on the runner's own list, because
|
|
40
|
+
// the grade of task 1004540 refused the same check written into the shared route
|
|
41
|
+
// helpers and claim reads, which neither list covers.
|
|
42
|
+
|
|
43
|
+
const api = require('../../src/module-api');
|
|
44
|
+
const githubPush = require('./github-push');
|
|
45
|
+
const { PR_FILES_CAP } = require('./ship-files');
|
|
46
|
+
const runnerSelf = require('./runner-self');
|
|
47
|
+
|
|
48
|
+
// The task's latest claim, released or not: a publish runs after the ship has
|
|
49
|
+
// resolved it. Both names are read because the server may store a disambiguated
|
|
50
|
+
// worktree_name beside the one the runner asked for.
|
|
51
|
+
const CLAIM_SQL = `
|
|
52
|
+
SELECT c.worktree_name, c.requested_worktree_name
|
|
53
|
+
FROM claims c WHERE c.task_id = $1
|
|
54
|
+
ORDER BY c.claimed_at DESC, c.id DESC LIMIT 1`;
|
|
55
|
+
|
|
56
|
+
// What a failed claim read answers: not "no claim", which an ordinary publish
|
|
57
|
+
// with no claim row also answers.
|
|
58
|
+
const CLAIM_UNREADABLE = Object.freeze({ unreadable: true });
|
|
59
|
+
|
|
60
|
+
async function readLatestClaim(taskId, exec = api.pool) {
|
|
61
|
+
try {
|
|
62
|
+
const { rows } = await exec.query(CLAIM_SQL, [taskId]);
|
|
63
|
+
return rows[0] || null;
|
|
64
|
+
} catch (_) {
|
|
65
|
+
return CLAIM_UNREADABLE;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isRunnerPublish(claim, branch) {
|
|
70
|
+
if (claim === CLAIM_UNREADABLE) return runnerSelf.isRunnerTree(branch);
|
|
71
|
+
return !!claim && (runnerSelf.isRunnerTree(claim.worktree_name) || runnerSelf.isRunnerTree(claim.requested_worktree_name));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Paths the registry has protected since ADR 0043; a matcher that misses either
|
|
75
|
+
// is not the registry's.
|
|
76
|
+
const MATCHER_CANARIES = Object.freeze(['modules/lifecycle/routes/tasks.js', 'migrations/001_canary.sql']);
|
|
77
|
+
function verifiedMatcher(match) {
|
|
78
|
+
try {
|
|
79
|
+
if (MATCHER_CANARIES.every((f) => (match([f]) || []).length === 1)) return match;
|
|
80
|
+
} catch (_) { /* a matcher that throws is not trusted */ }
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const unreadable = () => ({ code: 'self_modification_unreadable', files: [], protected: [] });
|
|
85
|
+
|
|
86
|
+
// The route's one call. Returns null (publish as usual) or the refusal
|
|
87
|
+
// { code, files, protected }. `deps` is the test seam, and the route passes a
|
|
88
|
+
// shared getPullRequestFiles so this and the auto-pickup check read GitHub once.
|
|
89
|
+
async function runnerSelfEditRefusal({ taskId, prNumber, branch = null }, deps = {}) {
|
|
90
|
+
const claim = await (deps.readLatestClaim || readLatestClaim)(taskId);
|
|
91
|
+
if (!isRunnerPublish(claim, branch)) return null;
|
|
92
|
+
const match = verifiedMatcher(deps.matchProtected || ((files) => api.permissionPathCheck.matchProtected(files)));
|
|
93
|
+
if (!match) return unreadable();
|
|
94
|
+
let rows;
|
|
95
|
+
try {
|
|
96
|
+
rows = await (deps.getPullRequestFiles || ((a) => githubPush.getPullRequestFiles(a)))({ number: Number(prNumber) });
|
|
97
|
+
} catch (_) {
|
|
98
|
+
return unreadable();
|
|
99
|
+
}
|
|
100
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
101
|
+
if (list.length >= PR_FILES_CAP) return unreadable();
|
|
102
|
+
// A rename is checked at both ends: moving a guard away is an edit to it.
|
|
103
|
+
const changed = [];
|
|
104
|
+
for (const f of list) {
|
|
105
|
+
if (f && typeof f.path === 'string' && f.path) changed.push(f.path);
|
|
106
|
+
if (f && typeof f.previous_path === 'string' && f.previous_path) changed.push(f.previous_path);
|
|
107
|
+
}
|
|
108
|
+
return runnerSelf.diffRefusal({ files: changed, protectedHits: match(changed) });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function refusalMessage(r) {
|
|
112
|
+
const why = r.code === 'self_modification_unreadable'
|
|
113
|
+
? "An Autobongos runner's pull request could not be checked in full for changes to the runner's own or protected files. This task goes to a person."
|
|
114
|
+
: runnerSelf.selfEditMessage(r);
|
|
115
|
+
return `${why} The pull request was closed (task 1004546).`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
readLatestClaim,
|
|
120
|
+
runnerSelfEditRefusal,
|
|
121
|
+
refusalMessage,
|
|
122
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/lifecycle/runner-self.js — the files an Autobongos runner must never
|
|
4
|
+
// change: its own supervisor, its safety checks and its kill switch (task 1004546,
|
|
5
|
+
// carrying task 1004540's work; ADR 0307 §6).
|
|
6
|
+
//
|
|
7
|
+
// WHY. Owner rule, 2026-10-02: the runner never edits its own supervisor, safety
|
|
8
|
+
// checks or kill switch. The fence only refused tasks that hit the ADR 0043
|
|
9
|
+
// registry, and none of the runner's own files were on it, so an unattended worker
|
|
10
|
+
// could be handed a bug whose fix loosened the fence, the gauge, the grade cap or
|
|
11
|
+
// the kill switch. This is the ONE list of those files and the ONE spelling of what
|
|
12
|
+
// a runner's claim looks like. Three places ask it:
|
|
13
|
+
//
|
|
14
|
+
// - the runner, BEFORE the claim, over the task's declared touches[]
|
|
15
|
+
// (scripts/bongos/autobongos-run.js pickTask, then modules/autonomy/fence.js
|
|
16
|
+
// decideTask as the gate of record);
|
|
17
|
+
// - ship.js, inside the worker, over the branch's real diff, so a worker is
|
|
18
|
+
// stopped before it spends a grade (selfEditRefusal);
|
|
19
|
+
// - the server, at publish, over the pull request's own files as GitHub reports
|
|
20
|
+
// them (runner-self-guard.js). This is the wall: the worker can unset its own
|
|
21
|
+
// environment, it cannot change what the deployed server checks.
|
|
22
|
+
//
|
|
23
|
+
// The last two also refuse an ADR 0043 protected file in the real diff (see
|
|
24
|
+
// diffRefusal), which is what keeps a runner from deleting the server guard's call.
|
|
25
|
+
//
|
|
26
|
+
// A RUNNER-ONLY LIST, NOT THE ADR 0043 REGISTRY. A person may still edit these
|
|
27
|
+
// files at their own rank; only an unattended worker may not. Widening the registry
|
|
28
|
+
// would have raised the floor for every human builder to stop one machine.
|
|
29
|
+
//
|
|
30
|
+
// THE LIST COVERS ITS OWN ENFORCEMENT. Every file that implements a check above is
|
|
31
|
+
// on it: this file, the server guard, the fence, the supervisor, the ship pipeline
|
|
32
|
+
// that runs the ship check, the tests that pin them. The route line that calls the
|
|
33
|
+
// server guard sits in modules/lifecycle/routes/tasks.js, which is deliberately NOT
|
|
34
|
+
// listed (the owner ruled that listing tasks.js whole would refuse a large share of
|
|
35
|
+
// ordinary work). It is on the ADR 0043 registry, which the real-diff checks also
|
|
36
|
+
// read, and tests/autobongos_self_modification.mjs drives the real publish route,
|
|
37
|
+
// so removing that line is refused for a runner and turns CI red for anyone.
|
|
38
|
+
//
|
|
39
|
+
// Requires nothing, so the runner, ship.js and the server all read the same list
|
|
40
|
+
// without the module doorway in between (the grade of task 1004540 found that a
|
|
41
|
+
// matcher resolved through src/module-api.js could be swapped there). The doorway
|
|
42
|
+
// itself is on the list too, for what the server guard still reads through it.
|
|
43
|
+
|
|
44
|
+
// One pattern per line: an exact path, a directory ending in '/', or a prefix
|
|
45
|
+
// ending in '*' that stays inside one directory.
|
|
46
|
+
const RUNNER_SELF_SURFACES = Object.freeze([
|
|
47
|
+
// The supervisor and its CLIs (scripts/gds/ holds their forwarders).
|
|
48
|
+
{ pattern: 'scripts/bongos/autobongos-*', why: 'the supervisor, its loop, verify, grade cap, tweak lane and Windows service' },
|
|
49
|
+
{ pattern: 'scripts/gds/autobongos-*', why: 'forwarders to the supervisor' },
|
|
50
|
+
{ pattern: 'scripts/bongos/autonomy-*', why: 'the usage gauge and the autonomy gate' },
|
|
51
|
+
{ pattern: 'scripts/gds/autonomy-*', why: 'forwarders to the gauge and the gate' },
|
|
52
|
+
{ pattern: 'scripts/bongos/sequence.js', why: 'the picker and its halt matrix' },
|
|
53
|
+
// Its safety checks and kill switch.
|
|
54
|
+
{ pattern: 'modules/autonomy/', why: 'the fence, gauge, cadence, heartbeat and the kill-switch routes' },
|
|
55
|
+
{ pattern: 'modules/lifecycle/runner-self.js', why: 'this list' },
|
|
56
|
+
{ pattern: 'modules/lifecycle/runner-self-guard.js', why: 'the server refusal at publish' },
|
|
57
|
+
{ pattern: 'modules/lifecycle/autopickup.js', why: "the server's auto-pickup limits" },
|
|
58
|
+
{ pattern: 'modules/lifecycle/autopickup-files.js', why: 'the auto-pickup page-files wall' },
|
|
59
|
+
{ pattern: 'modules/government/catalog.js', why: 'the kill switch is a system permission declared here' },
|
|
60
|
+
{ pattern: 'modules/government/protected-surfaces.json', why: 'the ADR 0043 registry the fence also reads' },
|
|
61
|
+
{ pattern: 'src/bongos/permission-path-check.js', why: "the registry's matcher" },
|
|
62
|
+
// Owner ruling 2026-11-03, reversing the earlier "not the doorway": the server
|
|
63
|
+
// guard reaches the registry matcher and the claims pool through it, so a runner
|
|
64
|
+
// that could rewrite it could swap both. About 43 tasks in 90 days touched it.
|
|
65
|
+
{ pattern: 'src/module-api.js', why: 'the module doorway the server guard reaches the registry matcher and the pool through' },
|
|
66
|
+
// The only review between a worker and main, and the road to it.
|
|
67
|
+
{ pattern: 'modules/grading/', why: 'the grader' },
|
|
68
|
+
{ pattern: 'scripts/bongos/ship.js', why: 'the ship driver' },
|
|
69
|
+
{ pattern: 'scripts/bongos/ship-*', why: 'the ship phases, including the ship-time self-edit check' },
|
|
70
|
+
{ pattern: 'modules/lifecycle/github-push.js', why: 'the server-mediated publish and its pull-request files read' },
|
|
71
|
+
{ pattern: 'modules/lifecycle/ship-files.js', why: 'the files-read cap the server guard fails closed on' },
|
|
72
|
+
// The tests that pin all of the above.
|
|
73
|
+
{ pattern: 'tests/autobongos_*', why: 'the tests that pin the runner' },
|
|
74
|
+
{ pattern: 'tests/autonomy_*', why: 'the tests that pin the autonomy module' },
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
// A repo path as git spells it. Windows separators and a leading './' are folded,
|
|
78
|
+
// and case is ignored, so a declared touch cannot slip past on its spelling.
|
|
79
|
+
function normalisePath(p) {
|
|
80
|
+
return String(p || '').trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/^\/+/, '').toLowerCase();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function matches(file, pattern) {
|
|
84
|
+
const pat = pattern.toLowerCase();
|
|
85
|
+
if (pat.endsWith('/')) return file.startsWith(pat);
|
|
86
|
+
if (pat.endsWith('*')) {
|
|
87
|
+
const prefix = pat.slice(0, -1);
|
|
88
|
+
return file.startsWith(prefix) && !file.slice(prefix.length).includes('/');
|
|
89
|
+
}
|
|
90
|
+
return file === pat;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// The runner's own files among `files`: [{ file, pattern }], in input order.
|
|
94
|
+
function matchRunnerSelf(files) {
|
|
95
|
+
const out = [];
|
|
96
|
+
for (const raw of Array.isArray(files) ? files : []) {
|
|
97
|
+
const file = normalisePath(raw);
|
|
98
|
+
if (!file) continue;
|
|
99
|
+
const hit = RUNNER_SELF_SURFACES.find((s) => matches(file, s.pattern));
|
|
100
|
+
if (hit) out.push({ file: String(raw), pattern: hit.pattern });
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// What a runner's claim looks like: the worktree name autobongos-run.js's
|
|
106
|
+
// worktreeName() generates. The supervisor, its boot reconciliation and the server
|
|
107
|
+
// guard all read this one regex.
|
|
108
|
+
const RUNNER_TREE_RE = /^autobongos-\d+-[0-9a-f]{6}$/;
|
|
109
|
+
const isRunnerTree = (name) => RUNNER_TREE_RE.test(String(name || ''));
|
|
110
|
+
|
|
111
|
+
// Set by the supervisor on every worker it spawns, so ship.js knows it is running
|
|
112
|
+
// unattended. A worker can unset it; the server guard does not read it.
|
|
113
|
+
const RUNNER_WORKER_ENV = 'AUTOBONGOS_WORKER';
|
|
114
|
+
|
|
115
|
+
// The verdict over a runner's REAL diff, shared by ship.js and the server: null, or
|
|
116
|
+
// { code, files, protected }. `files` are the runner's own; `protected` are ADR
|
|
117
|
+
// 0043 registry hits, which ADR 0307 §6 says never reach an unattended worker. The
|
|
118
|
+
// pre-claim fence reads those from the declared touches; this reads them from what
|
|
119
|
+
// the work actually changed. That is also what holds the one line in
|
|
120
|
+
// modules/lifecycle/routes/tasks.js that calls the server guard: that file is on
|
|
121
|
+
// the registry, so a runner cannot publish a change to it.
|
|
122
|
+
function diffRefusal({ files, protectedHits = [] } = {}) {
|
|
123
|
+
const own = [...new Set(matchRunnerSelf(files).map((h) => h.file))];
|
|
124
|
+
const prot = [...new Set((protectedHits || []).map((h) => (h && h.file) || h).filter((f) => typeof f === 'string' && f))];
|
|
125
|
+
if (!own.length && !prot.length) return null;
|
|
126
|
+
return { code: own.length ? 'self_modification' : 'runner_protected_path', files: own, protected: prot };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// The ship-time check: null outside a runner's worker, else diffRefusal.
|
|
130
|
+
// `matchProtected` is the ADR 0043 matcher, passed in because this file requires
|
|
131
|
+
// nothing; it is only called inside a worker.
|
|
132
|
+
function selfEditRefusal({ files, env = process.env, matchProtected = null } = {}) {
|
|
133
|
+
if (!env || env[RUNNER_WORKER_ENV] !== '1') return null;
|
|
134
|
+
return diffRefusal({ files, protectedHits: matchProtected ? matchProtected(files) : [] });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function selfEditMessage(refusal) {
|
|
138
|
+
const r = refusal || {};
|
|
139
|
+
const parts = [];
|
|
140
|
+
if (r.files && r.files.length) parts.push(`its own supervisor, safety checks or kill switch (${r.files.slice(0, 10).join(', ')})`);
|
|
141
|
+
if (r.protected && r.protected.length) parts.push(`a protected permission, migration, infrastructure or pipeline file (${r.protected.slice(0, 10).join(', ')})`);
|
|
142
|
+
return `An Autobongos runner may not change ${parts.join(', or ') || 'these files'}. This task goes to a person.`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = {
|
|
146
|
+
RUNNER_SELF_SURFACES,
|
|
147
|
+
matchRunnerSelf,
|
|
148
|
+
RUNNER_TREE_RE,
|
|
149
|
+
isRunnerTree,
|
|
150
|
+
RUNNER_WORKER_ENV,
|
|
151
|
+
diffRefusal,
|
|
152
|
+
selfEditRefusal,
|
|
153
|
+
selfEditMessage,
|
|
154
|
+
};
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
</a>
|
|
165
165
|
</section>
|
|
166
166
|
|
|
167
|
-
<p class="loading" id="loading">Reading your account…</p>
|
|
167
|
+
<p class="loading loading-mark loading-mark--page" id="loading" role="status">Reading your account…</p>
|
|
168
168
|
<div class="alert" id="pageAlert" role="alert"></div>
|
|
169
169
|
|
|
170
170
|
<!-- signed in: ships hidden, revealed once GET /me/cross-project answers -->
|
|
@@ -223,7 +223,7 @@
|
|
|
223
223
|
<h2 id="aWaysIn-h">ways to sign in</h2>
|
|
224
224
|
<p class="aSide">GitHub, Google or both. Each one opens this same account, with the same credits, ranks and history.</p>
|
|
225
225
|
<div class="aBody">
|
|
226
|
-
<p class="aWhat" id="waysLoading">Reading your ways to sign in…</p>
|
|
226
|
+
<p class="aWhat loading-mark loading-mark--inline" id="waysLoading" role="status">Reading your ways to sign in…</p>
|
|
227
227
|
<ul class="mineRows" id="waysList"></ul>
|
|
228
228
|
<p class="finePrint">Linking never joins two accounts. If the other one already has its own account here, we say so and change nothing.</p>
|
|
229
229
|
<p class="projMsg" id="waysMsg" role="status" aria-live="polite"></p>
|
|
@@ -296,7 +296,7 @@
|
|
|
296
296
|
<h2 id="aBuilding-h">projects you build on</h2>
|
|
297
297
|
<p class="aSide">The projects you have joined as a builder. Only you see this list.</p>
|
|
298
298
|
<div class="aBody">
|
|
299
|
-
<p class="aWhat" id="buildingLoading">Reading the projects you build on…</p>
|
|
299
|
+
<p class="aWhat loading-mark loading-mark--inline" id="buildingLoading" role="status">Reading the projects you build on…</p>
|
|
300
300
|
<p class="aWhat" id="buildingEmpty" hidden>You’re not building on any other projects yet.</p>
|
|
301
301
|
<ul class="mineRows" id="buildingList"></ul>
|
|
302
302
|
<p class="projMsg" id="buildingMsg" role="status" aria-live="polite"></p>
|
|
@@ -545,6 +545,51 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
|
|
|
545
545
|
.alert summary{cursor:pointer;font-size:12.5px;font-weight:700;}
|
|
546
546
|
.alert details p{margin-top:6px;font-family:var(--font-mono);font-size:12px;font-weight:500;overflow-wrap:anywhere;}
|
|
547
547
|
.loading{font-size:14.5px;font-weight:600;color:var(--ink-faint);padding:14px 0;}
|
|
548
|
+
/* ── the loader (task 1005053; the hall's, task 1005043) ──────────────────────
|
|
549
|
+
The Bongos mark wherever an apex page waits for its data: the bar's own mark
|
|
550
|
+
(the ring, the core and the accent dot) as two masks on the placeholder the
|
|
551
|
+
page already had, whose words stay as its caption. The ring and the dot turn
|
|
552
|
+
together round the still core, easing out of and back into the logo's pose
|
|
553
|
+
each beat, and the ink breathes, after the Vibe skeleton the owner pointed at
|
|
554
|
+
(vibe.monday.com: 0.4 to 1 and back, 0.8s). Three sizes: the default for a
|
|
555
|
+
section, --page for a page with nothing else to show yet, --inline for a line
|
|
556
|
+
in a band or a list.
|
|
557
|
+
|
|
558
|
+
DESIGN.md holds it: CSS only, transform and filter only (the breath and the
|
|
559
|
+
arrival are filter: opacity(), not opacity), every colour a var() or a mix of
|
|
560
|
+
one; the black in the masks is alpha, not paint. The 20s floor is for the
|
|
561
|
+
ambient plates: a loader is the live thing on the page, so it keeps a loader's
|
|
562
|
+
tempo and its dot spends the accent. It arrives after 240ms, so a read that
|
|
563
|
+
answers faster never shows it; under the Kill Switch it is the still logo.
|
|
564
|
+
It is a grid, not a positioned dot: the ring and the dot share one cell, so
|
|
565
|
+
the dot stays on the ring whatever padding a page gives its placeholder. */
|
|
566
|
+
.loading-mark{
|
|
567
|
+
--lm-size:40px;
|
|
568
|
+
display:grid;justify-items:center;align-items:center;gap:12px;
|
|
569
|
+
padding:28px 0;text-align:center;color:var(--ink-faint);
|
|
570
|
+
animation:loading-mark-arrive 500ms cubic-bezier(.16,1,.3,1) 240ms both;
|
|
571
|
+
}
|
|
572
|
+
.loading-mark::before,.loading-mark::after{
|
|
573
|
+
content:"";grid-area:1/1;width:var(--lm-size);height:var(--lm-size);
|
|
574
|
+
-webkit-mask:var(--lm-shape) center/contain no-repeat;mask:var(--lm-shape) center/contain no-repeat;
|
|
575
|
+
animation:loading-mark-turn 1.6s cubic-bezier(.45,.05,.55,.95) infinite;
|
|
576
|
+
}
|
|
577
|
+
/* the ring and the core: one ink, breathing */
|
|
578
|
+
.loading-mark::before{
|
|
579
|
+
--lm-shape:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cpath d='M137 31 A78 78 0 1 0 170 66' fill='none' stroke='black' stroke-width='15' stroke-linecap='round'/%3E%3Ccircle cx='100' cy='100' r='55'/%3E%3C/svg%3E");
|
|
580
|
+
background:color-mix(in srgb,var(--ink) 30%,var(--bg-card));
|
|
581
|
+
animation:loading-mark-turn 1.6s cubic-bezier(.45,.05,.55,.95) infinite,loading-mark-breathe .8s ease-in-out infinite alternate-reverse;
|
|
582
|
+
}
|
|
583
|
+
/* the dot: the accent, laid exactly over the mark and turning with it */
|
|
584
|
+
.loading-mark::after{
|
|
585
|
+
--lm-shape:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Ccircle cx='156' cy='46' r='12'/%3E%3C/svg%3E");
|
|
586
|
+
background:var(--accent);
|
|
587
|
+
}
|
|
588
|
+
.loading-mark--page{--lm-size:64px;padding:72px 0;gap:16px;}
|
|
589
|
+
.loading-mark--inline{--lm-size:16px;grid-template-columns:var(--lm-size) minmax(0,1fr);justify-items:start;gap:8px;padding:0;text-align:left;}
|
|
590
|
+
@keyframes loading-mark-arrive{from{filter:opacity(0);transform:translateY(6px);}}
|
|
591
|
+
@keyframes loading-mark-turn{to{transform:rotate(1turn);}}
|
|
592
|
+
@keyframes loading-mark-breathe{from{filter:opacity(.45);}}
|
|
548
593
|
.empty{border:2px dashed var(--line);border-radius:18px;padding:22px;text-align:center;}
|
|
549
594
|
.empty p{font-size:14.5px;font-weight:600;color:var(--ink-soft);margin-bottom:14px;line-height:1.55;}
|
|
550
595
|
.foot{position:relative;z-index:1;text-align:center;font-size:13px;font-weight:600;color:var(--foot-ink);padding:0 20px 34px;text-shadow:var(--chrome-glow);}
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
same for every viewer but "Builders you may know", shown signed in like its menu item. -->
|
|
231
231
|
<section id="home" data-view="home" aria-labelledby="homeH">
|
|
232
232
|
<h2 id="homeH">Top builders</h2>
|
|
233
|
-
<p class="commMsg" id="homeLoading" role="status">Reading the top builders…</p>
|
|
233
|
+
<p class="commMsg loading-mark" id="homeLoading" role="status">Reading the top builders…</p>
|
|
234
234
|
<p class="projMsg err" id="homeFailed" role="alert" hidden></p>
|
|
235
235
|
<ul class="homeCrowns" id="homeCrowns" hidden>
|
|
236
236
|
<li class="homeCard"><h3 class="commSub">Builder of the week</h3><p class="crownWhen" id="crownWeekWhen"></p><div class="homeBody" id="crownWeek"></div></li>
|
|
@@ -282,7 +282,7 @@
|
|
|
282
282
|
</div>
|
|
283
283
|
<p class="commNote" id="boardNote">{{Currency}} earned across every project, since each builder started.</p>
|
|
284
284
|
<div class="commFor" id="boardFor" hidden></div>
|
|
285
|
-
<p class="commMsg" id="boardLoading" role="status">Reading the leaderboard…</p>
|
|
285
|
+
<p class="commMsg loading-mark" id="boardLoading" role="status">Reading the leaderboard…</p>
|
|
286
286
|
<p class="projMsg err" id="boardFailed" role="alert" hidden></p>
|
|
287
287
|
<p class="commMsg" id="boardEmpty" hidden></p>
|
|
288
288
|
<ol class="commRows" id="boardList" data-unit="{{currency}}" aria-labelledby="boardH"></ol>
|
|
@@ -295,7 +295,7 @@
|
|
|
295
295
|
every viewer, and states the rule in full (the spec: the formula is public). -->
|
|
296
296
|
<section id="week" data-view="week" aria-labelledby="weekH" hidden>
|
|
297
297
|
<h2 id="weekH">Builder of the week</h2>
|
|
298
|
-
<p class="commMsg" id="weekLoading" role="status">Reading the builder of the week…</p>
|
|
298
|
+
<p class="commMsg loading-mark" id="weekLoading" role="status">Reading the builder of the week…</p>
|
|
299
299
|
<p class="projMsg err" id="weekFailed" role="alert" hidden></p>
|
|
300
300
|
<div class="commCrown" id="weekCrown" hidden></div>
|
|
301
301
|
<p class="commMsg" id="weekNone" hidden></p>
|
|
@@ -339,7 +339,7 @@
|
|
|
339
339
|
<section id="recruit" data-view="recruiters" aria-labelledby="recruitH" hidden>
|
|
340
340
|
<h2 id="recruitH">Open to recruiters</h2>
|
|
341
341
|
<p class="lede">Builders who keep their numbers hidden but are open to hearing from projects. Only project owners see who they are, and only by name.</p>
|
|
342
|
-
<p class="commMsg" id="recruitLoading" role="status">Checking whether you can see this list…</p>
|
|
342
|
+
<p class="commMsg loading-mark" id="recruitLoading" role="status">Checking whether you can see this list…</p>
|
|
343
343
|
<div class="commDoor" id="recruitDoor" hidden>
|
|
344
344
|
<p>Sign in to see whether this list is open to you.</p>
|
|
345
345
|
<a class="btn" id="recruitSignin" href="/api/bongos/auth/web/start?return=/community/recruiters&repos=1">
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
</a>
|
|
138
138
|
</section>
|
|
139
139
|
|
|
140
|
-
<p class="loading" id="loading">Reading your connections…</p>
|
|
140
|
+
<p class="loading loading-mark loading-mark--page" id="loading" role="status">Reading your connections…</p>
|
|
141
141
|
<!-- a 403 from the connections routes is about the VIEWER: their platform
|
|
142
142
|
profile is not active yet (ADR 0202 D4 §1), so they cannot take part -->
|
|
143
143
|
<p class="mineEmpty" id="inactive" hidden>Connections open once your profile is active. It turns on the first time you sign in to a project with your {{productName}} account, in your browser.</p>
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
</div>
|
|
179
179
|
|
|
180
180
|
<main class="gld" id="guild">
|
|
181
|
-
<p class="loading" id="loading">Reading this guild…</p>
|
|
181
|
+
<p class="loading loading-mark loading-mark--page" id="loading" role="status">Reading this guild…</p>
|
|
182
182
|
<div class="alert" id="pageAlert" role="alert"></div>
|
|
183
183
|
|
|
184
184
|
<!-- NO GUILD HERE: an unknown slug, a malformed one, and an unlisted guild seen
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
</a>
|
|
148
148
|
</section>
|
|
149
149
|
|
|
150
|
-
<p class="loading" id="loading">Reading your invitations…</p>
|
|
150
|
+
<p class="loading loading-mark loading-mark--page" id="loading" role="status">Reading your invitations…</p>
|
|
151
151
|
|
|
152
152
|
<!-- ══ INVITATIONS · JOIN BY ADDRESS (task 1003774, moved here by task 1004276) ══
|
|
153
153
|
No Accept button on a project's invite — ADR 0335 retired that hub-only flip
|
|
@@ -798,9 +798,9 @@
|
|
|
798
798
|
return !s || p.name.toLowerCase().indexOf(s) !== -1 || p.host.indexOf(s) !== -1;
|
|
799
799
|
});
|
|
800
800
|
}
|
|
801
|
-
function pickNote(text) {
|
|
801
|
+
function pickNote(text, waiting) {
|
|
802
802
|
var li = document.createElement('li');
|
|
803
|
-
li.className = 'joinList__note';
|
|
803
|
+
li.className = 'joinList__note' + (waiting ? ' loading-mark loading-mark--inline' : '');
|
|
804
804
|
li.setAttribute('role', 'presentation');
|
|
805
805
|
li.textContent = text;
|
|
806
806
|
return li;
|
|
@@ -838,7 +838,7 @@
|
|
|
838
838
|
picker.active = -1;
|
|
839
839
|
input.removeAttribute('aria-activedescendant');
|
|
840
840
|
if (!picker.projects) {
|
|
841
|
-
list.appendChild(pickNote('Loading projects…'));
|
|
841
|
+
list.appendChild(pickNote('Loading projects…', true));
|
|
842
842
|
} else if (!picker.projects.length) {
|
|
843
843
|
closePicker();
|
|
844
844
|
return;
|