@bongos/core 1.18.19 → 1.19.2
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 +514 -194
- package/.claude/skills/builder-box/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +1 -1
- package/.claude/skills/goal-create/SKILL.md +1 -1
- package/.claude/skills/idea-triage/SKILL.md +1 -1
- package/.claude/skills/planning-session/SKILL.md +5 -5
- package/.claude/skills/scan-before-install/SKILL.md +165 -0
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +63 -41
- package/clients/bongos-client/index.cjs +63 -41
- package/clients/bongos-client/index.d.ts +78 -44
- package/clients/bongos-client/index.mjs +63 -41
- package/docs/adr/0020-security-model-public-stub.md +1 -1
- package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +2 -0
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +1 -1
- package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +2 -1
- package/docs/adr/0155-adopt-private-repo-widen-oauth-scope.md +51 -0
- package/docs/adr/0156-user-set-display-name-wins.md +62 -0
- package/docs/adr/0157-archon-is-rank-and-identity-only.md +93 -0
- package/docs/adr/0158-choke-point-verdict-taxonomy.md +174 -0
- package/docs/adr/0159-merge-gate-reconciliation.md +88 -0
- package/docs/adr/0160-main-audit-repo-verifiable-caller.md +77 -0
- package/docs/adr/README.md +8 -0
- package/docs/api/openapi.json +1209 -233
- package/docs/api-reference.md +61 -46
- package/docs/architecture.md +1 -1
- package/docs/canonical-permissions.md +61 -16
- package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
- package/docs/file-map.md +4 -1
- package/docs/onboarding/diagrams/assertions.json +3 -3
- package/docs/recipes/core-release-pipeline.md +179 -0
- package/docs/recipes/packaging-the-core.md +2 -0
- package/docs/recipes/parallel-goal-waves.md +1 -3
- package/docs/recipes/private-npm-distribution.md +2 -0
- package/docs/recipes/upgrading-the-core.md +2 -0
- package/migrations/core_197_display_name_source.sql +56 -0
- package/migrations/core_204_rekey_active_claim_per_session_worktree.sql +77 -0
- package/modules/autonomy/routes/autonomy.js +1 -1
- package/modules/builder-settings/db.js +7 -1
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +63 -41
- package/modules/dev-box/routes/box.js +11 -9
- package/modules/discord/auth.js +3 -1
- package/modules/discord/routes/discord.js +8 -2
- package/modules/discord/ship-broadcast.js +48 -1
- package/modules/governance/catalog.js +70 -20
- package/modules/governance/db.js +102 -0
- package/modules/governance/migrations/governance_006_version_create.sql +38 -0
- package/modules/governance/migrations/governance_007_delegate_archon_ops_to_metic.sql +72 -0
- package/modules/governance/routes/governance.js +235 -6
- package/modules/grading/grader-workers/index.js +18 -9
- package/modules/hall-ui/public/gate.js +6 -2
- package/modules/hall-ui/public/goals-page.js +6 -2
- package/modules/hall-ui/public/governance.css +100 -0
- package/modules/hall-ui/public/governance.html +77 -0
- package/modules/hall-ui/public/governance.js +353 -0
- package/modules/hall-ui/public/harbor.js +14 -8
- package/modules/hall-ui/public/instances-panel.js +3 -1
- package/modules/hall-ui/public/new-project-wizard.js +6 -0
- package/modules/hall-ui/public/people.html +51 -0
- package/modules/hall-ui/public/people.js +100 -0
- package/modules/hall-ui/public/profile.html +2 -1
- package/modules/hall-ui/public/profile.js +9 -3
- package/modules/hall-ui/public/sessions.js +4 -2
- package/modules/hall-ui/public/shell.js +28 -7
- package/modules/hall-ui/public/task-detail.js +6 -3
- package/modules/hall-ui/public/watch.js +17 -9
- package/modules/ideas/blockers.js +44 -7
- package/modules/ideas/routes/blockers.js +12 -7
- package/modules/ideas/routes/inbox.js +2 -2
- package/modules/lifecycle/db.js +84 -17
- package/modules/lifecycle/github-push.js +37 -10
- package/modules/lifecycle/publish-reconciler.js +128 -31
- package/modules/lifecycle/routes/claims.js +10 -4
- package/modules/lifecycle/routes/dependencies.js +2 -2
- package/modules/lifecycle/routes/done-when.js +4 -4
- package/modules/lifecycle/routes/gate-approvals.js +9 -3
- package/modules/lifecycle/routes/goals.js +5 -5
- package/modules/lifecycle/routes/tasks.js +16 -16
- package/modules/lifecycle/routes/versions.js +13 -2
- package/modules/lifecycle/ship-card.js +38 -7
- package/modules/memory/routes/memory.js +2 -2
- package/modules/onboarding/routes/access-requests.js +3 -3
- package/modules/platform-identity/routes/projects.js +10 -6
- package/modules/platform-identity/routes/scouting.js +2 -2
- package/modules/provisioning/migrations/provisioning_004_onboard_mode.sql +30 -0
- package/modules/provisioning/provisioning.js +24 -6
- package/modules/provisioning/routes/provisioning.js +24 -6
- package/modules/security/artifact-scan/aggregate.js +133 -0
- package/modules/security/artifact-scan/detectors/capabilities.js +53 -0
- package/modules/security/artifact-scan/detectors/config-obfuscation.js +41 -0
- package/modules/security/artifact-scan/detectors/credentials.js +112 -0
- package/modules/security/artifact-scan/detectors/dependencies.js +115 -0
- package/modules/security/artifact-scan/detectors/hooks.js +116 -0
- package/modules/security/artifact-scan/detectors/injection.js +118 -0
- package/modules/security/artifact-scan/detectors/mcp.js +102 -0
- package/modules/security/artifact-scan/detectors/network.js +67 -0
- package/modules/security/artifact-scan/detectors/npm-lifecycle.js +67 -0
- package/modules/security/artifact-scan/detectors/permissions.js +91 -0
- package/modules/security/artifact-scan/detectors/shared.js +219 -0
- package/modules/security/artifact-scan/fence-local.js +99 -0
- package/modules/security/artifact-scan/fetch.js +330 -0
- package/modules/security/artifact-scan/floor.js +319 -0
- package/modules/security/artifact-scan/limits.js +66 -0
- package/modules/security/artifact-scan/manifest.js +203 -0
- package/modules/security/artifact-scan/mirror.js +158 -0
- package/modules/security/artifact-scan/quarantine.js +149 -0
- package/modules/security/artifact-scan/rebuild-spec.js +120 -0
- package/modules/security/artifact-scan/report.js +155 -0
- package/modules/security/artifact-scan/sanitize.js +317 -0
- package/modules/security/migrations/security_001_tool_scans.sql +145 -0
- package/modules/security/module.json +2 -2
- package/modules/security/routes/reports.js +11 -7
- package/modules/security/routes/scans.js +115 -0
- package/modules/security/scan-store.js +214 -0
- package/modules/security/tests/aggregate.mjs +283 -0
- package/modules/security/tests/artifact_scan.mjs +591 -0
- package/modules/security/tests/floor.mjs +549 -0
- package/modules/security/tests/mirror.mjs +350 -0
- package/modules/security/tests/sanitize_rebuild.mjs +499 -0
- package/modules/security/tests/scan_store.mjs +250 -0
- package/modules/security/tests/tool_scans_migration.mjs +116 -0
- package/modules/sessions/routes/sessions.js +6 -5
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/create.html +13 -3
- package/scripts/gds/abandon.js +1 -1
- package/scripts/gds/audit-authorship.sh +6 -0
- package/scripts/gds/box.js +1 -1
- package/scripts/gds/claim.js +15 -0
- package/scripts/gds/cli-lib.js +16 -4
- package/scripts/gds/gating.js +28 -13
- package/scripts/gds/gen-api-docs.js +1 -1
- package/scripts/gds/goal.js +234 -0
- package/scripts/gds/init.js +70 -15
- package/scripts/gds/main-audit.js +80 -11
- package/scripts/gds/mirror-redact.js +6 -0
- package/scripts/gds/newcomer-floor.js +2 -2
- package/scripts/gds/provision.js +230 -94
- package/scripts/gds/run-unit-tests.js +9 -1
- package/scripts/gds/sandbox-stage.js +66 -9
- package/scripts/gds/search-ingest-md.js +1 -0
- package/scripts/gds/sequence.js +67 -8
- package/scripts/gds/ship.js +183 -50
- package/scripts/hall-preview/server.js +1 -1
- package/scripts/migrate.sh +5 -5
- package/src/bongos/auth.js +129 -27
- package/src/bongos/db-kernel.js +66 -5
- package/src/bongos/db.js +6 -7
- package/src/bongos/github-repos.js +19 -7
- package/src/bongos/goal-scope-check.js +1 -1
- package/src/bongos/module-scope-map.js +3 -1
- package/src/bongos/permission-path-check.js +1 -1
- package/src/bongos/route-rank-check.js +73 -30
- package/src/bongos/routes/audit-log.js +4 -2
- package/src/bongos/routes/auth.js +8 -4
- package/src/bongos/routes/backup.js +7 -4
- package/src/bongos/routes/builders.js +23 -11
- package/src/bongos/routes/github.js +15 -4
- package/src/bongos/routes/llm-cache.js +5 -2
- package/src/bongos/routes/modules.js +9 -5
- package/src/bongos/routes/security.js +21 -12
- package/src/bongos/serve-internal.js +22 -7
- package/src/module-api.js +86 -13
- package/src/platform-server.js +14 -0
- package/tests/api_client.mjs +18 -2
- package/tests/api_path_404.mjs +3 -0
- package/tests/art_stage.mjs +2 -1
- package/tests/atlas.mjs +32 -4
- package/tests/auth_admission_gate.mjs +17 -5
- package/tests/auth_error_brand.mjs +41 -21
- package/tests/auth_resolve_failure.mjs +155 -0
- package/tests/blocker_link.mjs +88 -0
- package/tests/bongos_downloads.mjs +2 -1
- package/tests/box_dns_repoint.mjs +2 -0
- package/tests/box_ship_permissions.mjs +2 -0
- package/tests/box_terminal_ssrf.mjs +23 -17
- package/tests/claim_error_surface.mjs +23 -0
- package/tests/claude_materialize.mjs +4 -0
- package/tests/cli_box_scope.mjs +40 -0
- package/tests/cloud_bongos_pack.mjs +34 -4
- package/tests/conductor.mjs +2 -1
- package/tests/criterion_authoring_gate.mjs +28 -5
- package/tests/currency_label.mjs +10 -1
- package/tests/deploy_instance.mjs +2 -0
- package/tests/devbox_downloads.mjs +2 -1
- package/tests/display_name.mjs +95 -6
- package/tests/eviction_policy.mjs +2 -1
- package/tests/file_map.mjs +10 -0
- package/tests/first_admin_bootstrap.mjs +10 -2
- package/tests/first_run_engine_check.mjs +2 -1
- package/tests/fitness.mjs +26 -6
- package/tests/gate_approvals.mjs +15 -2
- package/tests/github_push_land_proof.mjs +50 -4
- package/tests/github_repos_helpers.mjs +30 -5
- package/tests/github_token_store.mjs +23 -0
- package/tests/goal_abuse_matrix.mjs +13 -3
- package/tests/goal_cli.mjs +89 -0
- package/tests/governance_abuse_matrix.mjs +386 -0
- package/tests/governance_fold.mjs +156 -0
- package/tests/governance_fold_bench.mjs +50 -0
- package/tests/governance_live_proof.mjs +201 -0
- package/tests/governance_page_gates.mjs +21 -9
- package/tests/governance_parity.mjs +11 -2
- package/tests/governance_require_permission.mjs +125 -1
- package/tests/governance_routes.mjs +334 -0
- package/tests/grader.mjs +7 -3
- package/tests/hall_nav.mjs +14 -4
- package/tests/helpers.mjs +73 -0
- package/tests/hook_core_resolve.mjs +2 -1
- package/tests/host_topology_skips.mjs +97 -0
- package/tests/infra_exec_paths.mjs +14 -4
- package/tests/init.mjs +128 -0
- package/tests/known_failing_ratchet.mjs +89 -0
- package/tests/lifecycle-card.mjs +2 -1
- package/tests/main_audit.mjs +72 -1
- package/tests/memory_isolation.mjs +56 -9
- package/tests/migrate_root_resolution.mjs +1 -1
- package/tests/mirror_redact.mjs +19 -0
- package/tests/module_api.mjs +3 -1
- package/tests/module_loader.mjs +4 -0
- package/tests/modules.mjs +35 -7
- package/tests/modules_route.mjs +29 -5
- package/tests/onboarding_observability.mjs +49 -11
- package/tests/onboarding_route_signals.mjs +1 -0
- package/tests/one_active_claim_per_session.mjs +80 -0
- package/tests/pre_compact.mjs +2 -1
- package/tests/preview_server.mjs +2 -1
- package/tests/profile_route.mjs +1 -0
- package/tests/provision.mjs +170 -0
- package/tests/provisioning_onboard_mode.mjs +180 -0
- package/tests/publish_manifest.mjs +16 -3
- package/tests/publish_reconciler.mjs +181 -10
- package/tests/rank_tier_single_source.mjs +205 -0
- package/tests/recall_hints.mjs +2 -1
- package/tests/repo_map.mjs +4 -0
- package/tests/route_permission_check.mjs +14 -5
- package/tests/rubric_fixtures.mjs +3 -0
- package/tests/run_routine_two_root.mjs +2 -0
- package/tests/sandbox_stage.mjs +69 -7
- package/tests/search_ingest.mjs +7 -0
- package/tests/security_docs.mjs +28 -6
- package/tests/sequence.mjs +64 -0
- package/tests/session-cards.mjs +2 -1
- package/tests/session_start_freshness.mjs +2 -1
- package/tests/ship_broadcast_server.mjs +63 -1
- package/tests/ship_card.mjs +66 -0
- package/tests/ship_ci_deploy.mjs +66 -1
- package/tests/ship_smoke_gate.mjs +76 -0
- package/tests/versions_route.mjs +15 -0
- package/tests/workflow_exec_paths.mjs +16 -4
- package/tests/worktree_guard.mjs +2 -1
|
@@ -216,10 +216,15 @@ const EXPECTED_RANKS = {
|
|
|
216
216
|
// (dev-box fleet pins likewise live in MODULE_EXPECTED_RANKS below — BV1.R43.)
|
|
217
217
|
// security engagement docs — exploit-detail report store (task 991). Pinned
|
|
218
218
|
// because a silent downgrade here leaks the attacker cookbook below the
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
// trust line. (security-the-PROPERTY stays kernel — routes/security.js.)
|
|
220
|
+
// ADR 0157 lowered `security.report.adjudicate` archon→metic, so these are pinned by
|
|
221
|
+
// ATOM rather than by rank: the invariant worth guarding is "this route gates on the
|
|
222
|
+
// adjudication permission", and an atom pin states exactly that while surviving any
|
|
223
|
+
// future floor move without a pin edit (pinMismatch compares atom identity for
|
|
224
|
+
// `perm:` pins, rank only for rank pins).
|
|
225
|
+
'GET /security/docs': 'perm:security.report.adjudicate',
|
|
226
|
+
'GET /security/docs/:slug': 'perm:security.report.adjudicate',
|
|
227
|
+
'POST /security/docs': 'perm:security.report.adjudicate',
|
|
223
228
|
// The full threat model + defenses inventory (ADR 0020). Same class as the docs
|
|
224
229
|
// pins above — a silent downgrade puts the defense inventory below the trust line.
|
|
225
230
|
// Newly VISIBLE in BV1.R109b (declared multi-line, so previously unpinnable).
|
|
@@ -248,10 +253,13 @@ const EXPECTED_RANKS = {
|
|
|
248
253
|
// so tightening these to `perm:` pins is a clean follow-up once R104-R107 have landed.
|
|
249
254
|
const MODULE_EXPECTED_RANKS = {
|
|
250
255
|
'dev-box': {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
'
|
|
256
|
+
// ADR 0157 lowered `box.fleet.manage` archon→metic (operating paid infra is admin
|
|
257
|
+
// work, not an authority grant). Re-pinned by ATOM so the guarded invariant is
|
|
258
|
+
// "these gate on the fleet permission", independent of its floor.
|
|
259
|
+
'GET /boxes': 'perm:box.fleet.manage',
|
|
260
|
+
'GET /boxes/cost-ledger': 'perm:box.fleet.manage',
|
|
261
|
+
'POST /boxes/:builderId/close': 'perm:box.fleet.manage',
|
|
262
|
+
'PATCH /boxes/:builderId/block': 'perm:box.fleet.manage',
|
|
255
263
|
},
|
|
256
264
|
// The lifecycle module's privileged routes (carved BV1.R86 — the pins follow the
|
|
257
265
|
// routes from EXPECTED_RANKS). Verified only when the lifecycle module is in scope.
|
|
@@ -264,12 +272,15 @@ const MODULE_EXPECTED_RANKS = {
|
|
|
264
272
|
'POST /tasks/:id/promote': 'metic+archon',
|
|
265
273
|
'POST /tasks/:id/abandon': 'metic+archon',
|
|
266
274
|
// override-request adjudication (lands credit + ship attribution).
|
|
267
|
-
|
|
268
|
-
'
|
|
275
|
+
// ADR 0157: `override_request.decide` lowered archon→metic; pinned by atom.
|
|
276
|
+
'GET /override-requests': 'perm:override_request.decide',
|
|
277
|
+
'POST /override-requests/:id/decide': 'perm:override_request.decide',
|
|
269
278
|
// polymorphic dependency edges (ADR 0086 §5) — authoring a cross-tier
|
|
270
279
|
// dependency reshapes execution order + feeds the claim gate; both pinned.
|
|
271
|
-
|
|
272
|
-
|
|
280
|
+
// These STAYED archon under ADR 0157: `dependency.cross_tier.manage` is system:true,
|
|
281
|
+
// and the catalog forbids granting a system key below archon.
|
|
282
|
+
'POST /dependencies': 'perm:dependency.cross_tier.manage',
|
|
283
|
+
'DELETE /dependencies': 'perm:dependency.cross_tier.manage',
|
|
273
284
|
// goal tier (ADR 0086 §3) — CREATE Metic+ (Archon for a protected-module scope,
|
|
274
285
|
// enforced in-handler); member admission is the protected-scope approval grant.
|
|
275
286
|
'POST /goals': 'metic+archon',
|
|
@@ -279,9 +290,10 @@ const MODULE_EXPECTED_RANKS = {
|
|
|
279
290
|
// below). The wall is in-handler (owner | manager | Archon), with a protected-
|
|
280
291
|
// scope goal staying Archon-only (actor AND Metic+ target) inside the handler.
|
|
281
292
|
'POST /goals/:id/members': 'any-builder',
|
|
282
|
-
// per-version priority reorder (task 1760) —
|
|
283
|
-
//
|
|
284
|
-
|
|
293
|
+
// per-version priority reorder (task 1760) — never self-granted by a goal's own
|
|
294
|
+
// member/manager authority. ADR 0157 lowered `goal.reorder` archon→metic (ordering
|
|
295
|
+
// work is prioritisation, not an authority grant); pinned by atom.
|
|
296
|
+
'POST /goals/reorder': 'perm:goal.reorder',
|
|
285
297
|
// ask-to-widen (ADR 0086 §4) — widening a goal's scope_modules; never self-granted.
|
|
286
298
|
'POST /goals/:id/scope': 'metic+archon',
|
|
287
299
|
// goal lifecycle disposition (ADR 0086 §6) — a rank drop here would let any
|
|
@@ -342,23 +354,34 @@ const MODULE_EXPECTED_RANKS = {
|
|
|
342
354
|
// shipped scope-truth; ADR 0154's whole safety argument is that the backward
|
|
343
355
|
// ratchet did not move with the create. Previously unpinned — pinned here so a
|
|
344
356
|
// silent downgrade reds the build (task 1002538).
|
|
345
|
-
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
'POST /
|
|
357
|
+
// ADR 0157 lowered `criterion.unsatisfy` archon→metic. The backward-ratchet warning
|
|
358
|
+
// above still holds — un-flipping a confirmed criterion rewrites shipped scope-truth
|
|
359
|
+
// — but it is scope disposition, not an authority grant, so it delegates with the
|
|
360
|
+
// rest. Pinned by atom so the guarded invariant is the permission, not its floor.
|
|
361
|
+
'POST /done-when/:criterionId/unsatisfy': 'perm:criterion.unsatisfy',
|
|
362
|
+
// gate-surface PR approval from the hall (task 1179, ADR 0058 amendment). ADR 0157
|
|
363
|
+
// SPLIT these two: the list route is `gate_approval.read` (metic — the Gate page
|
|
364
|
+
// opens to the working rank), while APPROVING still clears the gate-review hard
|
|
365
|
+
// floor and stays `gate_approval.decide` (archon, system). The split is the reason
|
|
366
|
+
// the page is usable at all — see the atom's note in catalog.js.
|
|
367
|
+
'GET /gate-approvals': 'perm:gate_approval.read',
|
|
368
|
+
'POST /gate-approvals/:pr/approve': 'perm:gate_approval.decide',
|
|
350
369
|
},
|
|
351
370
|
// Ratifying a criterion proposal WRITES scope-truth (it creates a done-when
|
|
352
|
-
// criterion). Metic+ may file the proposal;
|
|
371
|
+
// criterion). Metic+ may file the proposal; ADR 0157 also lets Metic ratify it
|
|
372
|
+
// (`idea.ratify` archon→metic) — the rank that triages ideas now also ratifies them.
|
|
353
373
|
ideas: {
|
|
354
|
-
'POST /inbox/:id/ratify': '
|
|
374
|
+
'POST /inbox/:id/ratify': 'perm:idea.ratify',
|
|
355
375
|
},
|
|
356
376
|
// Cross-builder memory READS — the counterpart to the BFG cross-builder WRITE the
|
|
357
377
|
// KNOWN_RANKS note above already protects. A downgrade here breaks the memory
|
|
358
378
|
// isolation invariant: any builder could read every other builder's memory.
|
|
379
|
+
// ADR 0157 lowered `memory.read.any` archon→metic. The isolation invariant the note
|
|
380
|
+
// above protects is unchanged in KIND — a cross-builder read still requires an
|
|
381
|
+
// explicit permission no ordinary builder holds — only its floor moved. Pinned by atom.
|
|
359
382
|
memory: {
|
|
360
|
-
'GET /memory/builders/:builderId/files': '
|
|
361
|
-
'GET /memory/builders/:builderId/file': '
|
|
383
|
+
'GET /memory/builders/:builderId/files': 'perm:memory.read.any',
|
|
384
|
+
'GET /memory/builders/:builderId/file': 'perm:memory.read.any',
|
|
362
385
|
},
|
|
363
386
|
// Vulnerability-report intake + adjudication. The four adjudication routes move a
|
|
364
387
|
// PAYOUT (confirm awards drachmae, dispute clears it), and the list route exposes
|
|
@@ -367,12 +390,32 @@ const MODULE_EXPECTED_RANKS = {
|
|
|
367
390
|
// is DELIBERATELY Metic+ (the rank gate is the bounty noise-floor, V3.R63): the pin
|
|
368
391
|
// catches an accidental widening to Xenos as loudly as it catches a gate removal.
|
|
369
392
|
security: {
|
|
370
|
-
'POST /security/reports': '
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
'
|
|
375
|
-
'POST /security/reports/:id/
|
|
393
|
+
'POST /security/reports': 'perm:security.report.file',
|
|
394
|
+
// ADR 0157 lowered `security.report.adjudicate` archon→metic and extended its guards
|
|
395
|
+
// to name the LIST route, which BV1.R105 had left on raw requireRank for want of an
|
|
396
|
+
// atom that covered the read. All five now pin by atom.
|
|
397
|
+
'GET /security/reports': 'perm:security.report.adjudicate',
|
|
398
|
+
'POST /security/reports/:id/confirm': 'perm:security.report.adjudicate',
|
|
399
|
+
'POST /security/reports/:id/dispute': 'perm:security.report.adjudicate',
|
|
400
|
+
'POST /security/reports/:id/fix': 'perm:security.report.adjudicate',
|
|
401
|
+
'POST /security/reports/:id/link-fix': 'perm:security.report.adjudicate',
|
|
402
|
+
},
|
|
403
|
+
// The governance tab's server half (BV1.R110, ADR 0151) — the routes that EDIT
|
|
404
|
+
// the role/permission graph itself. Every route, the three reads included, is
|
|
405
|
+
// pinned to the `governance.manage` ATOM rather than a rank: on this surface
|
|
406
|
+
// the atom IS the authority (it is the catalog's meta-escalation key), and a
|
|
407
|
+
// downgrade here is the model's worst case — the routes that write authority
|
|
408
|
+
// drifting below the wall that guards authority. Reads are pinned too because
|
|
409
|
+
// the full role/grant/assignment map is the authority model's blueprint.
|
|
410
|
+
governance: {
|
|
411
|
+
'GET /governance/roles': 'perm:governance.manage',
|
|
412
|
+
'GET /governance/permissions': 'perm:governance.manage',
|
|
413
|
+
'GET /governance/assignments': 'perm:governance.manage',
|
|
414
|
+
'POST /governance/roles': 'perm:governance.manage',
|
|
415
|
+
'PUT /governance/roles/:roleKey/permissions/:permissionKey': 'perm:governance.manage',
|
|
416
|
+
'DELETE /governance/roles/:roleKey/permissions/:permissionKey': 'perm:governance.manage',
|
|
417
|
+
'POST /governance/assignments': 'perm:governance.manage',
|
|
418
|
+
'DELETE /governance/assignments/:builderId/:roleKey': 'perm:governance.manage',
|
|
376
419
|
},
|
|
377
420
|
};
|
|
378
421
|
|
|
@@ -19,8 +19,10 @@ module.exports = function buildAuditLogRouter() {
|
|
|
19
19
|
// Paginated, newest-first. Clamps:
|
|
20
20
|
// limit → 1..500, default 100
|
|
21
21
|
// offset → >= 0, default 0
|
|
22
|
-
// rank: archon — forensics surface; only the Archon should see who did what.
|
|
23
|
-
|
|
22
|
+
// rank: archon — forensics surface; only the Archon should see who did what. Gate:
|
|
23
|
+
// requirePermission('audit_log.read') — BV1.R104; the seeded rank-role makes the
|
|
24
|
+
// permission and the old rank floor identical (ADR 0151).
|
|
25
|
+
router.get('/audit-log', auth.requireBuilder, auth.requirePermission('audit_log.read'), asyncHandler('GET /audit-log', async (req, res) => {
|
|
24
26
|
// R14 (#2001 / ADR 0119): adopt the shared helper (was a hand-rolled clamp);
|
|
25
27
|
// per-endpoint cap preserved (limit 1..500 default 100, offset ≥ 0).
|
|
26
28
|
const { limit, offset } = parsePagination(req, { defaultLimit: 100, maxLimit: 500 });
|
|
@@ -884,8 +884,10 @@ module.exports = function buildAuthRouter() {
|
|
|
884
884
|
// its primary-key id. Used for surgical lockouts (e.g. a compromised CLI on
|
|
885
885
|
// a known builder's laptop) without disturbing that builder's other sessions.
|
|
886
886
|
// Body: { session_id: <bigint> }. Response: { ok, revoked: 0|1 }.
|
|
887
|
-
// rank: archon — kills an arbitrary session; can lock other builders out.
|
|
888
|
-
|
|
887
|
+
// rank: archon — kills an arbitrary session; can lock other builders out. Gate:
|
|
888
|
+
// requirePermission('auth.session.revoke') — BV1.R104; the seeded rank-role makes
|
|
889
|
+
// the permission and the old rank floor identical (ADR 0151).
|
|
890
|
+
router.post('/auth/revoke', auth.requireBuilder, auth.requirePermission('auth.session.revoke'), async (req, res) => {
|
|
889
891
|
if (validateOrRespond(req, res, { session_id: {} })) return; // bigint as number or string; validated below
|
|
890
892
|
const sessionId = req.body && req.body.session_id;
|
|
891
893
|
if (sessionId === undefined || sessionId === null) {
|
|
@@ -910,8 +912,10 @@ module.exports = function buildAuthRouter() {
|
|
|
910
912
|
// setBuilderRank when newRank moves down the ladder — C7 rank-change-near-
|
|
911
913
|
// instant) and as a manual operator hammer.
|
|
912
914
|
// Body: { builder_id: <bigint> }. Response: { ok, revoked: <count> }.
|
|
913
|
-
// rank: archon — same authority as PATCH /builders/:id/rank.
|
|
914
|
-
|
|
915
|
+
// rank: archon — same authority as PATCH /builders/:id/rank. Gate:
|
|
916
|
+
// requirePermission('auth.session.revoke') — BV1.R104; the same atom /auth/revoke
|
|
917
|
+
// gates on (ADR 0151).
|
|
918
|
+
router.post('/auth/revoke-all', auth.requireBuilder, auth.requirePermission('auth.session.revoke'), async (req, res) => {
|
|
915
919
|
if (validateOrRespond(req, res, { builder_id: {} })) return; // bigint as number or string; validated below
|
|
916
920
|
const builderId = req.body && req.body.builder_id;
|
|
917
921
|
if (builderId === undefined || builderId === null) {
|
|
@@ -103,8 +103,10 @@ function latestDump(nowMs = Date.now()) {
|
|
|
103
103
|
module.exports = function buildBackupRouter() {
|
|
104
104
|
const router = express.Router();
|
|
105
105
|
|
|
106
|
-
// rank: metic+archon — any working builder may check or trigger a backup.
|
|
107
|
-
|
|
106
|
+
// rank: metic+archon — any working builder may check or trigger a backup. Gate:
|
|
107
|
+
// requirePermission('backup.manage') — BV1.R104; the seeded rank-role makes the
|
|
108
|
+
// permission and the old rank floor identical (ADR 0151).
|
|
109
|
+
router.get('/backup/status', auth.requireBuilder, auth.requirePermission('backup.manage'),
|
|
108
110
|
asyncHandler('GET /backup/status', async (_req, res) => {
|
|
109
111
|
const dump = latestDump();
|
|
110
112
|
res.json({
|
|
@@ -120,8 +122,9 @@ module.exports = function buildBackupRouter() {
|
|
|
120
122
|
});
|
|
121
123
|
}));
|
|
122
124
|
|
|
123
|
-
// rank: metic+archon
|
|
124
|
-
|
|
125
|
+
// rank: metic+archon — kicks off a fresh dump; same gate as the status read.
|
|
126
|
+
// Gate: requirePermission('backup.manage') — BV1.R104.
|
|
127
|
+
router.post('/backup/trigger', auth.requireBuilder, auth.requirePermission('backup.manage'),
|
|
125
128
|
asyncHandler('POST /backup/trigger', async (req, res) => {
|
|
126
129
|
if (triggerRunning) {
|
|
127
130
|
return res.fail('backup_already_running', { status: 409, message: 'A backup is already in progress — poll /backup/status to see it land.' });
|
|
@@ -113,7 +113,11 @@ module.exports = function buildBuildersRouter() {
|
|
|
113
113
|
const router = express.Router();
|
|
114
114
|
|
|
115
115
|
// rank: archon — sole chokepoint that mutates builders.rank; ADR 0016 §enforcement.
|
|
116
|
-
|
|
116
|
+
// Gate: requirePermission('builder.rank.set') — BV1.R104; the seeded rank-role makes
|
|
117
|
+
// the permission and the old rank floor identical (ADR 0151). The atom is
|
|
118
|
+
// `system: true`, so no CUSTOM role may ever be granted it — only the seeded archon
|
|
119
|
+
// role holds it, which is exactly the meta-escalation wall ADR 0016 wants.
|
|
120
|
+
router.patch('/builders/:id/rank', auth.requireBuilder, auth.requirePermission('builder.rank.set'), async (req, res) => {
|
|
117
121
|
if (validateOrRespond(req, res, {
|
|
118
122
|
rank: { required: true, type: 'string', maxLength: 32 },
|
|
119
123
|
})) return;
|
|
@@ -188,8 +192,10 @@ module.exports = function buildBuildersRouter() {
|
|
|
188
192
|
// claims, seals their session_logs, rank → xenos, blocks new claims while
|
|
189
193
|
// preserving all credit/history); { status: 'active' } reactivates. Cannot
|
|
190
194
|
// change your own status (no last-Archon footgun — same guard as /rank).
|
|
191
|
-
// rank: archon — deactivation/reactivation; preserves credit history but blocks
|
|
192
|
-
|
|
195
|
+
// rank: archon — deactivation/reactivation; preserves credit history but blocks
|
|
196
|
+
// claims. Gate: requirePermission('builder.status.set') — BV1.R104; the seeded
|
|
197
|
+
// rank-role makes the permission and the old rank floor identical (ADR 0151).
|
|
198
|
+
router.patch('/builders/:id/status', auth.requireBuilder, auth.requirePermission('builder.status.set'), async (req, res) => {
|
|
193
199
|
if (validateOrRespond(req, res, {
|
|
194
200
|
status: { required: true, type: 'string', enum: ['active', 'inactive'] },
|
|
195
201
|
reason: { type: 'string', maxLength: LIMITS.NOTES },
|
|
@@ -238,8 +244,10 @@ module.exports = function buildBuildersRouter() {
|
|
|
238
244
|
// engages the warn/over thresholds. { monthly_budget_usd: null } clears it: the
|
|
239
245
|
// builder becomes UNCAPPED (there is no default cap — ADR 0085). This is the
|
|
240
246
|
// "budget per-builder configurable by Archon" done-when. 404 if no such builder.
|
|
241
|
-
// rank: archon — changes another builder's cost ceiling.
|
|
242
|
-
|
|
247
|
+
// rank: archon — changes another builder's cost ceiling. NOT ownership-scoped:
|
|
248
|
+
// archon-only admin op. Gate: requirePermission('builder.budget.set') — BV1.R104;
|
|
249
|
+
// the seeded rank-role makes the permission and the old rank floor identical (ADR 0151).
|
|
250
|
+
router.patch('/builders/:id/budget', auth.requireBuilder, auth.requirePermission('builder.budget.set'), async (req, res) => {
|
|
243
251
|
const targetId = parseId(req, res, { code: 'bad_builder_id', positiveInt: true });
|
|
244
252
|
if (targetId === null) return;
|
|
245
253
|
if (validateOrRespond(req, res, { monthly_budget_usd: {} })) return; // number ≥0, or null to clear; validated below
|
|
@@ -277,8 +285,10 @@ module.exports = function buildBuildersRouter() {
|
|
|
277
285
|
// stale-claim-release sweeper uses but exposed as a roster knob so the
|
|
278
286
|
// Archon can dial it in if needed.
|
|
279
287
|
//
|
|
280
|
-
// rank: archon — surfaces every builder's onboarding state.
|
|
281
|
-
|
|
288
|
+
// rank: archon — surfaces every builder's onboarding state. Gate:
|
|
289
|
+
// requirePermission('builder.roster.read') — BV1.R104; the same atom
|
|
290
|
+
// /grades/by-builder and /builders/:id/active-claims already gate on (ADR 0151).
|
|
291
|
+
router.get('/builders/roster', auth.requireBuilder, auth.requirePermission('builder.roster.read'), async (req, res) => {
|
|
282
292
|
let staleHours = 24;
|
|
283
293
|
if (req.query && req.query.stale_hours != null) {
|
|
284
294
|
const n = Number(req.query.stale_hours);
|
|
@@ -323,8 +333,9 @@ module.exports = function buildBuildersRouter() {
|
|
|
323
333
|
// the Ruling Hand's alone (the project-wide aggregate stays public via
|
|
324
334
|
// /public/grades; this per-builder split does not).
|
|
325
335
|
//
|
|
326
|
-
// rank: archon.
|
|
327
|
-
|
|
336
|
+
// rank: archon — NOT ownership-scoped: archon-only cross-builder read.
|
|
337
|
+
// Gate: requirePermission('builder.roster.read') — BV1.R104.
|
|
338
|
+
router.get('/grades/by-builder', auth.requireBuilder, auth.requirePermission('builder.roster.read'), async (req, res) => {
|
|
328
339
|
let days = 30;
|
|
329
340
|
if (req.query && req.query.days != null) {
|
|
330
341
|
const n = Number(req.query.days);
|
|
@@ -349,8 +360,9 @@ module.exports = function buildBuildersRouter() {
|
|
|
349
360
|
// boolean "has any stale claim?"; this endpoint surfaces *which* claim
|
|
350
361
|
// is stale so the Archon can target the release.
|
|
351
362
|
//
|
|
352
|
-
// rank: archon — privileged read of another builder's work surface.
|
|
353
|
-
|
|
363
|
+
// rank: archon — privileged read of another builder's work surface. NOT
|
|
364
|
+
// ownership-scoped. Gate: requirePermission('builder.roster.read') — BV1.R104.
|
|
365
|
+
router.get('/builders/:id/active-claims', auth.requireBuilder, auth.requirePermission('builder.roster.read'), async (req, res) => {
|
|
354
366
|
const targetId = parseId(req, res, { code: 'bad_builder_id', positiveInt: true });
|
|
355
367
|
if (targetId === null) return;
|
|
356
368
|
let staleHours = 24;
|
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
// These act on the user's OWN GitHub identity, so they live in core auth alongside
|
|
6
6
|
// the sign-in flow that captured the token — not the provisioning module (whose
|
|
7
7
|
// charter holds no tokens / calls no cloud API, ADR 0111 §2). They read the
|
|
8
|
-
// short-lived, secret-box-encrypted `
|
|
9
|
-
// (db.js github_oauth_tokens
|
|
10
|
-
// branches on:
|
|
8
|
+
// short-lived, secret-box-encrypted `repo`-scoped token the web callback stored
|
|
9
|
+
// (db.js github_oauth_tokens; `repo` since ADR 0155 — private repos must be
|
|
10
|
+
// adoptable). Three "please reconnect / fall back" signals the wizard branches on:
|
|
11
11
|
// • no stored token → repo_scope_required (403) → prompt a repos sign-in
|
|
12
|
+
// • stale pre-0155 scope → repo_scope_required (403) → same reconnect path
|
|
12
13
|
// • secret-box unconfigured → github_repos_unavailable (503) → manual repo entry
|
|
13
|
-
//
|
|
14
|
+
// All are behind requireBuilder + the router-wide rate-limit middleware.
|
|
14
15
|
|
|
15
16
|
const express = require('express');
|
|
16
17
|
const auth = require('../auth');
|
|
@@ -35,6 +36,16 @@ async function resolveRepoToken(req, res) {
|
|
|
35
36
|
res.fail('repo_scope_required', { status: 403, message: 'Grant repository access to list or create repos.' });
|
|
36
37
|
return null;
|
|
37
38
|
}
|
|
39
|
+
// A token minted BEFORE ADR 0155 carries only `public_repo`. That is enough to
|
|
40
|
+
// list + create PUBLIC repos, so without this check every call below still 200s —
|
|
41
|
+
// while silently dropping the user's PRIVATE repos from the picker, and the real
|
|
42
|
+
// failure surfaces minutes later as an opaque 403 on the provisioner's push (the
|
|
43
|
+
// task-1002561 failure mode). Re-prompt instead: the wizard already branches on
|
|
44
|
+
// repo_scope_required, so this reuses the well-exercised reconnect path.
|
|
45
|
+
if (!auth.grantedScopeCoversRepos(row.scope)) {
|
|
46
|
+
res.fail('repo_scope_required', { status: 403, message: 'Reconnect GitHub to include your private repositories.' });
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
38
49
|
return row.token;
|
|
39
50
|
}
|
|
40
51
|
|
|
@@ -59,7 +59,9 @@ module.exports = function buildLlmCacheRouter() {
|
|
|
59
59
|
// POST /llm-cache/lookup — hit/miss + atomic hit_count bump. Best-effort:
|
|
60
60
|
// any DB error inside lookup() already returns null → we report a miss.
|
|
61
61
|
// rank: metic — only the same trust tier that can store may read a stored result.
|
|
62
|
-
|
|
62
|
+
// Gate: requirePermission('llm_cache.use') — BV1.R104; the seeded rank-role makes
|
|
63
|
+
// the permission and the old rank floor identical (ADR 0151).
|
|
64
|
+
router.post('/llm-cache/lookup', auth.requireBuilder, auth.requirePermission('llm_cache.use'), async (req, res) => {
|
|
63
65
|
// Fields validated in depth below (HEX64 key / clamp); schema declares the
|
|
64
66
|
// accepted set so strict-mode rejects a typo'd extra field.
|
|
65
67
|
if (validateOrRespond(req, res, { cache_key: {}, tokens_saved_est: {} })) return;
|
|
@@ -81,7 +83,8 @@ module.exports = function buildLlmCacheRouter() {
|
|
|
81
83
|
|
|
82
84
|
// POST /llm-cache/store — upsert a freshly-produced result.
|
|
83
85
|
// rank: metic — a store seeds a result that a later run replays at $0 and trusts.
|
|
84
|
-
|
|
86
|
+
// Gate: requirePermission('llm_cache.use') — BV1.R104.
|
|
87
|
+
router.post('/llm-cache/store', auth.requireBuilder, auth.requirePermission('llm_cache.use'), bigJson, async (req, res) => {
|
|
85
88
|
// spec.* is re-built server-side (below) so its inner fields need no top-level
|
|
86
89
|
// schema; declare the three top-level fields so strict-mode rejects extras.
|
|
87
90
|
if (validateOrRespond(req, res, { spec: {}, result_stdout: {}, result_cost_usd: {} })) return;
|
|
@@ -117,8 +117,10 @@ module.exports = function buildModulesRouter() {
|
|
|
117
117
|
});
|
|
118
118
|
}));
|
|
119
119
|
|
|
120
|
-
// rank: metic+archon — toggling a module changes running behavior for the whole
|
|
121
|
-
|
|
120
|
+
// rank: metic+archon — toggling a module changes running behavior for the whole
|
|
121
|
+
// instance, not just the caller. Gate: requirePermission('module.enable') — BV1.R104;
|
|
122
|
+
// the seeded rank-role makes the permission and the old rank floor identical (ADR 0151).
|
|
123
|
+
router.post('/modules/:key/enable', auth.requireBuilder, auth.requirePermission('module.enable'),
|
|
122
124
|
asyncHandler('POST /modules/:key/enable', async (req, res) => {
|
|
123
125
|
const { key } = req.params;
|
|
124
126
|
if (validateOrRespond(req, res, { enabled: {} })) return; // boolean; enforced by the handler check below (preserves invalid_body)
|
|
@@ -153,8 +155,9 @@ module.exports = function buildModulesRouter() {
|
|
|
153
155
|
// signed_by records a NEW sign-off only if none is on file; an existing
|
|
154
156
|
// .upstream-signoff.json is reused as-is (not overwritten).
|
|
155
157
|
//
|
|
156
|
-
// rank: metic+archon — files into a shared review queue, not just a toggle for
|
|
157
|
-
|
|
158
|
+
// rank: metic+archon — files into a shared review queue, not just a toggle for
|
|
159
|
+
// the caller. Gate: requirePermission('module.submit') — BV1.R104.
|
|
160
|
+
router.post('/modules/:key/submit', auth.requireBuilder, auth.requirePermission('module.submit'),
|
|
158
161
|
asyncHandler('POST /modules/:key/submit', async (req, res) => {
|
|
159
162
|
const { key } = req.params;
|
|
160
163
|
const known = listModules();
|
|
@@ -212,7 +215,8 @@ module.exports = function buildModulesRouter() {
|
|
|
212
215
|
// future GET .../:id can add a with-files fetch if the review UI needs it).
|
|
213
216
|
//
|
|
214
217
|
// rank: metic+archon — the review queue, same rank as filing into it.
|
|
215
|
-
|
|
218
|
+
// Gate: requirePermission('module.submit') — BV1.R104.
|
|
219
|
+
router.get('/modules/submissions', auth.requireBuilder, auth.requirePermission('module.submit'),
|
|
216
220
|
asyncHandler('GET /modules/submissions', async (req, res) => {
|
|
217
221
|
const status = req.query.status ? String(req.query.status) : undefined;
|
|
218
222
|
if (status && !['pending', 'accepted', 'rejected'].includes(status)) {
|
|
@@ -81,12 +81,15 @@ module.exports = function buildSecurityRouter() {
|
|
|
81
81
|
// Xenos sees 403 regardless of whether the document is provisioned — we
|
|
82
82
|
// don't want existence-or-not to leak below the rank line.
|
|
83
83
|
//
|
|
84
|
-
// rank: metic+archon — trust-tier access to the threat model + defenses
|
|
85
|
-
//
|
|
84
|
+
// rank: metic+archon — trust-tier access to the threat model + defenses inventory
|
|
85
|
+
// (ADR 0020). Gate: requirePermission('security.report.file') — BV1.R104; the seeded
|
|
86
|
+
// rank-role makes the permission and the old rank floor identical (ADR 0151).
|
|
87
|
+
// NB the 403 code on this route is `permission_forbidden`, not `rank_forbidden`
|
|
88
|
+
// (ADR 0020 §"A Xenos hitting the endpoint" updated to match).
|
|
86
89
|
router.get(
|
|
87
90
|
'/security/adr',
|
|
88
91
|
auth.requireBuilder,
|
|
89
|
-
auth.
|
|
92
|
+
auth.requirePermission('security.report.file'),
|
|
90
93
|
async (_req, res) => {
|
|
91
94
|
const result = await readFullAdr();
|
|
92
95
|
if (result.ok) {
|
|
@@ -113,17 +116,21 @@ module.exports = function buildSecurityRouter() {
|
|
|
113
116
|
// (threat-model §6.3 / ADR 0020), so a committed exploit report is an
|
|
114
117
|
// attacker cookbook on every box. The SAFE conceptual docs (threat-model.md,
|
|
115
118
|
// the README) stay in docs/security/; only the dated reports live here, and
|
|
116
|
-
// ALL three routes
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
+
// ALL three routes gate on requirePermission('security.report.adjudicate')
|
|
120
|
+
// — an archon-floored atom (BV1.R104; was requireRank('archon') and admits
|
|
121
|
+
// exactly the same builders, ADR 0151) — because they serve exploit detail.
|
|
122
|
+
// Content is uploaded by the director via the POST route after deploy — no
|
|
123
|
+
// exploit content ever touches the repo. NB the 403 code on these three is
|
|
124
|
+
// now `permission_forbidden`, not `rank_forbidden`.
|
|
119
125
|
// -----------------------------------------------------------------------
|
|
120
126
|
|
|
121
127
|
// GET /api/gds/security/docs — list doc METADATA (no content).
|
|
122
128
|
//
|
|
123
|
-
// rank: archon — index of exploit-detail engagement reports.
|
|
129
|
+
// rank: archon — index of exploit-detail engagement reports. Gate:
|
|
130
|
+
// requirePermission('security.report.adjudicate') — BV1.R104.
|
|
124
131
|
router.get('/security/docs',
|
|
125
132
|
auth.requireBuilder,
|
|
126
|
-
auth.
|
|
133
|
+
auth.requirePermission('security.report.adjudicate'),
|
|
127
134
|
async (_req, res) => {
|
|
128
135
|
try {
|
|
129
136
|
const docs = await db.listSecurityDocs();
|
|
@@ -138,10 +145,11 @@ module.exports = function buildSecurityRouter() {
|
|
|
138
145
|
|
|
139
146
|
// GET /api/gds/security/docs/:slug — one doc WITH content, or 404.
|
|
140
147
|
//
|
|
141
|
-
// rank: archon — reads the full exploit-detail report body.
|
|
148
|
+
// rank: archon — reads the full exploit-detail report body. Gate:
|
|
149
|
+
// requirePermission('security.report.adjudicate') — BV1.R104.
|
|
142
150
|
router.get('/security/docs/:slug',
|
|
143
151
|
auth.requireBuilder,
|
|
144
|
-
auth.
|
|
152
|
+
auth.requirePermission('security.report.adjudicate'),
|
|
145
153
|
async (req, res) => {
|
|
146
154
|
const slug = String(req.params.slug || '');
|
|
147
155
|
if (!SECURITY_DOC_SLUG_RE.test(slug)) {
|
|
@@ -174,10 +182,11 @@ module.exports = function buildSecurityRouter() {
|
|
|
174
182
|
// Body: { slug, title, content_md }. content_md uses a large explicit cap
|
|
175
183
|
// (full reports), NOT the small LIMITS.DESCRIPTION. Returns 201 + the row.
|
|
176
184
|
//
|
|
177
|
-
// rank: archon — writes exploit-detail report bodies.
|
|
185
|
+
// rank: archon — writes exploit-detail report bodies. Gate:
|
|
186
|
+
// requirePermission('security.report.adjudicate') — BV1.R104.
|
|
178
187
|
router.post('/security/docs',
|
|
179
188
|
auth.requireBuilder,
|
|
180
|
-
auth.
|
|
189
|
+
auth.requirePermission('security.report.adjudicate'),
|
|
181
190
|
async (req, res) => {
|
|
182
191
|
if (validateOrRespond(req, res, {
|
|
183
192
|
slug: { required: true, type: 'string', maxLength: 128, minLength: 1 },
|
|
@@ -673,21 +673,36 @@ function mountInternalSurfaces(app) {
|
|
|
673
673
|
});
|
|
674
674
|
|
|
675
675
|
// The /watch monitoring page and the /harbor dev-box management page (idea 257)
|
|
676
|
-
// and the /gate page (task 1179)
|
|
677
|
-
// gate, same posture: a
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
//
|
|
681
|
-
//
|
|
676
|
+
// and the /gate page (task 1179) all sit behind `page.view.governance` — Metic
|
|
677
|
+
// and up since ADR 0157. Same server-side page gate, same posture: a builder
|
|
678
|
+
// without the permission never receives the shell (requireGovernancePage — named
|
|
679
|
+
// requireArchonPage before ADR 0157 — redirects them to the hall, and an
|
|
680
|
+
// unauthenticated visitor into sign-in). The subdomain shim above has already
|
|
681
|
+
// rewritten the bare /watch + /harbor + /gate onto /builders/*. (Defense in
|
|
682
|
+
// depth: every data endpoint these pages call carries its own server-side gate.)
|
|
682
683
|
const WATCH_PAGE_RE = /^\/builders\/watch(?:\.html)?\/?$/;
|
|
683
684
|
const HARBOR_PAGE_RE = /^\/builders\/harbor(?:\.html)?\/?$/;
|
|
684
685
|
const GATE_PAGE_RE = /^\/builders\/gate(?:\.html)?\/?$/;
|
|
686
|
+
// The /governance roles-and-permissions tab (BV1.R113) sits one floor higher:
|
|
687
|
+
// the `governance.manage` atom itself (archon), the same key its data routes
|
|
688
|
+
// check — requireGovernanceManagePage, not the metic-floored gate above.
|
|
689
|
+
const GOVERNANCE_PAGE_RE = /^\/builders\/governance(?:\.html)?\/?$/;
|
|
690
|
+
// The /people builder directory (task 1002577): gated on `project.curate`, the
|
|
691
|
+
// exact permission its data route (GET /scouting) checks — same posture as the
|
|
692
|
+
// /governance tab above (the page can never drift wider than the API it fronts).
|
|
693
|
+
const PEOPLE_PAGE_RE = /^\/builders\/people(?:\.html)?\/?$/;
|
|
685
694
|
app.use((req, res, next) => {
|
|
686
695
|
if (isModuleSurfaceHost(req)) return next();
|
|
687
696
|
if (!req.path.startsWith('/builders')) return next();
|
|
688
697
|
const norm = path.posix.normalize(req.path);
|
|
689
698
|
if (WATCH_PAGE_RE.test(norm) || HARBOR_PAGE_RE.test(norm) || GATE_PAGE_RE.test(norm)) {
|
|
690
|
-
return gdsAuth.
|
|
699
|
+
return gdsAuth.requireGovernancePage(req, res, next);
|
|
700
|
+
}
|
|
701
|
+
if (GOVERNANCE_PAGE_RE.test(norm)) {
|
|
702
|
+
return gdsAuth.requireGovernanceManagePage(req, res, next);
|
|
703
|
+
}
|
|
704
|
+
if (PEOPLE_PAGE_RE.test(norm)) {
|
|
705
|
+
return gdsAuth.requireProjectCuratePage(req, res, next);
|
|
691
706
|
}
|
|
692
707
|
next();
|
|
693
708
|
});
|