@bongos/core 1.17.0 → 1.17.1
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 +154 -83
- package/CLAUDE.md +11 -238
- package/bin/bongos.js +1 -0
- package/clients/bongos-client/README.md +4 -4
- package/clients/bongos-client/bongos-client.global.js +14 -1
- package/clients/bongos-client/index.cjs +14 -1
- package/clients/bongos-client/index.d.ts +19 -2
- package/clients/bongos-client/index.mjs +11 -1
- package/clients/bongos-client/package.json +1 -1
- package/docs/adr/0133-github-app-manifest-onboarding-signin.md +2 -0
- package/docs/adr/0134-private-first-npm-distribution.md +20 -6
- package/docs/adr/0135-module-upstream-submission-interim-queue.md +34 -0
- package/docs/adr/README.md +5 -4
- package/docs/api/openapi.json +315 -6
- package/docs/api-reference.md +10 -5
- package/docs/architecture.md +1 -1
- package/docs/file-map.md +2 -2
- package/docs/modules-contract.md +2 -0
- package/docs/recipes/instance-deploy.md +4 -4
- package/docs/recipes/instance-first-run.md +51 -0
- package/docs/recipes/packaging-the-core.md +5 -5
- package/docs/recipes/private-npm-distribution.md +22 -12
- package/docs/recipes/self-host.md +1 -1
- package/docs/recipes/upgrading-the-core.md +8 -8
- package/migrations/core_191_module_submissions.sql +45 -0
- package/modules/autonomy/CLAUDE.md +11 -45
- package/modules/builder-settings/CLAUDE.md +11 -54
- package/modules/dev-box/CLAUDE.md +11 -34
- package/modules/discord/CLAUDE.md +11 -46
- package/modules/economy/CLAUDE.md +11 -44
- package/modules/grading/CLAUDE.md +11 -38
- package/modules/hall-ui/CLAUDE.md +11 -57
- package/modules/hall-ui/public/goals-page.js +227 -22
- package/modules/hall-ui/public/goals.html +16 -0
- package/modules/hall-ui/public/new-project-wizard.js +5 -4
- package/modules/ideas/CLAUDE.md +11 -33
- package/modules/lifecycle/CLAUDE.md +11 -59
- package/modules/lifecycle/routes/goals.js +80 -0
- package/modules/memory/CLAUDE.md +11 -36
- package/modules/onboarding/CLAUDE.md +11 -51
- package/modules/security/CLAUDE.md +11 -40
- package/modules/sessions/CLAUDE.md +11 -35
- package/modules/status-ui/CLAUDE.md +11 -43
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/create.html +58 -12
- package/public-landing/index.html +40 -21
- package/scripts/gds/CLAUDE.md +11 -140
- package/scripts/gds/claude-materialize.js +151 -18
- package/scripts/gds/claude-md-template.js +84 -0
- package/scripts/gds/cli-lib.js +112 -2
- package/scripts/gds/gen-api-client.js +10 -10
- package/scripts/gds/init.js +53 -13
- package/scripts/gds/migration-namespace.js +1 -1
- package/scripts/gds/module.js +118 -4
- package/scripts/gds/npmrc.js +93 -0
- package/scripts/gds/onboard.js +13 -24
- package/scripts/gds/package-core.js +56 -14
- package/scripts/gds/provision.js +102 -17
- package/scripts/gds/seed-adopt-tasks.js +1 -1
- package/scripts/gds/status.js +6 -1
- package/scripts/gds/task.js +166 -0
- package/scripts/gds/upgrade.js +58 -18
- package/src/bongos/CLAUDE.md +11 -60
- package/src/bongos/auth.js +11 -0
- package/src/bongos/module-submissions.js +56 -0
- package/src/bongos/route-rank-check.js +6 -0
- package/src/bongos/routes/CLAUDE.md +11 -39
- package/src/bongos/routes/auth.js +6 -1
- package/src/bongos/routes/modules.js +123 -21
- package/src/instance-config.js +1 -1
- package/src/module-api.js +13 -1
- package/tests/claude_materialize.mjs +105 -0
- package/tests/device_flow_start.mjs +73 -0
- package/tests/first_run_engine_check.mjs +79 -0
- package/tests/goal_membership_requests.mjs +33 -3
- package/tests/hook_user_agents.mjs +62 -0
- package/tests/init.mjs +59 -6
- package/tests/instance_mismatch_guard.mjs +101 -0
- package/tests/module_cli.mjs +130 -2
- package/tests/module_submissions.mjs +135 -0
- package/tests/npmrc.mjs +105 -0
- package/tests/onboard.mjs +49 -0
- package/tests/package_core.mjs +68 -22
- package/tests/platform_boot.mjs +5 -2
- package/tests/provision.mjs +107 -8
- package/tests/provisioning_github_app_routes.mjs +29 -2
- package/tests/served_paths_shipped.mjs +2 -2
- package/tests/task_cli.mjs +99 -0
- package/tests/upgrade.mjs +89 -25
package/scripts/gds/upgrade.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
// pre-flight clean git tree + module-compat vs the TARGET core version
|
|
9
9
|
// → bump pin vendor the new core .tgz, rewrite the package.json `file:` dependency
|
|
10
|
-
// → npm install node_modules/@
|
|
10
|
+
// → npm install node_modules/@bongos/core becomes the new version
|
|
11
11
|
// → migrate `npm run migrate` → the core's migrate.sh applies additive `core_*` migrations
|
|
12
12
|
// → materialize refresh .claude/ skills+settings+hooks from the new core (claude-materialize / W3)
|
|
13
13
|
// → restart restart the instance's service so the new core is live
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// SCOPE (ADR 0100 §2, reshaped by ADR 0103 §4): the SMALLEST channel the FIRST real consumer needs
|
|
17
17
|
// on its first core bump — driven by that consumer, not designed up front. That consumer is
|
|
18
18
|
// emersonian-circles (task 1917): a greenfield npm-dependency instance that pins the core as
|
|
19
|
-
// "@
|
|
19
|
+
// "@bongos/core": "file:vendor/bongos-core-<ver>.tgz"
|
|
20
20
|
// with `start`/`migrate` npm scripts and a materialized .claude/. Every step below maps to something
|
|
21
21
|
// that consumer actually has. A published-registry source, rollback, and multi-consumer shapes are
|
|
22
22
|
// deliberately OUT — they are follow-ups a second consumer's needs will define.
|
|
@@ -30,9 +30,10 @@ const fs = require('node:fs');
|
|
|
30
30
|
const path = require('node:path');
|
|
31
31
|
const { spawnSync } = require('node:child_process');
|
|
32
32
|
const { arg, hasFlag } = require('./cli-lib');
|
|
33
|
+
const { writeNpmrc, hasNpmToken } = require('./npmrc'); // ADR 0134 (task 2090): the --registry channel's env-fed .npmrc
|
|
33
34
|
|
|
34
|
-
const CORE_PKG = '@
|
|
35
|
-
const ARTIFACT = '
|
|
35
|
+
const CORE_PKG = '@bongos/core';
|
|
36
|
+
const ARTIFACT = 'bongos-core';
|
|
36
37
|
|
|
37
38
|
// ---- pure helpers (no I/O side effects beyond the injected fs) --------------
|
|
38
39
|
|
|
@@ -44,6 +45,16 @@ function versionFromTgzPath(p) {
|
|
|
44
45
|
return m ? m[1] : null;
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
// The version from a REGISTRY dep spec — a bare semver, optionally range-prefixed
|
|
49
|
+
// (`1.17.0`, `^1.17.0`, `~1.17.0`). Returns null for a `file:` tarball spec (that's
|
|
50
|
+
// versionFromTgzPath's job) so the two never both match. ADR 0134 (task 2090).
|
|
51
|
+
function versionFromDep(dep) {
|
|
52
|
+
const s = String(dep || '').trim();
|
|
53
|
+
if (!s || /^file:/.test(s)) return null;
|
|
54
|
+
const m = s.match(/^[\^~>=<\s]*v?(\d+\.\d+\.\d+[A-Za-z0-9.+-]*)$/);
|
|
55
|
+
return m ? m[1] : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
47
58
|
function readJson(file, fsImpl = fs) {
|
|
48
59
|
try { return JSON.parse(fsImpl.readFileSync(file, 'utf8')); } catch { return null; }
|
|
49
60
|
}
|
|
@@ -54,11 +65,13 @@ function readInstalledCoreVersion(instanceDir, fsImpl = fs) {
|
|
|
54
65
|
return pkg && pkg.version ? pkg.version : null;
|
|
55
66
|
}
|
|
56
67
|
|
|
57
|
-
// The version the consumer's package.json currently pins
|
|
68
|
+
// The version the consumer's package.json currently pins — from either the `file:`
|
|
69
|
+
// tarball path (vendored) or a bare registry semver (ADR 0134). A best-effort "from"
|
|
70
|
+
// hint; readInstalledCoreVersion (node_modules) is the authoritative source.
|
|
58
71
|
function readPinnedCoreVersion(instanceDir, fsImpl = fs) {
|
|
59
72
|
const pkg = readJson(path.join(instanceDir, 'package.json'), fsImpl);
|
|
60
73
|
const dep = pkg && ((pkg.dependencies || {})[CORE_PKG] || (pkg.devDependencies || {})[CORE_PKG]);
|
|
61
|
-
return versionFromTgzPath(dep);
|
|
74
|
+
return versionFromTgzPath(dep) || versionFromDep(dep);
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
// Rewrite the consumer package.json to pin `file:<vendorRel>`. Returns the previous dep string.
|
|
@@ -73,6 +86,20 @@ function writePin(instanceDir, vendorRel, fsImpl = fs) {
|
|
|
73
86
|
return prev;
|
|
74
87
|
}
|
|
75
88
|
|
|
89
|
+
// Rewrite the consumer package.json to pin an EXACT registry version (ADR 0134, task
|
|
90
|
+
// 2090): the registry sibling of writePin. `npm install` then resolves @bongos/core@version
|
|
91
|
+
// from the registry (auth via the env-fed .npmrc). Returns the previous dep string.
|
|
92
|
+
function writeRegistryPin(instanceDir, version, fsImpl = fs) {
|
|
93
|
+
const pkgPath = path.join(instanceDir, 'package.json');
|
|
94
|
+
const pkg = readJson(pkgPath, fsImpl);
|
|
95
|
+
if (!pkg) throw new Error(`no readable package.json at ${pkgPath}`);
|
|
96
|
+
pkg.dependencies = pkg.dependencies || {};
|
|
97
|
+
const prev = pkg.dependencies[CORE_PKG];
|
|
98
|
+
pkg.dependencies[CORE_PKG] = version;
|
|
99
|
+
fsImpl.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
100
|
+
return prev;
|
|
101
|
+
}
|
|
102
|
+
|
|
76
103
|
// ---- steps (each injectable for tests) --------------------------------------
|
|
77
104
|
|
|
78
105
|
function gitTreeClean(instanceDir, run = spawnSync) {
|
|
@@ -282,18 +309,29 @@ async function runUpgrade(opts, deps = {}) {
|
|
|
282
309
|
log(` ✓ pre-flight: ${mods.note}`);
|
|
283
310
|
if (!mods.ok) return { ok: false, error: `module pre-check failed: ${mods.blocking.map((b) => b.key).join(', ')} incompatible with core ${targetVersion}` };
|
|
284
311
|
|
|
285
|
-
// 2. bump the pin
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
312
|
+
// 2. bump the pin. Two mutually exclusive core sources (ADR 0134, task 2090):
|
|
313
|
+
// REGISTRY (--registry): pin @bongos/core@<version> + ensure the env-fed .npmrc, so
|
|
314
|
+
// the `npm install` below pulls the PRIVATE package from the registry (needs NPM_TOKEN).
|
|
315
|
+
// VENDORED (default, --from <tgz>): copy the .tgz into vendor/ + rewrite the file: dep.
|
|
316
|
+
if (opts.registry) {
|
|
317
|
+
if (opts.from) return { ok: false, error: '--registry and --from are mutually exclusive (registry pull vs vendored tarball) — pick one core source' };
|
|
318
|
+
if (!hasNpmToken()) log(' ! no NPM_TOKEN in env — `npm install` will 404 on the PRIVATE @bongos/core until a read-only automation token is provided (ADR 0134).');
|
|
319
|
+
const prevPin = dryRun ? null : writeRegistryPin(instanceDir, targetVersion, fsImpl);
|
|
320
|
+
if (!dryRun) writeNpmrc(instanceDir, { fsImpl, overwrite: false, log });
|
|
321
|
+
log(` ${dryRun ? '[dry-run] would pin' : '✓ pinned'} ${CORE_PKG} → ${targetVersion} (registry)${dryRun ? ' + write an env-fed .npmrc' : ''}${prevPin ? ` (was ${prevPin})` : ''}`);
|
|
322
|
+
} else {
|
|
323
|
+
let vendorRel = opts.pinPath || (opts.from ? null : path.join('vendor', `${ARTIFACT}-${targetVersion}.tgz`));
|
|
324
|
+
if (opts.from) {
|
|
325
|
+
if (!fsImpl.existsSync(opts.from)) return { ok: false, error: `--from tarball not found: ${opts.from}` };
|
|
326
|
+
if (dryRun) { log(` [dry-run] would vendor ${opts.from} → vendor/${ARTIFACT}-${targetVersion}.tgz`); vendorRel = path.join('vendor', `${ARTIFACT}-${targetVersion}.tgz`); }
|
|
327
|
+
else vendorRel = vendorTarball({ instanceDir, fromTgz: path.resolve(opts.from), targetVersion }, fsImpl);
|
|
328
|
+
}
|
|
329
|
+
if (!fsImpl.existsSync(path.join(instanceDir, vendorRel)) && !dryRun) {
|
|
330
|
+
return { ok: false, error: `no core tarball at ${vendorRel} — pass --from <path-to-.tgz> to vendor it (or --registry to pull from the npm registry)` };
|
|
331
|
+
}
|
|
332
|
+
const prevPin = dryRun ? null : writePin(instanceDir, vendorRel, fsImpl);
|
|
333
|
+
log(` ${dryRun ? '[dry-run] would pin' : '✓ pinned'} ${CORE_PKG} → file:${vendorRel}${prevPin ? ` (was ${prevPin})` : ''}`);
|
|
294
334
|
}
|
|
295
|
-
const prevPin = dryRun ? null : writePin(instanceDir, vendorRel, fsImpl);
|
|
296
|
-
log(` ${dryRun ? '[dry-run] would pin' : '✓ pinned'} ${CORE_PKG} → file:${vendorRel}${prevPin ? ` (was ${prevPin})` : ''}`);
|
|
297
335
|
|
|
298
336
|
if (dryRun) { log(' [dry-run] would: npm install → npm run migrate → materialize .claude → regen API docs+client → restart → verify'); return { ok: true, dryRun: true, fromVersion, toVersion: targetVersion }; }
|
|
299
337
|
|
|
@@ -366,6 +404,7 @@ function parseArgs(argv) {
|
|
|
366
404
|
sourceCommit: arg('--source-commit', argv),
|
|
367
405
|
note: arg('--note', argv),
|
|
368
406
|
pinPath: arg('--pin-path', argv),
|
|
407
|
+
registry: hasFlag('--registry', argv), // ADR 0134: pull @bongos/core@<--to> from the npm registry instead of vendoring
|
|
369
408
|
dryRun: hasFlag('--dry-run', argv),
|
|
370
409
|
force: hasFlag('--force', argv),
|
|
371
410
|
skipMigrate: hasFlag('--skip-migrate', argv),
|
|
@@ -385,6 +424,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
385
424
|
'',
|
|
386
425
|
' --to <version> target core version — presence switches from pre-check to a real bump',
|
|
387
426
|
' --from <tgz> source tarball to vendor into vendor/ (else it must already be there)',
|
|
427
|
+
' --registry pull @bongos/core@<--to> from the npm registry (env-fed .npmrc + NPM_TOKEN) instead of vendoring a tarball (ADR 0134); mutually exclusive with --from',
|
|
388
428
|
' --instance <dir> consumer instance root (default: cwd)',
|
|
389
429
|
' --service <unit> systemd unit to restart after applying',
|
|
390
430
|
' --restart-cmd <cmd> explicit restart command (overrides --service)',
|
|
@@ -411,7 +451,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
411
451
|
|
|
412
452
|
module.exports = {
|
|
413
453
|
runUpgrade, parseArgs, main,
|
|
414
|
-
versionFromTgzPath, readInstalledCoreVersion, readPinnedCoreVersion, writePin,
|
|
454
|
+
versionFromTgzPath, versionFromDep, readInstalledCoreVersion, readPinnedCoreVersion, writePin, writeRegistryPin,
|
|
415
455
|
vendorTarball, gitTreeClean, preflightModules, npmInstall, runMigrate, regenerateApiArtifacts, restartService, recordLedger, pollHealth,
|
|
416
456
|
CORE_PKG, ARTIFACT,
|
|
417
457
|
};
|
package/src/bongos/CLAUDE.md
CHANGED
|
@@ -1,64 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @bongos/core — installed platform dependency (not your project's CLAUDE.md)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This file is a placeholder shipped inside the **@bongos/core** package. The core's own
|
|
4
|
+
internal agent notes are intentionally omitted from the published artifact so they cannot
|
|
5
|
+
load into your instance's context.
|
|
4
6
|
|
|
5
|
-
**
|
|
7
|
+
**It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
|
|
8
|
+
directory, and the live Bongos database are the source of truth for how work happens here
|
|
9
|
+
and who may do what.
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **Lifecycle graduated out** → [`modules/lifecycle/`](../../modules/lifecycle/CLAUDE.md) (the build state machine — tasks · claims · versions · done-when · goals · dependencies · the claim→ship→grade→credit lifecycle · github-push + merge-lock + conflict-resolve + publish-reconciler + ship-card/preflight + version-close; the LAST + most-coupled domain, BV1.R86). Core reaches it only through the `lifecycle` kernel **port** the module registers — `routes/{public,me,builders}.js` + `cascade-dispatch.js` resolve `seams.resolveOptional('lifecycle')` for the version/ship feed, per-builder claims/grades, the Archon roster, and the task-kind classifier; the moved routes (`routes/{tasks,claims,versions,done-when,goals,dependencies,gate-approvals,analytics}.js`) mount via the loader. It **retired the last three doorway leaks** (`createTask`/`VOTABLE_TASK_STATUSES`/`tallyPeerVotes` → behind the port). As a transaction-participant it RESOLVES the `reward`+`onboarding` ports inside its claim/ship/confirm txns ([ADR 0093](../../docs/adr/<redacted>.md) §1,§3).
|
|
10
|
-
- **Grading graduated out** → [`modules/grading/`](../../modules/grading/CLAUDE.md) (the quality gate: v4 adversarial worker panel Narc/Quality/Hacker/Efficiency + deterministic pre-passes + the rubric; BV1.R73). Core reaches it only through the `grade` kernel **port** — the lifecycle module's `routes/tasks.js` + core `routes/public.js` resolve `seams.resolveOptional('grade')`, never a direct require ([ADR 0091](../../docs/adr/<redacted>.md) §4, [ADR 0024](../../docs/adr/<redacted>.md)). `cascade-dispatch.js` (dormant System 3) stayed core.
|
|
11
|
-
- **Economy graduated out** → [`modules/economy/`](../../modules/economy/CLAUDE.md) (credit allocation + the cost-plus/kind-multiplier model, the cost ledger + dashboard rollups, streaks, achievements, the leaderboard; BV1.R77). Core reaches it only through the `reward` kernel **port** — a **transaction-participant** port: the lifecycle (`applyGrade`/`confirmTask`/`decideOverrideRequest`/`claimTask`/`upsertSessionRecordWithReward`) keeps its state-machine SQL and the `credit_log`/`cost_log` *guards*, but resolves `seams.resolveOptional('reward')` and passes its own tx `client` for every credit/achievement WRITE; the read routes (`me.js`/`public.js`/`builders.js`/`sessions.js`) + `publish-reconciler.js` resolve the same port ([ADR 0093](../../docs/adr/<redacted>.md) §1-2). `llm-pricing.js` stays core shared LLM-cost infra (doorway leak `api.llmPricing`, like `llm-cache.js`).
|
|
12
|
-
- **Ideas graduated out** → [`modules/ideas/`](../../modules/ideas/CLAUDE.md) (the `idea_inbox` capture/triage/criterion-proposal flow + the `blockers` queue; BV1.R78). The discord `#ideas` inbound reaches it only through the `ideas.capture` kernel **port** the module registers — which **retired** the `captureIdea` doorway leak ([ADR 0093](../../docs/adr/<redacted>.md) §5). `routes/public.js` keeps its **own** inline `idea_inbox`/`blockers` SELECTs (core querying a shared table with its own SQL is fine — no import, no twin).
|
|
13
|
-
- **Security FEATURE graduated out** → [`modules/security/`](../../modules/security/CLAUDE.md) (red-team / vulnerability **reports** + the **bounty** payout table, rank-gated Metic+; `security.js` + `routes/reports.js`; BV1.R79). Core reaches the bounty schedule only through the `security` kernel **port** the module registers — `routes/public.js`'s `/public/bounty-table` resolves `seams.resolveOptional('security').bountyTableDetailed()`, never a direct require ([ADR 0093](../../docs/adr/<redacted>.md) §1; [02-module-map §5](../../docs/design/modular-architecture/02-module-map.md)). **<redacted> stays kernel:** `permission-path-check.js`/`route-rank-check.js` (authz) AND `routes/security.js` (the `/security/adr` threat-model reader + the Archon-only `/security/docs` exploit-detail store, backed by core `db.js` `security_docs` queries) all stayed in core.
|
|
14
|
-
- **Builder-settings graduated out** → [`modules/builder-settings/`](../../modules/builder-settings/CLAUDE.md) (the per-builder PREFS domain — wandering · skill · sound · render — + builder-needs + their `db.js` slice; the five `/me/*-prefs` routes; BV1.R80). The `GET /me` aggregator (`routes/me.js`) + `cascade-dispatch.js` reach it only through the `builder-settings` kernel **port** the module registers (`seams.resolveOptional('builder-settings')` for the wandering/render/needs profile slices + the skill ceiling), never a direct require ([ADR 0093](../../docs/adr/<redacted>.md) §1). The five `skill_model_prefs`/`sound_prefs`/`behavior_prefs`/`event_sound_prefs` getter/setter pairs left `db.js`; the `builders` table + the Gemini-key columns (art-pipeline credential infra) stay core.
|
|
15
|
-
- `auth.js` — GitHub Device Flow (CLI) + web OAuth → `builder_sessions`. Exports the request guards: `requireBuilder`, `requireRank`, `allowBoxScope` (box tokens are deny-by-default, [ADR 0053](../../docs/adr/0053-scoped-dev-box-session.md)), plus Discord OAuth helpers.
|
|
16
|
-
- `github-push.js` — server-mediated branch publish ([ADR 0055](../../docs/adr/<redacted>.md)): box uploads a git bundle, server pushes + opens PR + auto-merges via a short-lived **GitHub App** installation token (no standing PAT). Also `mergeIfGreen`, gate commit-status helpers.
|
|
17
|
-
- `routes.js` + `routes/` — **thin mounter**: each feature is a sub-router in `routes/`; adding a feature = one new file + one `.use()` line. Audit + rate-limit middleware mount first. See `routes/`'s own nested CLAUDE.md.
|
|
18
|
-
- `permission-path-check.js` / `route-rank-check.js` — the pure protected-path + route-rank matchers (SEC [#863](https://example.com/builders#/task/863) / [ADR 0043](../../docs/adr/<redacted>.md)), reused by the grading module (via the doorway — `api.routeRankCheck`/`api.permissionPathCheck`), pre-push hook, and `--fit` runner.
|
|
19
|
-
- **Onboarding graduated out** → [`modules/onboarding/`](../../modules/onboarding/CLAUDE.md) (admission · the server-ticked newcomer onboarding state machine · the friction analyzer · the evergreen newcomer-chore restock; `onboarding-state.js` + `onboarding.js` + `newcomer-restock.js` + `routes/access-requests.js` + `approval-broadcast.js` + its `db.js` slice; BV1.R81). The novel shape: it is the FIRST core→module port the LIFECYCLE drives — core reaches the state machine + restock only through the `onboarding` kernel **port** the module registers (`seams.resolveOptional('onboarding')` in `db.js` claim/ship, `auth.js`, `routes/me.js`/`builders.js`/`tasks.js`/`claims.js`), never a direct require ([ADR 0093](../../docs/adr/<redacted>.md) §1). The admission GATE READS (`hasInvitedAccessRequest`/`getBuilderByGithubId`) stay core `db.js` (kernel identity/admission).
|
|
20
|
-
- **Sessions/BFG graduated out** → [`modules/sessions/`](../../modules/sessions/CLAUDE.md) (the `session_records` corpus + its `/sessions` data plane, the BFG cross-builder scorer, the session-pulse/stale-claim helpers; `bfg.js` + `pulse.js` + `routes/sessions.js` + its `db.js` slice; BV1.R82). Like `memory`, it registers **NO port** — core never calls back in: the ship-time session-digest write is an **HTTP POST** to the module's own `/sessions` route. Its one outward edge is the economy `reward` port it *resolves* (`seams.resolveOptional('reward')` in `db.js` `upsertSessionRecordWithReward` + `routes/sessions.js`) for the cost-plus token reward + `sumCreditsForTasks` ([ADR 0093](../../docs/adr/<redacted>.md) §1). **Stays core:** `session_logs` (the per-claim ship LOG, `sealSessionLog` — lifecycle) and `builder_sessions` (auth tokens) are NOT this domain.
|
|
21
|
-
- **Autonomy/MAS graduated out** → [`modules/autonomy/`](../../modules/autonomy/CLAUDE.md) (the Multi-Agent System — the autonomy gate, the conductor/architect/run-routine CLIs, and the in-process routine timers; `routes/autonomy.js` + `pollers/routine-timers.js` + the `autonomy_runs` `db.js` slice; BV1.R83). Like `sessions`/`memory`, it registers **NO port** — core never calls back in. The cross-cutting bit: the **routine timer** (was `routineTimers.start()` in `server.js`) is now a loader-started **poller** (`contributes.pollers`), and the **precheck route** (was mounted in `routes.js`) a module route; both severed their `src/`/`server.js` import. The timer's one outward edge is the lifecycle **`tallyPeerVotes`** capability it resolves through the **doorway** (`api.tallyPeerVotes` — the SAME tx `POST /tasks/peer-votes/tally` runs; a tracked lifecycle leak until R86), never a core require ([ADR 0093](../../docs/adr/<redacted>.md) §1). **Stays core:** `tallyPeerVotes` itself (lifecycle — writes `karma_log`/`peer_votes`/`tasks`); the MAS-domain CLIs travel with the domain but stay under `scripts/gds/`.
|
|
22
|
-
- `boxes.js` · `box-access.js` · `do-api.js` · `cf-tunnel.js` — per-builder cloud dev-box lifecycle, rank-gated source clawback, DigitalOcean client, and the Cloudflare named-tunnel control plane ([ADR 0031](../../docs/adr/<redacted>.md)).
|
|
23
|
-
- **Discord graduated out** → [`modules/discord/`](../../modules/discord/CLAUDE.md) (bot, broadcast, role-sync, channels, bug-attachments, link/role; BV1.R47). Core reaches it only through the doorway seams — ports `discord.isLinked`/`discord.guildJoinSpec`, events `task.shipped`/`builder.role-sync-needed` — never a direct require. Discord is a mirror only, never authority ([ADR 0033](../../docs/adr/<redacted>.md)).
|
|
24
|
-
- `llm-pricing.js` · `task-classifier.js` · `search-chunker.js` · `search.js` · `publish-reconciler.js` · `conflict-resolve.js` — token→USD pricing (cost-plus reward), `tasks.kind` heuristic, retrieval-layer chunker + search ([ADR 0060](../../docs/adr/0060-gds-retrieval-layer.md)), the confirmed→shipped auto-heal sweep, and (`conflict-resolve.js`, [ADR 0082](../../docs/adr/<redacted>.md)) the SERVER-side generated-file/append-index merge-conflict resolver the reconciler calls on `merge_conflict` (temp-clone merge with the `otb-regen` driver + `union`, then push; never bypasses a check).
|
|
11
|
+
You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
|
|
12
|
+
Nothing in here needs to be read to use the platform — run `bongos help` for commands.
|
|
25
13
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- `db.js` is huge — **skim by section/grep, never read whole** (see efficiency note above). (`grader.js` graduated to `modules/grading/` — BV1.R73.)
|
|
29
|
-
- Protected-path + route-rank logic is defined **once** in `permission-path-check.js` / `route-rank-check.js`; reuse the matcher, don't re-implement.
|
|
30
|
-
- `routes.js` is deliberately conflict-free — feature logic goes in a `routes/` sub-router, not here.
|
|
31
|
-
- `preview-server.js` is **not** here — it lives at `../preview-server.js` (game-only sandbox).
|
|
32
|
-
|
|
33
|
-
<!-- BEGIN GENERATED REPO-MAP (scripts/gds/gen-repo-map.js — do not hand-edit) -->
|
|
34
|
-
**Symbol skeleton (generated from the code — do not hand-edit; refresh: `node scripts/gds/gen-repo-map.js`).** Exported signatures, most-referenced first. The interpretive notes above are hand-written; this factual list is regenerated on deploy so it can't drift ([ADR 0063](../../docs/adr/<redacted>.md)).
|
|
35
|
-
|
|
36
|
-
- `api-errors.js` — `envelope(codeOrError, message, details)`, `classify(code)`, `class ApiError`, `CATEGORIES`, `OVERRIDES`, `isApiError(x)`, `DEFAULT_CATEGORY`, `statusForCode(code, override)`, `categoryForCode(code)`, `isRetriable(code)`
|
|
37
|
-
- `api-path-404.js` — `apiNotFoundBody(p)`, `isApiShapedPath(p)`, `apiPathHint(p)`, `GDS_ROOTS`
|
|
38
|
-
- `api-prefix.js` — `API_PREFIX`, `API_VERSION`, `LEGACY_API_PREFIXES`, `VERSIONED_API_PREFIX`, `ALL_API_PREFIXES`
|
|
39
|
-
- `app-pair.js` — `pairStore`, `createPairStore(opts = {})`, `normalizeCode(raw)`
|
|
40
|
-
- `auth.js` — `requireBuilder(req, res, next)`, `requireRank(...allowed)`, `allowBoxScope(req, _res, next)`, `authConfigured()`, `requireBfgPrincipal(req, res, next)`, `loadDiscordBotToken()`, `requireArchonPage(req, res, next)`, `isValidDiscordUserId(id)`, `requireBuilderPage(req, res, next)`, `completeWebFlow({ code, redirectUri, userAgent })` _(+20 more)_
|
|
41
|
-
- `cascade-dispatch.js` — `dispatch({ prompt, taskKind, title, description, discipline, ceiling = 'opus', expectSchema = null,…)`, `LADDER`, `startRungForTask({ taskKind, title, description } = {})`, `startRungForKind(taskKind)`, `ceilingForBuilder(builderPrefs, skillName)`, `MAX_ESCALATIONS`, `rungIndex(model)`, `shouldEscalate(result, expectSchema)`
|
|
42
|
-
- `db-kernel.js` — `getBuilderById(id)`, `insertAuditLog({)`, `withTx(fn, { pool: p = pool } = {})`, `RANKS`, `LIVE_RANK_LADDER`, `createSession({ builderId, token, source, userAgent, expiresAt })`, `lookupSession(token)`, `upsertBuilder({ githubId, githubLogin, displayName, avatarUrl })`, `getBuilderByGithubId(githubId)`, `hasInvitedAccessRequest(login)` _(+3 more)_
|
|
43
|
-
- `db.js` — `setBuilderRank({ builderId, newRank, actorBuilderId })`, `getDiscordLink(builderId)`, `hasCliSession(builderId)`, `recordApplause({ targetTaskId, discordUserId })`, `singleRungPromotionError(oldRank, newRank)`, `deactivateBuilder({ builderId, reason, actorBuilderId })`, `<redacted>(builderId)`, `listAuditLog({ limit = 100, offset = 0, builderId, method, surface } = {})`, `revokeAllSessionsForBuilder(builderId)`, `unlinkDiscord(builderId)` _(+11 more)_
|
|
44
|
-
- `goal-scope-check.js` — `check(goalScopeModules, touches, builderRank)`, `isCovered(touch, goalGlobs)`, `protectedPathBlock(touches, builderRank)`, `scopeSubset(goalScopeModules, touches)`
|
|
45
|
-
- `hierarchy-config.js` — `tier`, `hierarchy()`, `tiers`, `INSTANCE_PATH`, `DEFAULT_HIERARCHY`, `ROLE_KEYS`, `_resetForTests()`, `STANDARD_KEYS`, `loadHierarchy({ instancePath, env = process.env } = {})`, `membershipTier` _(+9 more)_
|
|
46
|
-
- `live-channel.js` — `subscribe(fn)`, `CHANNEL`, `_state()`
|
|
47
|
-
- `llm-cache.js` — `store(spec, { resultStdout, resultCostUsd = 0 } = {})`, `normalize(taskType, prompt)`, `makeCacheSpec({ prompt, model, task_type, scope_sha, owner_builder_id = null, ttl_ms = null } = {})`, `NORMALIZERS`, `poolReachable()`, `withCache(spec, producer)`, `computeCacheKey({ prompt, model, task_type, scope_sha } = {})`, `recordSaving(spec, { resultCostUsd = 0, hitCount = 0 } = {})`, `scopeShaFor(paths, { runner, cwd } = {})`, `stripVolatile(text)` _(+2 more)_
|
|
48
|
-
- `llm-pricing.js` — `priceUsage(model, usage)`, `PRICES`, `priceModelUsage(modelUsage)`, `componentsFromModelUsage(modelUsage)`, `familyFor(model)`, `canonicalizeModelId(model)`
|
|
49
|
-
- `metrics.js` — `render(p = pool)`, `HIST_BUCKETS`, `authorizeScrape(req)`, `httpMiddleware()`, `incCounter(name, help, labels = {}, by = 1)`, `collectDbGauges(p = pool)`, `metricsHandler()`, `normalizeRoute(p)`, `observeHistogram(name, help, labels, value)`, `__resetForTests()`
|
|
50
|
-
- `module-scope-map.js` — `MODULE_SCOPE_MAP`, `moduleKeys()`, `scopeIncludesProtected(keys)`, `isModuleKey(key)`, `isProtected(globs)`, `MODULE_GLOBS`, `globsForModules(keys)`, `scopeForModule(key)`, `isProtectedModule(key)`, `protectedModules()`
|
|
51
|
-
- `optimizer.js` — `VALID_MODES`, `DEFAULT_MAX_PARALLEL`, `optimizeSession({ builderId, mode, timeBudgetMin, maxParallel })`
|
|
52
|
-
- `path-match.js` — `touchesOverlap(a, b)`, `matchOne(file, touches)`
|
|
53
|
-
- `permission-path-check.js` — `PROTECTED_GLOBS`, `SPECIALTY`, `matchProtected(files)`, `checkPermissionPaths({ changedFiles, builderRank } = {})`, `globToRegExp(glob)`, `RANK_TIER`, `tierOf(rank)`, `toWorkerResult(result)`, `MIN_TIER`, `isBelowFloor(rank)`
|
|
54
|
-
- `pool.js` — `instanceDbName()`
|
|
55
|
-
- `repo-info.js` — `loadRepoInfo()`, `parseRemote(remote)`, `_resetCacheForTests()`
|
|
56
|
-
- `route-rank-check.js` — `EXPECTED_RANKS`, `SPECIALTY`, `MODULES_DIR`, `MODULE_EXPECTED_RANKS`, `checkRouteRanks({ routesDir = ROUTES_DIR, modulesDir } = {})`, `toWorkerResult(result)`, `KNOWN_RANKS`, `WRITE_METHODS`, `classifyRank(lines, i)`, `classifyText(text)` _(+2 more)_
|
|
57
|
-
- `routes.js` — `buildGdsRouter()`
|
|
58
|
-
- `secret-box.js` — `VERSION`, `isConfigured()`, `decrypt(blob)`, `encrypt(plaintext)`, `provisionMasterKey(opts = {})`, `masterKeyFile()`
|
|
59
|
-
- `sensitive-surfaces.js` — `isSensitive(file)`, `SENSITIVE_SURFACES`
|
|
60
|
-
- `serve-internal.js` — `isBuildersHost(req)`, `mountInternalSurfaces(app)`, `isStatusHost(req)`, `injectBrandingGlobal(html, brand = clientBranding()`, `applyBrandTokens(html, brand = clientBranding()`, `injectBrandingInto(stamped, brand = clientBranding()`, `injectModuleHallWidgets(html, scripts = hallWidgetScripts({ isEnabled: isModuleEnabled })`, `themeMarkup(theme)`
|
|
61
|
-
- `uptime-poller.js` — `start()`
|
|
62
|
-
<!-- END GENERATED REPO-MAP -->
|
|
63
|
-
|
|
64
|
-
**See also.** [ADR 0010](../../docs/adr/0010-pms-architecture.md) (GDS architecture) · [ADR 0016](../../docs/adr/<redacted>.md) (trust boundary) · [docs/architecture.md](../../docs/architecture.md) · `routes/CLAUDE.md` (HTTP surface).
|
|
14
|
+
_To work ON the platform core itself, use the standalone @bongos/core source repository,
|
|
15
|
+
not this vendored copy._
|
package/src/bongos/auth.js
CHANGED
|
@@ -224,6 +224,17 @@ async function startDeviceFlow() {
|
|
|
224
224
|
throw new Error(`device/code failed (${res.status}): ${text}`);
|
|
225
225
|
}
|
|
226
226
|
const data = await res.json();
|
|
227
|
+
// GitHub returns HTTP 200 with an { error, error_description } body for OAuth-App
|
|
228
|
+
// misconfig — most often device_flow_disabled (Device Flow not enabled on the App)
|
|
229
|
+
// or incorrect_client_credentials. res.ok is TRUE there, so without this the missing
|
|
230
|
+
// device_code silently produced a broken flow / an opaque 500 (the 2026-07-08 demo
|
|
231
|
+
// login failure). Mirror the token-exchange guard (data.error, above); task 2126.
|
|
232
|
+
if (data.error) {
|
|
233
|
+
throw new Error(`GitHub rejected device-flow start: ${data.error}${data.error_description ? ` — ${data.error_description}` : ''}`);
|
|
234
|
+
}
|
|
235
|
+
if (!data.device_code) {
|
|
236
|
+
throw new Error('GitHub device/code returned no device_code (unexpected response shape).');
|
|
237
|
+
}
|
|
227
238
|
// data: { device_code, user_code, verification_uri, expires_in, interval }
|
|
228
239
|
deviceFlows.set(data.device_code, {
|
|
229
240
|
interval: data.interval || 5,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/bongos/module-submissions.js — the ADR 0107 §1 upstreaming review queue
|
|
2
|
+
// (task 1770, migration core_191). A rank-gated (metic+archon) queue a builder
|
|
3
|
+
// files a packaged module into (manifest + attribution + file snapshot + DCO
|
|
4
|
+
// sign-off) before it can be reviewed/accepted upstream. See
|
|
5
|
+
// docs/adr/0135-module-upstream-submission-interim-queue.md for why this queue
|
|
6
|
+
// lives in THIS instance's own DB rather than a live cross-repo call.
|
|
7
|
+
//
|
|
8
|
+
// Pure DB access — the packaging (pre-check, file read, sign-off) is
|
|
9
|
+
// scripts/gds/module.js's job (build tooling, fs-only); the route wires the
|
|
10
|
+
// two together.
|
|
11
|
+
|
|
12
|
+
const { pool } = require('./pool');
|
|
13
|
+
|
|
14
|
+
async function createModuleSubmission({
|
|
15
|
+
moduleKey, submittedBy, manifest, files, signOff, precheckPassedAt, sourceRepo,
|
|
16
|
+
}) {
|
|
17
|
+
const { rows } = await pool.query(
|
|
18
|
+
`INSERT INTO module_submissions
|
|
19
|
+
(module_key, submitted_by, manifest, files, sign_off, precheck_passed_at, source_repo)
|
|
20
|
+
VALUES ($1, $2, $3::jsonb, $4::jsonb, $5::jsonb, $6, $7)
|
|
21
|
+
RETURNING id, module_key, submitted_by, manifest, sign_off, precheck_passed_at,
|
|
22
|
+
source_repo, status, reviewed_by, reviewed_at, review_notes, submitted_at`,
|
|
23
|
+
[
|
|
24
|
+
moduleKey,
|
|
25
|
+
submittedBy,
|
|
26
|
+
JSON.stringify(manifest),
|
|
27
|
+
JSON.stringify(files),
|
|
28
|
+
JSON.stringify(signOff),
|
|
29
|
+
precheckPassedAt,
|
|
30
|
+
sourceRepo || null,
|
|
31
|
+
]
|
|
32
|
+
);
|
|
33
|
+
return rows[0];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Metadata-only list (no files jsonb — the file snapshot can run to hundreds of
|
|
37
|
+
// KB per submission, and no list consumer needs it; a future GET .../:id can add
|
|
38
|
+
// a with-files fetch if task 1771's review UI needs the code inline).
|
|
39
|
+
async function listModuleSubmissions({ status, moduleKey } = {}) {
|
|
40
|
+
const clauses = [];
|
|
41
|
+
const params = [];
|
|
42
|
+
if (status) { params.push(status); clauses.push(`status = $${params.length}`); }
|
|
43
|
+
if (moduleKey) { params.push(moduleKey); clauses.push(`module_key = $${params.length}`); }
|
|
44
|
+
const where = clauses.length ? `WHERE ${clauses.join(' AND ')}` : '';
|
|
45
|
+
const { rows } = await pool.query(
|
|
46
|
+
`SELECT id, module_key, submitted_by, manifest, sign_off, precheck_passed_at,
|
|
47
|
+
source_repo, status, reviewed_by, reviewed_at, review_notes, submitted_at
|
|
48
|
+
FROM module_submissions
|
|
49
|
+
${where}
|
|
50
|
+
ORDER BY submitted_at DESC`,
|
|
51
|
+
params
|
|
52
|
+
);
|
|
53
|
+
return rows;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { createModuleSubmission, listModuleSubmissions };
|
|
@@ -177,6 +177,12 @@ const MODULE_EXPECTED_RANKS = {
|
|
|
177
177
|
'POST /goals/:id/requests/:reqId/respond': 'any-builder',
|
|
178
178
|
'POST /goals/:id/invitations': 'any-builder',
|
|
179
179
|
'POST /goals/:id/invitations/:reqId/respond': 'any-builder',
|
|
180
|
+
// the goal's own pending-queue view + requester self-withdraw (task 1953) —
|
|
181
|
+
// same in-handler walls: GET /requests is owner|manager|Archon
|
|
182
|
+
// (authorizeGoalManagement); withdraw is the requester only
|
|
183
|
+
// (authorizeWithdrawJoinRequest).
|
|
184
|
+
'GET /goals/:id/requests': 'any-builder',
|
|
185
|
+
'POST /goals/:id/requests/:reqId/withdraw': 'any-builder',
|
|
180
186
|
// criterion confirmation + goal auto-achieve (ADR 0086 §6) — the planner's close loop.
|
|
181
187
|
'POST /done-when/:criterionId/satisfy': 'metic+archon',
|
|
182
188
|
// runtime criterion create (ADR 0105 gap fix, task 1874) — scope-truth write, Archon-only.
|
|
@@ -1,43 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @bongos/core — installed platform dependency (not your project's CLAUDE.md)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This file is a placeholder shipped inside the **@bongos/core** package. The core's own
|
|
4
|
+
internal agent notes are intentionally omitted from the published artifact so they cannot
|
|
5
|
+
load into your instance's context.
|
|
4
6
|
|
|
5
|
-
**
|
|
7
|
+
**It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
|
|
8
|
+
directory, and the live Bongos database are the source of truth for how work happens here
|
|
9
|
+
and who may do what.
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- Public surface: `public.js` + `analytics.js` are unauthenticated read-only (status page reads cross-origin) — wrap with `corsPublicGet(scope)` from `_helpers.js`; no secrets.
|
|
10
|
-
- Validation: [`_helpers.js`](./_helpers.js) — `validateOrRespond(req,res,schema)` (opt-in, never rejects extras, structured 400), `LIMITS` (TITLE/DESCRIPTION/NOTES caps), `parseId`, `asyncHandler(tag, fn)`.
|
|
11
|
-
- GitHub-rendered output only: the PR title/body built in `tasks.js` de-hashes bare `#NNN` (so it doesn't 404-autolink to GitHub issues) via the local `deHashRefs` helper — which mirrors ship.js's `sanitizeGitHubRefs` (not requirable here). Mirror `deHashRefs`; don't hand-roll. DB-row prose (tasks/inbox/memory) is stored as-is — sanitization happens only at the GitHub boundary.
|
|
11
|
+
You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
|
|
12
|
+
Nothing in here needs to be read to use the platform — run `bongos help` for commands.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- Auth/identity: `auth.js`, `me.js`, `sessions.js` — Device/web/app-pair flows, self profile/prefs, session digests. (Admission gating — `access-requests.js` — carved to [`modules/onboarding/`](../../../modules/onboarding/CLAUDE.md) at R81; the loader mounts it.)
|
|
16
|
-
- Builder ops: `builders.js`, `cost.js`, `leaderboard.js`, `memory.js`, `learnings.js` — builder records/budget, cost log, rankings, BFG-scoped memory, learnings.
|
|
17
|
-
- Dev-box: `box.js` — provision/wake/close, SSH keys, terminal/sandbox tunnels, server-mediated branch publish.
|
|
18
|
-
- Social/data: `discord.js`, `inbox.js`, `blockers.js`, `analytics.js`, `audit-log.js` — idea inbox, blockers, Discord link, analytics, Archon audit trail.
|
|
19
|
-
- GDS-V4 / security: `search.js` (recall), `gate-approvals.js` (Archon gate PRs), `security.js` (red-team reports + Archon-served docs).
|
|
20
|
-
- Util: `healthz.js`, `public.js`, `optimizer.js`, `bug-attachments.js`.
|
|
21
|
-
|
|
22
|
-
**Gotchas / invariants.**
|
|
23
|
-
- `tasks.js` (~79 KB), `box.js`, `auth.js`, `memory.js` are large — grep `router.(get|post|...)(` for the route, don't read end-to-end.
|
|
24
|
-
- Rank gates are load-bearing (trust boundary). Default a write to `requireRank('archon')` unless it's deliberately broader; never loosen one without the permissions doc + an ADR.
|
|
25
|
-
- Mount order: `public.js` is mounted LAST in routes.js; keep new files' prefixes from shadowing it.
|
|
26
|
-
|
|
27
|
-
<!-- BEGIN GENERATED REPO-MAP (scripts/gds/gen-repo-map.js — do not hand-edit) -->
|
|
28
|
-
**Symbol skeleton (generated from the code — do not hand-edit; refresh: `node scripts/gds/gen-repo-map.js`).** Exported signatures, most-referenced first. The interpretive notes above are hand-written; this factual list is regenerated on deploy so it can't drift ([ADR 0063](../../../docs/adr/<redacted>.md)).
|
|
29
|
-
|
|
30
|
-
- `_helpers.js` — `validateOrRespond(req, res, schema, opts = {})`, `parseId(req, res, { param = 'id', code = 'bad_id', positiveInt = false } = {})`, `LIMITS`, `validate(body, schema, { strict = false, query = false } = {})`, `asyncHandler(tag, handler, { message = true, errorCode = 'server_error' } = {})`, `publicOrigin(req)`, `pageMeta(limit, offset, total)`, `parsePagination(req, { defaultLimit = PAGINATION.DEFAULT_LIMIT, maxLimit = PAGINATION.MAX_LIMIT } = {})`, `corsPublicGet(scope)`, `parseCookie(req, name)` _(+2 more)_
|
|
31
|
-
- `auth.js` — `renderAuthErrorPage(heading, detail, opts = {})`, `sessionCookieDomainOpts(req)`, `safeReturnTo(raw)`, `reqHost(req)`
|
|
32
|
-
- `backup.js` — `latestDump()`
|
|
33
|
-
- `builders.js` — `boxHasEverConnected(builderId)`, `discordIsLinked(builderId)`, `displayRank(rank)`, `<redacted>({ displayName, direction, newRank })`, `fireDiscordBroadcast(text)`
|
|
34
|
-
- `instance.js` — `MANIFEST_SCHEMA`, `buildInstanceManifest({)`, `AUTH_METHODS`
|
|
35
|
-
- `live.js` — `buildLiveRouter()`, `frameEvent(event, data)`, `MAX_CONN_PER_IP`, `streamLive(req, res, deps = {})`
|
|
36
|
-
- `llm-cache.js` — `badKey(res)`, `knownTaskType(t)`, `clampedCost(raw)`
|
|
37
|
-
- `me.js` — `boxHasEverConnected(builderId)`, `artSharedKeyConfigured()`, `discordIsLinked(builderId)`
|
|
38
|
-
- `modules.js` — `listModules()`
|
|
39
|
-
- `public.js` — `projectPublicTask(task)`, `projectPublicBlocker(blocker)`, `projectPublicIdea(idea)`, `recentShippedWindow(rawDays)`
|
|
40
|
-
- `security.js` — `adrPath()`, `readFullAdr()`
|
|
41
|
-
<!-- END GENERATED REPO-MAP -->
|
|
42
|
-
|
|
43
|
-
**See also.** [ADR 0016](../../../docs/adr/<redacted>.md) (trust boundary), [`docs/api-reference.md`](../../../docs/api-reference.md) (generated per-endpoint reference; `/docs` renders it), [`docs/canonical-permissions.md`](../../../docs/canonical-permissions.md) (rank ladder), [`../routes.js`](../routes.js) (mounter), [`../auth.js`](../auth.js).
|
|
14
|
+
_To work ON the platform core itself, use the standalone @bongos/core source repository,
|
|
15
|
+
not this vendored copy._
|
|
@@ -209,7 +209,12 @@ module.exports = function buildAuthRouter() {
|
|
|
209
209
|
res.json(out);
|
|
210
210
|
} catch (err) {
|
|
211
211
|
console.error('[gds] device/start', err);
|
|
212
|
-
|
|
212
|
+
// Surface the underlying reason (task 2126). A thrown device-flow start is almost
|
|
213
|
+
// always a GitHub OAuth-App misconfig (device_flow_disabled / bad client) — safe,
|
|
214
|
+
// non-secret, and actionable — so return it instead of an opaque "internal error".
|
|
215
|
+
// 502: the failure is upstream (GitHub), not a bug in this server.
|
|
216
|
+
const reason = String(err && err.message ? err.message : 'internal error').slice(0, 300);
|
|
217
|
+
res.fail('device_start_failed', { status: 502, message: reason });
|
|
213
218
|
}
|
|
214
219
|
});
|
|
215
220
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/gds/routes/modules.js — the hall "Modules" tab backend (ADR 0107 §9,
|
|
2
|
-
// task 1776).
|
|
2
|
+
// task 1776). Four jobs, matching the ADR:
|
|
3
3
|
//
|
|
4
4
|
// GET /modules list every on-disk module: enabled state,
|
|
5
5
|
// attribution (author/origin/license/maintainer),
|
|
@@ -9,37 +9,72 @@
|
|
|
9
9
|
// and reload the live process (§6: default-off
|
|
10
10
|
// respected — this only ever flips an existing
|
|
11
11
|
// module's flag, never invents a new key).
|
|
12
|
-
// POST /modules/:key/submit the "submit for review" front door (§1)
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
12
|
+
// POST /modules/:key/submit the "submit for review" front door (§1) — files
|
|
13
|
+
// a packaged submission into the upstreaming
|
|
14
|
+
// review queue (task 1770). Re-runs the §4
|
|
15
|
+
// pre-check + requires a DCO sign-off against
|
|
16
|
+
// THIS box's own on-disk copy of the module (the
|
|
17
|
+
// authoritative check — `bongos module submit`
|
|
18
|
+
// is a thin CLI client of this same route).
|
|
19
|
+
// GET /modules/submissions list queued submissions (task 1771 reads this).
|
|
16
20
|
//
|
|
17
21
|
// Read is open to any signed-in builder (same gate as the atlas/primer pages);
|
|
18
|
-
//
|
|
19
|
-
// it changes running behavior for the whole instance
|
|
22
|
+
// every write (enable/disable, submit) is metic+archon, same rank as
|
|
23
|
+
// /backup/trigger — it changes running behavior for the whole instance (or
|
|
24
|
+
// files something into a shared review queue), not just the caller.
|
|
20
25
|
//
|
|
21
|
-
// Persistence note:
|
|
22
|
-
// in-process cache, so the effect is immediate on this box.
|
|
23
|
-
// hand-edit of that file, surviving the next `git reset --hard`
|
|
24
|
-
// the change committed — this route does not auto-commit (an
|
|
25
|
-
// auto-pushing to main bypasses the ship/grade/deploy pipeline,
|
|
26
|
-
// project's trust boundary treats as a bigger risk than a toggle
|
|
27
|
-
// human/agent to land it properly). Captured as a follow-up
|
|
28
|
-
// (DB-backed) module enablement that survives a deploy with no
|
|
26
|
+
// Persistence note: /modules/:key/enable writes config/modules.json on disk
|
|
27
|
+
// and reloads the in-process cache, so the effect is immediate on this box.
|
|
28
|
+
// Like any other hand-edit of that file, surviving the next `git reset --hard`
|
|
29
|
+
// deploy needs the change committed — this route does not auto-commit (an
|
|
30
|
+
// HTTP handler auto-pushing to main bypasses the ship/grade/deploy pipeline,
|
|
31
|
+
// which the project's trust boundary treats as a bigger risk than a toggle
|
|
32
|
+
// that needs a human/agent to land it properly). Captured as a follow-up
|
|
33
|
+
// idea: durable (DB-backed) module enablement that survives a deploy with no
|
|
34
|
+
// extra step.
|
|
35
|
+
//
|
|
36
|
+
// Why the review queue is a table in THIS instance's own DB (not a live
|
|
37
|
+
// cross-repo call to the extracted core repo): see
|
|
38
|
+
// docs/adr/0135-module-upstream-submission-interim-queue.md.
|
|
29
39
|
|
|
30
40
|
const express = require('express');
|
|
31
41
|
const fs = require('node:fs');
|
|
32
42
|
const path = require('node:path');
|
|
33
43
|
const auth = require('../auth');
|
|
34
|
-
const { asyncHandler, validateOrRespond } = require('./_helpers');
|
|
44
|
+
const { asyncHandler, validateOrRespond, LIMITS } = require('./_helpers');
|
|
35
45
|
const loader = require('../../module-loader/loader');
|
|
36
46
|
const { deriveProvenance } = require('../../module-loader/provenance');
|
|
37
47
|
const modulesLib = require('../../modules');
|
|
48
|
+
// scripts/gds/ is a sibling CORE-package dir (both resolve under
|
|
49
|
+
// resolveCoreRoot() — see scripts/gds/CLAUDE.md), so requiring its pure fs
|
|
50
|
+
// logic from here is in-boundary. module.js never statically requires the CLI
|
|
51
|
+
// session/fetch layer (cli-lib.js) — only cmdSubmit lazy-requires it, and only
|
|
52
|
+
// when actually invoked from the CLI — so this stays safe in the always-on
|
|
53
|
+
// server process.
|
|
54
|
+
const moduleCli = require('../../../scripts/gds/module');
|
|
55
|
+
const moduleSubmissions = require('../module-submissions');
|
|
56
|
+
|
|
57
|
+
// A packaged submission embeds every file's content (base64) — cap the total
|
|
58
|
+
// so a runaway/misbuilt module can't bloat the review-queue table. Generous
|
|
59
|
+
// headroom over what a real feature module carries (routes + migrations + UI
|
|
60
|
+
// + a CLAUDE.md are typically tens of KB); mirrors the security_docs content
|
|
61
|
+
// cap (SECURITY_DOC_CONTENT_MAX) in routes/security.js.
|
|
62
|
+
const SUBMISSION_MAX_BYTES = 5_000_000;
|
|
38
63
|
|
|
39
64
|
function ownProjectName() {
|
|
40
65
|
try { return require('../../branding').branding().identity.productName; } catch { return null; }
|
|
41
66
|
}
|
|
42
67
|
|
|
68
|
+
// "owner/name", e.g. "lars589/amazonprimea" — the provenance a submission
|
|
69
|
+
// carries (ADR 0107 §2: attribution/provenance travels WITH the module as a
|
|
70
|
+
// portable record, not a cross-GDS link). null if the branding pack has none.
|
|
71
|
+
function sourceRepoLabel() {
|
|
72
|
+
try {
|
|
73
|
+
const repo = require('../../branding').branding().repo;
|
|
74
|
+
return repo && repo.name ? `${repo.owner || '?'}/${repo.name}` : null;
|
|
75
|
+
} catch { return null; }
|
|
76
|
+
}
|
|
77
|
+
|
|
43
78
|
function readInstanceConfig() {
|
|
44
79
|
try { return JSON.parse(fs.readFileSync(modulesLib.INSTANCE_PATH, 'utf8')); }
|
|
45
80
|
catch (e) { if (e.code === 'ENOENT') return { modules: {} }; throw e; }
|
|
@@ -78,7 +113,7 @@ module.exports = function buildModulesRouter() {
|
|
|
78
113
|
modules: all,
|
|
79
114
|
own_built: all.filter((m) => m.provenance === 'own').length,
|
|
80
115
|
core_loaded: all.filter((m) => m.provenance === 'core').length,
|
|
81
|
-
submit_pipeline_live:
|
|
116
|
+
submit_pipeline_live: true, // task 1770 shipped — POST /modules/:key/submit is real
|
|
82
117
|
});
|
|
83
118
|
}));
|
|
84
119
|
|
|
@@ -108,7 +143,17 @@ module.exports = function buildModulesRouter() {
|
|
|
108
143
|
res.json({ ok: true, key, enabled, note: 'Live on this box now. Persisting past the next deploy needs config/modules.json committed.' });
|
|
109
144
|
}));
|
|
110
145
|
|
|
111
|
-
//
|
|
146
|
+
// POST /api/gds/modules/:key/submit — ADR 0107 §1 upstreaming submission.
|
|
147
|
+
//
|
|
148
|
+
// task 1770. Body: { signed_by?: "Name <email>" }. Re-runs the §4
|
|
149
|
+
// AGPL/publish pre-check + requires a DCO-style sign-off against THIS box's
|
|
150
|
+
// OWN on-disk copy of modules/:key (the authoritative check — a local
|
|
151
|
+
// `bongos module check` pass on a builder's box is a courtesy, not proof, if
|
|
152
|
+
// the module's changes aren't committed + shipped + deployed here yet).
|
|
153
|
+
// signed_by records a NEW sign-off only if none is on file; an existing
|
|
154
|
+
// .upstream-signoff.json is reused as-is (not overwritten).
|
|
155
|
+
//
|
|
156
|
+
// rank: metic+archon — files into a shared review queue, not just a toggle for the caller.
|
|
112
157
|
router.post('/modules/:key/submit', auth.requireBuilder, auth.requireRank('metic', 'archon'),
|
|
113
158
|
asyncHandler('POST /modules/:key/submit', async (req, res) => {
|
|
114
159
|
const { key } = req.params;
|
|
@@ -116,10 +161,67 @@ module.exports = function buildModulesRouter() {
|
|
|
116
161
|
if (!known.find((m) => m.key === key)) {
|
|
117
162
|
return res.fail('not_found', { status: 404, message: `No on-disk module "${key}".` });
|
|
118
163
|
}
|
|
119
|
-
res
|
|
120
|
-
|
|
121
|
-
|
|
164
|
+
if (validateOrRespond(req, res, { signed_by: { type: 'string', maxLength: LIMITS.TITLE } })) return;
|
|
165
|
+
|
|
166
|
+
const precheck = moduleCli.checkModulePublishability(key);
|
|
167
|
+
if (!precheck.ok) {
|
|
168
|
+
return res.fail('precheck_failed', {
|
|
169
|
+
status: 422,
|
|
170
|
+
message: `"${key}" is not clear to submit upstream (ADR 0107 §4 AGPL/publish pre-check failed).`,
|
|
171
|
+
findings: precheck.findings,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let signOff = moduleCli.readSignOff(key);
|
|
176
|
+
const signedBy = req.body && typeof req.body.signed_by === 'string' ? req.body.signed_by.trim() : '';
|
|
177
|
+
if (!signOff && signedBy) {
|
|
178
|
+
moduleCli.recordSignOff(key, signedBy);
|
|
179
|
+
signOff = moduleCli.readSignOff(key);
|
|
180
|
+
}
|
|
181
|
+
if (!signOff) {
|
|
182
|
+
return res.fail('sign_off_required', {
|
|
183
|
+
status: 422,
|
|
184
|
+
message: `No DCO-style sign-off on file for "${key}". Pass "signed_by": "Name <email>" to affirm + record it.`,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const pkg = moduleCli.packageModuleForSubmission(key);
|
|
189
|
+
if (pkg.totalBytes > SUBMISSION_MAX_BYTES) {
|
|
190
|
+
return res.fail('submission_too_large', {
|
|
191
|
+
status: 413,
|
|
192
|
+
message: `"${key}" is ${pkg.totalBytes} bytes, over the ${SUBMISSION_MAX_BYTES}-byte upstreaming cap.`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const submission = await moduleSubmissions.createModuleSubmission({
|
|
197
|
+
moduleKey: key,
|
|
198
|
+
submittedBy: req.builder.id,
|
|
199
|
+
manifest: pkg.manifest,
|
|
200
|
+
files: pkg.files,
|
|
201
|
+
signOff,
|
|
202
|
+
precheckPassedAt: new Date(),
|
|
203
|
+
sourceRepo: sourceRepoLabel(),
|
|
122
204
|
});
|
|
205
|
+
|
|
206
|
+
console.log(`[gds] module "${key}" submitted upstream by builder ${req.builder.id} — submission ${submission.id}`);
|
|
207
|
+
res.status(201).json({ ok: true, submission });
|
|
208
|
+
}));
|
|
209
|
+
|
|
210
|
+
// GET /api/gds/modules/submissions?status=&module_key= — the upstreaming
|
|
211
|
+
// review queue (task 1771 reads this). Metadata only — no file contents (a
|
|
212
|
+
// future GET .../:id can add a with-files fetch if the review UI needs it).
|
|
213
|
+
//
|
|
214
|
+
// rank: metic+archon — the review queue, same rank as filing into it.
|
|
215
|
+
router.get('/modules/submissions', auth.requireBuilder, auth.requireRank('metic', 'archon'),
|
|
216
|
+
asyncHandler('GET /modules/submissions', async (req, res) => {
|
|
217
|
+
const status = req.query.status ? String(req.query.status) : undefined;
|
|
218
|
+
if (status && !['pending', 'accepted', 'rejected'].includes(status)) {
|
|
219
|
+
return res.fail('bad_status', { status: 400, message: 'status must be one of pending|accepted|rejected.' });
|
|
220
|
+
}
|
|
221
|
+
const moduleKey = req.query.module_key ? String(req.query.module_key) : undefined;
|
|
222
|
+
const submissions = await moduleSubmissions.listModuleSubmissions({ status, moduleKey });
|
|
223
|
+
res.set('Cache-Control', 'no-store');
|
|
224
|
+
res.json({ submissions });
|
|
123
225
|
}));
|
|
124
226
|
|
|
125
227
|
return router;
|