@bongos/core 1.19.1072 → 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 +6 -2
- 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 +2 -1
- 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 +4 -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 +84 -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
|
@@ -93,8 +93,7 @@ async function respondWithLedger(res, builderId, opts) {
|
|
|
93
93
|
module.exports = function buildCreditsRouter() {
|
|
94
94
|
const router = express.Router();
|
|
95
95
|
|
|
96
|
-
// Route ORDER is deliberate
|
|
97
|
-
// documents: the route-rank auditor classifies from a lookahead window that
|
|
96
|
+
// Route ORDER is deliberate: the route-rank auditor classifies from a lookahead window that
|
|
98
97
|
// checks requireRank/requirePermission before requireBuilder, so the
|
|
99
98
|
// PRIVILEGED route is declared FIRST. Below the own-scoped one, its gate
|
|
100
99
|
// would bleed upward and mislabel that route as privileged.
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
// one; the Archon tab reads `PRINCIPALS` to SHOW them and has no counterpart write.
|
|
48
48
|
//
|
|
49
49
|
// OWNERSHIP SCOPE — the third AXIS (BV1.R100, ADR 0151 §3). "May act on OWN X" is a
|
|
50
|
-
// PARAMETERIZED permission, not a global grant:
|
|
51
|
-
//
|
|
50
|
+
// PARAMETERIZED permission, not a global grant: task.abandon.own ≠ the cross-owner
|
|
51
|
+
// task authority. Two optional fields make that axis explicit DATA (the way
|
|
52
52
|
// `principal` made the identity axis data) rather than something inferred from a
|
|
53
53
|
// key's spelling:
|
|
54
54
|
// • scope — 'own' = ownership-scoped. Holding it authorizes acting ONLY on a
|
|
@@ -62,11 +62,11 @@
|
|
|
62
62
|
// 'any' = the CROSS-OWNER counterpart over the same resource: it
|
|
63
63
|
// reaches every owner's row, so no predicate applies.
|
|
64
64
|
// null = not ownership-scoped (the resource has no owner axis).
|
|
65
|
-
// • resource — WHAT is owned ('claim', '
|
|
65
|
+
// • resource — WHAT is owned ('claim', 'task', 'memory', …). It is what LINKS an
|
|
66
66
|
// 'own' key to its 'any' counterpart, which a name cannot: the
|
|
67
|
-
// cross-owner
|
|
68
|
-
// `task.claim.any` ends in `.any` and means
|
|
69
|
-
// not "any owner's resource". Inferring the axis from the suffix
|
|
67
|
+
// cross-owner authority over `task.abandon.own` is spelled plain
|
|
68
|
+
// `task.abandon`, while `task.claim.any` ends in `.any` and means
|
|
69
|
+
// "any task in the pool", not "any owner's resource". Inferring the axis from the suffix
|
|
70
70
|
// would get both of those wrong — hence data.
|
|
71
71
|
// Asserted at load: scope ⟺ resource; a `.own`-suffixed key MUST declare scope
|
|
72
72
|
// 'own' (a namespace can never outrun the data); a scoped key needs a real rank
|
|
@@ -220,7 +220,6 @@ const PERMISSIONS = [
|
|
|
220
220
|
{ key: 'llm_cache.use', system: false, floor: 'metic', guards: 'POST /llm-cache/{lookup,store}' },
|
|
221
221
|
{ key: 'backup.manage', system: false, floor: 'metic', guards: 'GET /backup/status, POST /backup/trigger' },
|
|
222
222
|
{ key: 'source.clone.local', system: false, floor: 'metic', guards: 'Metic+ local clone / collaborator invite (ADR 0035)' },
|
|
223
|
-
{ key: 'box.source.full', system: false, floor: 'metic', guards: 'GET /box/source-access → full (vs starter); boxScopeForRank' },
|
|
224
223
|
// Agent authoring, split in two because the two acts differ in consequence
|
|
225
224
|
// (task 1002493). Authoring writes a definition; ARMING is what turns a piece
|
|
226
225
|
// of text into something that spends money and reads the repo, and it is the
|
|
@@ -252,7 +251,6 @@ const PERMISSIONS = [
|
|
|
252
251
|
{ key: 'criterion.unsatisfy', system: false, floor: 'metic', guards: 'POST /done-when/:id/unsatisfy (ADR 0157, was archon)' },
|
|
253
252
|
{ key: 'security.report.adjudicate', system: false, floor: 'metic', guards: 'GET /security/reports, POST /security/reports/:id/{confirm,dispute,fix,link-fix}, /security/docs CRUD (ADR 0157, was archon)' },
|
|
254
253
|
{ key: 'provisioning.fleet.manage', system: false, floor: 'metic', guards: 'GET /provisioning/fleet[/cost-ledger], POST .../force-teardown (ADR 0157, was archon)' },
|
|
255
|
-
{ key: 'box.fleet.manage', system: false, floor: 'metic', scope: 'any', resource: 'box', verb: 'manage', guards: 'GET /boxes[/cost-ledger], POST /boxes/:b/close, PATCH .../block (ADR 0157, was archon)' },
|
|
256
254
|
{ key: 'autonomy.precheck', system: false, floor: 'metic', guards: 'GET /autonomy/precheck — unsupervised-runner gate (ADR 0157, was archon); also GET /autonomy/runs, the gate\'s past decisions (task 1002669)' },
|
|
257
255
|
{ key: 'discord.channels.manage', system: false, floor: 'metic', guards: 'GET /discord/channels/plan, POST .../reconcile (ADR 0157, was archon)' },
|
|
258
256
|
// Metic by the discord.channels.manage analogue — configuring a project-wide
|
|
@@ -334,9 +332,6 @@ const PERMISSIONS = [
|
|
|
334
332
|
// against another builder's claim. A future `claim.manage.any` (verb `manage`)
|
|
335
333
|
// would dominate `act` and belongs here; `claim.release.any` never does.
|
|
336
334
|
{ key: 'task.act.own', system: false, floor: 'xenos', scope: 'own', resource: 'claim', verb: 'act', counterpart: [], guards: 'act on OWN active claim (release, cost, notes) — ownership-scoped' },
|
|
337
|
-
// The one family whose counterpart is REAL: `box.fleet.manage` is the same `manage`
|
|
338
|
-
// verb over every owner's box, so it genuinely subsumes managing your own.
|
|
339
|
-
{ key: 'box.manage.own', system: false, floor: 'xenos', scope: 'own', resource: 'box', verb: 'manage', counterpart: ['box.fleet.manage'], guards: 'own dev-box lifecycle — ownership-scoped' },
|
|
340
335
|
// Empty for the same reason as task.act.own: `memory.read.any` READS every
|
|
341
336
|
// builder's memory, which is not the authority to MANAGE one. This is the pair the
|
|
342
337
|
// task-1002671 verification probed (finding 12) — a read grant was admitting a
|
|
@@ -373,12 +368,12 @@ const PERMISSIONS = [
|
|
|
373
368
|
{ key: 'blocker.file', system: false, floor: 'xenos', guards: 'POST /blockers' },
|
|
374
369
|
{ key: 'idea.file', system: false, floor: 'xenos', guards: 'POST /inbox (own idea capture)' },
|
|
375
370
|
{ key: 'page.view.public', system: false, floor: 'xenos', guards: 'public read surfaces (incl. xenos)' },
|
|
376
|
-
// ADR 0157: floor archon→metic, which is what opens /watch
|
|
371
|
+
// ADR 0157: floor archon→metic, which is what opens /watch and /gate to the
|
|
377
372
|
// working rank. The gate FUNCTION was renamed to match (`requireArchonPage` →
|
|
378
373
|
// `requireGovernmentPage`) once the floor moved. Viewing is all this grants: every
|
|
379
374
|
// write those pages reach is its own atom, and `government.manage` (the
|
|
380
375
|
// ranks/permissions tab) stayed archon.
|
|
381
|
-
{ key: 'page.view.government', system: false, floor: 'metic', guards: 'the government/monitoring pages — /watch, /
|
|
376
|
+
{ key: 'page.view.government', system: false, floor: 'metic', guards: 'the government/monitoring pages — /watch, /gate, /government (requireGovernmentPage; write = government.manage, archon)' },
|
|
382
377
|
];
|
|
383
378
|
|
|
384
379
|
// ── invariants asserted at module load (fail loud on an authoring typo) ─────────
|
|
@@ -394,8 +389,9 @@ const OWNERSHIP_SCOPES = Object.freeze(['own', 'any']);
|
|
|
394
389
|
// row — a read grant admitting a write.
|
|
395
390
|
//
|
|
396
391
|
// The verb is DECLARED, never parsed out of the key string. A positional parse is
|
|
397
|
-
// wrong on the live population (`
|
|
398
|
-
//
|
|
392
|
+
// wrong on the live population (`task.abandon` is verb `act`, a word the key never
|
|
393
|
+
// spells; `me.wandering.set` is resource `builder_prefs`, not `me`), and the same
|
|
394
|
+
// reason `scope` is data:
|
|
399
395
|
// "the namespace can never outrun the data".
|
|
400
396
|
const OWNERSHIP_VERBS = Object.freeze(['read', 'set', 'release', 'act', 'manage']);
|
|
401
397
|
// Which verbs a verb SUBSUMES, beyond itself (every verb dominates itself). Kept
|
|
@@ -549,7 +545,7 @@ for (const [resource, fam] of _byResource) {
|
|
|
549
545
|
throw new Error(`government catalog: resource "${resource}" declares a cross-owner permission (${fam.any.join(', ')}) with no ownership-scoped counterpart`);
|
|
550
546
|
}
|
|
551
547
|
// Reaching EVERY owner's row must never be cheaper than reaching your own. If it
|
|
552
|
-
// were, a rank could
|
|
548
|
+
// were, a rank could act on everyone's rows but not its own — the model
|
|
553
549
|
// inverted, and the Government tab would render the wider grant as the safer one.
|
|
554
550
|
for (const anyKey of fam.any) {
|
|
555
551
|
for (const ownKey of fam.own) {
|
|
@@ -740,7 +736,7 @@ function crossOwnerKeys() {
|
|
|
740
736
|
return PERMISSIONS.filter((p) => p.scope === 'any').map((p) => p.key);
|
|
741
737
|
}
|
|
742
738
|
// The DECLARED escalation path for `key` — what a route offers as the alternative
|
|
743
|
-
// to owning the row ("
|
|
739
|
+
// to owning the row ("a task you created, or task.abandon"). Empty when nothing may
|
|
744
740
|
// escalate past it, which is the common case.
|
|
745
741
|
//
|
|
746
742
|
// Read straight off the key's own `counterpart`, NOT derived from its resource
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- government_019_retire_box_permissions.sql — REVOKE the three dev-box permission
|
|
2
|
+
-- keys from every rank that holds them (task 1003894, goal 1000120).
|
|
3
|
+
--
|
|
4
|
+
-- Goal 1000120 removed the dev box: its module and routes (task 1003892), and in
|
|
5
|
+
-- task 1003894 the three catalog keys that gated them — `box.source.full`,
|
|
6
|
+
-- `box.fleet.manage` and `box.manage.own`. The catalog is the DECLARATION; the
|
|
7
|
+
-- grant rows are what a builder's permission set is folded from. With the keys
|
|
8
|
+
-- gone from modules/government/catalog.js the resolver already drops these rows
|
|
9
|
+
-- (a key the catalog does not define grants nothing), so this changes no one's
|
|
10
|
+
-- effective authority. It keeps the grant table honest: every row in it names a
|
|
11
|
+
-- permission that exists.
|
|
12
|
+
--
|
|
13
|
+
-- ONE statement for EVERY rank, seeded and custom. The three keys were
|
|
14
|
+
-- `system: false`, so an Archon could have granted them to a custom rank, and a
|
|
15
|
+
-- per-rank DELETE would leave those rows behind. tests/government_seed.mjs reads
|
|
16
|
+
-- this shape (no `rank_key` predicate) as "revoked from every rank" when it folds
|
|
17
|
+
-- the grant migrations into the effective seed.
|
|
18
|
+
--
|
|
19
|
+
-- NOT governance_002 or governance_007: those granted the keys and are applied
|
|
20
|
+
-- history — migrations are run-once and never rewritten.
|
|
21
|
+
--
|
|
22
|
+
-- NOT idempotent by ON CONFLICT — a DELETE needs none. Re-running it deletes zero
|
|
23
|
+
-- rows and raises nothing.
|
|
24
|
+
--
|
|
25
|
+
-- `government_rank_permissions`, NOT the `governance_*` spelling:
|
|
26
|
+
-- government_001_rename_from_governance renamed the table with no shim.
|
|
27
|
+
|
|
28
|
+
BEGIN;
|
|
29
|
+
|
|
30
|
+
DELETE FROM government_rank_permissions
|
|
31
|
+
WHERE permission_key IN ('box.source.full', 'box.fleet.manage', 'box.manage.own');
|
|
32
|
+
|
|
33
|
+
COMMIT;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"key": "government",
|
|
3
3
|
"title": "Government (configurable ranks & permissions)",
|
|
4
4
|
"description": "Discord-style configurable ranks and permissions (ADR 0151, BONGOS-V1 goal 1000030; renamed governance\u2192government by ADR 0174). Permissions are the atomic authority unit and the four ranks (xenos/thetes/metic/archon) are the positions in the government; this module owns the code-defined permission catalog (each key flagged `system` = trust-boundary, `substrate` = enforced out of band, or operational = delegable) plus the compatibility seed that reproduces the rank thresholds exactly. A core-domain module, default-on: a Cloud Bongos instance ships configurable ranks the way it ships its status page. The word `role` is deliberately absent here \u2014 it is reserved for the three crafts (Artist, Ideator, Builder). Preserves ADR 0016 (authority resolved server-side, per request, uncached).",
|
|
5
|
+
"version": "1.0.0",
|
|
5
6
|
"coreVersion": "^1.18.0",
|
|
6
7
|
"default": true,
|
|
7
8
|
"maintenance": {
|
|
@@ -41,17 +41,9 @@
|
|
|
41
41
|
{ "glob": "src/bongos/routes.js", "group": "authz", "why": "the route MOUNT table (a new ungated route lands here)" },
|
|
42
42
|
{ "glob": "server.js", "group": "authz", "why": "the Express entrypoint — wires every router + host routing" },
|
|
43
43
|
{ "glob": "modules/government/", "group": "authz", "why": "the same authz core under its post-ADR-0174 name (goal 1000068, task 1003003). Listed BEFORE the folder moves, deliberately: `protected` is DERIVED from this registry, so a module key whose globs are absent here computes protected:false — which would silently drop the Archon gate on goal creation AND the pre-push block on these files for the whole window between the key landing and the folder arriving. The prospective-carve precedent is the modules/government/ entry directly above (ADR 0086 §4)." },
|
|
44
|
-
{ "glob": "
|
|
45
|
-
{ "glob": "
|
|
46
|
-
{ "glob": "
|
|
47
|
-
{ "glob": "scripts/gds/provision-config.js", "group": "box-access", "why": "task 1002891. CREDENTIAL READER: reads process.env.DO_API_TOKEN. The group already protects who DECIDES box access (boxes.js, box-access.js, box-credential.js) but protected nothing that HOLDS the infrastructure credentials those decisions spend — an asymmetry idea 1000692 raised and this entry closes. The set is computed, not listed: the tripwire in tests/government_protected_surfaces.mjs recomputes every process.env credential read from the tracked tree and reds if one is unregistered." },
|
|
48
|
-
{ "glob": "scripts/gds/provision-net.js", "group": "box-access", "why": "task 1002891. CREDENTIAL READER: reads process.env.CLOUDFLARE_API_TOKEN in checkDnsTokenScope. The filing task asserted this code lived in scripts/gds/provision.js; it had moved, and provision.js reads no credential at all. Registering the named file rather than the computed one would have recorded the gap as closed while closing nothing." },
|
|
49
|
-
{ "glob": "scripts/gds/box-infra.js", "group": "box-access", "why": "task 1002891. CREDENTIAL READER: reads process.env.CLOUDFLARE_API_TOKEN and process.env.CLOUDFLARE_ACCOUNT_ID into the CF config the box plane provisions against." },
|
|
50
|
-
{ "glob": "scripts/gds/cf-tunnel.js", "group": "box-access", "why": "task 1002891. CREDENTIAL READER: defaults its token parameter to process.env.CLOUDFLARE_API_TOKEN, so it reads the credential whenever a caller omits one. Not named by the recorded decision — the recomputation found it, which is the whole argument for computing the set instead of transcribing it." },
|
|
51
|
-
{ "glob": "scripts/gds/box.js", "group": "box-access", "why": "task 1002891. CREDENTIAL READER: reads process.env.DO_API_TOKEN — the DigitalOcean control plane for the box fleet. Also not named by the recorded decision. Registering it does NOT widen any module wall: the dev-box module key already lists this file and already derives protected:true from the modules/dev-box/ entries above, so the derived protected-module set is unchanged (verified before and after against src/bongos/module-scope-map.js)." },
|
|
52
|
-
{ "glob": "scripts/gds/box-auth-check.js", "group": "box-access", "why": "task 1004024. Split out of the box.js family and never registered; box-infra.js was already listed by name above." },
|
|
53
|
-
{ "glob": "scripts/gds/box-sync.js", "group": "box-access", "why": "task 1004024. Split out of the box.js family and never registered; box-infra.js was already listed by name above." },
|
|
54
|
-
{ "glob": "scripts/gds/do-api.js", "group": "box-access", "why": "task 1002891. The DigitalOcean API client the token is handed TO. Unlike the five above it reads no process.env value — it takes the token as a parameter and throws when a live call has none — so the drift tripwire does not demand it and its absence from that computed set is expected, not a bug. It is registered because the recorded decision named it and because it is the code that puts the credential on the wire." },
|
|
44
|
+
{ "glob": "scripts/gds/provision-config.js", "group": "control-plane", "why": "task 1002891. CREDENTIAL READER: reads process.env.DO_API_TOKEN. Before this entry nothing that HOLDS the control plane's infrastructure credentials was protected at any rank — an asymmetry idea 1000692 raised and this entry closes. The set is computed, not listed: the tripwire in tests/government_protected_surfaces.mjs recomputes every process.env credential read from the tracked tree and reds if one is unregistered." },
|
|
45
|
+
{ "glob": "scripts/gds/provision-net.js", "group": "control-plane", "why": "task 1002891. CREDENTIAL READER: reads process.env.CLOUDFLARE_API_TOKEN in checkDnsTokenScope. The filing task asserted this code lived in scripts/gds/provision.js; it had moved, and provision.js reads no credential at all. Registering the named file rather than the computed one would have recorded the gap as closed while closing nothing." },
|
|
46
|
+
{ "glob": "scripts/gds/do-api.js", "group": "control-plane", "why": "task 1002891. The DigitalOcean API client the token is handed TO. Unlike the two above it reads no process.env value — it takes the token as a parameter and throws when a live call has none — so the drift tripwire does not demand it and its absence from that computed set is expected, not a bug. It is registered because the recorded decision named it and because it is the code that puts the credential on the wire." },
|
|
55
47
|
{ "glob": "modules/grading/grader.js", "group": "pipeline", "why": "BV1.R73: grading carved to modules/grading/ — the glob follows the file" },
|
|
56
48
|
{ "glob": "modules/grading/grader-config.js", "group": "pipeline", "why": "task 1004024. Carved out of grader.js: the deterministic permission-path pre-pass, the server-side score guards a model cannot talk past, the subagent spawn and the prompt. grader.js alone protected the facade and none of the gate." },
|
|
57
49
|
{ "glob": "modules/grading/grader-model.js", "group": "pipeline", "why": "task 1004024. Carved out of grader.js: the deterministic permission-path pre-pass, the server-side score guards a model cannot talk past, the subagent spawn and the prompt. grader.js alone protected the facade and none of the gate." },
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
|
|
30
30
|
const catalog = require('./catalog');
|
|
31
31
|
const db = require('./db');
|
|
32
|
-
const sessionScopes = require('./session-scopes');
|
|
33
32
|
|
|
34
33
|
// Pure core of the RANK axis, split out so it is unit-testable without a database.
|
|
35
34
|
//
|
|
@@ -93,7 +92,7 @@ async function resolveBuilderPermissions(builderId) {
|
|
|
93
92
|
// false so a mis-authored gate denies rather than admits (the same posture
|
|
94
93
|
// requireRank takes on an all-unknown allow-list).
|
|
95
94
|
//
|
|
96
|
-
// It also REFUSES ownership-scoped keys (R100). "Do you hold
|
|
95
|
+
// It also REFUSES ownership-scoped keys (R100). "Do you hold task.abandon.own" has
|
|
97
96
|
// no unconditional answer — the permission is parameterized by a resource this
|
|
98
97
|
// function was never given — and returning true would be precisely the collapse
|
|
99
98
|
// of `.own` into an unconditional grant the axis exists to prevent. The caller
|
|
@@ -116,7 +115,7 @@ async function builderHasPermissions(builderId, required) {
|
|
|
116
115
|
// handler that loaded the row knows it)
|
|
117
116
|
//
|
|
118
117
|
// The resolver structurally cannot know (2): it is handed a builder id, never a
|
|
119
|
-
//
|
|
118
|
+
// claim/memory/task row. So the caller SUPPLIES the owner and the model refuses to
|
|
120
119
|
// answer without it — `ownerId` is not an option, it is the second half of the
|
|
121
120
|
// question. That is what keeps `.own` from degrading into `.any`: there is no code
|
|
122
121
|
// path here that turns an ownership-scoped key into an admit on its own.
|
|
@@ -156,7 +155,7 @@ async function authorizeOwned(builderId, key, context) {
|
|
|
156
155
|
|
|
157
156
|
// The composition every own-scoped route actually wants: reach this row because it
|
|
158
157
|
// is YOURS, or because you hold the cross-owner permission over its resource
|
|
159
|
-
// family (
|
|
158
|
+
// family (task.abandon for task.abandon.own). One resolve, both
|
|
160
159
|
// branches. A resource with no cross-owner form by design (credential) simply has
|
|
161
160
|
// no second branch, so the answer stays own-only.
|
|
162
161
|
async function authorizeOwnedOrAny(builderId, ownKey, context) {
|
|
@@ -167,46 +166,6 @@ async function authorizeOwnedOrAny(builderId, ownKey, context) {
|
|
|
167
166
|
return sameOwner(context && context.ownerId, builderId) && held.includes(ownKey);
|
|
168
167
|
}
|
|
169
168
|
|
|
170
|
-
// ── the SESSION-SCOPE axis (R99, ADR 0151 §3) ───────────────────────────────────
|
|
171
|
-
// The FOURTH entry point, separate for the same reason the other three are: it
|
|
172
|
-
// answers a different question with a different input. Ranks answer "what may this
|
|
173
|
-
// PERSON do"; a principal answers "what may this MACHINE do"; an ownership scope
|
|
174
|
-
// answers "whose row may it be done to"; a session-scope answers "how much of that
|
|
175
|
-
// does THIS CREDENTIAL carry". A dev-box token is minted onto a machine outside the
|
|
176
|
-
// ADR 0016 trust boundary (#919/SR#2, ADR 0053), so it must stay limited even when
|
|
177
|
-
// its builder is an Archon — which no rank-shaped answer can express.
|
|
178
|
-
//
|
|
179
|
-
// A SESSION-SCOPE CAN ONLY EVER NARROW, in both directions:
|
|
180
|
-
// • INTERSECTION, never union. The scope may not GRANT a permission the ranks do
|
|
181
|
-
// not confer — so the composition below is written as a FILTER of the RANK
|
|
182
|
-
// answer. There is no branch here that can add a key, which is what makes
|
|
183
|
-
// "a box token cannot escalate" structural rather than merely intended.
|
|
184
|
-
// • DENY-BY-DEFAULT. A key the allow-list does not name is denied; an
|
|
185
|
-
// unrecognised session source resolves to NOTHING rather than to "full".
|
|
186
|
-
// Widening is therefore not expressible without deleting the filter, and R103's
|
|
187
|
-
// parity is untouched: effectivePermissions is the unchanged RANK answer this
|
|
188
|
-
// composes ON TOP of, never inside.
|
|
189
|
-
//
|
|
190
|
-
// Pure and DB-free (the DB half is resolveBuilderPermissions'), so an unknown key
|
|
191
|
-
// on an allow-list is inert as well as refused at load: it can never appear in the
|
|
192
|
-
// catalog-filtered rank answer, so it can never survive the intersection.
|
|
193
|
-
function narrowToSessionScope(rawKeys, source) {
|
|
194
|
-
const scope = sessionScopes.scopeFor(source);
|
|
195
|
-
if (!scope) return Object.freeze([]); // unrecognised source → no authority at all
|
|
196
|
-
const held = effectivePermissions(rawKeys);
|
|
197
|
-
if (scope.unscoped) return held; // this credential narrows nothing (cli/web)
|
|
198
|
-
const allowed = new Set(scope.permissions);
|
|
199
|
-
return Object.freeze(held.filter((key) => allowed.has(key)));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// What can THIS SESSION actually do? The builder's ranks ∩ this session's scope,
|
|
203
|
-
// as a frozen sorted array of catalog keys. Fails closed BEFORE touching the DB on
|
|
204
|
-
// a source the registry does not declare.
|
|
205
|
-
async function resolveSessionPermissions(builderId, source) {
|
|
206
|
-
if (!sessionScopes.scopeFor(source)) return Object.freeze([]);
|
|
207
|
-
return narrowToSessionScope(await resolveBuilderPermissions(builderId), source);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
169
|
module.exports = {
|
|
211
170
|
resolveBuilderPermissions,
|
|
212
171
|
builderHasPermissions,
|
|
@@ -216,6 +175,4 @@ module.exports = {
|
|
|
216
175
|
sameOwner,
|
|
217
176
|
authorizeOwned,
|
|
218
177
|
authorizeOwnedOrAny,
|
|
219
|
-
narrowToSessionScope,
|
|
220
|
-
resolveSessionPermissions,
|
|
221
178
|
};
|
|
@@ -78,8 +78,6 @@ const governmentPort = {
|
|
|
78
78
|
sameOwner: resolver.sameOwner,
|
|
79
79
|
isOwnershipScoped: catalog.isOwnershipScoped,
|
|
80
80
|
crossOwnerKeysFor: catalog.crossOwnerKeysFor,
|
|
81
|
-
resolveSessionPermissions: resolver.resolveSessionPermissions,
|
|
82
|
-
narrowToSessionScope: resolver.narrowToSessionScope,
|
|
83
81
|
sessionScopeFor: sessionScopes.scopeFor,
|
|
84
82
|
listSessionScopes: () => sessionScopes.SESSION_SCOPES,
|
|
85
83
|
// The Board Room's membership axis (R06 of goal 1000069): who governs, right
|
|
@@ -1,127 +1,87 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// modules/government/session-scopes.js — the SESSION-
|
|
3
|
+
// modules/government/session-scopes.js — the SESSION-SOURCE registry (ADR 0151 §3, BV1.R99).
|
|
4
4
|
//
|
|
5
|
-
// The
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// builder is an Archon.
|
|
5
|
+
// The one government axis attached to the TOKEN rather than to the builder: which
|
|
6
|
+
// kinds of session may authenticate at all. Every builder_sessions row carries a
|
|
7
|
+
// `source`; session-scopes.json (next to this file) declares the sources this
|
|
8
|
+
// instance accepts, and a session whose source it does not declare is DENIED by the
|
|
9
|
+
// kernel on every route and page. Every declared source carries its builder's full
|
|
10
|
+
// rank authority.
|
|
12
11
|
//
|
|
13
|
-
// A
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// written as a FILTER of the rank answer so widening is not expressible there.
|
|
12
|
+
// It used to be more. A row could declare a NARROWING scope — a deny-by-default
|
|
13
|
+
// permissions allow-list, composed with the rank answer as an intersection — for a
|
|
14
|
+
// token living outside the ADR 0016 trust boundary. Its only user was the
|
|
15
|
+
// machine-baked token (ADR 0053) that goal 1000120 retired, so the allow-list form,
|
|
16
|
+
// its catalog cross-check and the resolver's narrowing left with it (task 1003894).
|
|
17
|
+
// A row that still declares one is REFUSED at load: with the enforcement gone, it
|
|
18
|
+
// would read as a restriction while granting full authority.
|
|
21
19
|
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// the
|
|
26
|
-
// reads the same JSON as DATA (fs, never `require` — ADR 0083's one-way rule) and
|
|
27
|
-
// requireBuilder remains the gate every route composes. Nothing here enforces
|
|
28
|
-
// anything; re-mapping a scope here changes what the gate admits, it can never
|
|
29
|
-
// move the gate.
|
|
20
|
+
// GOVERNMENT OWNS THE LIST, THE KERNEL STAYS THE ENFORCER — the same split R101
|
|
21
|
+
// draws for the protected-surface registry: src/bongos/auth.js reads the same JSON
|
|
22
|
+
// as DATA (fs, never `require` — ADR 0083's one-way rule) and requireBuilder remains
|
|
23
|
+
// the gate every route composes. Nothing here enforces anything.
|
|
30
24
|
//
|
|
31
|
-
// FAIL-CLOSED at load:
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
// blur) the surface a token outside the trust boundary may reach — an unreadable
|
|
35
|
-
// policy is a broken install, not an opted-out one.
|
|
25
|
+
// FAIL-CLOSED at load: an empty registry, a duplicate or malformed source, or a row
|
|
26
|
+
// declaring a scope all THROW. An unreadable policy is a broken install, not an
|
|
27
|
+
// opted-out one.
|
|
36
28
|
|
|
37
|
-
const catalog = require('./catalog');
|
|
38
29
|
const registry = require('./session-scopes.json');
|
|
39
30
|
|
|
40
|
-
// Normalize
|
|
41
|
-
// `source` null for the legacy sourceless row
|
|
31
|
+
// Normalize a declared row into the same shape the kernel loader produces:
|
|
32
|
+
// `source` null for the legacy sourceless row.
|
|
42
33
|
function normalizeScope(row) {
|
|
43
34
|
return Object.freeze({
|
|
44
35
|
source: row && row.source !== undefined ? row.source : null,
|
|
45
36
|
label: row && typeof row.label === 'string' ? row.label : null,
|
|
46
|
-
|
|
47
|
-
permissions: Object.freeze(row && Array.isArray(row.permissions) ? [...row.permissions] : []),
|
|
37
|
+
declaresScope: !!(row && (row.permissions !== undefined || row.unscoped !== undefined)),
|
|
48
38
|
});
|
|
49
39
|
}
|
|
50
40
|
|
|
51
|
-
// The
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
// The load-time assertions. Exported so the shipped check itself is what the tests
|
|
42
|
+
// exercise, rather than a re-implementation of it.
|
|
43
|
+
function validateScopes(scopes) {
|
|
44
|
+
if (!Array.isArray(scopes) || scopes.length === 0) {
|
|
45
|
+
throw new Error('session-scope registry declares no session sources. Refusing to run with no session-source model (ADR 0151).');
|
|
46
|
+
}
|
|
47
|
+
const seen = new Set();
|
|
55
48
|
for (const scope of scopes) {
|
|
56
|
-
const where = `session-scope registry:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (scope.unscoped === (scope.permissions.length > 0)) {
|
|
60
|
-
throw new Error(`${where} must declare EITHER unscoped:true OR a non-empty permissions allow-list, never both/neither`);
|
|
49
|
+
const where = `session-scope registry: source "${scope.source}"`;
|
|
50
|
+
if (scope.source !== null && (typeof scope.source !== 'string' || !scope.source)) {
|
|
51
|
+
throw new Error('session-scope registry: every row needs a non-empty session source string, or null for the legacy sourceless row');
|
|
61
52
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
// R98's rule, kept structural: a machine principal's capability comes from
|
|
67
|
-
// its exact system_role, never from a rank — so a box-scoped session
|
|
68
|
-
// carrying one must not be expressible on a scope allow-list either.
|
|
69
|
-
if (catalog.isPrincipalKey(key)) {
|
|
70
|
-
throw new Error(`${where} allows the machine-principal permission "${key}" — a session-scope narrows a builder's RANK authority and never composes with the identity axis`);
|
|
71
|
-
}
|
|
72
|
-
// A session-scope IS the surface a credential outside the ADR 0016 trust
|
|
73
|
-
// boundary may reach. A trust-boundary permission must never appear on one,
|
|
74
|
-
// even though the intersection would make listing it inert today.
|
|
75
|
-
if (catalog.isSystem(key)) {
|
|
76
|
-
throw new Error(`${where} allows the system:true permission "${key}" — a session-scope may only list operational capability (a scoped token lives outside the trust boundary)`);
|
|
77
|
-
}
|
|
53
|
+
if (seen.has(scope.source)) throw new Error(`${where} is declared twice`);
|
|
54
|
+
seen.add(scope.source);
|
|
55
|
+
if (scope.declaresScope) {
|
|
56
|
+
throw new Error(`${where} 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.`);
|
|
78
57
|
}
|
|
79
58
|
}
|
|
80
59
|
return scopes;
|
|
81
60
|
}
|
|
82
61
|
|
|
83
62
|
const SESSION_SCOPES = Object.freeze(
|
|
84
|
-
|
|
63
|
+
validateScopes(Object.freeze((registry.scopes || []).map(normalizeScope))),
|
|
85
64
|
);
|
|
86
65
|
|
|
87
66
|
const _bySource = new Map(SESSION_SCOPES.map((s) => [s.source, s]));
|
|
88
67
|
|
|
89
|
-
// The
|
|
90
|
-
// declare it
|
|
91
|
-
//
|
|
92
|
-
// row here, not an implicit default: it predates the box source existing).
|
|
68
|
+
// The declared row for a session's `source`, or null when the registry does not
|
|
69
|
+
// declare it — which the kernel treats as DENY, never as "full" (a null/absent
|
|
70
|
+
// source is a DECLARED row here, not an implicit default).
|
|
93
71
|
function scopeFor(source) {
|
|
94
72
|
const key = source === undefined ? null : source;
|
|
95
73
|
if (key !== null && typeof key !== 'string') return null;
|
|
96
74
|
return _bySource.get(key) || null;
|
|
97
75
|
}
|
|
98
76
|
|
|
99
|
-
// Does this session's scope narrow at all? Unknown source → true (fail-closed:
|
|
100
|
-
// treated as narrowing to nothing, which is what scopeFor/narrowToSessionScope do).
|
|
101
|
-
function isScoped(source) {
|
|
102
|
-
const scope = scopeFor(source);
|
|
103
|
-
return !scope || !scope.unscoped;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// The deny-by-default allow-list for a source; empty for an unscoped source (it
|
|
107
|
-
// narrows nothing) AND for an unrecognised one (it admits nothing) — callers must
|
|
108
|
-
// consult scopeFor()/isScoped() to tell those apart, exactly as the resolver does.
|
|
109
|
-
const NO_KEYS = Object.freeze([]);
|
|
110
|
-
function allowedKeys(source) {
|
|
111
|
-
const scope = scopeFor(source);
|
|
112
|
-
return scope ? scope.permissions : NO_KEYS;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
77
|
function sources() {
|
|
116
78
|
return SESSION_SCOPES.map((s) => s.source);
|
|
117
79
|
}
|
|
118
80
|
|
|
119
81
|
module.exports = {
|
|
120
82
|
SESSION_SCOPES,
|
|
121
|
-
|
|
83
|
+
validateScopes,
|
|
122
84
|
normalizeScope,
|
|
123
85
|
scopeFor,
|
|
124
|
-
isScoped,
|
|
125
|
-
allowedKeys,
|
|
126
86
|
sources,
|
|
127
87
|
};
|
|
@@ -1,38 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": [
|
|
3
|
-
"The SESSION-
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"modules/government/session-scopes.js
|
|
10
|
-
"Read as DATA by src/bongos/auth.js (fs, never require — ADR 0083's one-way rule)."
|
|
3
|
+
"The SESSION-SOURCE registry (BV1.R99, ADR 0151 §3): which kinds of session token may authenticate.",
|
|
4
|
+
"Every builder_sessions row carries a `source`. A session whose source is declared here carries its",
|
|
5
|
+
"builder's full rank authority; a session whose source is NOT declared is DENIED on every route and page.",
|
|
6
|
+
"Rows declare a source only. The narrowing form (a deny-by-default permissions allow-list) was removed",
|
|
7
|
+
"with its last user in task 1003894, and both loaders refuse a row that still declares one.",
|
|
8
|
+
"Read as DATA by src/bongos/auth.js (fs, never require — ADR 0083's one-way rule) and by",
|
|
9
|
+
"modules/government/session-scopes.js."
|
|
11
10
|
],
|
|
12
11
|
"scopes": [
|
|
13
12
|
{
|
|
14
13
|
"source": "cli",
|
|
15
14
|
"label": "CLI session",
|
|
16
|
-
"unscoped": true,
|
|
17
15
|
"note": "Device Flow token on a builder's own machine — inside the trust boundary, carries the builder's full rank authority."
|
|
18
16
|
},
|
|
19
17
|
{
|
|
20
18
|
"source": "web",
|
|
21
19
|
"label": "web session",
|
|
22
|
-
"unscoped": true,
|
|
23
20
|
"note": "Hall browser cookie — same authority as the CLI session."
|
|
24
21
|
},
|
|
25
22
|
{
|
|
26
23
|
"source": null,
|
|
27
24
|
"label": "legacy session",
|
|
28
|
-
"
|
|
29
|
-
"note": "Rows minted before migration 090 added a source column; they predate the 'box' source existing, so they were never box tokens. Declared explicitly rather than defaulted — an UNRECOGNISED source denies."
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"source": "box",
|
|
33
|
-
"label": "box-scoped session",
|
|
34
|
-
"permissions": ["box.manage.own"],
|
|
35
|
-
"note": "Baked into cloud-init so a dev box can bootstrap (#818/SR#2, ADR 0053). The token lives ON the box — OUTSIDE the ADR 0016 trust boundary — so it reaches only its own box record, whatever its builder's rank."
|
|
25
|
+
"note": "Rows minted before migration 090 added a source column. Declared explicitly rather than defaulted — an UNRECOGNISED source denies."
|
|
36
26
|
}
|
|
37
27
|
]
|
|
38
28
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"key": "grading",
|
|
3
3
|
"title": "Grading",
|
|
4
4
|
"description": "The grade-a-diff adversarial worker panel (Narc/Quality/Hacker/Efficiency) + deterministic route-rank/permission-path pre-passes + the scoring rubric. A core-domain module carved out of the GDS core in BV1.R73 (ADR 0091); default-on because every instance grades its own ships. Registers the `grade` kernel PORT the lifecycle resolves at confirm/ship time.",
|
|
5
|
+
"version": "1.0.0",
|
|
5
6
|
"coreVersion": "^1.6.0",
|
|
6
7
|
"default": true,
|
|
7
8
|
"maintenance": { "status": "core-maintained" },
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"key": "hall-ui",
|
|
3
3
|
"title": "Builders' Hall",
|
|
4
4
|
"description": "The authenticated builders' hall served at builders.<apex> (builders.example.com) — the roster, work board, standing/karma, leaderboard, achievements, primer, system maps, settings, and the Archon-only watch/gate pages, all polling /api/bongos/* and the read-only /api/bongos/public/* surface. The largest WEB-SURFACE core-domain module (~13k LOC), carved out of the GDS core in BV1.R85 (ADR 0093 §1; 02-module-map §6). default-on: a Cloud Bongos instance ships its hall like it ships its status page. Registers no port and contributes no routes — it declares a webSurface (host prefix + static dir). Unlike the generic status surface, the hall keeps its dedicated serving block in serve-internal.js (the builders.<apex> + apex /builders/* shim, canonical redirects, the citizen/Archon page gates, asset-stamping, and the module hall-widget injection) — the webSurfaces declaration is the discovery + scope-map + window.__MODULES__ record; serve-internal serves it from this module's public/ dir with that richer machinery. The me/public/leaderboard read endpoints it renders stay SHARED core in routes/{me,public,builders}.js (the status surface + the off-box mirror read them too).",
|
|
5
|
+
"version": "1.0.0",
|
|
5
6
|
"coreVersion": "^1.11.0",
|
|
6
7
|
"default": true,
|
|
7
8
|
"maintenance": { "status": "core-maintained" },
|
|
@@ -133,8 +133,8 @@
|
|
|
133
133
|
// ---- shared row pieces ---------------------------------------------------
|
|
134
134
|
//
|
|
135
135
|
// board-room.js's header called these per-page boilerplate "by convention
|
|
136
|
-
// (gate.js
|
|
137
|
-
//
|
|
136
|
+
// (gate.js and watch.js each carry their own)". That is not what the tree
|
|
137
|
+
// says: those pages write .fact-pill markup inline and carry no such
|
|
138
138
|
// helper. This exact trio is declared in precisely two files — these two — as
|
|
139
139
|
// byte-identical copies, so it is a two-file duplication like the rest.
|
|
140
140
|
|