@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
|
@@ -102,7 +102,7 @@ function createClient(opts = {}) {
|
|
|
102
102
|
getAnalyticsBuilderId: (args) => request("GET", "/analytics/builder/{id}", { hasBody: false }, args),
|
|
103
103
|
},
|
|
104
104
|
"auditLog": {
|
|
105
|
-
// GET /audit-log — rank: archon — GET /audit-log
|
|
105
|
+
// GET /audit-log — rank: metic+archon — GET /audit-log
|
|
106
106
|
getAuditLog: (args) => request("GET", "/audit-log", { hasBody: false }, args),
|
|
107
107
|
},
|
|
108
108
|
"auth": {
|
|
@@ -144,7 +144,7 @@ function createClient(opts = {}) {
|
|
|
144
144
|
getAuthWebStart: (args) => request("GET", "/auth/web/start", { hasBody: false }, args),
|
|
145
145
|
},
|
|
146
146
|
"autonomy": {
|
|
147
|
-
// GET /autonomy/precheck — rank: archon — GET /autonomy/precheck
|
|
147
|
+
// GET /autonomy/precheck — rank: metic+archon — GET /autonomy/precheck
|
|
148
148
|
getAutonomyPrecheck: (args) => request("GET", "/autonomy/precheck", { hasBody: false }, args),
|
|
149
149
|
},
|
|
150
150
|
"backup": {
|
|
@@ -204,13 +204,13 @@ function createClient(opts = {}) {
|
|
|
204
204
|
postBoxVersion: (args) => request("POST", "/box/version", { hasBody: true }, args),
|
|
205
205
|
},
|
|
206
206
|
"boxes": {
|
|
207
|
-
// GET /boxes — rank: archon — GET /boxes
|
|
207
|
+
// GET /boxes — rank: metic+archon — GET /boxes
|
|
208
208
|
getBoxes: (args) => request("GET", "/boxes", { hasBody: false }, args),
|
|
209
|
-
// PATCH /boxes/{builderId}/block — rank: archon — PATCH /boxes/:builderId/block
|
|
209
|
+
// PATCH /boxes/{builderId}/block — rank: metic+archon — PATCH /boxes/:builderId/block
|
|
210
210
|
patchBoxesBuilderIdBlock: (args) => request("PATCH", "/boxes/{builderId}/block", { hasBody: true }, args),
|
|
211
|
-
// POST /boxes/{builderId}/close — rank: archon — POST /boxes/:builderId/close
|
|
211
|
+
// POST /boxes/{builderId}/close — rank: metic+archon — POST /boxes/:builderId/close
|
|
212
212
|
postBoxesBuilderIdClose: (args) => request("POST", "/boxes/{builderId}/close", { hasBody: true }, args),
|
|
213
|
-
// GET /boxes/cost-ledger — rank: archon — GET /boxes/cost-ledger
|
|
213
|
+
// GET /boxes/cost-ledger — rank: metic+archon — GET /boxes/cost-ledger
|
|
214
214
|
getBoxesCostLedger: (args) => request("GET", "/boxes/cost-ledger", { hasBody: false }, args),
|
|
215
215
|
},
|
|
216
216
|
"bugAttachments": {
|
|
@@ -218,9 +218,9 @@ function createClient(opts = {}) {
|
|
|
218
218
|
getBugAttachmentsName: (args) => request("GET", "/bug-attachments/{name}", { hasBody: false }, args),
|
|
219
219
|
},
|
|
220
220
|
"builders": {
|
|
221
|
-
// GET /builders/{id}/active-claims — rank: archon — GET /builders/:id/active-claims
|
|
221
|
+
// GET /builders/{id}/active-claims — rank: metic+archon — GET /builders/:id/active-claims
|
|
222
222
|
getBuildersIdActiveClaims: (args) => request("GET", "/builders/{id}/active-claims", { hasBody: false }, args),
|
|
223
|
-
// PATCH /builders/{id}/budget — rank: archon — PATCH /builders/:id/budget
|
|
223
|
+
// PATCH /builders/{id}/budget — rank: metic+archon — PATCH /builders/:id/budget
|
|
224
224
|
patchBuildersIdBudget: (args) => request("PATCH", "/builders/{id}/budget", { hasBody: true }, args),
|
|
225
225
|
// GET /builders/{id}/onboarding — rank: public — GET /builders/:id/onboarding
|
|
226
226
|
getBuildersIdOnboarding: (args) => request("GET", "/builders/{id}/onboarding", { hasBody: false }, args),
|
|
@@ -234,13 +234,13 @@ function createClient(opts = {}) {
|
|
|
234
234
|
getBuildersDirectory: (args) => request("GET", "/builders/directory", { hasBody: false }, args),
|
|
235
235
|
// GET /builders/me/pending-rank-change — rank: any-builder — GET /builders/me/pending-rank-change
|
|
236
236
|
getBuildersMePendingRankChange: (args) => request("GET", "/builders/me/pending-rank-change", { hasBody: false }, args),
|
|
237
|
-
// GET /builders/roster — rank: archon — GET /builders/roster
|
|
237
|
+
// GET /builders/roster — rank: metic+archon — GET /builders/roster
|
|
238
238
|
getBuildersRoster: (args) => request("GET", "/builders/roster", { hasBody: false }, args),
|
|
239
239
|
},
|
|
240
240
|
"claims": {
|
|
241
241
|
// POST /claims — rank: any-builder — POST /claims
|
|
242
242
|
postClaims: (args) => request("POST", "/claims", { hasBody: true }, args),
|
|
243
|
-
// POST /claims/{id}/release-on-behalf — rank: archon — POST /claims/:id/release-on-behalf
|
|
243
|
+
// POST /claims/{id}/release-on-behalf — rank: metic+archon — POST /claims/:id/release-on-behalf
|
|
244
244
|
postClaimsIdReleaseOnBehalf: (args) => request("POST", "/claims/{id}/release-on-behalf", { hasBody: true }, args),
|
|
245
245
|
// POST /claims/{id}/resolve — rank: any-builder — POST /claims/:id/resolve
|
|
246
246
|
postClaimsIdResolve: (args) => request("POST", "/claims/{id}/resolve", { hasBody: true }, args),
|
|
@@ -262,21 +262,21 @@ function createClient(opts = {}) {
|
|
|
262
262
|
postDependencies: (args) => request("POST", "/dependencies", { hasBody: true }, args),
|
|
263
263
|
},
|
|
264
264
|
"discord": {
|
|
265
|
-
// GET /discord/channels/plan — rank: archon — GET /discord/channels/plan
|
|
265
|
+
// GET /discord/channels/plan — rank: metic+archon — GET /discord/channels/plan
|
|
266
266
|
getDiscordChannelsPlan: (args) => request("GET", "/discord/channels/plan", { hasBody: false }, args),
|
|
267
|
-
// POST /discord/channels/reconcile — rank: archon — POST /discord/channels/reconcile
|
|
267
|
+
// POST /discord/channels/reconcile — rank: metic+archon — POST /discord/channels/reconcile
|
|
268
268
|
postDiscordChannelsReconcile: (args) => request("POST", "/discord/channels/reconcile", { hasBody: true }, args),
|
|
269
269
|
},
|
|
270
270
|
"doneWhen": {
|
|
271
271
|
// POST /done-when/{criterionId}/satisfy — rank: metic+archon — POST /done-when/:criterionId/satisfy
|
|
272
272
|
postDoneWhenCriterionIdSatisfy: (args) => request("POST", "/done-when/{criterionId}/satisfy", { hasBody: true }, args),
|
|
273
|
-
// POST /done-when/{criterionId}/unsatisfy — rank: archon — POST /done-when/:criterionId/unsatisfy
|
|
273
|
+
// POST /done-when/{criterionId}/unsatisfy — rank: metic+archon — POST /done-when/:criterionId/unsatisfy
|
|
274
274
|
postDoneWhenCriterionIdUnsatisfy: (args) => request("POST", "/done-when/{criterionId}/unsatisfy", { hasBody: true }, args),
|
|
275
275
|
// GET /done-when/pending-review — rank: metic+archon — GET /done-when/pending-review
|
|
276
276
|
getDoneWhenPendingReview: (args) => request("GET", "/done-when/pending-review", { hasBody: false }, args),
|
|
277
277
|
},
|
|
278
278
|
"gateApprovals": {
|
|
279
|
-
// GET /gate-approvals — rank: archon — GET /gate-approvals
|
|
279
|
+
// GET /gate-approvals — rank: metic+archon — GET /gate-approvals
|
|
280
280
|
getGateApprovals: (args) => request("GET", "/gate-approvals", { hasBody: false }, args),
|
|
281
281
|
// POST /gate-approvals/{pr}/approve — rank: archon — POST /gate-approvals/:pr/approve
|
|
282
282
|
postGateApprovalsPrApprove: (args) => request("POST", "/gate-approvals/{pr}/approve", { hasBody: true }, args),
|
|
@@ -334,11 +334,29 @@ function createClient(opts = {}) {
|
|
|
334
334
|
patchGoalsIdVisibility: (args) => request("PATCH", "/goals/{id}/visibility", { hasBody: true }, args),
|
|
335
335
|
// GET /goals/inbox — rank: any-builder — GET /goals/inbox
|
|
336
336
|
getGoalsInbox: (args) => request("GET", "/goals/inbox", { hasBody: false }, args),
|
|
337
|
-
// POST /goals/reorder — rank: archon — POST /goals/reorder
|
|
337
|
+
// POST /goals/reorder — rank: metic+archon — POST /goals/reorder
|
|
338
338
|
postGoalsReorder: (args) => request("POST", "/goals/reorder", { hasBody: true }, args),
|
|
339
339
|
},
|
|
340
|
+
"governance": {
|
|
341
|
+
// GET /governance/assignments — rank: archon — GET /governance/assignments
|
|
342
|
+
getGovernanceAssignments: (args) => request("GET", "/governance/assignments", { hasBody: false }, args),
|
|
343
|
+
// POST /governance/assignments — rank: archon — POST /governance/assignments
|
|
344
|
+
postGovernanceAssignments: (args) => request("POST", "/governance/assignments", { hasBody: true }, args),
|
|
345
|
+
// DELETE /governance/assignments/{builderId}/{roleKey} — rank: archon — DELETE /governance/assignments/:builderId/:roleKey
|
|
346
|
+
deleteGovernanceAssignmentsBuilderIdRoleKey: (args) => request("DELETE", "/governance/assignments/{builderId}/{roleKey}", { hasBody: false }, args),
|
|
347
|
+
// GET /governance/permissions — rank: archon — GET /governance/permissions
|
|
348
|
+
getGovernancePermissions: (args) => request("GET", "/governance/permissions", { hasBody: false }, args),
|
|
349
|
+
// GET /governance/roles — rank: archon — GET /governance/roles
|
|
350
|
+
getGovernanceRoles: (args) => request("GET", "/governance/roles", { hasBody: false }, args),
|
|
351
|
+
// POST /governance/roles — rank: archon — POST /governance/roles
|
|
352
|
+
postGovernanceRoles: (args) => request("POST", "/governance/roles", { hasBody: true }, args),
|
|
353
|
+
// DELETE /governance/roles/{roleKey}/permissions/{permissionKey} — rank: archon — DELETE /governance/roles/:roleKey/permissions/:permissionKey
|
|
354
|
+
deleteGovernanceRolesRoleKeyPermissionsPermissionKey: (args) => request("DELETE", "/governance/roles/{roleKey}/permissions/{permissionKey}", { hasBody: false }, args),
|
|
355
|
+
// PUT /governance/roles/{roleKey}/permissions/{permissionKey} — rank: archon — PUT /governance/roles/:roleKey/permissions/:permissionKey
|
|
356
|
+
putGovernanceRolesRoleKeyPermissionsPermissionKey: (args) => request("PUT", "/governance/roles/{roleKey}/permissions/{permissionKey}", { hasBody: true }, args),
|
|
357
|
+
},
|
|
340
358
|
"grades": {
|
|
341
|
-
// GET /grades/by-builder — rank: archon — GET /grades/by-builder
|
|
359
|
+
// GET /grades/by-builder — rank: metic+archon — GET /grades/by-builder
|
|
342
360
|
getGradesByBuilder: (args) => request("GET", "/grades/by-builder", { hasBody: false }, args),
|
|
343
361
|
},
|
|
344
362
|
"healthz": {
|
|
@@ -354,7 +372,7 @@ function createClient(opts = {}) {
|
|
|
354
372
|
getInboxId: (args) => request("GET", "/inbox/{id}", { hasBody: false }, args),
|
|
355
373
|
// PATCH /inbox/{id} — rank: metic+archon — PATCH /inbox/:id
|
|
356
374
|
patchInboxId: (args) => request("PATCH", "/inbox/{id}", { hasBody: true }, args),
|
|
357
|
-
// POST /inbox/{id}/ratify — rank: archon — POST /inbox/:id/ratify
|
|
375
|
+
// POST /inbox/{id}/ratify — rank: metic+archon — POST /inbox/:id/ratify
|
|
358
376
|
postInboxIdRatify: (args) => request("POST", "/inbox/{id}/ratify", { hasBody: true }, args),
|
|
359
377
|
},
|
|
360
378
|
"instance": {
|
|
@@ -432,9 +450,9 @@ function createClient(opts = {}) {
|
|
|
432
450
|
getMemoryBfgBuildersBuilderIdFiles: (args) => request("GET", "/memory/bfg/builders/{builderId}/files", { hasBody: false }, args),
|
|
433
451
|
// POST /memory/builders/{builderId}/bfg-write — rank: bfg-principal — POST /memory/builders/:builderId/bfg-write
|
|
434
452
|
postMemoryBuildersBuilderIdBfgWrite: (args) => request("POST", "/memory/builders/{builderId}/bfg-write", { hasBody: true }, args),
|
|
435
|
-
// GET /memory/builders/{builderId}/file — rank: archon — GET /memory/builders/:builderId/file
|
|
453
|
+
// GET /memory/builders/{builderId}/file — rank: metic+archon — GET /memory/builders/:builderId/file
|
|
436
454
|
getMemoryBuildersBuilderIdFile: (args) => request("GET", "/memory/builders/{builderId}/file", { hasBody: false }, args),
|
|
437
|
-
// GET /memory/builders/{builderId}/files — rank: archon — GET /memory/builders/:builderId/files
|
|
455
|
+
// GET /memory/builders/{builderId}/files — rank: metic+archon — GET /memory/builders/:builderId/files
|
|
438
456
|
getMemoryBuildersBuilderIdFiles: (args) => request("GET", "/memory/builders/{builderId}/files", { hasBody: false }, args),
|
|
439
457
|
// DELETE /memory/file — rank: any-builder — DELETE /memory/file
|
|
440
458
|
deleteMemoryFile: (args) => request("DELETE", "/memory/file", { hasBody: true }, args),
|
|
@@ -470,27 +488,27 @@ function createClient(opts = {}) {
|
|
|
470
488
|
postMyProjectsJoin: (args) => request("POST", "/my-projects/join", { hasBody: true }, args),
|
|
471
489
|
},
|
|
472
490
|
"overrideRequests": {
|
|
473
|
-
// GET /override-requests — rank: archon — GET /override-requests
|
|
491
|
+
// GET /override-requests — rank: metic+archon — GET /override-requests
|
|
474
492
|
getOverrideRequests: (args) => request("GET", "/override-requests", { hasBody: false }, args),
|
|
475
|
-
// POST /override-requests/{id}/decide — rank: archon — POST /override-requests/:id/decide
|
|
493
|
+
// POST /override-requests/{id}/decide — rank: metic+archon — POST /override-requests/:id/decide
|
|
476
494
|
postOverrideRequestsIdDecide: (args) => request("POST", "/override-requests/{id}/decide", { hasBody: true }, args),
|
|
477
495
|
},
|
|
478
496
|
"projects": {
|
|
479
|
-
// GET /projects — rank: archon — GET /projects
|
|
497
|
+
// GET /projects — rank: metic+archon — GET /projects
|
|
480
498
|
getProjects: (args) => request("GET", "/projects", { hasBody: false }, args),
|
|
481
|
-
// POST /projects — rank: archon — POST /projects
|
|
499
|
+
// POST /projects — rank: metic+archon — POST /projects
|
|
482
500
|
postProjects: (args) => request("POST", "/projects", { hasBody: true }, args),
|
|
483
|
-
// PATCH /projects/{id}/featured — rank: archon — PATCH /projects/:id/featured
|
|
501
|
+
// PATCH /projects/{id}/featured — rank: metic+archon — PATCH /projects/:id/featured
|
|
484
502
|
patchProjectsIdFeatured: (args) => request("PATCH", "/projects/{id}/featured", { hasBody: true }, args),
|
|
485
503
|
// GET /projects/featured — rank: public — GET /projects/featured
|
|
486
504
|
getProjectsFeatured: (args) => request("GET", "/projects/featured", { hasBody: false }, args),
|
|
487
|
-
// POST /projects/invite — rank: archon — POST /projects/invite
|
|
505
|
+
// POST /projects/invite — rank: metic+archon — POST /projects/invite
|
|
488
506
|
postProjectsInvite: (args) => request("POST", "/projects/invite", { hasBody: true }, args),
|
|
489
507
|
},
|
|
490
508
|
"provisioning": {
|
|
491
|
-
// GET /provisioning/fleet — rank: archon — GET /provisioning/fleet
|
|
509
|
+
// GET /provisioning/fleet — rank: metic+archon — GET /provisioning/fleet
|
|
492
510
|
getProvisioningFleet: (args) => request("GET", "/provisioning/fleet", { hasBody: false }, args),
|
|
493
|
-
// GET /provisioning/fleet/cost-ledger — rank: archon — GET /provisioning/fleet/cost-ledger
|
|
511
|
+
// GET /provisioning/fleet/cost-ledger — rank: metic+archon — GET /provisioning/fleet/cost-ledger
|
|
494
512
|
getProvisioningFleetCostLedger: (args) => request("GET", "/provisioning/fleet/cost-ledger", { hasBody: false }, args),
|
|
495
513
|
// GET /provisioning/github-app/callback — rank: public — GET /provisioning/github-app/callback
|
|
496
514
|
getProvisioningGithubAppCallback: (args) => request("GET", "/provisioning/github-app/callback", { hasBody: false }, args),
|
|
@@ -504,7 +522,7 @@ function createClient(opts = {}) {
|
|
|
504
522
|
postProvisioningInstances: (args) => request("POST", "/provisioning/instances", { hasBody: true }, args),
|
|
505
523
|
// GET /provisioning/instances/{id} — rank: any-builder — GET /provisioning/instances/:id
|
|
506
524
|
getProvisioningInstancesId: (args) => request("GET", "/provisioning/instances/{id}", { hasBody: false }, args),
|
|
507
|
-
// POST /provisioning/instances/{id}/force-teardown — rank: archon — POST /provisioning/instances/:id/force-teardown
|
|
525
|
+
// POST /provisioning/instances/{id}/force-teardown — rank: metic+archon — POST /provisioning/instances/:id/force-teardown
|
|
508
526
|
postProvisioningInstancesIdForceTeardown: (args) => request("POST", "/provisioning/instances/{id}/force-teardown", { hasBody: true }, args),
|
|
509
527
|
// POST /provisioning/instances/{id}/github-app — rank: any-builder — POST /provisioning/instances/:id/github-app
|
|
510
528
|
postProvisioningInstancesIdGithubApp: (args) => request("POST", "/provisioning/instances/{id}/github-app", { hasBody: true }, args),
|
|
@@ -554,7 +572,7 @@ function createClient(opts = {}) {
|
|
|
554
572
|
getPublicVersions: (args) => request("GET", "/public/versions", { hasBody: false }, args),
|
|
555
573
|
},
|
|
556
574
|
"scouting": {
|
|
557
|
-
// GET /scouting — rank: archon — GET /scouting
|
|
575
|
+
// GET /scouting — rank: metic+archon — GET /scouting
|
|
558
576
|
getScouting: (args) => request("GET", "/scouting", { hasBody: false }, args),
|
|
559
577
|
},
|
|
560
578
|
"search": {
|
|
@@ -566,24 +584,28 @@ function createClient(opts = {}) {
|
|
|
566
584
|
"security": {
|
|
567
585
|
// GET /security/adr — rank: metic+archon — GET /security/adr
|
|
568
586
|
getSecurityAdr: (args) => request("GET", "/security/adr", { hasBody: false }, args),
|
|
569
|
-
// GET /security/docs — rank: archon — GET /security/docs
|
|
587
|
+
// GET /security/docs — rank: metic+archon — GET /security/docs
|
|
570
588
|
getSecurityDocs: (args) => request("GET", "/security/docs", { hasBody: false }, args),
|
|
571
|
-
// POST /security/docs — rank: archon — POST /security/docs
|
|
589
|
+
// POST /security/docs — rank: metic+archon — POST /security/docs
|
|
572
590
|
postSecurityDocs: (args) => request("POST", "/security/docs", { hasBody: true }, args),
|
|
573
|
-
// GET /security/docs/{slug} — rank: archon — GET /security/docs/:slug
|
|
591
|
+
// GET /security/docs/{slug} — rank: metic+archon — GET /security/docs/:slug
|
|
574
592
|
getSecurityDocsSlug: (args) => request("GET", "/security/docs/{slug}", { hasBody: false }, args),
|
|
575
|
-
// GET /security/reports — rank: archon — GET /security/reports
|
|
593
|
+
// GET /security/reports — rank: metic+archon — GET /security/reports
|
|
576
594
|
getSecurityReports: (args) => request("GET", "/security/reports", { hasBody: false }, args),
|
|
577
595
|
// POST /security/reports — rank: metic+archon — POST /security/reports
|
|
578
596
|
postSecurityReports: (args) => request("POST", "/security/reports", { hasBody: true }, args),
|
|
579
|
-
// POST /security/reports/{id}/confirm — rank: archon — POST /security/reports/:id/confirm
|
|
597
|
+
// POST /security/reports/{id}/confirm — rank: metic+archon — POST /security/reports/:id/confirm
|
|
580
598
|
postSecurityReportsIdConfirm: (args) => request("POST", "/security/reports/{id}/confirm", { hasBody: true }, args),
|
|
581
|
-
// POST /security/reports/{id}/dispute — rank: archon — POST /security/reports/:id/dispute
|
|
599
|
+
// POST /security/reports/{id}/dispute — rank: metic+archon — POST /security/reports/:id/dispute
|
|
582
600
|
postSecurityReportsIdDispute: (args) => request("POST", "/security/reports/{id}/dispute", { hasBody: true }, args),
|
|
583
|
-
// POST /security/reports/{id}/fix — rank: archon — POST /security/reports/:id/fix
|
|
601
|
+
// POST /security/reports/{id}/fix — rank: metic+archon — POST /security/reports/:id/fix
|
|
584
602
|
postSecurityReportsIdFix: (args) => request("POST", "/security/reports/{id}/fix", { hasBody: true }, args),
|
|
585
|
-
// POST /security/reports/{id}/link-fix — rank: archon — POST /security/reports/:id/link-fix
|
|
603
|
+
// POST /security/reports/{id}/link-fix — rank: metic+archon — POST /security/reports/:id/link-fix
|
|
586
604
|
postSecurityReportsIdLinkFix: (args) => request("POST", "/security/reports/{id}/link-fix", { hasBody: true }, args),
|
|
605
|
+
// GET /security/scans — rank: any-builder — GET /security/scans
|
|
606
|
+
getSecurityScans: (args) => request("GET", "/security/scans", { hasBody: false }, args),
|
|
607
|
+
// POST /security/scans — rank: metic+archon — POST /security/scans
|
|
608
|
+
postSecurityScans: (args) => request("POST", "/security/scans", { hasBody: true }, args),
|
|
587
609
|
},
|
|
588
610
|
"session": {
|
|
589
611
|
// POST /session/optimize — rank: any-builder — POST /session/optimize
|
|
@@ -596,7 +618,7 @@ function createClient(opts = {}) {
|
|
|
596
618
|
getSessionsId: (args) => request("GET", "/sessions/{id}", { hasBody: false }, args),
|
|
597
619
|
// GET /sessions/mine — rank: any-builder — GET /sessions/mine
|
|
598
620
|
getSessionsMine: (args) => request("GET", "/sessions/mine", { hasBody: false }, args),
|
|
599
|
-
// GET /sessions/search — rank: archon — GET /sessions/search
|
|
621
|
+
// GET /sessions/search — rank: metic+archon — GET /sessions/search
|
|
600
622
|
getSessionsSearch: (args) => request("GET", "/sessions/search", { hasBody: false }, args),
|
|
601
623
|
},
|
|
602
624
|
"sso": {
|
|
@@ -664,17 +686,17 @@ function createClient(opts = {}) {
|
|
|
664
686
|
getTasksClaimable: (args) => request("GET", "/tasks/claimable", { hasBody: false }, args),
|
|
665
687
|
// GET /tasks/newcomer-floor — rank: any-builder — GET /tasks/newcomer-floor
|
|
666
688
|
getTasksNewcomerFloor: (args) => request("GET", "/tasks/newcomer-floor", { hasBody: false }, args),
|
|
667
|
-
// POST /tasks/newcomer-restock — rank: archon — POST /tasks/newcomer-restock
|
|
689
|
+
// POST /tasks/newcomer-restock — rank: metic+archon — POST /tasks/newcomer-restock
|
|
668
690
|
postTasksNewcomerRestock: (args) => request("POST", "/tasks/newcomer-restock", { hasBody: true }, args),
|
|
669
691
|
// GET /tasks/peer-votes/feed — rank: any-builder — GET /tasks/peer-votes/feed
|
|
670
692
|
getTasksPeerVotesFeed: (args) => request("GET", "/tasks/peer-votes/feed", { hasBody: false }, args),
|
|
671
|
-
// POST /tasks/peer-votes/tally — rank: archon — POST /tasks/peer-votes/tally
|
|
693
|
+
// POST /tasks/peer-votes/tally — rank: metic+archon — POST /tasks/peer-votes/tally
|
|
672
694
|
postTasksPeerVotesTally: (args) => request("POST", "/tasks/peer-votes/tally", { hasBody: true }, args),
|
|
673
695
|
},
|
|
674
696
|
"versions": {
|
|
675
697
|
// GET /versions — rank: public — GET /versions
|
|
676
698
|
getVersions: (args) => request("GET", "/versions", { hasBody: false }, args),
|
|
677
|
-
// POST /versions — rank: archon — POST /versions
|
|
699
|
+
// POST /versions — rank: metic+archon — POST /versions
|
|
678
700
|
postVersions: (args) => request("POST", "/versions", { hasBody: true }, args),
|
|
679
701
|
// GET /versions/{id}/done-when — rank: public — GET /versions/:id/done-when
|
|
680
702
|
getVersionsIdDoneWhen: (args) => request("GET", "/versions/{id}/done-when", { hasBody: false }, args),
|
|
@@ -214,7 +214,7 @@ module.exports = function buildBoxRouter() {
|
|
|
214
214
|
|
|
215
215
|
// GET /boxes — full roster across all builders. rank: archon (exposes every
|
|
216
216
|
// builder's box + IP + cost; same gate as the rest of roster management).
|
|
217
|
-
router.get('/boxes', auth.requireBuilder, auth.
|
|
217
|
+
router.get('/boxes', auth.requireBuilder, auth.requirePermission('box.fleet.manage'), async (req, res) => {
|
|
218
218
|
try {
|
|
219
219
|
const state = req.query.state || undefined;
|
|
220
220
|
const rows = await boxes.listBoxes(pool, { state });
|
|
@@ -249,7 +249,7 @@ module.exports = function buildBoxRouter() {
|
|
|
249
249
|
// (#602). rank: archon — cost oversight across every builder without a DB query.
|
|
250
250
|
// Declared among the archon routes (archon-first) so the route-rank auditor's
|
|
251
251
|
// 20-line lookahead classifies it correctly.
|
|
252
|
-
router.get('/boxes/cost-ledger', auth.requireBuilder, auth.
|
|
252
|
+
router.get('/boxes/cost-ledger', auth.requireBuilder, auth.requirePermission('box.fleet.manage'), async (req, res) => {
|
|
253
253
|
try {
|
|
254
254
|
// R14 (#2001 / ADR 0119): shared helper; per-endpoint cap preserved (default 500, max 2000).
|
|
255
255
|
const { limit } = api.parsePagination(req, { defaultLimit: 500, maxLimit: 2000 });
|
|
@@ -268,7 +268,7 @@ module.exports = function buildBoxRouter() {
|
|
|
268
268
|
// §9.4). rank: archon (acts on another builder's paid infrastructure).
|
|
269
269
|
// Declared among the archon routes (archon-first) so the route-rank auditor's
|
|
270
270
|
// 20-line lookahead classifies it correctly.
|
|
271
|
-
router.post('/boxes/:builderId/close', auth.requireBuilder, auth.
|
|
271
|
+
router.post('/boxes/:builderId/close', auth.requireBuilder, auth.requirePermission('box.fleet.manage'), async (req, res) => {
|
|
272
272
|
const builderId = Number(req.params.builderId);
|
|
273
273
|
if (!Number.isInteger(builderId) || builderId < 1) {
|
|
274
274
|
return res.fail('bad_builder_id', 400);
|
|
@@ -315,7 +315,7 @@ module.exports = function buildBoxRouter() {
|
|
|
315
315
|
// it does NOT touch a box that is already active. close_current adds the
|
|
316
316
|
// explicit teardown the owner asked be optional ("options for both"). rank:
|
|
317
317
|
// archon. Archon-first ordering (see above).
|
|
318
|
-
router.patch('/boxes/:builderId/block', auth.requireBuilder, auth.
|
|
318
|
+
router.patch('/boxes/:builderId/block', auth.requireBuilder, auth.requirePermission('box.fleet.manage'), async (req, res) => {
|
|
319
319
|
const builderId = Number(req.params.builderId);
|
|
320
320
|
if (!Number.isInteger(builderId) || builderId < 1) {
|
|
321
321
|
return res.fail('bad_builder_id', 400);
|
|
@@ -610,7 +610,7 @@ module.exports = function buildBoxRouter() {
|
|
|
610
610
|
if (state !== 'active') {
|
|
611
611
|
return res.fail('box_not_active', { status: 409, message: state === 'none' || state === 'destroyed'
|
|
612
612
|
? 'No active box to close.'
|
|
613
|
-
: `Box is currently '${state}' — wait for it to finish or
|
|
613
|
+
: `Box is currently '${state}' — wait for it to finish or ask a Metic+ operator.`, details: { state } });
|
|
614
614
|
}
|
|
615
615
|
const { intent, created } = await boxes.enqueueIntent(
|
|
616
616
|
pool, req.builder.id, 'deprovision', 'api:self'
|
|
@@ -802,13 +802,15 @@ module.exports = function buildBoxRouter() {
|
|
|
802
802
|
error: 'BOX_PROVISION_DENIED', reason: decision.reason,
|
|
803
803
|
required: decision.required ?? null, actual: decision.actual ?? null,
|
|
804
804
|
};
|
|
805
|
-
// idea 257 / task 1163: when an
|
|
805
|
+
// idea 257 / task 1163: when an operator block is the cause, tell the
|
|
806
806
|
// builder WHY (the recorded reason) rather than a bare denial — this is
|
|
807
|
-
// the `message` the box-panel surfaces to the builder verbatim.
|
|
807
|
+
// the `message` the box-panel surfaces to the builder verbatim. The block
|
|
808
|
+
// is written by a Metic+ operator (box.fleet.manage, ADR 0157), so don't
|
|
809
|
+
// attribute it to an Archon specifically.
|
|
808
810
|
if (decision.reason === 'BOX_PROVISION_BLOCKED') {
|
|
809
811
|
payload.message = builder && builder.box_blocked_reason
|
|
810
|
-
? `An
|
|
811
|
-
: 'An
|
|
812
|
+
? `An operator has paused thy dev-box access: ${builder.box_blocked_reason}`
|
|
813
|
+
: 'An operator has paused thy dev-box access. Ask in Discord #support.';
|
|
812
814
|
}
|
|
813
815
|
return res.status(403).json(payload);
|
|
814
816
|
}
|
package/modules/discord/auth.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// `const auth = require('./auth')` call sites unchanged:
|
|
5
5
|
//
|
|
6
6
|
// • KERNEL gates, re-exported off the published doorway — requireBuilder /
|
|
7
|
-
// requireRank (the same
|
|
7
|
+
// requireRank / requirePermission (the same authority machinery every route
|
|
8
|
+
// uses; ADR 0016, ADR 0151).
|
|
8
9
|
//
|
|
9
10
|
// • DISCORD-SPECIFIC helpers VENDORED here — the snowflake validator + the bot-
|
|
10
11
|
// token loader. These are deliberately NOT on the kernel doorway: the doorway
|
|
@@ -48,6 +49,7 @@ module.exports = {
|
|
|
48
49
|
// kernel gates (doorway)
|
|
49
50
|
requireBuilder: api.requireBuilder,
|
|
50
51
|
requireRank: api.requireRank,
|
|
52
|
+
requirePermission: api.requirePermission,
|
|
51
53
|
// vendored discord-specific helpers
|
|
52
54
|
isValidDiscordUserId,
|
|
53
55
|
loadDiscordBotToken,
|
|
@@ -31,6 +31,9 @@ const roleSync = require('../discord-role-sync');
|
|
|
31
31
|
// unlink; degrades to { guildId: null } so the callback simply skips the join.
|
|
32
32
|
// • EVENT task.shipped — core (publish-reconciler / routes/tasks) emits it on the
|
|
33
33
|
// confirmed→shipped transition; we post the #ship-news / #ship-feed broadcast.
|
|
34
|
+
// • EVENT task.strand-flagged — the publish-reconciler emits it the moment a
|
|
35
|
+
// confirmed task is newly flagged needs_rebase (task 1002606); we post the
|
|
36
|
+
// strand notice so the builder hears about it before their next session.
|
|
34
37
|
// • EVENT builder.role-sync-needed { builderId } — core emits it on a rank change
|
|
35
38
|
// (routes/builders.js) + after a link (routes/auth.js); we repair the Discord
|
|
36
39
|
// rank-role badge. Fire-and-forget; the listener owns its own error handling.
|
|
@@ -38,6 +41,7 @@ const roleSync = require('../discord-role-sync');
|
|
|
38
41
|
// hasProvider-guarded; listeners are replaced (off→on) so a test re-instantiating
|
|
39
42
|
// the router never accumulates duplicates.
|
|
40
43
|
let _shipListenerOff = null;
|
|
44
|
+
let _strandListenerOff = null;
|
|
41
45
|
let _roleSyncListenerOff = null;
|
|
42
46
|
function registerSeams() {
|
|
43
47
|
if (!api.hasProvider('discord.isLinked')) {
|
|
@@ -52,6 +56,8 @@ function registerSeams() {
|
|
|
52
56
|
}
|
|
53
57
|
if (_shipListenerOff) _shipListenerOff();
|
|
54
58
|
_shipListenerOff = api.on('task.shipped', (task) => shipBroadcast.postShipBroadcast(task));
|
|
59
|
+
if (_strandListenerOff) _strandListenerOff();
|
|
60
|
+
_strandListenerOff = api.on('task.strand-flagged', (info) => shipBroadcast.postStrandNotice(info));
|
|
55
61
|
if (_roleSyncListenerOff) _roleSyncListenerOff();
|
|
56
62
|
_roleSyncListenerOff = api.on('builder.role-sync-needed', ({ builderId }) =>
|
|
57
63
|
roleSync.syncBuilderRole(builderId).catch((e) =>
|
|
@@ -66,7 +72,7 @@ module.exports = function buildDiscordRouter() {
|
|
|
66
72
|
// apply to make Discord match docs/discord/channels.json, plus warnings. No
|
|
67
73
|
// writes. 503 when the bot is disabled / not connected to a guild.
|
|
68
74
|
// rank: archon — channel administration command.
|
|
69
|
-
router.get('/discord/channels/plan', auth.requireBuilder, auth.
|
|
75
|
+
router.get('/discord/channels/plan', auth.requireBuilder, auth.requirePermission('discord.channels.manage'), async (req, res) => {
|
|
70
76
|
try {
|
|
71
77
|
const channels = require('../discord-channels');
|
|
72
78
|
const plan = channels.computePlan();
|
|
@@ -84,7 +90,7 @@ module.exports = function buildDiscordRouter() {
|
|
|
84
90
|
// Best-effort; returns how many ops applied/failed. 503 when the bot is
|
|
85
91
|
// disabled / not connected.
|
|
86
92
|
// rank: archon — channel administration command.
|
|
87
|
-
router.post('/discord/channels/reconcile', auth.requireBuilder, auth.
|
|
93
|
+
router.post('/discord/channels/reconcile', auth.requireBuilder, auth.requirePermission('discord.channels.manage'), async (req, res) => {
|
|
88
94
|
try {
|
|
89
95
|
const channels = require('../discord-channels');
|
|
90
96
|
const result = await channels.reconcileChannels();
|
|
@@ -75,4 +75,51 @@ async function postShipBroadcast(task, deps = {}) {
|
|
|
75
75
|
return result;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
// task 1002606 (finding P-3) — the strand notice. Until now Discord fired only on
|
|
79
|
+
// task.shipped, so a STRAND (needs_rebase flagged by the publish-reconciler) had no
|
|
80
|
+
// push channel — the builder discovered it at their next /builder-start, up to a
|
|
81
|
+
// whole session later. The reconciler now emits `task.strand-flagged` the moment a
|
|
82
|
+
// strand is detected; this poster carries it to the builder channel.
|
|
83
|
+
//
|
|
84
|
+
// PURE routing, same discipline as chooseBroadcastTargets: a security-sensitive
|
|
85
|
+
// task's strand notice goes ONLY to the Archon-only #security-ship-feed (the reason
|
|
86
|
+
// string can name conflicted files of an unpatched fix). The line deliberately does
|
|
87
|
+
// NOT start with `#<taskId>` — a LEADING task marker is what the applause bot maps
|
|
88
|
+
// 👍 reactions onto (discord-reaction.parseShipMarker), and a strand is not
|
|
89
|
+
// applaudable shipped work.
|
|
90
|
+
function chooseStrandTargets({ securitySensitive, taskId, reason }) {
|
|
91
|
+
if (taskId == null) return [];
|
|
92
|
+
const line = `⚠️ task ${taskId} stranded at 'confirmed' (${reason || 'unknown'}) — its builder must fix + re-land it, or release it.`;
|
|
93
|
+
if (securitySensitive) {
|
|
94
|
+
return [{ channel: 'security_ship_feed', line }];
|
|
95
|
+
}
|
|
96
|
+
return [{ channel: 'ship_feed', line }];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Best-effort server-side strand poster — fired by the `task.strand-flagged` seam
|
|
100
|
+
// listener. `info` is the reconciler's event payload: { id, builder_id, title,
|
|
101
|
+
// security_sensitive, reason }. FAIL CLOSED on the security flag exactly like
|
|
102
|
+
// postShipBroadcast: if it is not a definite boolean the notice is suppressed
|
|
103
|
+
// entirely rather than risk naming an unpatched fix on a builder channel. Never
|
|
104
|
+
// throws — a channel failure is logged and swallowed.
|
|
105
|
+
async function postStrandNotice(info, deps = {}) {
|
|
106
|
+
const result = { posted: [], skipped: null };
|
|
107
|
+
if (!info || info.id == null) { result.skipped = 'no_task'; return result; }
|
|
108
|
+
const sec = info.security_sensitive;
|
|
109
|
+
if (sec !== true && sec !== false) { result.skipped = 'security_flag_indeterminate'; return result; }
|
|
110
|
+
|
|
111
|
+
const postWebhook = deps.postWebhook || require('./webhook').postWebhook;
|
|
112
|
+
const log = deps.log || console.log;
|
|
113
|
+
const targets = chooseStrandTargets({ securitySensitive: sec, taskId: info.id, reason: info.reason });
|
|
114
|
+
for (const { channel, line } of targets) {
|
|
115
|
+
try {
|
|
116
|
+
await postWebhook(channel, line);
|
|
117
|
+
result.posted.push(channel);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
log(`[gds] strand notice: #${channel.replace(/_/g, '-')} post failed (non-blocking): ${e && e.message ? e.message : e}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = { chooseBroadcastTargets, postShipBroadcast, chooseStrandTargets, postStrandNotice };
|
|
@@ -90,7 +90,7 @@ const PERMISSIONS = [
|
|
|
90
90
|
{ key: 'task.confirm.grade_bypass', system: true, floor: 'archon', guards: 'POST /tasks/:id/confirm — ratify past a failed grade (env-relaxable, ADR 0041)' },
|
|
91
91
|
{ key: 'dependency.cross_tier.manage', system: true, floor: 'archon', guards: 'POST|DELETE /dependencies — cross-tier structural edges (ADR 0086)' },
|
|
92
92
|
{ key: 'goal.scope.manage', system: true, floor: 'archon', guards: 'cross-tier goal-scope edges + protected-scope admission (ADR 0086)' },
|
|
93
|
-
{ key: 'governance.manage', system: true, floor: 'archon', guards: 'the governance tab
|
|
93
|
+
{ key: 'governance.manage', system: true, floor: 'archon', guards: 'the governance tab routes (roles/perms/assignments CRUD, reads included — R110) — meta-escalation' },
|
|
94
94
|
{ key: 'gate_approval.decide', system: true, floor: 'archon', guards: 'POST /gate-approvals/:pr/approve — CI deploy-gate override' },
|
|
95
95
|
{ key: 'deploy.push_main', system: true, floor: 'archon', guards: 'git push origin main / the deploy — substrate-enforced (pre-push, main-audit)' },
|
|
96
96
|
{ key: 'path.protected.modify', system: true, floor: 'archon', guards: 'modifying a PROTECTED_GLOBS path below the Metic floor — substrate (ADR 0043)' },
|
|
@@ -99,27 +99,21 @@ const PERMISSIONS = [
|
|
|
99
99
|
{ key: 'principal.discord_bot', system: true, floor: null, principal: 'discord_bot', guards: 'Discord bot routes — requireDiscordBot (exact system_role; dormant)' },
|
|
100
100
|
|
|
101
101
|
// ── operational — floor archon (admin ops; grantable to a custom role) ────────
|
|
102
|
+
//
|
|
103
|
+
// ADR 0157 (owner decision, 2026-08-05) narrowed this block to IDENTITY: the Archon
|
|
104
|
+
// rank is responsible for rank and identity only. Nineteen keys that sat here for
|
|
105
|
+
// administrative convenience moved to floor `metic` below; each carries a
|
|
106
|
+
// "(ADR 0157, was archon)" note there. What remains answers "who exists, who is
|
|
107
|
+
// admitted, and whose sessions are valid" — the identity half of the rank/identity
|
|
108
|
+
// mandate, with rank itself covered by `builder.rank.set` (system, above).
|
|
109
|
+
//
|
|
110
|
+
// The four trust-boundary keys that ALSO stay Archon are system:true above
|
|
111
|
+
// (`task.confirm.grade_bypass`, `gate_approval.decide`, `goal.scope.manage`,
|
|
112
|
+
// `governance.manage`) — see the ADR's escalation-chain argument for why moving any
|
|
113
|
+
// one of them would make this delegation self-reversible.
|
|
102
114
|
{ key: 'builder.status.set', system: false, floor: 'archon', guards: 'PATCH /builders/:id/status — offboard / (de)activate' },
|
|
103
|
-
{ key: 'builder.budget.set', system: false, floor: 'archon', guards: 'PATCH /builders/:id/budget' },
|
|
104
|
-
{ key: 'criterion.unsatisfy', system: false, floor: 'archon', guards: 'POST /done-when/:id/unsatisfy' },
|
|
105
115
|
{ key: 'auth.session.revoke', system: false, floor: 'archon', guards: 'POST /auth/revoke, /auth/revoke-all' },
|
|
106
116
|
{ key: 'access_request.review', system: false, floor: 'archon', guards: 'GET/PATCH /access-requests/:id — admit a builder' },
|
|
107
|
-
{ key: 'security.report.adjudicate', system: false, floor: 'archon', guards: 'POST /security/reports/:id/{confirm,dispute,fix,link-fix}, /security/docs CRUD' },
|
|
108
|
-
{ key: 'provisioning.fleet.manage', system: false, floor: 'archon', guards: 'GET /provisioning/fleet[/cost-ledger], POST .../force-teardown' },
|
|
109
|
-
{ key: 'box.fleet.manage', system: false, floor: 'archon', scope: 'any', resource: 'box', guards: 'GET /boxes[/cost-ledger], POST /boxes/:b/close, PATCH .../block' },
|
|
110
|
-
{ key: 'autonomy.precheck', system: false, floor: 'archon', guards: 'GET /autonomy/precheck — unsupervised-runner gate' },
|
|
111
|
-
{ key: 'discord.channels.manage', system: false, floor: 'archon', guards: 'GET /discord/channels/plan, POST .../reconcile' },
|
|
112
|
-
{ key: 'project.curate', system: false, floor: 'archon', guards: 'platform-identity: POST /projects, /projects/invite, PATCH /projects/:id/featured, GET /scouting' },
|
|
113
|
-
{ key: 'override_request.decide', system: false, floor: 'archon', guards: 'GET /override-requests, POST .../:id/decide' },
|
|
114
|
-
{ key: 'claim.release.any', system: false, floor: 'archon', scope: 'any', resource: 'claim', guards: 'POST /claims/:id/release-on-behalf' },
|
|
115
|
-
{ key: 'idea.ratify', system: false, floor: 'archon', guards: 'POST /inbox/:id/ratify' },
|
|
116
|
-
{ key: 'task.newcomer_restock', system: false, floor: 'archon', guards: 'POST /tasks/newcomer-restock' },
|
|
117
|
-
{ key: 'task.peer_votes.tally', system: false, floor: 'archon', guards: 'POST /tasks/peer-votes/tally' },
|
|
118
|
-
{ key: 'goal.reorder', system: false, floor: 'archon', guards: 'POST /goals/reorder' },
|
|
119
|
-
{ key: 'builder.roster.read', system: false, floor: 'archon', guards: 'GET /builders/roster, /grades/by-builder, /builders/:id/active-claims' },
|
|
120
|
-
{ key: 'session.search', system: false, floor: 'archon', guards: 'GET /sessions/search' },
|
|
121
|
-
{ key: 'memory.read.any', system: false, floor: 'archon', scope: 'any', resource: 'memory', guards: 'GET /memory/builders/:id/file[s] — cross-builder memory read' },
|
|
122
|
-
{ key: 'audit_log.read', system: false, floor: 'archon', guards: 'GET /audit-log' },
|
|
123
117
|
|
|
124
118
|
// ── operational — floor metic (the working rank's review/triage/authoring) ────
|
|
125
119
|
{ key: 'idea.triage', system: false, floor: 'metic', guards: 'PATCH /inbox/:id' },
|
|
@@ -152,6 +146,57 @@ const PERMISSIONS = [
|
|
|
152
146
|
{ key: 'source.clone.local', system: false, floor: 'metic', guards: 'Metic+ local clone / collaborator invite (ADR 0035)' },
|
|
153
147
|
{ key: 'box.source.full', system: false, floor: 'metic', guards: 'GET /box/source-access → full (vs starter); boxScopeForRank' },
|
|
154
148
|
|
|
149
|
+
// ── delegated archon→metic by ADR 0157 (owner decision, 2026-08-05) ───────────
|
|
150
|
+
//
|
|
151
|
+
// "Archon is responsible for rank and identity." These nineteen are administration
|
|
152
|
+
// and operations: they spend, read, adjudicate, and shuffle work, but none of them
|
|
153
|
+
// decides WHO HOLDS AUTHORITY, so the trusted working rank carries them. All were
|
|
154
|
+
// floor 'archon' before this ADR; none is system:true, so all were already
|
|
155
|
+
// delegable to a custom role — this only lowers the seeded floor.
|
|
156
|
+
//
|
|
157
|
+
// Deliberately NOT in this block, though the owner's rule would otherwise reach them:
|
|
158
|
+
// • `version.close` — retained at archon; it is the DISPOSITION that finalises a
|
|
159
|
+
// version's scope-truth, and canonical-permissions has long paired it with rank
|
|
160
|
+
// as the two powers that never delegate. ADR 0157 keeps that pairing.
|
|
161
|
+
// • `dependency.cross_tier.manage` — system:true, and the catalog's own invariant
|
|
162
|
+
// (tests/governance_catalog.mjs) forbids granting a system key to metic. Moving
|
|
163
|
+
// it would mean clearing the system flag, which widens what any CUSTOM role can
|
|
164
|
+
// be granted — a second-order change beyond the delegation that was asked for.
|
|
165
|
+
{ key: 'builder.budget.set', system: false, floor: 'metic', guards: 'PATCH /builders/:id/budget (ADR 0157, was archon)' },
|
|
166
|
+
{ key: 'criterion.unsatisfy', system: false, floor: 'metic', guards: 'POST /done-when/:id/unsatisfy (ADR 0157, was archon)' },
|
|
167
|
+
// BV1.R105. system:false DELIBERATELY, despite `version.close` being system:true.
|
|
168
|
+
// The `system` flag marks TRUST-BOUNDARY power — things that escalate or bypass the
|
|
169
|
+
// boundary (deploy, protected-path, rank-set, grade-bypass, cross-tier, principals).
|
|
170
|
+
// Creating a version is AUTHORING: it confers no authority on anyone. Its real
|
|
171
|
+
// siblings are `goal.create` and `task.create`, both operational. Close is the
|
|
172
|
+
// DISPOSITION that finalises a version's scope-truth; create opens it — the symmetry
|
|
173
|
+
// argument was considered and rejected, because system:true would permanently bar
|
|
174
|
+
// delegating version authoring to a custom role for no trust-boundary reason.
|
|
175
|
+
// ADR 0157 then lowered create's floor to metic while close stayed archon, which is
|
|
176
|
+
// the same asymmetry read from the other side: authoring delegates, disposition does not.
|
|
177
|
+
{ key: 'version.create', system: false, floor: 'metic', guards: 'POST /versions — create a version + its done-when criteria (ADR 0157, was archon)' },
|
|
178
|
+
{ 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)' },
|
|
179
|
+
{ key: 'provisioning.fleet.manage', system: false, floor: 'metic', guards: 'GET /provisioning/fleet[/cost-ledger], POST .../force-teardown (ADR 0157, was archon)' },
|
|
180
|
+
{ key: 'box.fleet.manage', system: false, floor: 'metic', scope: 'any', resource: 'box', guards: 'GET /boxes[/cost-ledger], POST /boxes/:b/close, PATCH .../block (ADR 0157, was archon)' },
|
|
181
|
+
{ key: 'autonomy.precheck', system: false, floor: 'metic', guards: 'GET /autonomy/precheck — unsupervised-runner gate (ADR 0157, was archon)' },
|
|
182
|
+
{ key: 'discord.channels.manage', system: false, floor: 'metic', guards: 'GET /discord/channels/plan, POST .../reconcile (ADR 0157, was archon)' },
|
|
183
|
+
{ key: 'project.curate', system: false, floor: 'metic', guards: 'platform-identity: GET/POST /projects, /projects/invite, PATCH /projects/:id/featured, GET /scouting (ADR 0157, was archon)' },
|
|
184
|
+
{ key: 'override_request.decide', system: false, floor: 'metic', guards: 'GET /override-requests, POST .../:id/decide (ADR 0157, was archon)' },
|
|
185
|
+
{ key: 'claim.release.any', system: false, floor: 'metic', scope: 'any', resource: 'claim', guards: 'POST /claims/:id/release-on-behalf (ADR 0157, was archon)' },
|
|
186
|
+
{ key: 'idea.ratify', system: false, floor: 'metic', guards: 'POST /inbox/:id/ratify (ADR 0157, was archon)' },
|
|
187
|
+
{ key: 'task.newcomer_restock', system: false, floor: 'metic', guards: 'POST /tasks/newcomer-restock (ADR 0157, was archon)' },
|
|
188
|
+
{ key: 'task.peer_votes.tally', system: false, floor: 'metic', guards: 'POST /tasks/peer-votes/tally (ADR 0157, was archon)' },
|
|
189
|
+
{ key: 'goal.reorder', system: false, floor: 'metic', guards: 'POST /goals/reorder (ADR 0157, was archon)' },
|
|
190
|
+
{ key: 'builder.roster.read', system: false, floor: 'metic', guards: 'GET /builders/roster, /grades/by-builder, /builders/:id/active-claims (ADR 0157, was archon)' },
|
|
191
|
+
{ key: 'session.search', system: false, floor: 'metic', guards: 'GET /sessions/search (ADR 0157, was archon)' },
|
|
192
|
+
{ key: 'memory.read.any', system: false, floor: 'metic', scope: 'any', resource: 'memory', guards: 'GET /memory/builders/:id/file[s] — cross-builder memory read (ADR 0157, was archon)' },
|
|
193
|
+
{ key: 'audit_log.read', system: false, floor: 'metic', guards: 'GET /audit-log (ADR 0157, was archon)' },
|
|
194
|
+
// ADR 0157 §4. Split from `gate_approval.decide` (system, archon) so the Gate PAGE
|
|
195
|
+
// can open to Metic while APPROVING a held PR stays Archon. Without the split, a
|
|
196
|
+
// Metic could reach /gate via `page.view.governance` and then 403 on its only data
|
|
197
|
+
// endpoint — a page that renders nothing but an error.
|
|
198
|
+
{ key: 'gate_approval.read', system: false, floor: 'metic', guards: 'GET /gate-approvals — the Gate page read side (approve = gate_approval.decide, archon)' },
|
|
199
|
+
|
|
155
200
|
// ── ownership-scoped + baseline own-work (floor thetes / xenos) ───────────────
|
|
156
201
|
{ key: 'task.claim.any', system: false, floor: 'thetes', guards: 'claim the general queue (per-task requires_rank floor still applies)' },
|
|
157
202
|
{ key: 'page.view.builder', system: false, floor: 'thetes', guards: 'authenticated hall pages (requireNonXenosPage / requireBuilderPage)' },
|
|
@@ -174,7 +219,12 @@ const PERMISSIONS = [
|
|
|
174
219
|
{ key: 'blocker.file', system: false, floor: 'xenos', guards: 'POST /blockers' },
|
|
175
220
|
{ key: 'idea.file', system: false, floor: 'xenos', guards: 'POST /inbox (own idea capture)' },
|
|
176
221
|
{ key: 'page.view.public', system: false, floor: 'xenos', guards: 'public read surfaces (incl. xenos)' },
|
|
177
|
-
|
|
222
|
+
// ADR 0157: floor archon→metic, which is what opens /watch, /harbor and /gate to the
|
|
223
|
+
// working rank. The gate FUNCTION was renamed to match (`requireArchonPage` →
|
|
224
|
+
// `requireGovernancePage`) once the floor moved. Viewing is all this grants: every
|
|
225
|
+
// write those pages reach is its own atom, and `governance.manage` (the
|
|
226
|
+
// roles/permissions tab) stayed archon.
|
|
227
|
+
{ key: 'page.view.governance', system: false, floor: 'metic', guards: 'the governance/monitoring pages — /watch, /harbor, /gate (requireGovernancePage; write = governance.manage, archon)' },
|
|
178
228
|
];
|
|
179
229
|
|
|
180
230
|
// ── invariants asserted at module load (fail loud on an authoring typo) ─────────
|