@bongos/core 1.19.1035 → 1.19.1037
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 +102 -92
- package/.claude/skills/grade-recover/SKILL.md +1 -1
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/adr/0302-the-third-identical-failure-stops-the-session-and-asks.md +3 -2
- package/docs/adr/0329-the-rendered-fit-check-is-a-kit-floor-and-an-advisory-ship-check-row.md +2 -2
- package/docs/adr/0339-a-projects-owner-upgrades-it-and-a-hosted-project-hands-off-to-the-hub-door.md +84 -0
- package/docs/adr/README.md +2 -1
- package/docs/api/openapi.json +7 -5
- package/docs/copy-inventory.md +10 -10
- package/docs/copy-registry.json +13 -13
- package/docs/module-api-changelog.md +4 -0
- package/modules/economy/credits.js +1 -1
- package/modules/grading/grader-config.js +3 -12
- package/modules/grading/grader-no-artifact.js +227 -2
- package/modules/grading/grader-prompt.js +1 -1
- package/modules/grading/grader-rubric.json +1 -86
- package/modules/grading/grader-score.js +1 -169
- package/modules/grading/grader-subagent.js +1 -51
- package/modules/grading/grader.js +26 -211
- 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/collab.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -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/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/modules.html +1 -1
- package/modules/hall-ui/public/people.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.html +1 -1
- package/modules/hall-ui/public/shell.js +36 -5
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/lifecycle/db-ship.js +3 -3
- package/modules/provisioning/core-upgrade-authz.js +85 -0
- package/modules/provisioning/core-upgrade.js +2 -1
- package/modules/provisioning/provisioning.js +4 -4
- package/modules/provisioning/routes/core-upgrade.js +12 -6
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +20 -0
- package/scripts/gds/claim.js +2 -1
- package/scripts/gds/grade-smoke.js +3 -3
- package/scripts/gds/provision-core-upgrade.js +12 -4
- package/scripts/gds/render-check.js +1 -1
- package/scripts/gds/ship-check.js +1 -1
- package/scripts/gds/ship-flow.js +1 -1
- package/scripts/gds/ship-grade-steps.js +2 -2
- package/scripts/gds/visual-review-nudge.js +2 -1
- package/src/bongos/auth.js +8 -2
- package/src/bongos/routes/core-update.js +25 -3
- package/src/module-api.js +1 -1
- package/tests/adr_0162_gate_citation.mjs +167 -0
- package/tests/core_upgrade_owner_door.mjs +444 -0
- package/tests/grader.mjs +48 -126
- package/tests/grader_unbacked_score.mjs +1 -1
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/provisioning_teardown_intent.mjs +28 -2
- package/tests/regrade_actual_minutes.mjs +18 -17
- package/tests/render_check.mjs +1 -1
- package/tests/scouting_page_gate.mjs +11 -3
- package/tests/skill_grade_audit.mjs +3 -3
- package/tests/skill_grader_health.mjs +2 -2
- package/scripts/gds/audit-ships.js +0 -319
- package/tests/audit_ships_git_argv.mjs +0 -173
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
68
68
|
|
|
69
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
69
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
70
70
|
<script src="/builders/palette.js" defer></script>
|
|
71
71
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
72
72
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
59
59
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
60
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
60
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
61
61
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
62
62
|
<script src="/builders/palette.js" defer></script>
|
|
63
63
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
|
|
163
163
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
164
164
|
chrome exists before the page script runs. -->
|
|
165
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
165
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
166
166
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
167
167
|
<script src="/builders/palette.js" defer></script>
|
|
168
168
|
<!-- shared frontend toolkit (window.OTB) — loaded before watch.js, which pulls from it. -->
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
|
|
172
172
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
173
173
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
174
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
174
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
175
175
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
176
176
|
<script src="/builders/palette.js" defer></script>
|
|
177
177
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
|
|
@@ -277,9 +277,9 @@ async function resolveClaim({ claimId, builderId, outcome, notesMd, valueSummary
|
|
|
277
277
|
// The ordering below is NOT for the grader, whatever this comment used to
|
|
278
278
|
// say. V3.R18 (#209) moved the grader out of resolveClaim — see the block
|
|
279
279
|
// after this one — and the live panel never receives actual_minutes at all
|
|
280
|
-
// (modules/grading/grader-prompt.js buildPrompt prints est_minutes only;
|
|
281
|
-
//
|
|
282
|
-
//
|
|
280
|
+
// (modules/grading/grader-prompt.js buildPrompt prints est_minutes only; the
|
|
281
|
+
// one scorer that read actual_minutes was retired with the v1 grader in task
|
|
282
|
+
// 1002842). The seal must
|
|
283
283
|
// still precede the recompute, because the recompute SUMs the row it writes.
|
|
284
284
|
// tests/regrade_actual_minutes.mjs pins the panel half of that.
|
|
285
285
|
await sealSessionLog(client, {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/provisioning/core-upgrade-authz.js — who may move ONE hosted project's core
|
|
4
|
+
// pin: its owner, or a holder of `core.pin.move` (task 1004174, ADR 0339).
|
|
5
|
+
//
|
|
6
|
+
// THE OWNER'S RULING, 2026-09-26: only a project's owner upgrades it — "owners and
|
|
7
|
+
// archons are the same" for the platform operator, because the operator owns the
|
|
8
|
+
// platform. Until this file the deploy door was `core.pin.move` alone: archon-floor
|
|
9
|
+
// and system:true, so the owner of a hosted project below Archon could see that they
|
|
10
|
+
// were behind and could never act on it. The door stays on the control plane (ADR
|
|
11
|
+
// 0293 D1): a hosted project hands its owner off to this hub's /deploy?project=<slug>
|
|
12
|
+
// and gains no capability of its own.
|
|
13
|
+
//
|
|
14
|
+
// THE SHAPE IS THE ONE THE MODULE ALREADY USES FOR "OWNER OR OPERATOR" (ADR 0328
|
|
15
|
+
// Decision 2, and platform-identity's requireScoutingReader, ADR 0210). Ownership is
|
|
16
|
+
// asked FIRST; on any "no" — not the owner, no such row, an unreadable id, a database
|
|
17
|
+
// blip — the request falls through to the UNMODIFIED permission gate. So a caller who
|
|
18
|
+
// is neither gets byte-for-byte the refusal the door has always given, whether or not
|
|
19
|
+
// the id exists, and the widening can only ever admit. The atom is still spelled in
|
|
20
|
+
// the route's middleware list, where route-rank-check.js and gen-api-docs.js read it.
|
|
21
|
+
//
|
|
22
|
+
// THE PLATFORM'S OWN ROW IS NOT A PROJECT AN OWNER MAY MOVE HERE. Moving the control
|
|
23
|
+
// plane changes the code every hosted project is driven by, so it stays on the atom:
|
|
24
|
+
// OWNER_SHAPES admits the two tenant shapes and nothing else, and a shape added later
|
|
25
|
+
// is excluded by omission.
|
|
26
|
+
|
|
27
|
+
// The tenant shapes the runner can actually reach (UPGRADE_SHAPES minus the platform).
|
|
28
|
+
const OWNER_SHAPES = Object.freeze(new Set(['co-tenant', 'cloud-host']));
|
|
29
|
+
|
|
30
|
+
// An id as the route names it: a positive integer and nothing else. Anything else is
|
|
31
|
+
// not looked up at all — it falls to the permission gate, and the handler's own
|
|
32
|
+
// parseId gives the 400 an admitted caller would get.
|
|
33
|
+
const ID_RE = /^[1-9][0-9]{0,15}$/;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Does this builder own this instance in a way that lets them move its core? PURE.
|
|
37
|
+
* Fail-closed on every missing piece: no row, no builder, no recorded owner, or a
|
|
38
|
+
* shape that is not a tenant.
|
|
39
|
+
*/
|
|
40
|
+
function ownsUpgradableProject(inst, builder) {
|
|
41
|
+
return !!inst && !!builder && builder.id != null
|
|
42
|
+
&& OWNER_SHAPES.has(inst.hosting_shape)
|
|
43
|
+
&& inst.owner_builder_id != null
|
|
44
|
+
&& String(inst.owner_builder_id) === String(builder.id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The OR gate, as one middleware mounted after requireBuilder. `pinGate` is the
|
|
49
|
+
* ALREADY-BUILT `requirePermission('core.pin.move')` middleware.
|
|
50
|
+
*
|
|
51
|
+
* It records WHICH door admitted the caller on `req.coreUpgradeAuthority`, because
|
|
52
|
+
* the intent's actor has to say so truthfully (coreUpgradeActor below).
|
|
53
|
+
*/
|
|
54
|
+
function requireOwnProjectOrPin({ pinGate, provisioning, pool, log }) {
|
|
55
|
+
return async function ownProjectOrPinGate(req, res, next) {
|
|
56
|
+
const raw = req.params ? req.params.id : undefined;
|
|
57
|
+
if (typeof raw === 'string' && ID_RE.test(raw)) {
|
|
58
|
+
try {
|
|
59
|
+
const inst = await provisioning.getInstanceById(pool, Number(raw));
|
|
60
|
+
if (ownsUpgradableProject(inst, req.builder)) {
|
|
61
|
+
req.coreUpgradeAuthority = 'owner';
|
|
62
|
+
return next();
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
// Message only: a query error can carry row data. The permission gate below
|
|
66
|
+
// then decides, so a blip here can deny an owner but never admit anyone.
|
|
67
|
+
if (log && log.error) log.error(`core-upgrade owner check failed, falling back to core.pin.move: ${err && err.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
req.coreUpgradeAuthority = 'pin';
|
|
71
|
+
return pinGate(req, res, next);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The actor recorded on the intent and the event. Both forms carry the builder id,
|
|
77
|
+
* because the runner hands it to the core_upgrades ledger as `--requested-by` and
|
|
78
|
+
* ADR 0293 D5 refuses an unattributed row for a move an HTTP request started.
|
|
79
|
+
*/
|
|
80
|
+
function coreUpgradeActor(req) {
|
|
81
|
+
const door = req.coreUpgradeAuthority === 'owner' ? 'owner' : 'archon';
|
|
82
|
+
return `api:${door}:${req.builder.id}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { OWNER_SHAPES, ownsUpgradableProject, requireOwnProjectOrPin, coreUpgradeActor };
|
|
@@ -177,7 +177,8 @@ function makeCoreUpgradeHandler({ provisioning, pool, parseId, validateOrRespond
|
|
|
177
177
|
// The actor rides the intent, and the runner carries it through to the
|
|
178
178
|
// core_upgrades ledger (ADR 0293 D5): a privileged action with an
|
|
179
179
|
// unattributed row is not acceptable here.
|
|
180
|
-
|
|
180
|
+
// `api:owner:<id>` when the project's owner pressed it (task 1004174), else `api:archon:<id>`.
|
|
181
|
+
const actor = require('./core-upgrade-authz').coreUpgradeActor(req);
|
|
181
182
|
// `dry_run: true` asks what the move WOULD do and moves nothing (task 1004121).
|
|
182
183
|
// Queued the same way as the move, through the same single-open-intent slot, so a
|
|
183
184
|
// preview cannot be requested on top of a move that is already running — and, just
|
|
@@ -1158,9 +1158,9 @@ async function enqueueIntent(db, instanceId, action, requestedBy, targetVersion
|
|
|
1158
1158
|
// delete hostage would repeat 1002691. A pending 'restart' or 'repo-private' (task 1004193) joins them for the same
|
|
1159
1159
|
// reason (task 1003238) — and more sharply, since the owner reaches for Restart
|
|
1160
1160
|
// precisely when a project looks broken, which is also when they give up and delete
|
|
1161
|
-
// it. A RUNNING intent is mid-flight and never yanked —
|
|
1162
|
-
//
|
|
1163
|
-
//
|
|
1161
|
+
// it. A RUNNING intent is mid-flight and never yanked — the caller gets { conflict: 'open_intent' } with the blocking intent so the route
|
|
1162
|
+
// can answer truthfully. The control-plane row is never superseded, in the same statement (task 1004286): enqueueIntent's guard refuses its
|
|
1163
|
+
// teardown, and a refused teardown must cancel nothing (it used to cancel a pending restart). Each statement is atomic, the index the arbiter:
|
|
1164
1164
|
// any interleaving with the runner or a concurrent enqueue lands on one of these
|
|
1165
1165
|
// truthful outcomes.
|
|
1166
1166
|
async function enqueueTeardown(db, instanceId, requestedBy) {
|
|
@@ -1169,7 +1169,7 @@ async function enqueueTeardown(db, instanceId, requestedBy) {
|
|
|
1169
1169
|
SET state = 'error', last_error = 'superseded: teardown requested',
|
|
1170
1170
|
updated_at = now(), resolved_at = now()
|
|
1171
1171
|
WHERE instance_id = $1 AND action IN ('provision', 'settings-apply', 'restart', 'repo-private') AND state = 'pending'
|
|
1172
|
-
|
|
1172
|
+
AND NOT EXISTS (SELECT 1 FROM provisioning_instances WHERE id = $1 AND hosting_shape = 'control-plane') RETURNING id`,
|
|
1173
1173
|
[instanceId]
|
|
1174
1174
|
);
|
|
1175
1175
|
const { intent, created } = await enqueueIntent(db, instanceId, 'teardown', requestedBy);
|
|
@@ -26,6 +26,7 @@ const express = require('express');
|
|
|
26
26
|
const api = require('../../../src/module-api');
|
|
27
27
|
const provisioning = require('../provisioning');
|
|
28
28
|
const { makeCoreUpgradeHandler, makeCoreUpgradePreviewHandler, makeControlPlaneHandler } = require('../core-upgrade');
|
|
29
|
+
const { requireOwnProjectOrPin } = require('../core-upgrade-authz');
|
|
29
30
|
|
|
30
31
|
const log = api.logger('provisioning');
|
|
31
32
|
|
|
@@ -40,6 +41,11 @@ module.exports = function buildCoreUpgradeRouter() {
|
|
|
40
41
|
// and not `requireRank`: tests/government_require_permission.mjs fails the build
|
|
41
42
|
// on a new requireRank in a modules/*/routes gate.
|
|
42
43
|
//
|
|
44
|
+
// OR THE PROJECT'S OWN OWNER (task 1004174, ADR 0339 — the owner's ruling that only
|
|
45
|
+
// a project's owner upgrades it). Ownership of a TENANT row is asked first; any other
|
|
46
|
+
// caller meets the unmodified `core.pin.move` gate and its unchanged refusal. The
|
|
47
|
+
// platform's own row is not an owner's to move here — it stays on the atom.
|
|
48
|
+
//
|
|
43
49
|
// Body: { to: "1.19.757" } — and requiring it IS the second step of the two-step
|
|
44
50
|
// the owner asked for (ADR 0293 D4). A request that cannot fire without naming
|
|
45
51
|
// its target cannot fire from a stray click, and "move to whatever is newest",
|
|
@@ -55,7 +61,7 @@ module.exports = function buildCoreUpgradeRouter() {
|
|
|
55
61
|
router.post(
|
|
56
62
|
'/provisioning/instances/:id/core-upgrade',
|
|
57
63
|
api.requireBuilder,
|
|
58
|
-
api.requirePermission('core.pin.move'),
|
|
64
|
+
requireOwnProjectOrPin({ pinGate: api.requirePermission('core.pin.move'), provisioning, pool: api.pool, log }),
|
|
59
65
|
makeCoreUpgradeHandler({
|
|
60
66
|
provisioning,
|
|
61
67
|
pool: api.pool,
|
|
@@ -71,19 +77,19 @@ module.exports = function buildCoreUpgradeRouter() {
|
|
|
71
77
|
// core version and how far behind it is, which is a vulnerability-window disclosure —
|
|
72
78
|
// precisely the sort of thing ADR 0043 keeps off unauthenticated surfaces. A preview
|
|
73
79
|
// anyone could read would also let an attacker time a request against a known-stale
|
|
74
|
-
// window. The read being no cheaper than the write costs the owner nothing:
|
|
75
|
-
// the
|
|
80
|
+
// window. The read being no cheaper than the write costs the owner nothing: whoever
|
|
81
|
+
// may press the button may read what it would do — the same two doors as the POST.
|
|
76
82
|
router.get(
|
|
77
83
|
'/provisioning/instances/:id/core-upgrade',
|
|
78
84
|
api.requireBuilder,
|
|
79
|
-
api.requirePermission('core.pin.move'),
|
|
85
|
+
requireOwnProjectOrPin({ pinGate: api.requirePermission('core.pin.move'), provisioning, pool: api.pool, log }),
|
|
80
86
|
makeCoreUpgradePreviewHandler({ provisioning, pool: api.pool, parseId: api.parseId, log }),
|
|
81
87
|
);
|
|
82
88
|
|
|
83
89
|
// GET /provisioning/control-plane — "which row is this platform, and where does it
|
|
84
90
|
// stand?" The deploy page's first call: the self row is created once by hand at
|
|
85
|
-
// install time, so its id is per-install and nobody's to memorise.
|
|
86
|
-
//
|
|
91
|
+
// install time, so its id is per-install and nobody's to memorise. The atom ALONE
|
|
92
|
+
// here, with no owner door: this is the platform's own row (ADR 0339).
|
|
87
93
|
router.get(
|
|
88
94
|
'/provisioning/control-plane',
|
|
89
95
|
api.requireBuilder,
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.1037",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.1037",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.1037",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -7487,5 +7487,25 @@
|
|
|
7487
7487
|
"id": "1002669",
|
|
7488
7488
|
"text": "The owner can now check from their own laptop what the automatic background jobs decided and when the project's core software was last upgraded, instead of logging into the server to find out."
|
|
7489
7489
|
}
|
|
7490
|
+
],
|
|
7491
|
+
"1.19.1036": [
|
|
7492
|
+
{
|
|
7493
|
+
"id": "1004286",
|
|
7494
|
+
"text": "Pressing delete on the platform's own server is still refused, and now it no longer quietly cancels a restart you had already asked for."
|
|
7495
|
+
},
|
|
7496
|
+
{
|
|
7497
|
+
"id": "1004174",
|
|
7498
|
+
"text": "The owner of a hosted project can now upgrade it themselves: the \"you're behind\" banner on their project links to the Deploy page on the hub, which lets a project's owner move their own project, and only their own."
|
|
7499
|
+
}
|
|
7500
|
+
],
|
|
7501
|
+
"1.19.1037": [
|
|
7502
|
+
{
|
|
7503
|
+
"id": "1002842",
|
|
7504
|
+
"text": "The grader's retired first generation and an unused one-off audit script are removed, and the pieces a newer reviewer still depends on now live with it — nothing about how work is graded changes."
|
|
7505
|
+
},
|
|
7506
|
+
{
|
|
7507
|
+
"id": "1004280",
|
|
7508
|
+
"text": "The project's own notes no longer claim a past decision banned all new checks; each one now gives its real reason, and a test stops the wrong claim coming back."
|
|
7509
|
+
}
|
|
7490
7510
|
]
|
|
7491
7511
|
}
|
package/scripts/gds/claim.js
CHANGED
|
@@ -739,7 +739,8 @@ async function main() {
|
|
|
739
739
|
// last thing the builder reads before deciding how to work — task 1267.
|
|
740
740
|
printDisciplineMode(task);
|
|
741
741
|
// task 1004202: a task that changes a page is told to use /design and get its
|
|
742
|
-
// human's OK on the before/after. A nudge, never a gate
|
|
742
|
+
// human's OK on the before/after. A nudge, never a gate: gating on that OK would
|
|
743
|
+
// put a person's approval in the land path, which ADR 0162 retired.
|
|
743
744
|
try {
|
|
744
745
|
const nudge = visualReviewNudge(task);
|
|
745
746
|
if (nudge) console.log(`\n${nudge.join('\n')}`);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Subagent grader smoke test — GDS-V3 task V3.R18 (#209) → MAS Phase 1 #395.
|
|
3
3
|
//
|
|
4
4
|
// Runs two synthetic ship scenarios end-to-end through the v3 multi-shot
|
|
5
|
-
// subagent grader (
|
|
5
|
+
// subagent grader (the worker panel, cross-family model, unanimous-PASS gate).
|
|
6
6
|
// Asserts:
|
|
7
7
|
//
|
|
8
8
|
// - Known-GOOD ship: a tiny, complete change → grade passes (unanimous
|
|
@@ -148,8 +148,8 @@ function summarize(result, label) {
|
|
|
148
148
|
// -------- Main --------
|
|
149
149
|
|
|
150
150
|
(async () => {
|
|
151
|
-
console.log(`Running subagent grader smoke (v${grader.VERSION}, model=${grader.DEFAULT_GRADER_MODEL}, ${grader.
|
|
152
|
-
console.log(
|
|
151
|
+
console.log(`Running subagent grader smoke (v${grader.VERSION}, model=${grader.DEFAULT_GRADER_MODEL}, ${grader.WORKERS.DEFAULT_PANEL.length}-worker panel, threshold=${grader.THRESHOLD})`);
|
|
152
|
+
console.log(`Two scenarios will run sequentially. Each runs the ${grader.WORKERS.DEFAULT_PANEL.length}-worker panel.`);
|
|
153
153
|
|
|
154
154
|
let assertFailures = 0;
|
|
155
155
|
|
|
@@ -140,6 +140,16 @@ function targetFault(to, available, installed, channel, channelAllows) {
|
|
|
140
140
|
return null;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
// The builder id a queued move is attributed to, from the actor the web tier recorded.
|
|
144
|
+
// PURE. Two doors reach the move (ADR 0339): an Archon (`api:archon:<id>`) and, since
|
|
145
|
+
// task 1004174, the project's own owner (`api:owner:<id>`). Both carry the id, because
|
|
146
|
+
// it becomes `--requested-by` on the core_upgrades ledger (ADR 0293 D5). Anything else
|
|
147
|
+
// yields '' — the id is interpolated into a shell command, so only digits ever pass.
|
|
148
|
+
function requestedByOf(intent) {
|
|
149
|
+
const m = intent && typeof intent.requested_by === 'string' && /^api:(?:archon|owner):(\d+)$/.exec(intent.requested_by);
|
|
150
|
+
return m ? m[1] : '';
|
|
151
|
+
}
|
|
152
|
+
|
|
143
153
|
/**
|
|
144
154
|
* Perform a queued core-pin move. Returns { ok } — NEVER throws for an expected refusal.
|
|
145
155
|
*
|
|
@@ -224,9 +234,7 @@ async function coreUpgradeInstance(inst, deps, intent) {
|
|
|
224
234
|
// it the pin lives only in the working tree, and the next pull-deploy resets it away —
|
|
225
235
|
// the silent thirteen-release revert upgrade.js's own header records.
|
|
226
236
|
const versionUrl = `http://127.0.0.1:${inst.port}/version`;
|
|
227
|
-
const requestedBy =
|
|
228
|
-
? intent.requested_by.replace(/^api:archon:/, '')
|
|
229
|
-
: '';
|
|
237
|
+
const requestedBy = requestedByOf(intent);
|
|
230
238
|
// Built by the SAME function the preview uses, so "what you were shown" and "what runs"
|
|
231
239
|
// cannot drift apart into two hand-maintained flag lists (task 1004121).
|
|
232
240
|
// The preview probes; the move must probe identically or the two commands diverge on
|
|
@@ -982,4 +990,4 @@ async function snapshotAllCoreVersions(deps) {
|
|
|
982
990
|
return { seen };
|
|
983
991
|
}
|
|
984
992
|
|
|
985
|
-
module.exports = { UPGRADE_SHAPES, CHANGE_CAP, REPO_NOTES, repoNote, parseChangeRecords, readInstalledNotes, readPackagedNotes, changesBetween, coreCheckoutCandidates, findCoreCheckout, gitFault, resolveRunAs, versionCmd, parseServedVersion, parseSchemaPending, targetFault, parsePreflight, upgradeInvocation, preflightUpgrade, preflightCoreUpgrade, coreUpgradeInstance, snapshotCoreVersions, snapshotAllCoreVersions };
|
|
993
|
+
module.exports = { UPGRADE_SHAPES, CHANGE_CAP, REPO_NOTES, repoNote, requestedByOf, parseChangeRecords, readInstalledNotes, readPackagedNotes, changesBetween, coreCheckoutCandidates, findCoreCheckout, gitFault, resolveRunAs, versionCmd, parseServedVersion, parseSchemaPending, targetFault, parsePreflight, upgradeInvocation, preflightUpgrade, preflightCoreUpgrade, coreUpgradeInstance, snapshotCoreVersions, snapshotAllCoreVersions };
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// count that still ran past its card (task 1003480) and a second grid child landing on the first (task 1003078)
|
|
8
8
|
// both shipped looking correct in code and were found only by opening a browser.
|
|
9
9
|
//
|
|
10
|
-
// A SIGNAL, never a gate (ADR
|
|
10
|
+
// A SIGNAL, never a gate (ADR 0329, decisions 3-4): /ship-check shows it as an advisory row; ship.js and the grader never
|
|
11
11
|
// read it. The measurement is the kit's `text-fit` + `sibling-overlap` floors (modules/ui-design/kit/lib.js);
|
|
12
12
|
// this file only picks the pages, runs the kit's renderer at 1280 + 390, and keeps those two floors.
|
|
13
13
|
//
|
|
@@ -25,7 +25,7 @@ const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
|
25
25
|
|
|
26
26
|
// Each step: a label, the argv to run (node scripts run with THIS node), the command that heals it,
|
|
27
27
|
// and how to read a non-zero exit. `optional` steps are skipped when their script is absent. An `advisory`
|
|
28
|
-
// step is a signal, never a gate (ADR
|
|
28
|
+
// step is a signal, never a gate (ADR 0329, decision 3): a non-zero exit reads `warn` (3 = no browser reads `skipped`).
|
|
29
29
|
// `onlyWith` names the flag that adds a step; `skipWith` the flag that leaves one out.
|
|
30
30
|
const STEPS = [
|
|
31
31
|
{ key: 'fitness', label: 'fitness.js (architecture + budgets + freshness gate)', argv: ['scripts/gds/fitness.js'], fix: 'read the ✗ lines above; each names its fix' },
|
package/scripts/gds/ship-flow.js
CHANGED
|
@@ -496,7 +496,7 @@ async function shipMain() {
|
|
|
496
496
|
passed,
|
|
497
497
|
// touches[] cleanup [4/8] (task 879): the drift scanner is gone; with no
|
|
498
498
|
// scanner there is nothing to drift, so this audit-trail signal
|
|
499
|
-
// (grader
|
|
499
|
+
// (read by the retired v1 grader, task 1002842) is vacuously clean.
|
|
500
500
|
scanner_clean: true,
|
|
501
501
|
smoke_clean: smoke.clean,
|
|
502
502
|
},
|
|
@@ -314,7 +314,7 @@ function formatOutageDiagnostic(g, cliFinding) {
|
|
|
314
314
|
|
|
315
315
|
// cliFinding: the preflight `claude CLI` row, injected by printGradeOutcome so a
|
|
316
316
|
// dark panel can say "your CLI is logged out" instead of leaving the builder to
|
|
317
|
-
// guess. Report-only — it never changes the outcome
|
|
317
|
+
// guess. Report-only — it never changes the outcome: it explains an outage, it does not decide one.
|
|
318
318
|
function formatGradeOutcome(g, taskId, cliFinding = null) {
|
|
319
319
|
const out = [];
|
|
320
320
|
// #943: a TOTAL grader outage (every gating worker failed to spawn / the
|
|
@@ -466,7 +466,7 @@ function printGradeOutcome(g, taskId) {
|
|
|
466
466
|
// task 1003313: on a TOTAL outage only, ask the existing preflight whether the
|
|
467
467
|
// local `claude` CLI is launchable and logged in — the two faults that produce
|
|
468
468
|
// this exact all-workers-dead signature. Report-only and outage-only, so the
|
|
469
|
-
// happy path pays nothing and no ship
|
|
469
|
+
// happy path pays nothing and no ship's outcome depends on the probe.
|
|
470
470
|
let cliFinding = null;
|
|
471
471
|
if (g?.signals?.panel_outcome === 'unavailable') {
|
|
472
472
|
try {
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// real browser, and get the human's OK on the before/after before shipping — the
|
|
6
6
|
// project default for every builder since the owner's 2026-09-23 decision.
|
|
7
7
|
//
|
|
8
|
-
// A NUDGE, never a gate
|
|
8
|
+
// A NUDGE, never a gate: as a gate, the human's OK would be a person's approval in
|
|
9
|
+
// the land path, which ADR 0162 retired. "Is this UI work" is the ONE predicate the
|
|
9
10
|
// artist review and the grader's copy lens already use (lifecycle/cascade.js), so
|
|
10
11
|
// the three cannot disagree. Nothing has changed yet at claim time, so it is fed
|
|
11
12
|
// the paths the task NAMES: its touches[] and any modules/*/public/ path written
|
package/src/bongos/auth.js
CHANGED
|
@@ -461,7 +461,13 @@ const PAGE_GATES = Object.freeze({
|
|
|
461
461
|
// preview discloses the exact core version the box serves and how far behind it is
|
|
462
462
|
// — a vulnerability window, and exactly the kind of topology ADR 0043 keeps off
|
|
463
463
|
// unauthenticated surfaces.
|
|
464
|
-
|
|
464
|
+
//
|
|
465
|
+
// SINCE ADR 0339 (task 1004174) its routes admit a SECOND audience — a hosted
|
|
466
|
+
// project's own owner, for that project only — so the shell follows through the same
|
|
467
|
+
// `alsoAdmits` door /people uses (ADR 0210). It admits the SHELL for an owner of any
|
|
468
|
+
// live project; which project they may read or move is still the routes' question,
|
|
469
|
+
// asked per request against that project's row.
|
|
470
|
+
requireCorePinPage: Object.freeze({ permission: 'core.pin.move', deniedTo: '/builders', alsoAdmits: 'isProjectOwner' }),
|
|
465
471
|
// The /fleet cost page (task 1001949) — on `provisioning.fleet.manage`, the SAME atom
|
|
466
472
|
// its one read (GET /provisioning/fleet/cost-ledger) checks: every owner's projects
|
|
467
473
|
// and what they cost, so the page reaches exactly as far as the data does.
|
|
@@ -469,7 +475,7 @@ const PAGE_GATES = Object.freeze({
|
|
|
469
475
|
});
|
|
470
476
|
|
|
471
477
|
// The `alsoAdmits` door — "is this visitor the page's OTHER audience?", asked of
|
|
472
|
-
// the module that owns the answer, only for
|
|
478
|
+
// the module that owns the answer, only for a gate that declares one.
|
|
473
479
|
//
|
|
474
480
|
// FAIL-CLOSED, EVERY WAY IT CAN FAIL: a gate with no `alsoAdmits`, a hub module
|
|
475
481
|
// that is off (every non-hub instance), a port that never grew the function, or a
|
|
@@ -22,6 +22,25 @@ const express = require('express');
|
|
|
22
22
|
const { asyncHandler } = require('./_helpers');
|
|
23
23
|
const auth = require('../auth');
|
|
24
24
|
const { readCoreUpdate, readCoreUpgrades } = require('../core-update');
|
|
25
|
+
const { loadIdpConfig } = require('../auth-config');
|
|
26
|
+
|
|
27
|
+
// WHERE A HOSTED PROJECT'S OWNER TAKES THE UPGRADE (task 1004174, ADR 0339): the hub's
|
|
28
|
+
// own deploy door, scoped to this project — `<hub>/deploy?project=<slug>`. The act stays
|
|
29
|
+
// on the control plane (ADR 0293 D1); this instance only names the address, so it gains
|
|
30
|
+
// no capability. Both halves are already here on a federated project: provisioning
|
|
31
|
+
// writes the hub origin and this project's hub client id, which IS its slug (ADR 0246),
|
|
32
|
+
// into web.env. null when this instance is not federated with a hub — a self-hosted
|
|
33
|
+
// instance, or the hub itself — so the hall offers no link rather than a wrong one.
|
|
34
|
+
// Only the origin and the client id are read; the client secret never leaves auth-config.
|
|
35
|
+
function hubDeployUrl(config) {
|
|
36
|
+
if (!config || typeof config.origin !== 'string' || !config.clientId) return null;
|
|
37
|
+
if (!/^https?:\/\/[^\s"'<>]+$/i.test(config.origin)) return null;
|
|
38
|
+
return `${config.origin.replace(/\/+$/, '')}/deploy?project=${encodeURIComponent(String(config.clientId))}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function safeIdpConfig() {
|
|
42
|
+
try { return loadIdpConfig(); } catch { return null; }
|
|
43
|
+
}
|
|
25
44
|
|
|
26
45
|
// ?limit for GET /core-upgrades: an integer 1..100, else 20.
|
|
27
46
|
function upgradesLimit(raw) {
|
|
@@ -30,7 +49,7 @@ function upgradesLimit(raw) {
|
|
|
30
49
|
return Math.min(100, Math.max(1, n));
|
|
31
50
|
}
|
|
32
51
|
|
|
33
|
-
|
|
52
|
+
function buildCoreUpdateRouter({ upgrades = readCoreUpgrades } = {}) {
|
|
34
53
|
const router = express.Router();
|
|
35
54
|
|
|
36
55
|
router.get(
|
|
@@ -48,7 +67,7 @@ module.exports = function buildCoreUpdateRouter({ upgrades = readCoreUpgrades }
|
|
|
48
67
|
// that cannot name its own core reports `no_answer` and renders no banner.
|
|
49
68
|
let running = null;
|
|
50
69
|
try { running = require('../../module-api').CORE_VERSION; } catch { /* version unknown */ }
|
|
51
|
-
res.json({ ok: true, update: await readCoreUpdate({ running }) });
|
|
70
|
+
res.json({ ok: true, update: await readCoreUpdate({ running }), deploy_url: hubDeployUrl(safeIdpConfig()) });
|
|
52
71
|
}, { errorCode: 'core_update_failed', message: false }),
|
|
53
72
|
);
|
|
54
73
|
|
|
@@ -71,4 +90,7 @@ module.exports = function buildCoreUpdateRouter({ upgrades = readCoreUpgrades }
|
|
|
71
90
|
);
|
|
72
91
|
|
|
73
92
|
return router;
|
|
74
|
-
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = buildCoreUpdateRouter;
|
|
96
|
+
module.exports.hubDeployUrl = hubDeployUrl;
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.1037'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|