@bongos/core 1.19.360 → 1.19.362
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 +45 -35
- package/docs/copy-inventory.md +61 -61
- package/docs/copy-registry.json +70 -70
- package/docs/file-map.md +2 -0
- package/modules/autonomy/routes/autonomy.js +19 -0
- package/modules/dev-box/app/src/config.js +13 -1
- package/modules/economy/achievements.js +21 -0
- package/modules/economy/routes/cost.js +16 -0
- package/modules/economy/routes/leaderboard.js +12 -0
- package/modules/hall-ui/public/government.js +35 -17
- package/modules/ideas/routes/blockers.js +24 -3
- package/modules/ideas/routes/inbox.js +34 -3
- package/modules/lifecycle/routes/claims.js +22 -0
- package/modules/lifecycle/routes/tasks.js +22 -0
- package/modules/lifecycle/routes/versions.js +18 -0
- package/modules/memory/routes/learnings.js +21 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/claude-md-template.js +1 -0
- package/scripts/gds/doc-comment-coverage.js +139 -0
- package/scripts/gds/fitness.js +5 -0
- package/scripts/gds/me.js +10 -1
- package/src/bongos/routes/auth.js +28 -0
- package/src/bongos/routes/builders.js +24 -0
- package/src/bongos/routes/healthz.js +10 -0
- package/src/bongos/routes/me.js +21 -0
- package/src/bongos/routes/optimizer.js +12 -0
- package/src/bongos/routes/public.js +19 -1
- package/src/module-api.js +5 -1
- package/tests/doc_comment_coverage.mjs +87 -0
- package/tests/hall_page_gate_map.mjs +5 -4
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
// modules/economy/routes/leaderboard.js — GET /leaderboard: who has earned what
|
|
2
|
+
// (ADR 0095 backfill).
|
|
3
|
+
//
|
|
4
|
+
// WHY. The instance publishes its build openly, and the leaderboard is the
|
|
5
|
+
// human-readable face of the credit ledger — it is read by the PUBLIC status
|
|
6
|
+
// dashboard, so it carries no auth gate. Kept deliberately tiny: the ordering and
|
|
7
|
+
// the join live in `../credits.js` beside the ledger they describe, so the ranking
|
|
8
|
+
// shown here can never drift from the balances it claims to rank.
|
|
9
|
+
//
|
|
10
|
+
// MODULE BOUNDARY: reaches core only through the published doorway
|
|
11
|
+
// `src/module-api.js`; `../credits` is an intra-module sibling.
|
|
12
|
+
|
|
1
13
|
const express = require('express');
|
|
2
14
|
// Carved into modules/economy/ (BV1.R77 / ADR 0093 §1-2): the leaderboard query
|
|
3
15
|
// is an intra-module require — a module never requires a core internal. The
|
|
@@ -25,14 +25,21 @@
|
|
|
25
25
|
// gated server-side on the `government.manage` atom (archon floor). On 401 →
|
|
26
26
|
// web sign-in.
|
|
27
27
|
//
|
|
28
|
-
// SINCE R15 (goal 1000069, ADR 0175) this page carries
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
// the
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
28
|
+
// SINCE R15 (goal 1000069, ADR 0175) this page carries a three-section IA,
|
|
29
|
+
// because Government is the page that answers "how is this project governed"
|
|
30
|
+
// and administration is only one part of that answer. Task 1003269 reordered
|
|
31
|
+
// the tabs to Permissions · Board Room · Constitution (the administering Archon
|
|
32
|
+
// works from the first of those, and it used to sit last) and shortened the
|
|
33
|
+
// "Ranks & Permissions" label to "Permissions". Tabs are OTBKit.makeTabs with
|
|
34
|
+
// hash deep links, and the HASH IDS DID NOT CHANGE with that reorder
|
|
35
|
+
// (#constitution / #board-room / #ranks) — they are linked from Discord and
|
|
36
|
+
// docs.
|
|
37
|
+
//
|
|
38
|
+
// The Constitution and Board Room sections began as FRAMES (named here by
|
|
39
|
+
// section, not by position, so a reorder like 1003269's cannot make this
|
|
40
|
+
// comment lie): R18 filled Constitution with the live values + change history,
|
|
41
|
+
// R19 added the charter library, and R16/R17 filled the Board Room with open
|
|
42
|
+
// items and the vote. Until a frame is filled it states what belongs there
|
|
36
43
|
// and shows an honest kit empty state — never a fake or a hardcoded value that
|
|
37
44
|
// could drift from the real config.
|
|
38
45
|
|
|
@@ -412,19 +419,29 @@
|
|
|
412
419
|
|
|
413
420
|
// ---- the three-section IA (R15) -------------------------------------------
|
|
414
421
|
|
|
415
|
-
// Tab order
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
//
|
|
422
|
+
// Tab order puts Permissions FIRST (task 1003269, owner request): it is the
|
|
423
|
+
// surface an administering Archon actually works from, and it sat last. The
|
|
424
|
+
// label is "Permissions" — the words the owner uses for it — not the compound
|
|
425
|
+
// "Ranks & Permissions"; ranks are still what the panel lists, but permissions
|
|
426
|
+
// are the atom (ADR 0151) and the shorter label is the clearer one.
|
|
427
|
+
//
|
|
428
|
+
// THE HASH IDS ARE LOAD-BEARING AND MUST NOT CHANGE: the Discord board
|
|
429
|
+
// announcement deep-links ?item=<id>#board-room (board-broadcast.js, and the
|
|
430
|
+
// reader in loadBoard below), and #constitution/#ranks are linked from docs.
|
|
431
|
+
// Reorder and relabel freely; the ids are the contract.
|
|
432
|
+
//
|
|
433
|
+
// A non-archon still lands on the Constitution (the page's IA claim: this is
|
|
434
|
+
// the page that answers "how is this project governed"); the Archon still
|
|
435
|
+
// lands on their working surface, which is now also the first tab.
|
|
419
436
|
function wireTabs({ isArchon }) {
|
|
420
437
|
window.OTBKit.makeTabs({
|
|
421
438
|
mount: document.getElementById('gov-tabs'),
|
|
422
439
|
hash: true,
|
|
423
440
|
initial: isArchon ? 'ranks' : 'constitution',
|
|
424
441
|
tabs: [
|
|
425
|
-
{ id: '
|
|
442
|
+
{ id: 'ranks', label: 'Permissions', panel: 'gov-panel-ranks' },
|
|
426
443
|
{ id: 'board-room', label: 'Board Room', panel: 'gov-panel-board' },
|
|
427
|
-
{ id: '
|
|
444
|
+
{ id: 'constitution', label: 'Constitution', panel: 'gov-panel-constitution' },
|
|
428
445
|
],
|
|
429
446
|
});
|
|
430
447
|
}
|
|
@@ -843,15 +860,16 @@
|
|
|
843
860
|
}
|
|
844
861
|
|
|
845
862
|
// Since R16 the page shell admits page.view.government (metic+), and only the
|
|
846
|
-
//
|
|
847
|
-
// page to that one panel. Cosmetic
|
|
863
|
+
// Permissions tab (panel id gov-panel-ranks) is the archon admin surface — so
|
|
864
|
+
// the seal narrowed from the whole page to that one panel. Cosmetic
|
|
865
|
+
// defence-in-depth as ever: every
|
|
848
866
|
// /government/* CRUD endpoint still gates on government.manage server-side,
|
|
849
867
|
// and the board read carries its own atom.
|
|
850
868
|
function sealRanksPanel() {
|
|
851
869
|
const panel = document.getElementById('gov-panel-ranks');
|
|
852
870
|
const body = document.getElementById('gov-body');
|
|
853
871
|
// Replace the admin surface with the sealed note, inside its own tab only.
|
|
854
|
-
panel.innerHTML = '<p class="gov-sealed">
|
|
872
|
+
panel.innerHTML = '<p class="gov-sealed">Permissions is the Archon administration surface. The Constitution and the Board Room are yours to read.</p>';
|
|
855
873
|
body.hidden = false;
|
|
856
874
|
}
|
|
857
875
|
|
|
@@ -1,7 +1,28 @@
|
|
|
1
|
+
// modules/ideas/routes/blockers.js — the queue of things stuck on a human
|
|
2
|
+
// (ADR 0095 backfill).
|
|
3
|
+
//
|
|
4
|
+
// WHY. An AI agent can build almost anything in this repo except the things only
|
|
5
|
+
// the owner can decide or provide — a credential, a purchase, a product call. Those
|
|
6
|
+
// used to stall silently inside a session nobody re-read. A blocker makes that
|
|
7
|
+
// stall a first-class, reviewable record (`/blocker-review`), linked to the tasks
|
|
8
|
+
// it holds up.
|
|
9
|
+
//
|
|
10
|
+
// THE PAYOFF IS IN `/resolve`: `blockers.resolveBlocker` leans on a DB TRIGGER on
|
|
11
|
+
// `blockers.status` to auto-promote every linked `blocked` task back to ready. So
|
|
12
|
+
// answering the owner's question un-sticks the work automatically — nobody has to
|
|
13
|
+
// remember which tasks were waiting on it. Do not reimplement that promotion here;
|
|
14
|
+
// the trigger is the single writer.
|
|
15
|
+
//
|
|
16
|
+
// `GET /blockers` is PUBLIC (the status dashboard renders it — being open about
|
|
17
|
+
// what the project is stuck on is the point). Filing is open to any builder;
|
|
18
|
+
// resolving and linking are Metic+.
|
|
19
|
+
//
|
|
20
|
+
// MODULE BOUNDARY (carved into modules/ideas/ — BV1.R78 / ADR 0093 §1): auth gates
|
|
21
|
+
// + request validators reach through the published doorway `src/module-api.js`;
|
|
22
|
+
// `blockers` is an intra-module sibling. Never a core internal, never another
|
|
23
|
+
// module.
|
|
24
|
+
|
|
1
25
|
const express = require('express');
|
|
2
|
-
// Carved into modules/ideas/ (BV1.R78 / ADR 0093 §1): auth gates + request
|
|
3
|
-
// validators reach through the published doorway; `blockers` is an intra-module
|
|
4
|
-
// sibling — a module never requires a core internal.
|
|
5
26
|
const api = require('../../../src/module-api');
|
|
6
27
|
const blockers = require('../blockers');
|
|
7
28
|
const { validateOrRespond, LIMITS, parseId } = api;
|
|
@@ -1,7 +1,38 @@
|
|
|
1
|
+
// modules/ideas/routes/inbox.js — where a thought enters the system and becomes
|
|
2
|
+
// work, or doesn't (ADR 0095 backfill).
|
|
3
|
+
//
|
|
4
|
+
// WHY. Ideas arrive mid-session, from Discord, from anyone — and an idea that has
|
|
5
|
+
// to wait for the right moment to be filed is an idea that gets lost. So capture is
|
|
6
|
+
// cheap and open to every rank, and JUDGEMENT happens later at triage
|
|
7
|
+
// (`/idea-triage`: promote → task, discard, merge, defer). This file is the whole
|
|
8
|
+
// of that intake surface. Its factory also REGISTERS the `ideas.capture` kernel
|
|
9
|
+
// port — the codebase's first module→module port, which is how the Discord inbound
|
|
10
|
+
// files an idea without importing this module.
|
|
11
|
+
//
|
|
12
|
+
// The three grades are the spine (see ../../CLAUDE.md for the full rules):
|
|
13
|
+
// quick — a title and a description; turning a thought into a task fast.
|
|
14
|
+
// spark — a quick idea whose AUTHOR marked it as wanting development. That mark
|
|
15
|
+
// is consent, which is why `/:id/spark` is author-gated and `/:id/develop`
|
|
16
|
+
// (picking one up) is open to ANY builder — gating pickup would make the
|
|
17
|
+
// queue unpickable.
|
|
18
|
+
// full — carries the template's five fields and a deterministic completeness
|
|
19
|
+
// score, which feeds credit and the ratification board.
|
|
20
|
+
//
|
|
21
|
+
// TWO THINGS THAT BITE, both proven by tests:
|
|
22
|
+
// - ROUTE ORDER IS LOAD-BEARING. Express matches in declaration order, so every
|
|
23
|
+
// literal path (`/inbox/sparks`, `/inbox/template`, `/inbox/awaiting-nod`) MUST
|
|
24
|
+
// be declared before `GET /inbox/:id` — otherwise `:id` swallows it, `parseId`
|
|
25
|
+
// 400s, and an entire feature disappears with no error that names it.
|
|
26
|
+
// - `/inbox/template` and `/inbox/score` are PURE (no DB, no idea id). They exist
|
|
27
|
+
// so the hall's composer renders and scores from the REAL declaration and the
|
|
28
|
+
// REAL scorer rather than a second copy of the rules in the browser.
|
|
29
|
+
//
|
|
30
|
+
// MODULE BOUNDARY (carved into modules/ideas/ — BV1.R78 / ADR 0093 §1+§5): auth
|
|
31
|
+
// gates + request validators reach through the published doorway
|
|
32
|
+
// `src/module-api.js`; `inbox` is an intra-module sibling. Never a core internal,
|
|
33
|
+
// never another module.
|
|
34
|
+
|
|
1
35
|
const express = require('express');
|
|
2
|
-
// Carved into modules/ideas/ (BV1.R78 / ADR 0093 §1+§5): auth gates + request
|
|
3
|
-
// validators reach through the published doorway; `inbox` is an intra-module
|
|
4
|
-
// sibling — a module never requires a core internal.
|
|
5
36
|
const api = require('../../../src/module-api');
|
|
6
37
|
const inbox = require('../inbox');
|
|
7
38
|
// Full Idea capture (task 1002919 / BV1.R143): the template validator (R141)
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
// modules/lifecycle/routes/claims.js — locking a task to one builder's session,
|
|
2
|
+
// and letting go of it again (ADR 0095 backfill).
|
|
3
|
+
//
|
|
4
|
+
// WHY. The claim is what makes PARALLEL builder sessions safe. Several agents work
|
|
5
|
+
// this repo at once; without an atomic lock two of them pick the same task, write
|
|
6
|
+
// the same files, and one loses the work at merge. `POST /claims` is that lock, and
|
|
7
|
+
// it must stay atomic — it refuses with a typed reason rather than racing: already
|
|
8
|
+
// claimed, task not ready, touches conflict, or 409 `DEPS_NOT_SHIPPED` when the
|
|
9
|
+
// dependency graph has not opened the task yet.
|
|
10
|
+
//
|
|
11
|
+
// Every claim ends deliberately, never by drifting: `/claims/:id/resolve` carries
|
|
12
|
+
// both the ship path (credits land) and the release path (no credits, task returns
|
|
13
|
+
// to `ready`). `release-on-behalf` is the privileged escape hatch for a claim whose
|
|
14
|
+
// builder is gone — a crashed session must not strand a task forever.
|
|
15
|
+
//
|
|
16
|
+
// The batch routes serve the multi-claim session flow; `batch/validate` is a DRY
|
|
17
|
+
// RUN, so a builder can see which of several tasks would be refused before locking
|
|
18
|
+
// any of them.
|
|
19
|
+
//
|
|
20
|
+
// MODULE BOUNDARY: reaches core only through the published doorway
|
|
21
|
+
// `src/module-api.js`; never a core internal, never another module.
|
|
22
|
+
|
|
1
23
|
const express = require('express');
|
|
2
24
|
// Intra-module + the published doorway — a module never requires a core internal.
|
|
3
25
|
const api = require('../../../src/module-api');
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
// modules/lifecycle/routes/tasks.js — the HTTP surface of the task itself: what
|
|
2
|
+
// work exists, what is claimable, and how a task moves through its statuses
|
|
3
|
+
// (ADR 0095 backfill).
|
|
4
|
+
//
|
|
5
|
+
// WHY. A task is the unit the whole methodology rests on — "every change is backed
|
|
6
|
+
// by a claimed task" only means something if the task's shape and its status
|
|
7
|
+
// transitions are enforced in ONE place. This is that place. It owns the
|
|
8
|
+
// backlog→ready→active→completed→confirmed→shipped walk (`/promote`, `/demote`,
|
|
9
|
+
// `/ship`, `/confirm`, `/abandon`), the DEPENDENCY graph that gates claimability,
|
|
10
|
+
// and the done-when criteria a version's progress rolls up from.
|
|
11
|
+
//
|
|
12
|
+
// Two invariants that are easy to break from here:
|
|
13
|
+
// - `/tasks/claimable` computes parallel-safety against everyone's ACTIVE claims.
|
|
14
|
+
// It is what makes concurrent builder sessions safe; a filter loosened here
|
|
15
|
+
// hands two builders the same work.
|
|
16
|
+
// - Dependencies are load-bearing; the `V##.R##` rank prefix is decorative.
|
|
17
|
+
// Shipping a task auto-promotes its now-unblocked dependents backlog→ready
|
|
18
|
+
// (ADR 0015), so the graph — not a human — decides what opens up next.
|
|
19
|
+
//
|
|
20
|
+
// MODULE BOUNDARY: reaches core only through the published doorway
|
|
21
|
+
// `src/module-api.js`; never a core internal, never another module.
|
|
22
|
+
|
|
1
23
|
const express = require('express');
|
|
2
24
|
const fs = require('node:fs');
|
|
3
25
|
const os = require('node:os');
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
// modules/lifecycle/routes/versions.js — the version as a container of scope, and
|
|
2
|
+
// the question "is it done yet?" (ADR 0095 backfill).
|
|
3
|
+
//
|
|
4
|
+
// WHY. The project ships version-by-version, so a version is not a label — it is
|
|
5
|
+
// the unit scope is committed to and closed against. These routes answer the two
|
|
6
|
+
// things anyone asks about one: what is IN it (`GET /versions`, `POST /versions`)
|
|
7
|
+
// and how far along it is (`/progress`, `/:id/progress` — the roll-up `/status`
|
|
8
|
+
// renders: each done-when criterion → its gating tasks → what remains, ADR 0025).
|
|
9
|
+
//
|
|
10
|
+
// `/versions/close-suggestions` exists because a version that is *actually*
|
|
11
|
+
// finished rarely announces itself — work drifts, criteria get met out of order,
|
|
12
|
+
// and a version can sit open long after its last real task shipped. Delegated to
|
|
13
|
+
// `../version-close.js`, it surfaces the candidates so closing is a decision
|
|
14
|
+
// someone makes rather than one nobody remembers to make.
|
|
15
|
+
//
|
|
16
|
+
// MODULE BOUNDARY: reaches core only through the published doorway
|
|
17
|
+
// `src/module-api.js`; never a core internal, never another module.
|
|
18
|
+
|
|
1
19
|
const express = require('express');
|
|
2
20
|
// Intra-module + the published doorway — a module never requires a core internal.
|
|
3
21
|
const api = require('../../../src/module-api');
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
// modules/memory/routes/learnings.js — GET/POST /learnings: pay for a discovery
|
|
2
|
+
// once (ADR 0095 backfill).
|
|
3
|
+
//
|
|
4
|
+
// WHY. A hard-won diagnosis — the kind that costs half an hour of a session — is
|
|
5
|
+
// worth exactly nothing if the next agent has to rediscover it. Learnings are the
|
|
6
|
+
// short-form half of that cure (the long-form half is a recipe under
|
|
7
|
+
// `docs/recipes/`): one durable, searchable rule per finding, captured via
|
|
8
|
+
// `node scripts/gds/learning-capture.js` and surfaced again through `/recall`.
|
|
9
|
+
//
|
|
10
|
+
// Capture is open to any signed-in builder on purpose. The agent that just paid
|
|
11
|
+
// for the discovery is the one holding it, and a rank gate on writing down what
|
|
12
|
+
// you learned would simply mean it never gets written down.
|
|
13
|
+
//
|
|
14
|
+
// PRIVACY: bodies pass through `stripPrivate()` before persisting, so anything
|
|
15
|
+
// fenced in `<private>…</private>` never enters the store or the recall index
|
|
16
|
+
// (ADR 0095 C4). The fence is fail-closed — an unbalanced marker strips to the end
|
|
17
|
+
// rather than leaking what it was meant to hide.
|
|
18
|
+
//
|
|
19
|
+
// MODULE BOUNDARY: reaches core only through the published doorway
|
|
20
|
+
// `src/module-api.js`; `../learnings` is an intra-module sibling.
|
|
21
|
+
|
|
1
22
|
const express = require('express');
|
|
2
23
|
// Carved into modules/memory/ (BV1.R72 / ADR 0091 §2): auth + the shared request
|
|
3
24
|
// validators (validateOrRespond/LIMITS) via the doorway; the learnings lib is an
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.362",
|
|
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.362",
|
|
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.362",
|
|
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",
|
|
@@ -60,6 +60,7 @@ ${ident}
|
|
|
60
60
|
- **\`bongos claim <id>\`** locks a task to your session. If it refuses (already claimed, not ready, dependencies not shipped), pick another.
|
|
61
61
|
- **\`bongos ship\`** runs the smoke + grade, then merges and deploys; **\`bongos release <id>\`** drops a claim with no credit. Ship or release before you sign off.
|
|
62
62
|
- **Dependencies gate everything** — a task becomes claimable only once every task it depends on has shipped.
|
|
63
|
+
- **Code you write or touch carries a doc-comment** — each module a header, each public function/class a line or two, saying *what* it does and *why* it exists (never how), in the words someone would search for. Agents find code by searching text, so meaning written into the source is what makes it findable.
|
|
63
64
|
|
|
64
65
|
## Where things live
|
|
65
66
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// scripts/gds/doc-comment-coverage.js — the ADVISORY nudge behind the ADR 0095
|
|
4
|
+
// doc-comment convention (task 1001654).
|
|
5
|
+
//
|
|
6
|
+
// WHY. ADR 0095 ratified one convention: every module and public symbol carries a
|
|
7
|
+
// short doc-comment saying WHAT it does and WHY it exists, because agents navigate
|
|
8
|
+
// this repo by literal text search and meaning written into the source is the only
|
|
9
|
+
// findability layer with no index to keep fresh. A convention nobody measures decays
|
|
10
|
+
// silently, so this reports the one part of it a machine can honestly check — does a
|
|
11
|
+
// source file open with a module header at all.
|
|
12
|
+
//
|
|
13
|
+
// IT WARNS; IT NEVER FAILS. That ceiling is deliberate and is stated in the ADR: a
|
|
14
|
+
// doc-comment gate that blocks a ship trains builders to write a filler line to get
|
|
15
|
+
// past it, which is worse than no comment because it LOOKS like documentation.
|
|
16
|
+
// Promoting this to hard-fail is an owner decision and needs its own ADR.
|
|
17
|
+
//
|
|
18
|
+
// What it cannot check: whether the prose is any good. Presence is a proxy. The
|
|
19
|
+
// convention itself — what to write, and the worked examples — lives in
|
|
20
|
+
// docs/code-conventions.md.
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const { execFileSync } = require('child_process');
|
|
25
|
+
|
|
26
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
27
|
+
|
|
28
|
+
// First-party server/CLI/module source. Deliberately NOT scanned:
|
|
29
|
+
// *.test.js — a test's subject is named by its filename and its cases
|
|
30
|
+
// **/public/** — browser assets; a worthwhile but separate sweep
|
|
31
|
+
// vendor / node_modules / *.min.js / *.generated.js — not ours to annotate
|
|
32
|
+
const INCLUDE_RE = /^(src|scripts|modules)\//;
|
|
33
|
+
const EXCLUDE_RE = /(^|\/)(node_modules|vendor|public|public-docs)\/|\.test\.js$|\.min\.js$|\.generated\.js$/;
|
|
34
|
+
|
|
35
|
+
// A header is prose that appears BEFORE any code. A shebang and a 'use strict'
|
|
36
|
+
// prologue are allowed to precede it — they are not code a reader learns from.
|
|
37
|
+
const SKIP_PROLOGUE_RE = /^(#!.*|['"]use strict['"];?)?$/;
|
|
38
|
+
|
|
39
|
+
// Minimum words for a header to count as saying something. A bare
|
|
40
|
+
// `// config.js — config` restates the filename and teaches nobody anything;
|
|
41
|
+
// the threshold is low on purpose so the check stays about ABSENCE, not style.
|
|
42
|
+
const MIN_WORDS = 12;
|
|
43
|
+
|
|
44
|
+
// Enumerate the files to scan. The glob goes to git as an argv ARRAY, never a
|
|
45
|
+
// shell string — quoting a glob as `'*.js'` is literal on cmd.exe and matches
|
|
46
|
+
// nothing, which is exactly how docs-entropy.js once "passed" on every Windows
|
|
47
|
+
// checkout by scanning zero files (task 1002526).
|
|
48
|
+
//
|
|
49
|
+
// A zero-file result is therefore treated as a SCAN DEFECT and thrown, not
|
|
50
|
+
// returned: this repo tracks hundreds of source files, so an empty enumeration is
|
|
51
|
+
// never real, and a lint that silently proves nothing is worse than no lint.
|
|
52
|
+
function trackedSourceFiles({ run } = {}) {
|
|
53
|
+
const exec = run || ((args) => execFileSync('git', args, { cwd: ROOT, encoding: 'utf8' }));
|
|
54
|
+
const out = exec(['ls-files', '*.js']);
|
|
55
|
+
const all = String(out).split('\n').map((s) => s.trim()).filter(Boolean);
|
|
56
|
+
if (!all.length) {
|
|
57
|
+
throw new Error('doc-comment-coverage scan defect: git ls-files returned no tracked .js files — the enumeration is broken, not the repo.');
|
|
58
|
+
}
|
|
59
|
+
return all.filter((f) => INCLUDE_RE.test(f) && !EXCLUDE_RE.test(f));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Extract the leading comment block of a file, or '' when the first real token is
|
|
63
|
+
// code. Handles both `//` runs and a `/* … */` block, and is CRLF-safe (every line
|
|
64
|
+
// is trimmed before it is classified).
|
|
65
|
+
function leadingComment(text) {
|
|
66
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
67
|
+
let i = 0;
|
|
68
|
+
while (i < lines.length && SKIP_PROLOGUE_RE.test(lines[i].trim())) i++;
|
|
69
|
+
|
|
70
|
+
const block = [];
|
|
71
|
+
if (lines[i] && lines[i].trim().startsWith('/*')) {
|
|
72
|
+
for (; i < lines.length; i++) {
|
|
73
|
+
block.push(lines[i]);
|
|
74
|
+
if (lines[i].includes('*/')) break;
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
for (; i < lines.length && lines[i].trim().startsWith('//'); i++) block.push(lines[i]);
|
|
78
|
+
}
|
|
79
|
+
return block.join('\n');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function wordCount(comment) {
|
|
83
|
+
return comment.replace(/[/*]/g, ' ').trim().split(/\s+/).filter(Boolean).length;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Returns { scanned, missing:[rel], thin:[{file,words}] }.
|
|
87
|
+
function scanHeaders(files = trackedSourceFiles(), read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8')) {
|
|
88
|
+
const missing = [];
|
|
89
|
+
const thin = [];
|
|
90
|
+
for (const f of files) {
|
|
91
|
+
let words;
|
|
92
|
+
try {
|
|
93
|
+
words = wordCount(leadingComment(read(f)));
|
|
94
|
+
} catch (_) {
|
|
95
|
+
continue; // a file listed by git but unreadable here is not this check's problem
|
|
96
|
+
}
|
|
97
|
+
if (words === 0) missing.push(f);
|
|
98
|
+
else if (words < MIN_WORDS) thin.push({ file: f, words });
|
|
99
|
+
}
|
|
100
|
+
return { scanned: files.length, missing, thin };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const MAX_LISTED = 15; // keep the warning readable; the count carries the rest
|
|
104
|
+
|
|
105
|
+
// Fitness-check entry point. ALWAYS returns ok:true / hardFail:false — see the
|
|
106
|
+
// header. Findings surface as warnings so a red board still means something real.
|
|
107
|
+
function checkDocCommentCoverage() {
|
|
108
|
+
const { scanned, missing, thin } = scanHeaders();
|
|
109
|
+
const warnings = [];
|
|
110
|
+
|
|
111
|
+
if (missing.length) {
|
|
112
|
+
const shown = missing.slice(0, MAX_LISTED).join(', ');
|
|
113
|
+
const more = missing.length > MAX_LISTED ? ` (+${missing.length - MAX_LISTED} more)` : '';
|
|
114
|
+
warnings.push(`${missing.length} source file(s) open with no module header — give each a line saying what it does and WHY it exists (docs/code-conventions.md, ADR 0095): ${shown}${more}`);
|
|
115
|
+
}
|
|
116
|
+
if (thin.length) {
|
|
117
|
+
const shown = thin.slice(0, MAX_LISTED).map((t) => `${t.file} (${t.words}w)`).join(', ');
|
|
118
|
+
const more = thin.length > MAX_LISTED ? ` (+${thin.length - MAX_LISTED} more)` : '';
|
|
119
|
+
warnings.push(`${thin.length} header(s) under ${MIN_WORDS} words — likely a restatement of the filename rather than the reason the file exists: ${shown}${more}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const covered = scanned - missing.length;
|
|
123
|
+
return {
|
|
124
|
+
name: 'module header doc-comments (ADR 0095)',
|
|
125
|
+
ok: true,
|
|
126
|
+
hardFail: false, // ADVISORY BY DECISION — promoting this to blocking needs owner sign-off (ADR 0095)
|
|
127
|
+
violations: [],
|
|
128
|
+
warnings,
|
|
129
|
+
note: `${covered}/${scanned} first-party source file(s) under src/·scripts/·modules/ open with a module header saying what they do and why (ADR 0095, task 1001654). ADVISORY — this check never fails the build; tests, browser assets and vendored code are out of scope.`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (require.main === module) {
|
|
134
|
+
const r = checkDocCommentCoverage();
|
|
135
|
+
process.stdout.write(`${r.note}\n`);
|
|
136
|
+
for (const w of r.warnings) process.stdout.write(`! ${w}\n`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = { checkDocCommentCoverage, scanHeaders, leadingComment, wordCount, MIN_WORDS };
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -1308,6 +1308,11 @@ const CHECKS = [
|
|
|
1308
1308
|
require('./routine-schedule.js').checkRoutineCadencesScheduled, // Check 25 — task 1003209: a routine that declares a cadence must have a scheduler firing it AT that period (ADR 0188)
|
|
1309
1309
|
require('./baseline-staleness.js').checkBaselineFloors, // Check 26 — task 1003210: a shrink-only baseline that never shrinks is unmanaged debt; a floor exempting one must say why
|
|
1310
1310
|
require('./client-baseurl-guard.js').checkClientBaseUrl, // Check 27 — task 1003263: a page must not combine baseUrl:'' with a generated namespaced call (that pairing 404s every request; three pages shipped it, two of them dead in prod)
|
|
1311
|
+
// Check 28 — task 1001654 / ADR 0095: does each source file open with a module
|
|
1312
|
+
// header saying what it does and why. ADVISORY ONLY (never hardFail) — a
|
|
1313
|
+
// doc-comment gate that blocks a ship buys filler prose, not understanding.
|
|
1314
|
+
// Making it blocking is an owner decision and needs its own ADR.
|
|
1315
|
+
require('./doc-comment-coverage.js').checkDocCommentCoverage,
|
|
1311
1316
|
];
|
|
1312
1317
|
|
|
1313
1318
|
function runAll() {
|
package/scripts/gds/me.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
2
|
+
// scripts/gds/me.js — "who am I, and what am I holding?" from the terminal
|
|
3
|
+
// (ADR 0095 backfill).
|
|
4
|
+
//
|
|
5
|
+
// WHY. A builder's identity lives on the SERVER (rank is read fresh from the DB on
|
|
6
|
+
// every request — ADR 0016), so a local file can never answer this honestly. This
|
|
7
|
+
// CLI asks the API instead: profile + rank, the leaderboard top 10, and — the part
|
|
8
|
+
// that actually matters mid-session — the ACTIVE CLAIM, so an agent resuming work
|
|
9
|
+
// can confirm what it is allowed to be changing before it changes anything.
|
|
10
|
+
//
|
|
11
|
+
// Read-only. It prints; it never mutates a claim (that is `claim.js`/`ship.js`).
|
|
3
12
|
|
|
4
13
|
const { cliClient, requireSession } = require('./cli-lib');
|
|
5
14
|
|
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
// src/bongos/routes/auth.js — every way a builder or a device proves who they are
|
|
2
|
+
// (ADR 0095 backfill).
|
|
3
|
+
//
|
|
4
|
+
// WHY. This is the front door to the trust boundary (ADR 0016): authority lives in
|
|
5
|
+
// `builders.rank` on the DB and is checked server-side per request, but a request
|
|
6
|
+
// can only be checked once it carries an identity — minting that identity is this
|
|
7
|
+
// file's whole job. Four flows land here, and they exist separately because the
|
|
8
|
+
// caller's shape differs each time:
|
|
9
|
+
//
|
|
10
|
+
// - /auth/device/* the CLI on a builder's machine (GitHub Device Flow, plus
|
|
11
|
+
// the IdP `exchange` variant) — no browser to redirect.
|
|
12
|
+
// - /auth/web/* the builders' hall in a browser (OAuth redirect + cookie).
|
|
13
|
+
// - /auth/discord/* LINKING an existing builder to a Discord id for role sync.
|
|
14
|
+
// It is NOT a way to sign in — Discord sits OUTSIDE the trust
|
|
15
|
+
// boundary (ADR 0032), so it can never mint a session.
|
|
16
|
+
// - /auth/app-pair/* the Dev Box desktop app pairing to its builder (ADR 0045).
|
|
17
|
+
//
|
|
18
|
+
// TWO INVARIANTS worth keeping in view before editing:
|
|
19
|
+
// - `/auth/cli-token/issue` mints a CLI bearer from a browser COOKIE, never from a
|
|
20
|
+
// bearer. A leaked CLI token must not be able to extend its own life.
|
|
21
|
+
// - Logout/revoke are session-destroying and must stay synchronous with rank
|
|
22
|
+
// changes: a demotion calls `revokeAllSessionsForBuilder` so a stale token
|
|
23
|
+
// cannot outlive the authority it was issued under.
|
|
24
|
+
//
|
|
25
|
+
// `escapeHtml` + `renderAuthErrorPage` below render the OAuth failure branches as a
|
|
26
|
+
// small standalone page (no template engine, no runtime deps) so a visitor who
|
|
27
|
+
// fails auth sees something coherent instead of a text/plain stack trace.
|
|
28
|
+
|
|
1
29
|
const express = require('express');
|
|
2
30
|
const crypto = require('node:crypto');
|
|
3
31
|
|
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
// src/bongos/routes/builders.js — the roster, the public profile, and the rank
|
|
2
|
+
// mutations (ADR 0095 backfill).
|
|
3
|
+
//
|
|
4
|
+
// WHY. Two very different things share this file because they share a subject —
|
|
5
|
+
// the builder as an IDENTITY, distinct from the work they do:
|
|
6
|
+
//
|
|
7
|
+
// - READS — /builders/roster · /builders/directory · /builders/:id/profile ·
|
|
8
|
+
// per-builder claims, grades and onboarding. Who is here, what have
|
|
9
|
+
// they shipped, how are they doing.
|
|
10
|
+
// - WRITES — PATCH /builders/:id/rank · /status · /budget. These are the
|
|
11
|
+
// highest-authority mutations in the platform: rank IS permission
|
|
12
|
+
// (ADR 0016), so a bug here is a privilege-escalation bug, not a
|
|
13
|
+
// display bug. They stay in CORE db (`setBuilderRank`,
|
|
14
|
+
// `deactivateBuilder`) precisely because identity and authority are
|
|
15
|
+
// kernel concerns and must not be reachable through a module.
|
|
16
|
+
//
|
|
17
|
+
// Promotion is deliberately single-rung (`singleRungPromotionError`) and every
|
|
18
|
+
// change writes an audit row — a builder cannot be walked from newcomer to Archon
|
|
19
|
+
// in one call, and no rank change is untraceable.
|
|
20
|
+
//
|
|
21
|
+
// The work-shaped reads (claims, grade breakdown, shipped counts) belong to the
|
|
22
|
+
// lifecycle module (BV1.R86) and are resolved through the `lifecycle` kernel port
|
|
23
|
+
// (`lc()`), never a direct module require — the core↔module boundary rule.
|
|
24
|
+
|
|
1
25
|
const path = require('node:path');
|
|
2
26
|
const { execFile } = require('node:child_process');
|
|
3
27
|
const express = require('express');
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
// src/bongos/routes/healthz.js — the unauthenticated liveness probe (ADR 0095 backfill).
|
|
2
|
+
//
|
|
3
|
+
// WHY. Every uptime checker, the status dashboard, and the deploy smoke need one
|
|
4
|
+
// endpoint they can hit with no credential to answer "is the process up, and did
|
|
5
|
+
// it come up with auth wired?". It is deliberately the thinnest router in the
|
|
6
|
+
// tree: no DB call, no rank gate, no branding read — anything it touches becomes
|
|
7
|
+
// a new way for the health check itself to go down. `auth_configured` is the one
|
|
8
|
+
// piece of state it reports, because a process that boots WITHOUT working auth
|
|
9
|
+
// (missing IdP creds) is live but useless, and that failure is otherwise silent.
|
|
10
|
+
|
|
1
11
|
const express = require('express');
|
|
2
12
|
const auth = require('../auth');
|
|
3
13
|
|
package/src/bongos/routes/me.js
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
// src/bongos/routes/me.js — GET /me, the one call that tells a builder everything
|
|
2
|
+
// about themselves (ADR 0095 backfill).
|
|
3
|
+
//
|
|
4
|
+
// WHY. The hall's first paint needs identity, rank, credits, streak, achievements,
|
|
5
|
+
// onboarding checklist, active claims, grade trend and preferences all at once.
|
|
6
|
+
// Fetching those as eight round-trips made the hall slow and the client
|
|
7
|
+
// order-dependent, so `/me` is a deliberate AGGREGATOR — one request, one shape.
|
|
8
|
+
//
|
|
9
|
+
// THE COST OF THAT CHOICE, and the thing to respect when editing: most of the
|
|
10
|
+
// slices it returns are no longer core. They live in modules carved out per ADR
|
|
11
|
+
// 0093 (`reward` → economy, `builder-settings` → prefs/needs, `onboarding` →
|
|
12
|
+
// checklist, `lifecycle` → claims/estimation/grades), and core may never require a
|
|
13
|
+
// module file directly. Every slice therefore resolves a kernel PORT through
|
|
14
|
+
// `module-seams`, per call. Use `resolveOptional` and degrade gracefully: an
|
|
15
|
+
// instance running without that module must still render `/me`, just without the
|
|
16
|
+
// slice — which is why a missing provider yields the unset/default shape rather
|
|
17
|
+
// than a 500.
|
|
18
|
+
//
|
|
19
|
+
// The per-builder `/me/*-prefs` write routes are NOT here — they moved into
|
|
20
|
+
// modules/builder-settings/ (BV1.R80). This file kept only the read aggregation.
|
|
21
|
+
|
|
1
22
|
const express = require('express');
|
|
2
23
|
const auth = require('../auth');
|
|
3
24
|
const db = require('../db');
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
// src/bongos/routes/optimizer.js — POST /session/optimize: recommend a session's
|
|
2
|
+
// worth of work to one builder (ADR 0095 backfill).
|
|
3
|
+
//
|
|
4
|
+
// WHY. A builder asking "what should I do with the next two hours?" would
|
|
5
|
+
// otherwise read the whole claimable list and guess. This hands the question to
|
|
6
|
+
// `../optimizer.js`, which picks a parallel-safe set against a time budget.
|
|
7
|
+
//
|
|
8
|
+
// READ-ONLY BY DESIGN: it recommends, it never claims. Claiming stays an explicit
|
|
9
|
+
// builder act through the lifecycle (`/builder-claim`), so a recommendation can
|
|
10
|
+
// never quietly lock work to someone. That is why the gate here is only
|
|
11
|
+
// `requireBuilder` — suggesting work to yourself needs no rank.
|
|
12
|
+
|
|
1
13
|
const express = require('express');
|
|
2
14
|
const auth = require('../auth');
|
|
3
15
|
const optimizer = require('../optimizer');
|
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
//
|
|
1
|
+
// src/bongos/routes/public.js — the anonymous, read-only surface that powers the
|
|
2
|
+
// status dashboard (ADR 0095 backfill).
|
|
3
|
+
//
|
|
4
|
+
// WHY. The instance publishes its build progress to the world — versions, shipped
|
|
5
|
+
// work, cost, leaderboard, grades, repo health — and that audience has no account.
|
|
6
|
+
// So this router carries NO auth gate at all, which makes it the one place where a
|
|
7
|
+
// projection bug leaks private work state to the public internet.
|
|
8
|
+
//
|
|
9
|
+
// THE RULE THAT KEEPS IT SAFE: nothing here returns a raw DB row. Every response
|
|
10
|
+
// goes through an explicit `projectPublic*` allowlist (`projectPublicTask`,
|
|
11
|
+
// `projectPublicBlocker`, `projectPublicIdea`) that names the fields it will
|
|
12
|
+
// disclose, so a column added to `tasks` later is private by default rather than
|
|
13
|
+
// silently published. A task/idea/blocker is only visible when its own `public`
|
|
14
|
+
// flag is set. Rate-limited (`publicReadRateLimitMiddleware`) and CORS-scoped
|
|
15
|
+
// (`corsPublicGet`) because an unauthenticated surface is also the cheapest one to
|
|
16
|
+
// scrape.
|
|
17
|
+
//
|
|
18
|
+
// The host it serves is NOT hardcoded — the dashboard origin comes from branding
|
|
19
|
+
// (`domains.statusOrigin`); this file must stay brand-neutral (ADR 0062 §3).
|
|
2
20
|
|
|
3
21
|
const express = require('express');
|
|
4
22
|
const db = require('../db');
|