@bongos/core 1.19.332 → 1.19.334

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.
Files changed (62) hide show
  1. package/.bongos-core.json +173 -43
  2. package/docs/adr/0187-oauth-handshake-cookie-lifetime.md +84 -0
  3. package/docs/adr/README.md +1 -0
  4. package/docs/api/openapi.json +2 -2
  5. package/docs/api-reference.md +1 -1
  6. package/docs/copy-inventory.md +202 -202
  7. package/docs/copy-registry.json +384 -384
  8. package/modules/grading/grader-config.js +98 -0
  9. package/modules/grading/grader-model.js +150 -0
  10. package/modules/grading/grader-prompt.js +139 -0
  11. package/modules/grading/grader-score.js +706 -0
  12. package/modules/grading/grader-subagent.js +328 -0
  13. package/modules/grading/grader.js +5 -1287
  14. package/modules/hall-ui/public/builders.js +19 -1182
  15. package/modules/hall-ui/public/goals-page.js +11 -997
  16. package/modules/hall-ui/public/goals-render.js +1032 -0
  17. package/modules/hall-ui/public/goals.html +4 -0
  18. package/modules/hall-ui/public/hall-render.js +1211 -0
  19. package/modules/hall-ui/public/index.html +6 -0
  20. package/modules/lifecycle/routes/goal-route-authz.js +400 -0
  21. package/modules/lifecycle/routes/goals.js +1 -367
  22. package/modules/lifecycle/routes/task-route-helpers.js +219 -0
  23. package/modules/lifecycle/routes/task-write-routes.js +542 -0
  24. package/modules/lifecycle/routes/tasks.js +3 -696
  25. package/package-lock.json +2 -2
  26. package/package.json +1 -1
  27. package/scripts/gds/box-infra.js +280 -0
  28. package/scripts/gds/box.js +1 -245
  29. package/scripts/gds/fitness-checks-identity.js +320 -0
  30. package/scripts/gds/fitness-checks-vocabulary.js +230 -0
  31. package/scripts/gds/fitness-checks-write-validation.js +144 -0
  32. package/scripts/gds/fitness-lib.js +82 -0
  33. package/scripts/gds/fitness.js +55 -668
  34. package/scripts/gds/provision-config.js +204 -0
  35. package/scripts/gds/provision-net.js +221 -0
  36. package/scripts/gds/provision-repo.js +849 -0
  37. package/scripts/gds/provision-units.js +262 -0
  38. package/scripts/gds/provision.js +4 -1395
  39. package/src/bongos/auth-admission.js +264 -0
  40. package/src/bongos/auth-config.js +151 -0
  41. package/src/bongos/auth-discord.js +198 -0
  42. package/src/bongos/auth-github.js +336 -0
  43. package/src/bongos/auth-idp.js +330 -0
  44. package/src/bongos/auth.js +5 -1100
  45. package/src/bongos/routes/auth.js +54 -10
  46. package/src/module-api.js +5 -1
  47. package/tests/auth_admission_gate.mjs +3 -1
  48. package/tests/auth_web_recovery.mjs +63 -0
  49. package/tests/est_advisory.mjs +3 -1
  50. package/tests/goal_advisory.mjs +3 -2
  51. package/tests/goal_criterion_confirm_ui.mjs +6 -0
  52. package/tests/goal_subtitle.mjs +5 -2
  53. package/tests/goals_card_view.mjs +10 -3
  54. package/tests/grader_generated_provenance.mjs +3 -1
  55. package/tests/hall_palette.mjs +3 -1
  56. package/tests/helpers.mjs +58 -25
  57. package/tests/lib_sh_resolution.mjs +3 -1
  58. package/tests/membership_kind_lockstep.mjs +3 -1
  59. package/tests/migration_allocation.mjs +5 -2
  60. package/tests/patch_gate_ordering.mjs +3 -1
  61. package/tests/rank_tier_single_source.mjs +16 -3
  62. package/tests/reward_gate.mjs +3 -2
