@bongos/core 1.19.1073 → 1.19.1074
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 +320 -465
- package/.claude/skills/feedback/SKILL.md +2 -2
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -60
- package/clients/bongos-client/index.cjs +4 -60
- package/clients/bongos-client/index.d.ts +5 -90
- package/clients/bongos-client/index.mjs +4 -60
- package/config/branding.neutral.json +1 -5
- package/config/modules.neutral.json +2 -3
- package/config/scheduled-routines.json +0 -2
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
- package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
- package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
- package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
- package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
- package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +171 -1661
- package/docs/api-reference.md +9 -41
- package/docs/architecture.md +16 -1
- package/docs/branding-contract.md +0 -3
- package/docs/copy-inventory.md +454 -479
- package/docs/copy-registry.json +746 -978
- package/docs/file-map.md +5 -34
- package/docs/module-api-changelog.md +2 -0
- package/docs/modules-contract.md +13 -1
- package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
- package/docs/onboarding/diagrams/README.md +3 -3
- package/docs/onboarding/diagrams/assertions.json +2 -22
- package/docs/onboarding/primer.md +9 -28
- package/docs/page-inventory.json +4 -1
- package/docs/page-readings.json +1032 -1077
- package/docs/recipes/render-pilot.md +29 -10
- package/migrations/core_257_module_store_registry.sql +87 -0
- package/migrations/core_258_drop_dev_box_tables.sql +55 -0
- package/modules/agents/module.json +1 -0
- package/modules/autonomy/module.json +1 -0
- package/modules/builder-settings/module.json +1 -0
- package/modules/builder-settings/render-prefs.js +4 -5
- package/modules/copy-desk/docx.js +78 -5
- package/modules/copy-desk/module.json +1 -0
- package/modules/copy-desk/routes/copy-desk.js +9 -9
- package/modules/copy-desk/tests/copy_docx.mjs +107 -1
- package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
- package/modules/discord/module.json +1 -0
- package/modules/economy/module.json +1 -0
- package/modules/economy/routes/credits.js +1 -2
- package/modules/government/catalog.js +13 -17
- package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
- package/modules/government/module.json +1 -0
- package/modules/government/protected-surfaces.json +3 -11
- package/modules/government/resolver.js +3 -46
- package/modules/government/routes/government.js +0 -2
- package/modules/government/session-scopes.js +43 -83
- package/modules/government/session-scopes.json +8 -18
- package/modules/grading/module.json +1 -0
- package/modules/hall-ui/module.json +1 -0
- package/modules/hall-ui/public/board-lib.js +2 -2
- package/modules/hall-ui/public/builders.js +29 -40
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/collab.js +1 -1
- package/modules/hall-ui/public/diagrams.html +3 -3
- package/modules/hall-ui/public/dom-utils.js +1 -1
- package/modules/hall-ui/public/gate.js +2 -2
- package/modules/hall-ui/public/gate.states.json +1 -1
- package/modules/hall-ui/public/hall-render.js +43 -79
- package/modules/hall-ui/public/index.html +5 -1
- package/modules/hall-ui/public/modules.js +1 -0
- package/modules/hall-ui/public/oversight.css +5 -5
- package/modules/hall-ui/public/palette.js +0 -11
- package/modules/hall-ui/public/primer.js +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings-sessions.js +1 -1
- package/modules/hall-ui/public/settings.css +0 -1
- package/modules/hall-ui/public/settings.html +4 -33
- package/modules/hall-ui/public/settings.js +3 -33
- package/modules/hall-ui/public/settings.states.json +2 -2
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +10 -13
- package/modules/hall-ui/public/task.html +4 -1
- package/modules/hall-ui/public/task.js +36 -1
- package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
- package/modules/hall-ui/public/tweak-editor.css +16 -0
- package/modules/hall-ui/public/tweak-editor.html +26 -3
- package/modules/hall-ui/public/tweak-editor.js +32 -1
- package/modules/hall-ui/public/work.js +1 -1
- package/modules/hall-ui/records/tweak-editor.md +1 -0
- package/modules/ideas/module.json +1 -0
- package/modules/lifecycle/db-claim-reads.js +1 -58
- package/modules/lifecycle/db-ship.js +0 -5
- package/modules/lifecycle/db.js +0 -4
- package/modules/lifecycle/kickoff-checklist.js +0 -4
- package/modules/lifecycle/lifecycle.js +0 -8
- package/modules/lifecycle/module.json +1 -0
- package/modules/lifecycle/routes/tasks.js +5 -7
- package/modules/memory/module.json +1 -0
- package/modules/npm-release/module.json +1 -0
- package/modules/onboarding/module.json +1 -0
- package/modules/onboarding/onboarding-state.js +17 -36
- package/modules/onboarding/routes/access-requests.js +3 -3
- package/modules/platform-identity/module.json +1 -0
- package/modules/platform-identity/platform-identity.js +13 -5
- package/modules/platform-identity/routes/sso.js +2 -2
- package/modules/platform-identity/tests/platform-identity.mjs +17 -3
- package/modules/provisioning/module.json +1 -0
- package/modules/provisioning/provisioning.js +2 -2
- package/modules/provisioning/starter-bundles.js +5 -25
- package/modules/public-landing/module.json +1 -0
- package/modules/public-landing/public/assets/cosmos.css +1 -1
- package/modules/public-landing/public/contact.html +1 -1
- package/modules/public-landing/public/privacy.html +1 -1
- package/modules/public-landing/public/projects.html +101 -32
- package/modules/public-landing/public/projects.probes.json +2 -2
- package/modules/public-landing/public/projects.states.json +4 -3
- package/modules/public-landing/public/terms.html +1 -1
- package/modules/security/module.json +1 -0
- package/modules/sessions/module.json +1 -0
- package/modules/specialities/module.json +1 -0
- package/modules/status-ui/module.json +1 -0
- package/modules/ui-design/module.json +1 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +78 -0
- package/scripts/gds/artifact-format.js +203 -0
- package/scripts/gds/audit-authorship.sh +1 -13
- package/scripts/gds/claim.js +0 -15
- package/scripts/gds/cli-lib.js +8 -8
- package/scripts/gds/diagram-facts.js +12 -23
- package/scripts/gds/do-api.js +13 -80
- package/scripts/gds/doc-cli-guard.js +2 -0
- package/scripts/gds/doctor.js +1 -1
- package/scripts/gds/feedback-latest.js +139 -9
- package/scripts/gds/fitness-checks-identity.js +2 -7
- package/scripts/gds/fitness-checks-write-validation.js +4 -0
- package/scripts/gds/fitness-ratchets.js +4 -4
- package/scripts/gds/fitness.js +10 -10
- package/scripts/gds/gen-diagrams.js +49 -71
- package/scripts/gds/http-api-client.js +12 -19
- package/scripts/gds/init.js +1 -1
- package/scripts/gds/lint-infra-exec.js +1 -1
- package/scripts/gds/local-preview-lib.js +8 -39
- package/scripts/gds/local-preview.js +2 -15
- package/scripts/gds/migration-namespace.js +3 -3
- package/scripts/gds/module-artifact.js +212 -0
- package/scripts/gds/module.js +95 -6
- package/scripts/gds/package-core.js +5 -116
- package/scripts/gds/provision-config.js +2 -2
- package/scripts/gds/provision-net.js +2 -3
- package/scripts/gds/provision.js +9 -9
- package/scripts/gds/publish-manifest.js +1 -2
- package/scripts/gds/regen-instance-docs.js +1 -1
- package/scripts/gds/run-unit-tests.js +2 -16
- package/scripts/gds/sandbox-stage.js +39 -341
- package/scripts/gds/ship-flow.js +6 -6
- package/scripts/gds/ship-land.js +3 -4
- package/scripts/gds/ship-regen.js +10 -41
- package/scripts/gds/ship.js +4 -24
- package/scripts/gds/start.js +0 -15
- package/scripts/hall-preview/README.md +5 -5
- package/scripts/hall-preview/server.js +6 -6
- package/scripts/render-diagrams.sh +1 -1
- package/src/bongos/api-errors.js +1 -1
- package/src/bongos/auth-github.js +2 -3
- package/src/bongos/auth.js +51 -126
- package/src/bongos/db-kernel.js +2 -3
- package/src/bongos/db.js +5 -28
- package/src/bongos/module-scope-map.js +14 -29
- package/src/bongos/module-store.js +141 -0
- package/src/bongos/route-rank-check.js +3 -16
- package/src/bongos/routes/auth.js +0 -116
- package/src/bongos/routes/builders.js +5 -15
- package/src/bongos/routes/instance.js +8 -14
- package/src/bongos/routes/me.js +6 -22
- package/src/bongos/routes/modules.js +83 -0
- package/src/bongos/routes.js +5 -0
- package/src/module-api.js +13 -6
- package/src/module-loader/catalog.js +3 -0
- package/src/module-loader/loader.js +4 -4
- package/src/module-loader/manifest-schema.js +15 -7
- package/src/module-seams.js +2 -3
- package/src/modules.js +42 -14
- package/tests/agents_spend_guard.mjs +1 -1
- package/tests/api_client.mjs +0 -15
- package/tests/auth_page_gate.mjs +10 -10
- package/tests/canonical_profile_url.mjs +1 -3
- package/tests/claim_action.mjs +4 -1
- package/tests/claim_from_task_and_home.mjs +127 -0
- package/tests/consumer_layout_boot.mjs +2 -0
- package/tests/copy_desk_page_docx.mjs +33 -2
- package/tests/core_upgrade_runner.mjs +6 -6
- package/tests/deploy_divergence_line.mjs +11 -11
- package/tests/diagram_facts_offset.mjs +1 -1
- package/tests/do_api.mjs +106 -0
- package/tests/feedback_latest.mjs +122 -0
- package/tests/fitness.mjs +44 -42
- package/tests/gate_approvals.mjs +0 -1
- package/tests/go_live.mjs +3 -3
- package/tests/government_abuse_matrix.mjs +20 -19
- package/tests/government_ownership_scope.mjs +33 -35
- package/tests/government_parity.mjs +2 -2
- package/tests/government_protected_surfaces.mjs +56 -37
- package/tests/government_require_permission.mjs +3 -3
- package/tests/government_seed.mjs +37 -4
- package/tests/government_session_scope.mjs +92 -366
- package/tests/hall_error_envelope.mjs +4 -0
- package/tests/hall_landing_boot.mjs +4 -1
- package/tests/hall_palette.mjs +1 -16
- package/tests/hall_record_world.mjs +6 -0
- package/tests/hall_tweak_editor.mjs +52 -1
- package/tests/host_topology_skips.mjs +1 -2
- package/tests/html_comment_nesting.mjs +137 -0
- package/tests/infra_exec_paths.mjs +9 -10
- package/tests/init.mjs +4 -5
- package/tests/instance_manifest.mjs +18 -25
- package/tests/kickoff_checklist.mjs +1 -1
- package/tests/lib_sh_resolution.mjs +0 -221
- package/tests/local_preview.mjs +0 -12
- package/tests/module-scope-map.mjs +9 -9
- package/tests/module_catalog.mjs +1 -1
- package/tests/module_cli.mjs +15 -15
- package/tests/module_contributions.mjs +6 -6
- package/tests/module_loader.mjs +6 -6
- package/tests/module_manifest.mjs +13 -4
- package/tests/module_store_publish.mjs +307 -0
- package/tests/module_store_publish_route.mjs +121 -0
- package/tests/module_store_registry_migration.mjs +70 -0
- package/tests/modules.mjs +56 -26
- package/tests/onboarding_route_signals.mjs +21 -31
- package/tests/onboarding_state.mjs +35 -49
- package/tests/permission_path.mjs +0 -3
- package/tests/platform_boot.mjs +1 -2
- package/tests/profile_route.mjs +1 -6
- package/tests/projects_hub.mjs +5 -5
- package/tests/projects_hub_app_step.mjs +138 -0
- package/tests/projects_hub_module_picker.mjs +2 -2
- package/tests/projects_hub_pre_uat.mjs +4 -5
- package/tests/provision.mjs +7 -7
- package/tests/provisioning_starter_bundles.mjs +0 -22
- package/tests/publish_branch_route.mjs +10 -18
- package/tests/publish_manifest.mjs +1 -2
- package/tests/rank_tier_single_source.mjs +1 -13
- package/tests/repo_map.mjs +3 -4
- package/tests/runner_drift.mjs +3 -21
- package/tests/sandbox_stage.mjs +57 -390
- package/tests/session_rename_fallback.mjs +0 -21
- package/tests/ship_error_shape.mjs +1 -1
- package/tests/task_visual_slots.mjs +31 -0
- package/tests/terms_acceptance.mjs +1 -1
- package/tests/upgrade.mjs +4 -4
- package/tests/watch_sealed_floor.mjs +0 -1
- package/tests/wizard_draft_resume.mjs +2 -2
- package/tests/wizard_intent_resume.mjs +21 -10
- package/tests/wizard_preselect_why.mjs +18 -18
- package/docs/onboarding/browser-terminal-guide.md +0 -73
- package/docs/recipes/managed-settings-remote-control.md +0 -416
- package/modules/dev-box/CLAUDE.md +0 -15
- package/modules/dev-box/box-access.js +0 -623
- package/modules/dev-box/box-credential.js +0 -94
- package/modules/dev-box/box-onboard.js +0 -486
- package/modules/dev-box/boxes.js +0 -1038
- package/modules/dev-box/db.js +0 -39
- package/modules/dev-box/module.json +0 -19
- package/modules/dev-box/routes/box.js +0 -1223
- package/scripts/gds/box-auth-check.js +0 -160
- package/scripts/gds/box-infra.js +0 -321
- package/scripts/gds/box-sync.js +0 -216
- package/scripts/gds/box.js +0 -1332
- package/scripts/gds/cf-tunnel.js +0 -558
- package/scripts/gds/smoke-box.sh +0 -150
- package/scripts/gds/tree-preflight.js +0 -241
- package/src/bongos/app-pair.js +0 -172
- package/tests/app_pair.mjs +0 -168
- package/tests/box_access.mjs +0 -578
- package/tests/box_auth_check.mjs +0 -114
- package/tests/box_code_staleness.mjs +0 -129
- package/tests/box_connect_e2e.mjs +0 -196
- package/tests/box_cost.mjs +0 -70
- package/tests/box_credential.mjs +0 -125
- package/tests/box_dns_repoint.mjs +0 -153
- package/tests/box_env.mjs +0 -64
- package/tests/box_host_keys.mjs +0 -126
- package/tests/box_infra_resolve.mjs +0 -246
- package/tests/box_onboard.mjs +0 -317
- package/tests/box_scope_session.mjs +0 -119
- package/tests/box_sweep_docs.mjs +0 -119
- package/tests/box_sync.mjs +0 -55
- package/tests/box_sync_scope_report.mjs +0 -126
- package/tests/box_task_scope.mjs +0 -169
- package/tests/box_task_scope_pause.mjs +0 -66
- package/tests/box_terminal_ssrf.mjs +0 -96
- package/tests/boxes.mjs +0 -1585
- package/tests/cf_ruleset_preflight.mjs +0 -142
- package/tests/cf_tunnel.mjs +0 -496
- package/tests/docs_scrubber_damage.mjs +0 -111
- package/tests/module_scope_active_claims.mjs +0 -94
- package/tests/ship_api_client_pathspec.mjs +0 -100
- package/tests/tree_preflight.mjs +0 -243
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
-
const fs = require('node:fs');
|
|
12
11
|
const path = require('node:path');
|
|
13
12
|
const { gitOk, shellOk } = require('./ship-git.js');
|
|
14
13
|
const { pushVia } = require('./ship-deploy-target.js');
|
|
@@ -130,27 +129,17 @@ function regenerateApiDocs() {
|
|
|
130
129
|
}
|
|
131
130
|
|
|
132
131
|
// Refresh the generated typed client (clients/bongos-client) FROM the just-regenerated
|
|
133
|
-
// spec
|
|
134
|
-
//
|
|
132
|
+
// spec (task 2051). Runs right after regenerateApiDocs so the client reflects the
|
|
133
|
+
// same route tree. Automatic here
|
|
135
134
|
// (not fail-if-forgotten): before this, ship regenerated the spec but not the client,
|
|
136
135
|
// so a route change left the client stale until the api_client test caught it in CI.
|
|
137
136
|
// Non-fatal + safe on every ship path; the api_client freshness test backstops main.
|
|
138
137
|
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// with no git/fs (task 1002417). `git status` tolerates an unmatched pathspec (exit 0) but
|
|
145
|
-
// `git add` aborts the ENTIRE staging step (exit 128), which leaves the regenerated client
|
|
146
|
-
// unstaged and the worktree dirty — and a dirty worktree makes the ship's auto-merge bail,
|
|
147
|
-
// stranding the task at `confirmed` with no PR. So the vendored copy is named ONLY when it
|
|
148
|
-
// is actually on disk.
|
|
149
|
-
function apiClientPathspec({ worktreeRoot, existsSync = fs.existsSync } = {}) {
|
|
150
|
-
const paths = ['clients/bongos-client'];
|
|
151
|
-
if (existsSync(path.join(worktreeRoot, VENDORED_CLIENT_REL))) paths.push(VENDORED_CLIENT_REL);
|
|
152
|
-
return paths;
|
|
153
|
-
}
|
|
138
|
+
// The regenerated client's git pathspec. It used to carry a second, conditional path —
|
|
139
|
+
// a vendored copy inside a desktop app, named only when on disk because `git add` of an
|
|
140
|
+
// unmatched pathspec aborts the whole stage (task 1002417). The app is gone (task
|
|
141
|
+
// 1003891), so the pathspec is the one directory the generator writes.
|
|
142
|
+
const API_CLIENT_PATHSPEC = 'clients/bongos-client';
|
|
154
143
|
|
|
155
144
|
function regenerateApiClient() {
|
|
156
145
|
console.log(' regenerating typed API client (clients/bongos-client) from the OpenAPI spec...');
|
|
@@ -158,31 +147,13 @@ function regenerateApiClient() {
|
|
|
158
147
|
console.log(' api-client regen failed (non-fatal) — CI api_client freshness test backstops staleness');
|
|
159
148
|
return 'regen-failed';
|
|
160
149
|
}
|
|
161
|
-
|
|
162
|
-
// build (ADR 0045); keep the copy byte-identical (the api_client test asserts it).
|
|
163
|
-
// Present only where the dev-box module ships — an instance without it has no vendored
|
|
164
|
-
// copy, so both the vendor step and the git pathspec below drop out (task 1002417: the
|
|
165
|
-
// app moved from top-level devbox-app/ to modules/dev-box/app/ per ADR 0145, and these
|
|
166
|
-
// paths were not updated, so the guard below silently never fired).
|
|
167
|
-
// Resolve it WORKTREE-LOCALLY (like the generator above, __dirname=scripts/gds), NOT
|
|
168
|
-
// via REPO_ROOT: on a ship from a linked worktree REPO_ROOT is the MAIN checkout, so a
|
|
169
|
-
// REPO_ROOT path would vendor the main tree while gen-api-client just wrote THIS tree,
|
|
170
|
-
// leaving this worktree's vendored copy stale → the api_client byte-identity test reds
|
|
171
|
-
// the PR (task 2060 fix). __dirname keeps the generator, the vendored file, and the
|
|
172
|
-
// git add all on the same tree.
|
|
173
|
-
const worktreeRoot = path.join(__dirname, '..', '..');
|
|
174
|
-
const vendorScript = path.join(worktreeRoot, 'modules', 'dev-box', 'app', 'scripts', 'vendor-client.js');
|
|
175
|
-
if (fs.existsSync(vendorScript)) {
|
|
176
|
-
shellOk(`"${process.execPath}" ${JSON.stringify(vendorScript)}`);
|
|
177
|
-
}
|
|
178
|
-
const clientPathspec = apiClientPathspec({ worktreeRoot }).join(' ');
|
|
179
|
-
const dirty = gitOk(`git status --porcelain ${clientPathspec}`);
|
|
150
|
+
const dirty = gitOk(`git status --porcelain ${API_CLIENT_PATHSPEC}`);
|
|
180
151
|
if (!dirty) {
|
|
181
152
|
console.log(' API client already current — nothing to commit');
|
|
182
153
|
return 'current';
|
|
183
154
|
}
|
|
184
|
-
shellOk(`git add ${
|
|
185
|
-
if (shellOk('git commit -m "chore(api-client): auto-regenerate typed client
|
|
155
|
+
shellOk(`git add ${API_CLIENT_PATHSPEC}`);
|
|
156
|
+
if (shellOk('git commit -m "chore(api-client): auto-regenerate typed client (task 2051)"')) {
|
|
186
157
|
console.log(' committed auto-regenerated API client');
|
|
187
158
|
return 'committed';
|
|
188
159
|
}
|
|
@@ -333,8 +304,6 @@ function mainWorktreeBusy() {
|
|
|
333
304
|
// (uncommitted work / wrong branch / empty touches[]); refuse.
|
|
334
305
|
|
|
335
306
|
module.exports = {
|
|
336
|
-
VENDORED_CLIENT_REL,
|
|
337
|
-
apiClientPathspec,
|
|
338
307
|
currentBranch,
|
|
339
308
|
describeMainWorktreeBusy,
|
|
340
309
|
mainWorktreeBusy,
|
package/scripts/gds/ship.js
CHANGED
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
// # Archon). The cheap default after fixing grader
|
|
47
47
|
// # findings — closes the task-595-regrade-gap (#678).
|
|
48
48
|
// node scripts/gds/ship.js <task-id> --skip-stage # skip the sandbox-first review gate (#927,
|
|
49
|
-
// # ADR 0046). On a
|
|
50
|
-
// # otherwise staged to
|
|
51
|
-
// # preview (
|
|
52
|
-
// # browser review BEFORE the claim resolves.
|
|
49
|
+
// # ADR 0046). On a clone with the game preview,
|
|
50
|
+
// # game-surface diffs are otherwise staged to
|
|
51
|
+
// # the builder's own local preview (ADR 0044) for
|
|
52
|
+
// # a browser review BEFORE the claim resolves.
|
|
53
53
|
// node scripts/gds/ship.js <task-id> --approved # record your sandbox sign-off (#1048): re-run with
|
|
54
54
|
// # this AFTER reviewing the staged change at the
|
|
55
55
|
// # sandbox URL. Persists a sandbox_review row on the
|
|
@@ -77,21 +77,6 @@
|
|
|
77
77
|
|
|
78
78
|
'use strict';
|
|
79
79
|
|
|
80
|
-
// TREE PREFLIGHT (task 1003098). A task-scoped dev box carries scripts/ and
|
|
81
|
-
// src/bongos/ but not every modules/ directory, so the requires below can fail —
|
|
82
|
-
// and Node's loader names only the FIRST missing one, which turned recovery into
|
|
83
|
-
// guess-read-fix-repeat. This enumerates every missing requirement in one message
|
|
84
|
-
// and exits before the first such require runs. It must stay ABOVE them.
|
|
85
|
-
//
|
|
86
|
-
// A PRE-REQUEST hard exit, like the usage banner and worktree guard: nothing has
|
|
87
|
-
// been sent yet, so there is no in-flight request for cliExit to drain.
|
|
88
|
-
const { action: _surfaceAction } = require('./surface.js');
|
|
89
|
-
const { checkTree: _checkTree } = require('./tree-preflight.js');
|
|
90
|
-
{
|
|
91
|
-
const _fault = _checkTree(__filename, { command: _surfaceAction("ship") });
|
|
92
|
-
if (_fault) { console.error(_fault); process.exit(1); }
|
|
93
|
-
}
|
|
94
|
-
|
|
95
80
|
const { buildMergeArgv, gitArgvOk, sanitizeGitHubRefs } = require('./ship-git.js');
|
|
96
81
|
const { apiErrorLine, normalizeApiError, postLandShipOutcome } = require('./ship-io.js');
|
|
97
82
|
const { deriveTagsFromPaths } = require('./ship-surprise.js');
|
|
@@ -137,8 +122,6 @@ const {
|
|
|
137
122
|
strandNextStep,
|
|
138
123
|
} = require('./ship-land.js');
|
|
139
124
|
const {
|
|
140
|
-
VENDORED_CLIENT_REL,
|
|
141
|
-
apiClientPathspec,
|
|
142
125
|
regenerateDiagrams,
|
|
143
126
|
regenerateFileMap,
|
|
144
127
|
regenerateRepoMap,
|
|
@@ -192,9 +175,6 @@ module.exports = {
|
|
|
192
175
|
// resolveTranscriptPath itself runs path.resolve, which can't carry a Windows
|
|
193
176
|
// path through a Linux CI run.
|
|
194
177
|
encodeProjectPath,
|
|
195
|
-
// task 1002417: the regenerated-client pathspec rule, exported pure so the
|
|
196
|
-
// "never name a path git add cannot match" invariant is tested without git/fs.
|
|
197
|
-
apiClientPathspec, VENDORED_CLIENT_REL,
|
|
198
178
|
// task 1003: transcript-grade scrubber internals, exported pure so the planted-
|
|
199
179
|
// secret corpus test (tests/scrubber_corpus.mjs) can exercise each layer + tune.
|
|
200
180
|
shannonEntropy, looksHighEntropySecret, redactHighEntropy, collectEnvSecrets,
|
package/scripts/gds/start.js
CHANGED
|
@@ -52,21 +52,6 @@
|
|
|
52
52
|
//
|
|
53
53
|
// Returns JSON if --json is passed (for the slash command to parse cleanly).
|
|
54
54
|
|
|
55
|
-
// TREE PREFLIGHT (task 1003098). A task-scoped dev box carries scripts/ and
|
|
56
|
-
// src/bongos/ but not every modules/ directory, so the requires below can fail —
|
|
57
|
-
// and Node's loader names only the FIRST missing one, which turned recovery into
|
|
58
|
-
// guess-read-fix-repeat. This enumerates every missing requirement in one message
|
|
59
|
-
// and exits before the first such require runs. It must stay ABOVE them.
|
|
60
|
-
//
|
|
61
|
-
// A PRE-REQUEST hard exit, like the usage banner and worktree guard: nothing has
|
|
62
|
-
// been sent yet, so there is no in-flight request for cliExit to drain.
|
|
63
|
-
const { action: _surfaceAction } = require('./surface.js');
|
|
64
|
-
const { checkTree: _checkTree } = require('./tree-preflight.js');
|
|
65
|
-
{
|
|
66
|
-
const _fault = _checkTree(__filename, { command: _surfaceAction("start") });
|
|
67
|
-
if (_fault) { console.error(_fault); process.exit(1); }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
55
|
const { cliClient, errCode, errDetails, requireSession, fmtUsd, printOnboardingNudge, printCostWarning, printNeedsNudge, hallBase, API_BASE, rewardPolicy, costPlusOnlyNote, isDispatcherIncompatError, cardFlagsFromMe } = require('./cli-lib');
|
|
71
56
|
const { plainCardLines } = require('./plain-cards');
|
|
72
57
|
const { contextPack, formatContextPackCompact } = require('./context-pack');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Hall preview harness
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`/builder-stage` previews the **game only** — `/builders` 404s there (idea 490) — so
|
|
4
4
|
hall-ui changes are reviewed here: a DB-less fixture server that serves the **real**
|
|
5
5
|
`modules/hall-ui/public` files through the **real** serving seams (boot-time `?v=`
|
|
6
6
|
content-hash stamping, `{{token}}` replacement, `window.__BRANDING__`/`__MODULES__`
|
|
@@ -19,7 +19,7 @@ node scripts/hall-preview/server.js # neutral pack, port 4600
|
|
|
19
19
|
|---|---|
|
|
20
20
|
| `--port 4600` | listen port |
|
|
21
21
|
| `--brand config/branding.json` | serve the instance pack (default: `config/branding.neutral.json`, the vanilla skin) |
|
|
22
|
-
| `--modules-off
|
|
22
|
+
| `--modules-off discord,provisioning` | report those modules disabled in `window.__MODULES__` — verifies `moduleOn()` gating (the Discord links, Instances/Start-a-project widgets must vanish) |
|
|
23
23
|
| `--empty` | drain every array in every fixture — the empty-state screenshot pass |
|
|
24
24
|
| `--fixture-me` | don't read your own account live — serve the checked-in `me.json` snapshot (see below) |
|
|
25
25
|
|
|
@@ -32,7 +32,7 @@ A GET with no fixture answers 404 and logs
|
|
|
32
32
|
The fixtures are one particular builder's snapshot, so the harness used to greet
|
|
33
33
|
everyone as that builder — their name, credits, achievements, goals and **rank**.
|
|
34
34
|
A metic snapshot also sealed the Archon-gated pages, so a UI change to Watch /
|
|
35
|
-
|
|
35
|
+
Gate / Permissions could not be reviewed at all.
|
|
36
36
|
|
|
37
37
|
So at boot the harness reads **your** account: if this machine holds a GDS CLI
|
|
38
38
|
session, the me-scoped endpoints (`/me`, `/me/*`, `/builders/<you>/…`,
|
|
@@ -136,8 +136,8 @@ The hard seams every redesign must keep. Run on each changed page:
|
|
|
136
136
|
// after connect): watch the network panel, or count via a fixture hit log line.
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
Module gating: rerun the page with `--modules-off
|
|
140
|
-
|
|
139
|
+
Module gating: rerun the page with `--modules-off discord,provisioning` — the
|
|
140
|
+
Discord links and the Instances/Start-a-project widgets must be absent, with
|
|
141
141
|
zero console errors.
|
|
142
142
|
|
|
143
143
|
## Gotchas
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// scripts/hall-preview/server.js — the hall preview harness (task 2167).
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// /builder-stage previews the GAME only (/builders 404s there, idea 490),
|
|
5
5
|
// so hall-ui changes are reviewed HERE: a tiny express app that serves the real
|
|
6
6
|
// modules/hall-ui/public files through the REAL serving seams (boot-time ?v=
|
|
7
7
|
// stamping, {{token}} replacement, __BRANDING__/__MODULES__ injection, theme
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// finally checked in.
|
|
11
11
|
//
|
|
12
12
|
// node scripts/hall-preview/server.js [--port 4600] [--brand <file>]
|
|
13
|
-
// [--modules-off
|
|
13
|
+
// [--modules-off discord,provisioning] [--empty]
|
|
14
14
|
//
|
|
15
15
|
// --port listen port (default 4600)
|
|
16
16
|
// --brand branding pack file — default config/branding.neutral.json
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// --empty serve every fixture with its arrays emptied — the
|
|
21
21
|
// empty-state screenshot pass
|
|
22
22
|
// --rank archon override the served /me builder.rank (live or fixture) so the
|
|
23
|
-
// Archon-gated pages (watch,
|
|
23
|
+
// Archon-gated pages (watch, gate) render in preview
|
|
24
24
|
// --fixture-me do NOT read your own account live; serve the checked-in
|
|
25
25
|
// fixtures/me.json snapshot (deterministic screenshot passes,
|
|
26
26
|
// and the offline/signed-out path) — task 1003315
|
|
@@ -50,15 +50,15 @@ function arg(name) {
|
|
|
50
50
|
const PORT = Number(arg('--port')) || 4600;
|
|
51
51
|
const EMPTY = process.argv.includes('--empty');
|
|
52
52
|
// --rank: view the hall as a different rank — the served /me builder.rank is
|
|
53
|
-
// overridden in the response, so the Archon-gated pages (watch,
|
|
53
|
+
// overridden in the response, so the Archon-gated pages (watch, gate)
|
|
54
54
|
// render their real content instead of the sealed notice. Since task 1003315 the
|
|
55
55
|
// served /me is normally your OWN account, so this is now only needed to view
|
|
56
56
|
// the hall as a rank you are not. Preview-only theatre:
|
|
57
57
|
// the real pages are server-gated (requireGovernmentPage / ADR 0016).
|
|
58
58
|
const RANK_OVERRIDE = arg('--rank');
|
|
59
59
|
const MODULES_OFF = String(arg('--modules-off') || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
60
|
-
// --modules-on: force-report modules as enabled — a
|
|
61
|
-
// often lacks prod's instance modules (provisioning,
|
|
60
|
+
// --modules-on: force-report modules as enabled — a local checkout's config
|
|
61
|
+
// often lacks prod's instance modules (provisioning, discord), and their
|
|
62
62
|
// widgets can't be reviewed without it (task 2169).
|
|
63
63
|
const MODULES_ON = String(arg('--modules-on') || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
64
64
|
// --fixture-me: pin the checked-in /me snapshot instead of reading the caller's
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
# already-installed browser and forbids the download when it finds one; it only
|
|
20
20
|
# falls back to puppeteer's own download where nothing is installed (a dev
|
|
21
21
|
# laptop, where that fetch works fine). This is what makes render-at-deploy
|
|
22
|
-
# viable (deploy-prod.sh
|
|
22
|
+
# viable (deploy-prod.sh calls this).
|
|
23
23
|
set -euo pipefail
|
|
24
24
|
|
|
25
25
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
package/src/bongos/api-errors.js
CHANGED
|
@@ -61,7 +61,7 @@ const OVERRIDES = Object.freeze({
|
|
|
61
61
|
unauthenticated: 'unauthenticated',
|
|
62
62
|
cookie_required: 'unauthenticated',
|
|
63
63
|
unknown_or_expired: 'unauthenticated',
|
|
64
|
-
|
|
64
|
+
session_scope: 'forbidden',
|
|
65
65
|
|
|
66
66
|
// --- forbidden: 403 (rank/ownership, not "missing" or "bad input") ---
|
|
67
67
|
forbidden: 'forbidden',
|
|
@@ -253,9 +253,8 @@ async function completeWebFlow({ code, redirectUri, userAgent, captureRepoToken
|
|
|
253
253
|
// Approval gate (2026-05-30): the web hall sign-in no longer admits anyone
|
|
254
254
|
// with a GitHub account. A first-time visitor must have been approved by an
|
|
255
255
|
// Archon (their access_request marked 'invited') before a builder row is
|
|
256
|
-
// created. This Archon approval is the ONLY gate
|
|
257
|
-
//
|
|
258
|
-
// (2026-06-07). Established builders (a row already exists for this github_id)
|
|
256
|
+
// created. This Archon approval is the ONLY gate sign-in applies — there is
|
|
257
|
+
// no GitHub repo-access / collaborator check in it (2026-06-07). Established builders (a row already exists for this github_id)
|
|
259
258
|
// always pass — this only blocks NEW first-time entries.
|
|
260
259
|
//
|
|
261
260
|
// SR-19 / task #1000 (2026-06-13): the CLI device flow (pollDeviceFlow,
|
package/src/bongos/auth.js
CHANGED
|
@@ -128,47 +128,35 @@ async function resolveSession(req) {
|
|
|
128
128
|
return resolved;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
// ── SESSION
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
// (
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
// against the permission catalog at load by modules/government/session-scopes.js.
|
|
158
|
-
// Core reads it as DATA with fs — never `require` (ADR 0083's one-way rule: a
|
|
159
|
-
// module's code and enable/disable state must never be something the kernel
|
|
160
|
-
// depends on). The sibling trust primitive permission-path-check.js reads the
|
|
161
|
-
// protected-surface registry exactly this way (R101). Reading it statically rather
|
|
162
|
-
// than through the `governance` kernel port is the deliberate call: a request must
|
|
163
|
-
// not need a module to be mounted to stay deny-by-default, and the alternative
|
|
164
|
-
// fails badly in both directions — denying on an absent port would take every
|
|
165
|
-
// cli/web session down, admitting would hand a box token full authority.
|
|
131
|
+
// ── SESSION SOURCES — which kinds of token may authenticate at all (BV1.R99) ──
|
|
132
|
+
//
|
|
133
|
+
// Every builder_sessions row carries a `source` saying where it was minted. The
|
|
134
|
+
// session-scope registry (modules/government/session-scopes.json) DECLARES the
|
|
135
|
+
// sources this instance accepts; a session whose source it does not declare is
|
|
136
|
+
// DENIED — by requireBuilder on every route and by runPageGate on every page —
|
|
137
|
+
// rather than defaulting to its builder's full authority. A null/absent source is
|
|
138
|
+
// a DECLARED row (sessions minted before migration 090 added the column), not an
|
|
139
|
+
// implicit default.
|
|
140
|
+
//
|
|
141
|
+
// Every declared source is UNSCOPED: it carries its builder's full rank authority.
|
|
142
|
+
// The registry used to be able to declare a NARROWING scope too — a
|
|
143
|
+
// deny-by-default allow-list for a token living outside the ADR 0016 trust
|
|
144
|
+
// boundary — and its only user was a machine-baked token (ADR 0053) that goal
|
|
145
|
+
// 1000120 retired. With no other narrowing scope the allow-list machinery went
|
|
146
|
+
// with it (task 1003894). The parser therefore REFUSES a row that declares an
|
|
147
|
+
// allow-list: without the enforcement behind it, such a row would read as a
|
|
148
|
+
// restriction while granting full authority. A session row still carrying a
|
|
149
|
+
// retired source simply fails the "declared?" test and is denied.
|
|
150
|
+
//
|
|
151
|
+
// GOVERNANCE OWNS THE LIST, THE KERNEL STAYS THE ENFORCER. Core reads the registry
|
|
152
|
+
// as DATA with fs — never `require` (ADR 0083's one-way rule: a module's code and
|
|
153
|
+
// enable/disable state must never be something the kernel depends on), exactly as
|
|
154
|
+
// permission-path-check.js reads the protected-surface registry (R101). A request
|
|
155
|
+
// must not need a module mounted to stay fail-closed: denying on an absent port
|
|
156
|
+
// would take every cli/web session down.
|
|
166
157
|
//
|
|
167
158
|
// FAIL-CLOSED both ways: an unreadable or malformed registry THROWS (it never
|
|
168
|
-
// degrades to "every
|
|
169
|
-
// registry does not declare is DENIED rather than defaulting to full authority. A
|
|
170
|
-
// null/absent source is a DECLARED row (rows minted before migration 090 added the
|
|
171
|
-
// column, which predate the 'box' source existing), not an implicit default.
|
|
159
|
+
// degrades to "every source is accepted"), and an undeclared source is denied.
|
|
172
160
|
const SESSION_SCOPE_REGISTRY_PATH = path.join(__dirname, '..', '..', 'modules', 'government', 'session-scopes.json');
|
|
173
161
|
|
|
174
162
|
// Pure: validate + normalize the parsed registry. Split from the file read so the
|
|
@@ -177,31 +165,21 @@ const SESSION_SCOPE_REGISTRY_PATH = path.join(__dirname, '..', '..', 'modules',
|
|
|
177
165
|
function parseSessionScopes(raw, label = 'session-scope registry') {
|
|
178
166
|
const rows = Array.isArray(raw && raw.scopes) ? raw.scopes : null;
|
|
179
167
|
if (!rows || rows.length === 0) {
|
|
180
|
-
throw new Error(`${label} declares no session
|
|
168
|
+
throw new Error(`${label} declares no session sources. Refusing to run with no session-source model (ADR 0151).`);
|
|
181
169
|
}
|
|
182
170
|
const bySource = new Map();
|
|
183
171
|
for (const row of rows) {
|
|
184
172
|
const source = row && row.source !== undefined ? row.source : null;
|
|
185
173
|
if (source !== null && (typeof source !== 'string' || !source)) {
|
|
186
|
-
throw new Error(`${label}: every
|
|
174
|
+
throw new Error(`${label}: every row needs a non-empty session source string, or null for the legacy sourceless row`);
|
|
187
175
|
}
|
|
188
176
|
if (bySource.has(source)) throw new Error(`${label}: duplicate session source "${source}"`);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// unscoped XOR an allow-list. Both would be ambiguous (does it narrow?);
|
|
192
|
-
// neither would be a scope that can never admit anything — an authoring slip
|
|
193
|
-
// either way, and both must fail loud rather than pick a default.
|
|
194
|
-
if (unscoped === (permissions.length > 0)) {
|
|
195
|
-
throw new Error(`${label}: "${source}" must declare EITHER unscoped:true OR a non-empty permissions allow-list, never both/neither`);
|
|
196
|
-
}
|
|
197
|
-
if (permissions.some((key) => typeof key !== 'string' || !key)) {
|
|
198
|
-
throw new Error(`${label}: "${source}" lists a non-string permission key`);
|
|
177
|
+
if (row && (row.permissions !== undefined || row.unscoped !== undefined)) {
|
|
178
|
+
throw new Error(`${label}: "${source}" declares a scope (permissions/unscoped). Narrowing session scopes were removed (task 1003894); a row may only declare a source that carries its builder's full authority.`);
|
|
199
179
|
}
|
|
200
180
|
bySource.set(source, Object.freeze({
|
|
201
181
|
source,
|
|
202
182
|
label: row && typeof row.label === 'string' ? row.label : null,
|
|
203
|
-
unscoped,
|
|
204
|
-
permissions: Object.freeze(permissions),
|
|
205
183
|
}));
|
|
206
184
|
}
|
|
207
185
|
return bySource;
|
|
@@ -212,7 +190,7 @@ function loadSessionScopes(file) {
|
|
|
212
190
|
try {
|
|
213
191
|
raw = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
214
192
|
} catch (e) {
|
|
215
|
-
throw new Error(`session-scope registry unreadable at ${file}: ${e.message}. Refusing to run with no session-
|
|
193
|
+
throw new Error(`session-scope registry unreadable at ${file}: ${e.message}. Refusing to run with no session-source model (ADR 0151).`);
|
|
216
194
|
}
|
|
217
195
|
return parseSessionScopes(raw, `session-scope registry ${file}`);
|
|
218
196
|
}
|
|
@@ -220,7 +198,7 @@ function loadSessionScopes(file) {
|
|
|
220
198
|
const _sessionScopes = loadSessionScopes(SESSION_SCOPE_REGISTRY_PATH);
|
|
221
199
|
const SESSION_SCOPES = Object.freeze([..._sessionScopes.values()]);
|
|
222
200
|
|
|
223
|
-
// The
|
|
201
|
+
// The declared row for a session's `source`, or null when the registry does not
|
|
224
202
|
// declare it (⇒ deny; never "full").
|
|
225
203
|
function sessionScopeFor(source) {
|
|
226
204
|
const key = source === undefined ? null : source;
|
|
@@ -228,39 +206,6 @@ function sessionScopeFor(source) {
|
|
|
228
206
|
return _sessionScopes.get(key) || null;
|
|
229
207
|
}
|
|
230
208
|
|
|
231
|
-
// Route reachability, deny-by-default. An unscoped session reaches everything its
|
|
232
|
-
// builder's gates admit; a SCOPED session reaches only a route that declared
|
|
233
|
-
// capability its allow-list names — so a route that declares nothing (every route
|
|
234
|
-
// that has not opted in, including any added later) is out of reach.
|
|
235
|
-
function scopeCovers(scope, declared) {
|
|
236
|
-
if (!scope) return false; // unrecognised source
|
|
237
|
-
if (scope.unscoped) return true;
|
|
238
|
-
const needs = Array.isArray(declared) ? declared : [];
|
|
239
|
-
return needs.length > 0 && needs.every((key) => scope.permissions.includes(key));
|
|
240
|
-
}
|
|
241
|
-
function sessionScopeAdmits(source, declared) {
|
|
242
|
-
return scopeCovers(sessionScopeFor(source), declared);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// The route-side DECLARATION: "reaching this route needs these capabilities from
|
|
246
|
-
// the session's scope." Mount it BEFORE requireBuilder. Inert for an unscoped
|
|
247
|
-
// session — requireBuilder compares the declaration only against a narrowing scope.
|
|
248
|
-
function sessionScope(...keys) {
|
|
249
|
-
const needs = Object.freeze([...keys]);
|
|
250
|
-
return function declareSessionScope(req, _res, next) {
|
|
251
|
-
req.sessionScopeNeeds = needs;
|
|
252
|
-
next();
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// The dev-box bootstrap declaration (#919) — the name modules/dev-box mounts
|
|
257
|
-
// through the plugin API (docs/modules-contract.md 1.2.0), kept as the module-
|
|
258
|
-
// facing spelling. It is no longer a bare boolean: it declares the box scope's OWN
|
|
259
|
-
// allow-list, read from the registry, so the declaration cannot drift from the
|
|
260
|
-
// scope it declares. If the registry stopped declaring a box scope it would
|
|
261
|
-
// declare nothing and every box session would be denied — fail closed.
|
|
262
|
-
const allowBoxScope = sessionScope(...((sessionScopeFor('box') || { permissions: [] }).permissions));
|
|
263
|
-
|
|
264
209
|
async function requireBuilder(req, res, next) {
|
|
265
210
|
let session;
|
|
266
211
|
try {
|
|
@@ -295,24 +240,13 @@ async function requireBuilder(req, res, next) {
|
|
|
295
240
|
res.fail('builder_inactive', 403);
|
|
296
241
|
return;
|
|
297
242
|
}
|
|
298
|
-
// SECURITY
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// is DATA (the session-scope registry above), not a hardcoded source test: a
|
|
304
|
-
// narrowing session reaches ONLY a route that declared capability its allow-list
|
|
305
|
-
// names, so EVERY other route (all of /tasks, /me, the rest of /box …) fails
|
|
306
|
-
// closed here, including any route added later; and a source the registry does not
|
|
307
|
-
// declare at all is denied rather than treated as unscoped. A builder who wants to
|
|
308
|
-
// do real work FROM the box runs /builder-reauth, minting a normal cli/web session.
|
|
309
|
-
const scope = sessionScopeFor(session.source);
|
|
310
|
-
if (!scopeCovers(scope, req.sessionScopeNeeds)) {
|
|
311
|
-
res.fail('box_scope', {
|
|
243
|
+
// SECURITY: an UNDECLARED session source is denied (see the registry notes
|
|
244
|
+
// above). This is what keeps a token of a retired source out of every route, and
|
|
245
|
+
// any source added later out until the registry declares it.
|
|
246
|
+
if (!sessionScopeFor(session.source)) {
|
|
247
|
+
res.fail('session_scope', {
|
|
312
248
|
status: 403,
|
|
313
|
-
message:
|
|
314
|
-
? 'This is a box-scoped session; it may only call dev-box bootstrap endpoints.'
|
|
315
|
-
: 'This session carries an unrecognised scope and may not call authenticated endpoints.',
|
|
249
|
+
message: 'This session carries an unrecognised source and may not call authenticated endpoints. Sign in again to mint a normal session.',
|
|
316
250
|
});
|
|
317
251
|
return;
|
|
318
252
|
}
|
|
@@ -382,9 +316,9 @@ function signInWithReturn(req) {
|
|
|
382
316
|
// 1. IDENTITY — is there a live session? (token from the gds_session cookie /
|
|
383
317
|
// legacy pms_session / Bearer, looked up fresh against builder_sessions).
|
|
384
318
|
// No → 302 into the web sign-in flow.
|
|
385
|
-
// 2.
|
|
386
|
-
//
|
|
387
|
-
// requireBuilder denies it every route
|
|
319
|
+
// 2. SOURCE — does the session-scope registry declare this session's source?
|
|
320
|
+
// An undeclared one reaches no page, whatever its builder's rank, exactly as
|
|
321
|
+
// requireBuilder denies it every route (task 1003197).
|
|
388
322
|
// No → 302 into the web sign-in flow: the remedy is a different session.
|
|
389
323
|
// 3. AUTHORITY — does the builder HOLD the page's catalog permission, resolved
|
|
390
324
|
// per request through the `governance` port exactly as requirePermission
|
|
@@ -557,21 +491,15 @@ async function runPageGate(name, req, res, next) {
|
|
|
557
491
|
res.redirect(302, signInWithReturn(req));
|
|
558
492
|
return;
|
|
559
493
|
}
|
|
560
|
-
// SECURITY (task 1003197 (A)):
|
|
561
|
-
//
|
|
562
|
-
//
|
|
563
|
-
// could fetch a gated page SHELL
|
|
564
|
-
//
|
|
565
|
-
// re-gates through requireBuilder and 403s box_scope, so the exposure was shells
|
|
566
|
-
// only — but a shell is still a surface a token living outside the ADR 0016 trust
|
|
567
|
-
// boundary was never meant to reach. Page routes declare no capability, so
|
|
568
|
-
// scopeCovers refuses EVERY narrowing scope here (and an unrecognised source with
|
|
569
|
-
// it), while an unscoped cli/web session passes untouched.
|
|
494
|
+
// SECURITY (task 1003197 (A)): the same undeclared-source check requireBuilder
|
|
495
|
+
// runs above — the two auth paths must not diverge. resolveSession accepts a
|
|
496
|
+
// Bearer token with NO source filter, so without this a token of an undeclared
|
|
497
|
+
// source could fetch a gated page SHELL whenever its builder's rank held the
|
|
498
|
+
// page's permission.
|
|
570
499
|
//
|
|
571
500
|
// Sign-in, not `deniedTo`: unlike the authority branch below, the remedy is a
|
|
572
|
-
// DIFFERENT SESSION
|
|
573
|
-
|
|
574
|
-
if (!scopeCovers(sessionScopeFor(session.source), req.sessionScopeNeeds)) {
|
|
501
|
+
// DIFFERENT SESSION, not a different page.
|
|
502
|
+
if (!sessionScopeFor(session.source)) {
|
|
575
503
|
res.redirect(302, signInWithReturn(req));
|
|
576
504
|
return;
|
|
577
505
|
}
|
|
@@ -598,14 +526,14 @@ async function runPageGate(name, req, res, next) {
|
|
|
598
526
|
}
|
|
599
527
|
|
|
600
528
|
// The authenticated /builders HTML surface — the Work Board, Roadmap, Goals, the
|
|
601
|
-
// task-detail page, the Repo Atlas
|
|
529
|
+
// task-detail page, the Repo Atlas and Settings. A Xenos IS
|
|
602
530
|
// allowed (page.view.public floors at xenos); an unauthenticated visitor is bounced
|
|
603
531
|
// into sign-in rather than served the page shell.
|
|
604
532
|
async function requireBuilderPage(req, res, next) {
|
|
605
533
|
return runPageGate('requireBuilderPage', req, res, next);
|
|
606
534
|
}
|
|
607
535
|
|
|
608
|
-
// The /watch monitoring surface
|
|
536
|
+
// The /watch monitoring surface and the /gate page
|
|
609
537
|
// (#726, idea 257, task 1179) — Metic and up, per `page.view.government`'s catalog
|
|
610
538
|
// floor. Named `requireArchonPage` until ADR 0157 lowered that floor archon→metic
|
|
611
539
|
// (older ADRs still use the old name; this line is the trail). Defense-in-depth:
|
|
@@ -917,10 +845,7 @@ module.exports = {
|
|
|
917
845
|
extractTokens,
|
|
918
846
|
SESSION_COOKIE_NAMES,
|
|
919
847
|
resolveSession,
|
|
920
|
-
allowBoxScope,
|
|
921
|
-
sessionScope,
|
|
922
848
|
sessionScopeFor,
|
|
923
|
-
sessionScopeAdmits,
|
|
924
849
|
parseSessionScopes,
|
|
925
850
|
SESSION_SCOPES,
|
|
926
851
|
requireBuilder,
|
package/src/bongos/db-kernel.js
CHANGED
|
@@ -45,7 +45,7 @@ async function withTx(fn, { pool: p = pool } = {}) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// getBuilderById — the canonical builder-identity read (id, rank, status, the
|
|
48
|
-
// economy counters
|
|
48
|
+
// economy counters). The kernel's identity primitive; auth and
|
|
49
49
|
// every rank gate ultimately resolve a builder through this shape.
|
|
50
50
|
async function getBuilderById(id) {
|
|
51
51
|
// karma was added in migration 040 (V3.R24 / #244) as a separate
|
|
@@ -55,8 +55,7 @@ async function getBuilderById(id) {
|
|
|
55
55
|
const { rows } = await pool.query(
|
|
56
56
|
`SELECT id, github_id, github_login, display_name, avatar_url, total_credits, karma, created_at, rank,
|
|
57
57
|
status, deactivated_at, reactivated_at, deactivated_reason, preferred_disciplines, system_role,
|
|
58
|
-
anthropic_email, anthropic_user_id, monthly_budget_usd
|
|
59
|
-
box_blocked, box_blocked_reason, box_blocked_at, box_blocked_by
|
|
58
|
+
anthropic_email, anthropic_user_id, monthly_budget_usd
|
|
60
59
|
FROM builders WHERE id = $1`,
|
|
61
60
|
[id]
|
|
62
61
|
);
|