@@ -0,0 +1,262 @@
1
+ // scripts/gds/provision-units.js — the systemd + environment templates: the
2
+ // instance service unit, the db-backup service and timer, and the web.env body.
3
+ //
4
+ // Carved out of scripts/gds/provision.js by task 1003206 (goal 1000079, the R03
5
+ // de-monolith plan) to bring the control-plane CLI under the 1,500-line ratchet.
6
+ // A STRUCTURAL move: bodies unchanged, provision.js requires every name back, so
7
+ // the intent runner and its unit templates behave exactly as before.
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('node:fs');
12
+ const path = require('node:path');
13
+ const { CONFIG, REPO_ROOT } = require('./provision-config.js');
14
+ const { APP_USER, CORE_PKG_DIR, dbName, parseTargetRef, standaloneRoot } = require('./provision-repo.js');
15
+
16
+ function serviceUnit(inst, { etcBase = CONFIG.etcBase } = {}) {
17
+ const envFile = path.posix.join(etcBase, inst.slug, 'web.env');
18
+ const standalone = inst.hosting_shape === 'standalone';
19
+ const workDir = standalone ? standaloneRoot(inst) : REPO_ROOT;
20
+ const execStart = standalone
21
+ ? `/usr/bin/node ${CORE_PKG_DIR}/src/platform-server.js`
22
+ : `/usr/bin/node src/platform-server.js`;
23
+ return `[Unit]
24
+ Description=Cloud Bongos instance — ${inst.slug} (provisioned by provision.js, ADR 0111)
25
+ After=network-online.target postgresql.service
26
+ Wants=network-online.target
27
+ Requires=postgresql.service
28
+
29
+ [Service]
30
+ Type=simple
31
+ User=lars
32
+ Group=lars
33
+ WorkingDirectory=${workDir}
34
+ EnvironmentFile=${envFile}
35
+ Environment=PGDATABASE=${dbName(inst)}
36
+ Environment=PORT=${inst.port}
37
+ ExecStart=${execStart}
38
+ Restart=always
39
+ RestartSec=3
40
+ StandardOutput=journal
41
+ StandardError=journal
42
+ NoNewPrivileges=true
43
+
44
+ [Install]
45
+ WantedBy=multi-user.target
46
+ `;
47
+ }
48
+ function serviceUnitPath(inst) { return `/etc/systemd/system/${inst.slug}.service`; }
49
+
50
+ // ── nightly DB backup: per-instance timer + service (task 2213) ─────────────
51
+ // PRODUCTIZES the manual task-2210 fix: a newly-provisioned instance was born with
52
+ // NO backups (dir absent, GDS_BACKUP_DB unset → the script fell back to the
53
+ // nonexistent `amazonprimea` default, and the repo's global gds-db-backup units
54
+ // hardcode /home/lars/amazonprimea — wrong for the consumer node_modules/@bongos/
55
+ // core layout). These generators emit a PER-INSTANCE unit pair that fixes all three:
56
+ // • name → `<slug>-db-backup.{service,timer}` so co-tenant instances on one box
57
+ // never collide (each dumps its OWN db; the script's retention prune is
58
+ // already `<db>-*.sql.gz`-scoped, so a shared backupDir is safe).
59
+ // • DB → Environment=GDS_BACKUP_DB=<instance db>, set on the unit (not read from
60
+ // the web.env) so the dump targets the right database with no fallback.
61
+ // • path → ExecStart resolves the canonical script INSIDE the instance's pinned
62
+ // @bongos/core (standalone) or this control-plane checkout (co-tenant),
63
+ // the SAME workDir/CORE_PKG_DIR split serviceUnit uses — never the old
64
+ // monorepo path.
65
+ function backupUnitName(inst) { return `${inst.slug}-db-backup`; }
66
+ function backupServicePath(inst) { return `/etc/systemd/system/${backupUnitName(inst)}.service`; }
67
+ function backupTimerPath(inst) { return `/etc/systemd/system/${backupUnitName(inst)}.timer`; }
68
+ // The consumer-layout-correct absolute path to db-backup-nightly.sh. Standalone runs
69
+ // it from the instance's own pinned core; co-tenant/dedicated from this checkout's
70
+ // scripts/gds/ — mirrors serviceUnit's execStart resolution so they can't diverge.
71
+ function backupScriptPath(inst) {
72
+ const rel = 'scripts/gds/db-backup-nightly.sh';
73
+ return inst.hosting_shape === 'standalone'
74
+ ? path.posix.join(standaloneRoot(inst), CORE_PKG_DIR, rel)
75
+ : path.posix.join(REPO_ROOT, rel);
76
+ }
77
+ function backupService(inst, { backupDir = CONFIG.backupDir } = {}) {
78
+ return `[Unit]
79
+ Description=Nightly pg_dump of the ${inst.slug} GDS DB (provisioned by provision.js, task 2213)
80
+ After=postgresql.service
81
+ Requires=postgresql.service
82
+
83
+ [Service]
84
+ Type=oneshot
85
+ User=${APP_USER}
86
+ Group=${APP_USER}
87
+ # The DB to dump + where dumps land — set on the unit (not via the instance web.env)
88
+ # so the dump targets the right database with no fallback to a stale default (root
89
+ # cause of the task-2210 breakage). db-backup-nightly.sh reads both as env.
90
+ Environment=GDS_BACKUP_DB=${dbName(inst)}
91
+ Environment=GDS_BACKUP_DIR=${backupDir}
92
+ # Consumer-layout path: the canonical script inside this instance's pinned core.
93
+ ExecStart=/bin/bash ${backupScriptPath(inst)}
94
+ # Modest hardening — the script only needs read on the pg socket + write on the
95
+ # backup dir. Mirrors the repo's gds-db-backup.service.
96
+ NoNewPrivileges=true
97
+ ProtectSystem=strict
98
+ ReadWritePaths=${backupDir}
99
+ ProtectHome=read-only
100
+ PrivateTmp=true
101
+
102
+ [Install]
103
+ # Not socket-activated; the timer drives this. The timer's WantedBy is what enables.
104
+ `;
105
+ }
106
+ function backupTimer(inst) {
107
+ return `[Unit]
108
+ Description=Daily timer for the ${inst.slug} GDS DB pg_dump (provisioned by provision.js, task 2213)
109
+
110
+ [Timer]
111
+ # 04:00 UTC daily, mirroring the repo's gds-db-backup.timer (low-traffic window).
112
+ OnCalendar=*-*-* 04:00:00 UTC
113
+ # Catch up on next boot if the box was off when the timer was due.
114
+ Persistent=true
115
+ # Jitter so simultaneous timers don't dogpile the disk at exactly 04:00.
116
+ RandomizedDelaySec=300
117
+ Unit=${backupUnitName(inst)}.service
118
+
119
+ [Install]
120
+ WantedBy=timers.target
121
+ `;
122
+ }
123
+
124
+ // The branding pack a provisioned CO-TENANT instance loads: the shared, committed
125
+ // neutral starter (the co-tenant model shares ONE checkout, differentiated only by
126
+ // env — src/branding.js's GDS_BRANDING_FILE knob). Per-instance identity/domains
127
+ // therefore ride ENV OVERRIDES, not a divergent pack. Kept as a constant so the
128
+ // GDS_BRANDING_FILE line below and the env-var PREFIX (which must equal this pack's
129
+ // envPrefix, or src/branding.js's ENV_OVERRIDES never fire) can't drift apart.
130
+ const PROVISIONED_BRANDING_FILE = 'config/branding.neutral.json';
131
+
132
+ // The env-var prefix a provisioned instance actually RESOLVES — i.e. the envPrefix
133
+ // of the pack it loads (PROVISIONED_BRANDING_FILE). src/instance-config.resolveEnv
134
+ // reads <envPrefix>_<SUFFIX>, and src/branding.resolveBranding applies
135
+ // <envPrefix>_{PUBLIC,BUILDERS,…}_ORIGIN overrides — so a web.env var only takes
136
+ // effect under THIS prefix. Read from the pack (fail-soft to the vanilla default)
137
+ // so it stays in lockstep instead of a magic string. (The prior slug-based prefix
138
+ // was a latent bug: it never matched the neutral pack's CLOUDBONGOS envPrefix, so
139
+ // the OAuth creds were silently unreadable — task 1972.)
140
+ function provisionedEnvPrefix() {
141
+ try {
142
+ const pack = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, PROVISIONED_BRANDING_FILE), 'utf8'));
143
+ return pack.envPrefix || 'CLOUDBONGOS';
144
+ } catch { return 'CLOUDBONGOS'; }
145
+ }
146
+
147
+ // The per-instance secrets file body (the standup runbook's /etc/<inst>/web.env).
148
+ // The OAuth client id/secret are GUIDED/manual inputs (no GitHub create-API) — the
149
+ // runner writes placeholders the owner fills, unless provided via the intent later.
150
+ // A domain (bring-your-own or provisioned) drives the single-host origin overrides:
151
+ // with them set, src/branding.js resolves the instance's REAL buildersOrigin, so the
152
+ // core's single-host router (isBuildersHost === buildersOrigin host, task 1971) serves
153
+ // the hall on this host + OAuth uses the real callback origin — without them the pack
154
+ // stays at neutral localhost and the hall never loads (task 1972). No domain yet
155
+ // (bring-your-own deferred, ADR 0111 §4) ⇒ origins stay neutral until one is set.
156
+ function webEnvBody(inst, { clientId = '', clientSecret = '', appId = null, appPem = null, federation = null } = {}) {
157
+ const prefix = provisionedEnvPrefix(), { settingsEnvLines } = require(path.join(REPO_ROOT, 'modules/provisioning/provisioning')); // eslint-disable-line global-require
158
+ const origin = inst.domain ? `https://${inst.domain}` : '';
159
+ const originLines = origin
160
+ ? `# Single-host origins (task 1972): the hall serves on THIS host + OAuth/redirects
161
+ # use the real address. Resolved by src/branding.js ENV_OVERRIDES under the pack's
162
+ # ${prefix} envPrefix. A single-host instance has no dedicated builders./status.
163
+ # subdomain, so all four point at the one host.
164
+ ${prefix}_PUBLIC_ORIGIN=${origin}
165
+ ${prefix}_BUILDERS_ORIGIN=${origin}
166
+ ${prefix}_STATUS_ORIGIN=${origin}
167
+ ${prefix}_OAUTH_ORIGIN=${origin}
168
+ `
169
+ : `# No domain yet (bring-your-own-domain pending, ADR 0111 §4) — origins stay
170
+ # neutral (localhost); add ${prefix}_{PUBLIC,BUILDERS,OAUTH}_ORIGIN once a domain
171
+ # is set, or the hall won't load on the instance's host.
172
+ `;
173
+ // The OAuth app creds are filled by ONE of two paths (task 2080): the GitHub App
174
+ // Manifest flow (placeManifestCreds fetches them control-plane-side + re-writes this
175
+ // file with clientId/clientSecret set), or the manual oauth-secret.js placement. On
176
+ // the first provision write they are blank placeholders the flow fills later.
177
+ const credComment = federation
178
+ ? `# Provisioned by scripts/gds/provision.js (ADR 0111). This instance FEDERATES sign-in
179
+ # through the Cloud Bongos hub (ADR 0141 §4) — it needs no GitHub app of its own, so the
180
+ # GitHub client id/secret below stay blank; the ${prefix}_HUB_* block drives sign-in.`
181
+ : clientId || clientSecret
182
+ ? `# Provisioned by scripts/gds/provision.js (ADR 0111). OAuth app creds placed via the
183
+ # GitHub App Manifest flow (ADR 0133) — the owner clicked one link; no secret was pasted.`
184
+ : `# Provisioned by scripts/gds/provision.js (ADR 0111). Fill the OAuth app creds via the
185
+ # one-click GitHub App Manifest flow (ADR 0133), or manually (oauth-secret.js place):`;
186
+ // The founding owner (firstAdmin): the GitHub login src/bongos/auth.js seats as the
187
+ // instance's first Archon on first sign-in (maybeSeatFirstAdmin checks
188
+ // <prefix>_FIRST_ADMIN before branding.firstAdmin). WITHOUT it a provisioned instance
189
+ // has NO owner — first sign-in is refused / lands rankless and the owner can never
190
+ // claim their own instance (goal 35 / criterion 165 regression, caught live 2026-07-07:
191
+ // demo had every origin override but no FIRST_ADMIN, so the owner could not be seated).
192
+ // Derived from the instance repo owner (target_ref = "<owner>/<repo>"), the SAME source
193
+ // instanceInitSpec uses for the scaffold's branding.firstAdmin — so this env override and
194
+ // the scaffolded config/branding.json agree. (Org-owned repo: the org login is not a
195
+ // sign-in identity, so it is left unset; the owner sets <prefix>_FIRST_ADMIN by hand.)
196
+ const ownerRepo = parseTargetRef(inst.target_ref);
197
+ const firstAdminLine = ownerRepo && ownerRepo.owner
198
+ ? `${prefix}_FIRST_ADMIN=${ownerRepo.owner}\n`
199
+ : '';
200
+ // Cross-site identity federation block (ADR 0141 §4, task #1002109). Present only for
201
+ // a HOSTED (federated) instance: flips auth.idp ON via the env override branding.js
202
+ // reads (idpEnabled), and carries the hub origin, this instance's per-client secret
203
+ // (authenticates it TO the hub, server-to-server), and the hub's PUBLIC key (verifies
204
+ // the CLI's hub-signed assertion offline). All written straight to this chmod-600
205
+ // file — never through a browser or chat (the ADR 0133 secret-placement posture).
206
+ const fedLines = federation
207
+ ? `${prefix}_AUTH_IDP=true
208
+ ${prefix}_HUB_ORIGIN=${federation.hubOrigin}
209
+ ${prefix}_HUB_CLIENT_ID=${federation.clientId}
210
+ ${prefix}_HUB_CLIENT_SECRET=${federation.clientSecret}
211
+ ${prefix}_HUB_PUBLIC_KEY=${federation.hubPublicKey}
212
+ `
213
+ : '';
214
+ // The publish credential (task 1003049, ADR 0055): the manifest-born App's id +
215
+ // private key, read BARE (no prefix) by modules/lifecycle/github-push.js. The
216
+ // pem is env-file-flattened to a single line with literal \n (the ADR 0055
217
+ // rotation convention; normalizePem restores it). The installation id is NOT
218
+ // written — the server discovers it from the repo once the owner installs the
219
+ // App (the onboarding app-install step).
220
+ const appLines = appId && appPem
221
+ ? `# Publish credential (ADR 0055): the sign-in GitHub App doubles as it. The server
222
+ # mints short-lived installation tokens once the App is INSTALLED on the repo.
223
+ GITHUB_APP_ID=${appId}
224
+ GITHUB_APP_PRIVATE_KEY=${String(appPem).replace(/\r?\n/g, '\\n')}
225
+ `
226
+ : '';
227
+ return `# /etc/${inst.slug}/web.env — per-instance secrets (chmod 600, OUTSIDE the repo).
228
+ ${credComment}
229
+ ${prefix}_GITHUB_CLIENT_ID=${clientId || ''}
230
+ ${prefix}_GITHUB_CLIENT_SECRET=${clientSecret || ''}
231
+ ${firstAdminLine}GDS_BRANDING_FILE=${PROVISIONED_BRANDING_FILE}
232
+ ${originLines}${appLines}PORT=${inst.port}
233
+ PGDATABASE=${dbName(inst)}
234
+ ${fedLines}${settingsEnvLines(inst, prefix)}`;
235
+ }
236
+ function webEnvPath(inst, { etcBase = CONFIG.etcBase } = {}) {
237
+ return path.posix.join(etcBase, inst.slug, 'web.env');
238
+ }
239
+
240
+ // ── Cross-site identity: federate a hosted instance's sign-in through the hub ───
241
+ // (ADR 0141 §4, task #1002109). The control plane IS the hub (cloudbongos.com), so
242
+ // registration is a direct in-process call against the hub pool — no HTTP hop.
243
+
244
+ // The hub's own public origin, written into each federated instance as
245
+ // <PREFIX>_HUB_ORIGIN (what auth.js loadIdpConfig reads). Explicit PROVISION_HUB_ORIGIN
246
+ // wins; otherwise the control plane's OWN public origin (<PREFIX>_PUBLIC_ORIGIN, the
247
+ // env the hub already runs under). Null → not hub-configured → shouldFederate is false.
248
+
249
+ module.exports = {
250
+ PROVISIONED_BRANDING_FILE,
251
+ backupScriptPath,
252
+ backupService,
253
+ backupServicePath,
254
+ backupTimer,
255
+ backupTimerPath,
256
+ backupUnitName,
257
+ provisionedEnvPrefix,
258
+ serviceUnit,
259
+ serviceUnitPath,
260
+ webEnvBody,
261
+ webEnvPath,
262
+ };