@bongos/core 1.19.1072 → 1.19.1074

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/.bongos-core.json +320 -465
  2. package/.claude/skills/feedback/SKILL.md +2 -2
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +4 -60
  5. package/clients/bongos-client/index.cjs +4 -60
  6. package/clients/bongos-client/index.d.ts +5 -90
  7. package/clients/bongos-client/index.mjs +4 -60
  8. package/config/branding.neutral.json +1 -5
  9. package/config/modules.neutral.json +2 -3
  10. package/config/scheduled-routines.json +0 -2
  11. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
  12. package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
  13. package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
  14. package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
  15. package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +6 -2
  16. package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
  17. package/docs/adr/README.md +2 -1
  18. package/docs/api/openapi.json +171 -1661
  19. package/docs/api-reference.md +9 -41
  20. package/docs/architecture.md +16 -1
  21. package/docs/branding-contract.md +0 -3
  22. package/docs/copy-inventory.md +454 -479
  23. package/docs/copy-registry.json +746 -978
  24. package/docs/file-map.md +5 -34
  25. package/docs/module-api-changelog.md +4 -0
  26. package/docs/modules-contract.md +13 -1
  27. package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
  28. package/docs/onboarding/diagrams/README.md +3 -3
  29. package/docs/onboarding/diagrams/assertions.json +2 -22
  30. package/docs/onboarding/primer.md +9 -28
  31. package/docs/page-inventory.json +4 -1
  32. package/docs/page-readings.json +1032 -1077
  33. package/docs/recipes/render-pilot.md +29 -10
  34. package/migrations/core_257_module_store_registry.sql +87 -0
  35. package/migrations/core_258_drop_dev_box_tables.sql +55 -0
  36. package/modules/agents/module.json +1 -0
  37. package/modules/autonomy/module.json +1 -0
  38. package/modules/builder-settings/module.json +1 -0
  39. package/modules/builder-settings/render-prefs.js +4 -5
  40. package/modules/copy-desk/docx.js +78 -5
  41. package/modules/copy-desk/module.json +1 -0
  42. package/modules/copy-desk/routes/copy-desk.js +9 -9
  43. package/modules/copy-desk/tests/copy_docx.mjs +107 -1
  44. package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
  45. package/modules/discord/module.json +1 -0
  46. package/modules/economy/module.json +1 -0
  47. package/modules/economy/routes/credits.js +1 -2
  48. package/modules/government/catalog.js +13 -17
  49. package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
  50. package/modules/government/module.json +1 -0
  51. package/modules/government/protected-surfaces.json +3 -11
  52. package/modules/government/resolver.js +3 -46
  53. package/modules/government/routes/government.js +0 -2
  54. package/modules/government/session-scopes.js +43 -83
  55. package/modules/government/session-scopes.json +8 -18
  56. package/modules/grading/module.json +1 -0
  57. package/modules/hall-ui/module.json +1 -0
  58. package/modules/hall-ui/public/board-lib.js +2 -2
  59. package/modules/hall-ui/public/builders.js +29 -40
  60. package/modules/hall-ui/public/collab.html +1 -1
  61. package/modules/hall-ui/public/collab.js +1 -1
  62. package/modules/hall-ui/public/diagrams.html +3 -3
  63. package/modules/hall-ui/public/dom-utils.js +1 -1
  64. package/modules/hall-ui/public/gate.js +2 -2
  65. package/modules/hall-ui/public/gate.states.json +1 -1
  66. package/modules/hall-ui/public/hall-render.js +43 -79
  67. package/modules/hall-ui/public/index.html +5 -1
  68. package/modules/hall-ui/public/modules.js +1 -0
  69. package/modules/hall-ui/public/oversight.css +5 -5
  70. package/modules/hall-ui/public/palette.js +0 -11
  71. package/modules/hall-ui/public/primer.js +1 -1
  72. package/modules/hall-ui/public/sessions.html +1 -1
  73. package/modules/hall-ui/public/settings-sessions.js +1 -1
  74. package/modules/hall-ui/public/settings.css +0 -1
  75. package/modules/hall-ui/public/settings.html +4 -33
  76. package/modules/hall-ui/public/settings.js +3 -33
  77. package/modules/hall-ui/public/settings.states.json +2 -2
  78. package/modules/hall-ui/public/studio.html +1 -1
  79. package/modules/hall-ui/public/style.css +10 -13
  80. package/modules/hall-ui/public/task.html +4 -1
  81. package/modules/hall-ui/public/task.js +36 -1
  82. package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
  83. package/modules/hall-ui/public/tweak-editor.css +16 -0
  84. package/modules/hall-ui/public/tweak-editor.html +26 -3
  85. package/modules/hall-ui/public/tweak-editor.js +32 -1
  86. package/modules/hall-ui/public/work.js +1 -1
  87. package/modules/hall-ui/records/tweak-editor.md +1 -0
  88. package/modules/ideas/module.json +1 -0
  89. package/modules/lifecycle/db-claim-reads.js +1 -58
  90. package/modules/lifecycle/db-ship.js +0 -5
  91. package/modules/lifecycle/db.js +0 -4
  92. package/modules/lifecycle/kickoff-checklist.js +0 -4
  93. package/modules/lifecycle/lifecycle.js +0 -8
  94. package/modules/lifecycle/module.json +1 -0
  95. package/modules/lifecycle/routes/tasks.js +5 -7
  96. package/modules/memory/module.json +1 -0
  97. package/modules/npm-release/module.json +1 -0
  98. package/modules/onboarding/module.json +1 -0
  99. package/modules/onboarding/onboarding-state.js +17 -36
  100. package/modules/onboarding/routes/access-requests.js +3 -3
  101. package/modules/platform-identity/module.json +1 -0
  102. package/modules/platform-identity/platform-identity.js +13 -5
  103. package/modules/platform-identity/routes/sso.js +2 -2
  104. package/modules/platform-identity/tests/platform-identity.mjs +17 -3
  105. package/modules/provisioning/module.json +1 -0
  106. package/modules/provisioning/provisioning.js +2 -2
  107. package/modules/provisioning/starter-bundles.js +5 -25
  108. package/modules/public-landing/module.json +1 -0
  109. package/modules/public-landing/public/assets/cosmos.css +1 -1
  110. package/modules/public-landing/public/contact.html +1 -1
  111. package/modules/public-landing/public/privacy.html +1 -1
  112. package/modules/public-landing/public/projects.html +101 -32
  113. package/modules/public-landing/public/projects.probes.json +2 -2
  114. package/modules/public-landing/public/projects.states.json +4 -3
  115. package/modules/public-landing/public/terms.html +1 -1
  116. package/modules/security/module.json +1 -0
  117. package/modules/sessions/module.json +1 -0
  118. package/modules/specialities/module.json +1 -0
  119. package/modules/status-ui/module.json +1 -0
  120. package/modules/ui-design/module.json +1 -0
  121. package/package-lock.json +2 -2
  122. package/package.json +1 -1
  123. package/release-notes.json +84 -0
  124. package/scripts/gds/artifact-format.js +203 -0
  125. package/scripts/gds/audit-authorship.sh +1 -13
  126. package/scripts/gds/claim.js +0 -15
  127. package/scripts/gds/cli-lib.js +8 -8
  128. package/scripts/gds/diagram-facts.js +12 -23
  129. package/scripts/gds/do-api.js +13 -80
  130. package/scripts/gds/doc-cli-guard.js +2 -0
  131. package/scripts/gds/doctor.js +1 -1
  132. package/scripts/gds/feedback-latest.js +139 -9
  133. package/scripts/gds/fitness-checks-identity.js +2 -7
  134. package/scripts/gds/fitness-checks-write-validation.js +4 -0
  135. package/scripts/gds/fitness-ratchets.js +4 -4
  136. package/scripts/gds/fitness.js +10 -10
  137. package/scripts/gds/gen-diagrams.js +49 -71
  138. package/scripts/gds/http-api-client.js +12 -19
  139. package/scripts/gds/init.js +1 -1
  140. package/scripts/gds/lint-infra-exec.js +1 -1
  141. package/scripts/gds/local-preview-lib.js +8 -39
  142. package/scripts/gds/local-preview.js +2 -15
  143. package/scripts/gds/migration-namespace.js +3 -3
  144. package/scripts/gds/module-artifact.js +212 -0
  145. package/scripts/gds/module.js +95 -6
  146. package/scripts/gds/package-core.js +5 -116
  147. package/scripts/gds/provision-config.js +2 -2
  148. package/scripts/gds/provision-net.js +2 -3
  149. package/scripts/gds/provision.js +9 -9
  150. package/scripts/gds/publish-manifest.js +1 -2
  151. package/scripts/gds/regen-instance-docs.js +1 -1
  152. package/scripts/gds/run-unit-tests.js +2 -16
  153. package/scripts/gds/sandbox-stage.js +39 -341
  154. package/scripts/gds/ship-flow.js +6 -6
  155. package/scripts/gds/ship-land.js +3 -4
  156. package/scripts/gds/ship-regen.js +10 -41
  157. package/scripts/gds/ship.js +4 -24
  158. package/scripts/gds/start.js +0 -15
  159. package/scripts/hall-preview/README.md +5 -5
  160. package/scripts/hall-preview/server.js +6 -6
  161. package/scripts/render-diagrams.sh +1 -1
  162. package/src/bongos/api-errors.js +1 -1
  163. package/src/bongos/auth-github.js +2 -3
  164. package/src/bongos/auth.js +51 -126
  165. package/src/bongos/db-kernel.js +2 -3
  166. package/src/bongos/db.js +5 -28
  167. package/src/bongos/module-scope-map.js +14 -29
  168. package/src/bongos/module-store.js +141 -0
  169. package/src/bongos/route-rank-check.js +3 -16
  170. package/src/bongos/routes/auth.js +0 -116
  171. package/src/bongos/routes/builders.js +5 -15
  172. package/src/bongos/routes/instance.js +8 -14
  173. package/src/bongos/routes/me.js +6 -22
  174. package/src/bongos/routes/modules.js +83 -0
  175. package/src/bongos/routes.js +5 -0
  176. package/src/module-api.js +13 -6
  177. package/src/module-loader/catalog.js +3 -0
  178. package/src/module-loader/loader.js +4 -4
  179. package/src/module-loader/manifest-schema.js +15 -7
  180. package/src/module-seams.js +2 -3
  181. package/src/modules.js +42 -14
  182. package/tests/agents_spend_guard.mjs +1 -1
  183. package/tests/api_client.mjs +0 -15
  184. package/tests/auth_page_gate.mjs +10 -10
  185. package/tests/canonical_profile_url.mjs +1 -3
  186. package/tests/claim_action.mjs +4 -1
  187. package/tests/claim_from_task_and_home.mjs +127 -0
  188. package/tests/consumer_layout_boot.mjs +2 -0
  189. package/tests/copy_desk_page_docx.mjs +33 -2
  190. package/tests/core_upgrade_runner.mjs +6 -6
  191. package/tests/deploy_divergence_line.mjs +11 -11
  192. package/tests/diagram_facts_offset.mjs +1 -1
  193. package/tests/do_api.mjs +106 -0
  194. package/tests/feedback_latest.mjs +122 -0
  195. package/tests/fitness.mjs +44 -42
  196. package/tests/gate_approvals.mjs +0 -1
  197. package/tests/go_live.mjs +3 -3
  198. package/tests/government_abuse_matrix.mjs +20 -19
  199. package/tests/government_ownership_scope.mjs +33 -35
  200. package/tests/government_parity.mjs +2 -2
  201. package/tests/government_protected_surfaces.mjs +56 -37
  202. package/tests/government_require_permission.mjs +3 -3
  203. package/tests/government_seed.mjs +37 -4
  204. package/tests/government_session_scope.mjs +92 -366
  205. package/tests/hall_error_envelope.mjs +4 -0
  206. package/tests/hall_landing_boot.mjs +4 -1
  207. package/tests/hall_palette.mjs +1 -16
  208. package/tests/hall_record_world.mjs +6 -0
  209. package/tests/hall_tweak_editor.mjs +52 -1
  210. package/tests/host_topology_skips.mjs +1 -2
  211. package/tests/html_comment_nesting.mjs +137 -0
  212. package/tests/infra_exec_paths.mjs +9 -10
  213. package/tests/init.mjs +4 -5
  214. package/tests/instance_manifest.mjs +18 -25
  215. package/tests/kickoff_checklist.mjs +1 -1
  216. package/tests/lib_sh_resolution.mjs +0 -221
  217. package/tests/local_preview.mjs +0 -12
  218. package/tests/module-scope-map.mjs +9 -9
  219. package/tests/module_catalog.mjs +1 -1
  220. package/tests/module_cli.mjs +15 -15
  221. package/tests/module_contributions.mjs +6 -6
  222. package/tests/module_loader.mjs +6 -6
  223. package/tests/module_manifest.mjs +13 -4
  224. package/tests/module_store_publish.mjs +307 -0
  225. package/tests/module_store_publish_route.mjs +121 -0
  226. package/tests/module_store_registry_migration.mjs +70 -0
  227. package/tests/modules.mjs +56 -26
  228. package/tests/onboarding_route_signals.mjs +21 -31
  229. package/tests/onboarding_state.mjs +35 -49
  230. package/tests/permission_path.mjs +0 -3
  231. package/tests/platform_boot.mjs +1 -2
  232. package/tests/profile_route.mjs +1 -6
  233. package/tests/projects_hub.mjs +5 -5
  234. package/tests/projects_hub_app_step.mjs +138 -0
  235. package/tests/projects_hub_module_picker.mjs +2 -2
  236. package/tests/projects_hub_pre_uat.mjs +4 -5
  237. package/tests/provision.mjs +7 -7
  238. package/tests/provisioning_starter_bundles.mjs +0 -22
  239. package/tests/publish_branch_route.mjs +10 -18
  240. package/tests/publish_manifest.mjs +1 -2
  241. package/tests/rank_tier_single_source.mjs +1 -13
  242. package/tests/repo_map.mjs +3 -4
  243. package/tests/runner_drift.mjs +3 -21
  244. package/tests/sandbox_stage.mjs +57 -390
  245. package/tests/session_rename_fallback.mjs +0 -21
  246. package/tests/ship_error_shape.mjs +1 -1
  247. package/tests/task_visual_slots.mjs +31 -0
  248. package/tests/terms_acceptance.mjs +1 -1
  249. package/tests/upgrade.mjs +4 -4
  250. package/tests/watch_sealed_floor.mjs +0 -1
  251. package/tests/wizard_draft_resume.mjs +2 -2
  252. package/tests/wizard_intent_resume.mjs +21 -10
  253. package/tests/wizard_preselect_why.mjs +18 -18
  254. package/docs/onboarding/browser-terminal-guide.md +0 -73
  255. package/docs/recipes/managed-settings-remote-control.md +0 -416
  256. package/modules/dev-box/CLAUDE.md +0 -15
  257. package/modules/dev-box/box-access.js +0 -623
  258. package/modules/dev-box/box-credential.js +0 -94
  259. package/modules/dev-box/box-onboard.js +0 -486
  260. package/modules/dev-box/boxes.js +0 -1038
  261. package/modules/dev-box/db.js +0 -39
  262. package/modules/dev-box/module.json +0 -19
  263. package/modules/dev-box/routes/box.js +0 -1223
  264. package/scripts/gds/box-auth-check.js +0 -160
  265. package/scripts/gds/box-infra.js +0 -321
  266. package/scripts/gds/box-sync.js +0 -216
  267. package/scripts/gds/box.js +0 -1332
  268. package/scripts/gds/cf-tunnel.js +0 -558
  269. package/scripts/gds/smoke-box.sh +0 -150
  270. package/scripts/gds/tree-preflight.js +0 -241
  271. package/src/bongos/app-pair.js +0 -172
  272. package/tests/app_pair.mjs +0 -168
  273. package/tests/box_access.mjs +0 -578
  274. package/tests/box_auth_check.mjs +0 -114
  275. package/tests/box_code_staleness.mjs +0 -129
  276. package/tests/box_connect_e2e.mjs +0 -196
  277. package/tests/box_cost.mjs +0 -70
  278. package/tests/box_credential.mjs +0 -125
  279. package/tests/box_dns_repoint.mjs +0 -153
  280. package/tests/box_env.mjs +0 -64
  281. package/tests/box_host_keys.mjs +0 -126
  282. package/tests/box_infra_resolve.mjs +0 -246
  283. package/tests/box_onboard.mjs +0 -317
  284. package/tests/box_scope_session.mjs +0 -119
  285. package/tests/box_sweep_docs.mjs +0 -119
  286. package/tests/box_sync.mjs +0 -55
  287. package/tests/box_sync_scope_report.mjs +0 -126
  288. package/tests/box_task_scope.mjs +0 -169
  289. package/tests/box_task_scope_pause.mjs +0 -66
  290. package/tests/box_terminal_ssrf.mjs +0 -96
  291. package/tests/boxes.mjs +0 -1585
  292. package/tests/cf_ruleset_preflight.mjs +0 -142
  293. package/tests/cf_tunnel.mjs +0 -496
  294. package/tests/docs_scrubber_damage.mjs +0 -111
  295. package/tests/module_scope_active_claims.mjs +0 -94
  296. package/tests/ship_api_client_pathspec.mjs +0 -100
  297. package/tests/tree_preflight.mjs +0 -243
package/tests/fitness.mjs CHANGED
@@ -261,7 +261,7 @@ test('module boundary: both checks pass (green) on the current tree', () => {
261
261
  const b = fitness.checkModulesImportOnlyApi();
262
262
  assert.equal(a.hardFail, false, a.violations.concat(a.warnings).join('\n'));
263
263
  assert.equal(b.hardFail, false, b.violations.concat(b.warnings).join('\n'));
264
- // modules/ now holds dev-box + art-pipeline (the M07/M08 moves); both import
264
+ // modules/ holds the carved modules (the M07/M08 moves onward); each imports
265
265
  // only the doorway and the core imports neither → zero findings even though
266
266
  // the ratchet is now ENFORCING (BV1.R45).
267
267
  assert.equal(a.violations.length + a.warnings.length, 0, 'core→module findings: ' + a.warnings.concat(a.violations).join('; '));
@@ -282,7 +282,7 @@ test('module boundary ratchet is FLIPPED — enforcing by default (BV1.R45)', ()
282
282
 
283
283
  test('resolvesToModule: a core require into modules/<key>/ is detected', () => {
284
284
  const coreFile = path.join(ROOT, 'src', 'bongos', 'x.js');
285
- assert.equal(fitness.resolvesToModule(coreFile, '../../modules/dev-box/box.js'), 'dev-box');
285
+ assert.equal(fitness.resolvesToModule(coreFile, '../../modules/art-pipeline/stage.js'), 'art-pipeline');
286
286
  assert.equal(fitness.resolvesToModule(coreFile, './sibling.js'), null);
287
287
  });
288
288
 
@@ -292,7 +292,7 @@ test('moduleImportViolation: doorway + intra-module OK; deep-core + sibling flag
292
292
  assert.equal(fitness.moduleImportViolation(f, '../db.js'), null, 'intra-module relative is allowed');
293
293
  assert.equal(fitness.moduleImportViolation(f, 'express'), null, 'bare npm specs are allowed');
294
294
  assert.match(fitness.moduleImportViolation(f, '../../../src/bongos/db') || '', /core internals/);
295
- assert.match(fitness.moduleImportViolation(f, '../../dev-box/box') || '', /sibling module/);
295
+ assert.match(fitness.moduleImportViolation(f, '../../art-pipeline/stage') || '', /sibling module/);
296
296
  });
297
297
 
298
298
  test('module boundary actually CATCHES violations when enforcing (ratchet flip)', () => {
@@ -301,7 +301,7 @@ test('module boundary actually CATCHES violations when enforcing (ratchet flip)'
301
301
  try {
302
302
  const f = path.join(ROOT, 'modules', 'demo', 'x.js');
303
303
  assert.ok(fitness.moduleImportViolation(f, '../../src/bongos/db'), 'deep-core import must be a violation');
304
- // the live checks stay green (dev-box + art-pipeline are clean) even when enforcing
304
+ // the live checks stay green (every carved module is clean) even when enforcing
305
305
  assert.equal(fitness.checkCoreNeverImportsModule().hardFail, false);
306
306
  assert.equal(fitness.checkModulesImportOnlyApi().hardFail, false);
307
307
  } finally {
@@ -390,15 +390,15 @@ test('the discovered-module roster is derived from a NON-EMPTY disk read (mutati
390
390
 
391
391
  test('core-carries-no-module-code actually CATCHES a hardcoded key (and ignores comments)', () => {
392
392
  // a key named in CODE is a violation
393
- const hit = fitness.scanKernelForModuleKeys("if (key === 'dev-box') mountSpecially();", ['dev-box', 'art-pipeline']);
394
- assert.ok(hit.some((v) => /dev-box/.test(v)), 'a hardcoded module key in code must be flagged');
393
+ const hit = fitness.scanKernelForModuleKeys("if (key === 'art-pipeline') mountSpecially();", ['art-pipeline', 'discord']);
394
+ assert.ok(hit.some((v) => /art-pipeline/.test(v)), 'a hardcoded module key in code must be flagged');
395
395
  // the same key in a comment / doc-example line is NOT a violation
396
- const clean = fitness.scanKernelForModuleKeys("// e.g. logger('dev-box')\n * the dev-box doorway\nconst x = 1;", ['dev-box']);
396
+ const clean = fitness.scanKernelForModuleKeys("// e.g. logger('art-pipeline')\n * the art-pipeline doorway\nconst x = 1;", ['art-pipeline']);
397
397
  assert.deepEqual(clean, [], 'a key on a comment line must be ignored: ' + clean.join('; '));
398
- // token-delimited: `dev-box` must not fire inside the larger token `dev-boxes`
399
- assert.deepEqual(fitness.scanKernelForModuleKeys("const x = 'dev-boxes';", ['dev-box']), [], 'a substring inside a larger token must not fire');
398
+ // token-delimited: `art-pipeline` must not fire inside the larger token `art-pipelines`
399
+ assert.deepEqual(fitness.scanKernelForModuleKeys("const x = 'art-pipelines';", ['art-pipeline']), [], 'a substring inside a larger token must not fire');
400
400
  // no keys discovered → never fires (vacuous-safe before the first move)
401
- assert.deepEqual(fitness.scanKernelForModuleKeys("mount('dev-box')", []), []);
401
+ assert.deepEqual(fitness.scanKernelForModuleKeys("mount('art-pipeline')", []), []);
402
402
  });
403
403
 
404
404
  // --- kernel imports no domain (BV1.R70, ADR 0091 §1) ---
@@ -442,45 +442,45 @@ test('module-migrations-additive: the live tree is clean (no module migrations y
442
442
  });
443
443
 
444
444
  test('module-migrations-additive actually CATCHES destructive + non-namespaced DDL', () => {
445
- // the SQL namespace prefix derives from the key: dev-box → dev_box
446
- assert.equal(fitness.moduleNamespace('dev-box'), 'dev_box');
445
+ // the SQL namespace prefix derives from the key: art-pipeline → art_pipeline
446
+ assert.equal(fitness.moduleNamespace('art-pipeline'), 'art_pipeline');
447
447
 
448
448
  // additive + namespaced → clean (table, index-on-own-table, additive ALTER)
449
- const good = fitness.moduleMigrationViolations('dev-box',
450
- 'CREATE TABLE IF NOT EXISTS dev_box_runs (id serial primary key);\n' +
451
- 'CREATE INDEX idx_dev_box_runs_id ON dev_box_runs (id);\n' +
452
- 'ALTER TABLE dev_box_runs ADD COLUMN note text;');
449
+ const good = fitness.moduleMigrationViolations('art-pipeline',
450
+ 'CREATE TABLE IF NOT EXISTS art_pipeline_runs (id serial primary key);\n' +
451
+ 'CREATE INDEX idx_art_pipeline_runs_id ON art_pipeline_runs (id);\n' +
452
+ 'ALTER TABLE art_pipeline_runs ADD COLUMN note text;');
453
453
  assert.deepEqual(good, [], 'an additive, namespaced migration must pass: ' + good.join('; '));
454
454
 
455
455
  // destructive DDL is rejected
456
- assert.ok(fitness.moduleMigrationViolations('dev-box', 'DROP TABLE dev_box_runs;').some((v) => /destructive/i.test(v)), 'DROP TABLE must be flagged');
457
- assert.ok(fitness.moduleMigrationViolations('dev-box', 'ALTER TABLE dev_box_runs DROP COLUMN note;').some((v) => /destructive/i.test(v)), 'ALTER … DROP must be flagged');
458
- assert.ok(fitness.moduleMigrationViolations('dev-box', 'TRUNCATE dev_box_runs;').some((v) => /destructive/i.test(v)), 'TRUNCATE must be flagged');
456
+ assert.ok(fitness.moduleMigrationViolations('art-pipeline', 'DROP TABLE art_pipeline_runs;').some((v) => /destructive/i.test(v)), 'DROP TABLE must be flagged');
457
+ assert.ok(fitness.moduleMigrationViolations('art-pipeline', 'ALTER TABLE art_pipeline_runs DROP COLUMN note;').some((v) => /destructive/i.test(v)), 'ALTER … DROP must be flagged');
458
+ assert.ok(fitness.moduleMigrationViolations('art-pipeline', 'TRUNCATE art_pipeline_runs;').some((v) => /destructive/i.test(v)), 'TRUNCATE must be flagged');
459
459
 
460
460
  // a CHECK-enum WIDENING (drop-if-exists + re-add the SAME-named constraint) is
461
461
  // additive-in-spirit and ALLOWED (task 1978: provisioning gains 'standalone')
462
- const widen = fitness.moduleMigrationViolations('dev-box',
463
- 'ALTER TABLE dev_box_runs DROP CONSTRAINT IF EXISTS dev_box_runs_shape_check;\n' +
464
- "ALTER TABLE dev_box_runs ADD CONSTRAINT dev_box_runs_shape_check CHECK (shape IN ('a','b','c'));");
462
+ const widen = fitness.moduleMigrationViolations('art-pipeline',
463
+ 'ALTER TABLE art_pipeline_runs DROP CONSTRAINT IF EXISTS art_pipeline_runs_shape_check;\n' +
464
+ "ALTER TABLE art_pipeline_runs ADD CONSTRAINT art_pipeline_runs_shape_check CHECK (shape IN ('a','b','c'));");
465
465
  assert.deepEqual(widen, [], 'a CHECK-enum widening (drop+re-add same constraint) must pass: ' + widen.join('; '));
466
466
  // but a BARE drop constraint (no matching re-add) is STILL destructive
467
- assert.ok(fitness.moduleMigrationViolations('dev-box', 'ALTER TABLE dev_box_runs DROP CONSTRAINT dev_box_runs_shape_check;').some((v) => /destructive/i.test(v)), 'a bare DROP CONSTRAINT (no re-add) must still be flagged');
467
+ assert.ok(fitness.moduleMigrationViolations('art-pipeline', 'ALTER TABLE art_pipeline_runs DROP CONSTRAINT art_pipeline_runs_shape_check;').some((v) => /destructive/i.test(v)), 'a bare DROP CONSTRAINT (no re-add) must still be flagged');
468
468
 
469
469
  // an ACKNOWLEDGED contract-phase column drop (task 1003092, ADR 0177's expand-then-
470
470
  // contract) is the ONE destructive shape allowed: the file carries the same
471
471
  // `-- forward-unsafe:` pragma lint-migrations.js requires, the statement is exactly
472
472
  // one DROP COLUMN, and the table is the module's own.
473
- const PRAGMA = '-- forward-unsafe: drop-column on dev_box_runs — the contract phase of a two-step rename\n';
474
- const contract = fitness.moduleMigrationViolations('dev-box',
475
- PRAGMA + 'ALTER TABLE dev_box_runs DROP COLUMN IF EXISTS note;\nALTER TABLE dev_box_runs ALTER COLUMN note2 SET NOT NULL;');
473
+ const PRAGMA = '-- forward-unsafe: drop-column on art_pipeline_runs — the contract phase of a two-step rename\n';
474
+ const contract = fitness.moduleMigrationViolations('art-pipeline',
475
+ PRAGMA + 'ALTER TABLE art_pipeline_runs DROP COLUMN IF EXISTS note;\nALTER TABLE art_pipeline_runs ALTER COLUMN note2 SET NOT NULL;');
476
476
  assert.deepEqual(contract, [], 'an acknowledged own-table DROP COLUMN must pass: ' + contract.join('; '));
477
477
  // the pragma is per-STATEMENT permission for that one shape, not a per-file switch
478
- assert.ok(fitness.moduleMigrationViolations('dev-box', PRAGMA + 'DROP TABLE dev_box_runs;').some((v) => /destructive/i.test(v)), 'DROP TABLE stays flagged even under the pragma');
479
- assert.ok(fitness.moduleMigrationViolations('dev-box', PRAGMA + 'TRUNCATE dev_box_runs;').some((v) => /destructive/i.test(v)), 'TRUNCATE stays flagged even under the pragma');
480
- assert.ok(fitness.moduleMigrationViolations('dev-box', PRAGMA + 'ALTER TABLE dev_box_runs DROP CONSTRAINT dev_box_runs_shape_check;').some((v) => /destructive/i.test(v)), 'a bare DROP CONSTRAINT stays flagged even under the pragma');
481
- assert.ok(fitness.moduleMigrationViolations('dev-box', PRAGMA + 'ALTER TABLE dev_box_runs DROP COLUMN note, DROP COLUMN note2;').some((v) => /destructive/i.test(v)), 'a multi-clause drop is not the one allowed shape');
478
+ assert.ok(fitness.moduleMigrationViolations('art-pipeline', PRAGMA + 'DROP TABLE art_pipeline_runs;').some((v) => /destructive/i.test(v)), 'DROP TABLE stays flagged even under the pragma');
479
+ assert.ok(fitness.moduleMigrationViolations('art-pipeline', PRAGMA + 'TRUNCATE art_pipeline_runs;').some((v) => /destructive/i.test(v)), 'TRUNCATE stays flagged even under the pragma');
480
+ assert.ok(fitness.moduleMigrationViolations('art-pipeline', PRAGMA + 'ALTER TABLE art_pipeline_runs DROP CONSTRAINT art_pipeline_runs_shape_check;').some((v) => /destructive/i.test(v)), 'a bare DROP CONSTRAINT stays flagged even under the pragma');
481
+ assert.ok(fitness.moduleMigrationViolations('art-pipeline', PRAGMA + 'ALTER TABLE art_pipeline_runs DROP COLUMN note, DROP COLUMN note2;').some((v) => /destructive/i.test(v)), 'a multi-clause drop is not the one allowed shape');
482
482
  // and a column drop on a table OUTSIDE the namespace is refused twice over
483
- const foreign = fitness.moduleMigrationViolations('dev-box', PRAGMA + 'ALTER TABLE builders DROP COLUMN note;');
483
+ const foreign = fitness.moduleMigrationViolations('art-pipeline', PRAGMA + 'ALTER TABLE builders DROP COLUMN note;');
484
484
  assert.ok(foreign.some((v) => /destructive/i.test(v)), 'a core-table DROP COLUMN is still destructive under the pragma');
485
485
  assert.ok(foreign.some((v) => /namespace/i.test(v)), 'and still a namespace violation');
486
486
  // the real contract migration is the shape's first user — it must pass the REAL check
@@ -489,13 +489,13 @@ test('module-migrations-additive actually CATCHES destructive + non-namespaced D
489
489
  'platform_identity_021 is the acknowledged contract phase and must be clean');
490
490
 
491
491
  // a created object outside the <key>_ namespace is rejected
492
- assert.ok(fitness.moduleMigrationViolations('dev-box', 'CREATE TABLE runs (id int);').some((v) => /namespace/i.test(v)), 'a table outside dev_box_ must be flagged');
492
+ assert.ok(fitness.moduleMigrationViolations('art-pipeline', 'CREATE TABLE runs (id int);').some((v) => /namespace/i.test(v)), 'a table outside art_pipeline_ must be flagged');
493
493
  // a module migration altering a CORE table is rejected (reach core via the seam)
494
- assert.ok(fitness.moduleMigrationViolations('dev-box', 'ALTER TABLE builders ADD COLUMN x int;').some((v) => /namespace/i.test(v)), 'altering a core table must be flagged');
494
+ assert.ok(fitness.moduleMigrationViolations('art-pipeline', 'ALTER TABLE builders ADD COLUMN x int;').some((v) => /namespace/i.test(v)), 'altering a core table must be flagged');
495
495
 
496
496
  // a commented-out destructive line is ignored
497
497
  assert.deepEqual(
498
- fitness.moduleMigrationViolations('dev-box', '-- DROP TABLE dev_box_runs;\nCREATE TABLE dev_box_runs (id int);'),
498
+ fitness.moduleMigrationViolations('art-pipeline', '-- DROP TABLE art_pipeline_runs;\nCREATE TABLE art_pipeline_runs (id int);'),
499
499
  [], 'a commented-out DROP must be ignored',
500
500
  );
501
501
  });
@@ -781,12 +781,12 @@ test('demo proof module is live + clean (boundary + rank + migration green)', ()
781
781
  assert.equal(fitness.checkModuleMigrationsAdditive().hardFail, false, 'demo migration is additive + namespaced');
782
782
  });
783
783
 
784
- test('demo RED-LINE (a): a cross-module import (demo → dev-box internals) is REJECTED', () => {
784
+ test('demo RED-LINE (a): a cross-module import (demo → art-pipeline internals) is REJECTED', () => {
785
785
  // the exact violation the BV1.R52 transcript injects: demo deep-importing a
786
786
  // sibling module. moduleImportViolation runs on the real demo route path.
787
787
  const demoRoute = path.join(ROOT, 'modules', 'demo', 'routes', 'demo.js');
788
- const v = fitness.moduleImportViolation(demoRoute, '../../dev-box/box');
789
- assert.ok(v && /sibling module "dev-box"/.test(v), 'demo importing dev-box internals must be flagged: ' + v);
788
+ const v = fitness.moduleImportViolation(demoRoute, '../../art-pipeline/stage');
789
+ assert.ok(v && /sibling module "art-pipeline"/.test(v), 'demo importing art-pipeline internals must be flagged: ' + v);
790
790
  // the doorway is still allowed (the legitimate single core import)
791
791
  assert.equal(fitness.moduleImportViolation(demoRoute, '../../../src/module-api'), null, 'the doorway import is allowed');
792
792
  });
@@ -922,11 +922,13 @@ test('write-route validation check actually CATCHES an unvalidated write route (
922
922
  assert.ok(/POST \/widgets/.test(flagged[0]), 'the offending route is named');
923
923
  });
924
924
 
925
- test('write-route validation exemptions are the known dynamic-key prefs routes, plus the one raw-file upload', () => {
926
- // The .docx upload (task 1004320) has no JSON body to hold a field schema: its
927
- // body is the file's bytes, capped by the route's parser and read by
928
- // docx.readUpload. Named here so a second raw-body exemption is a reviewed diff.
929
- const RAW_FILE_UPLOADS = ['POST /copy-desk/pages/:pageId/draft.docx'];
925
+ test('write-route validation exemptions are the known dynamic-key prefs routes, plus the named raw-file uploads', () => {
926
+ // The .docx upload (task 1004320) and the module-store tarball (task 1004271) have no
927
+ // JSON body to hold a field schema: each body is a file's bytes, capped by the route's
928
+ // own parser and read by a checker that refuses anything malformed (docx.readUpload,
929
+ // module-artifact.verifyModuleArtifact). Named here so each raw-body exemption is a
930
+ // reviewed diff.
931
+ const RAW_FILE_UPLOADS = ['POST /copy-desk/pages/:pageId/draft.docx', 'POST /store/modules/:key/versions'];
930
932
  const exempt = [...fitness.WRITE_VALIDATION_EXEMPT];
931
933
  assert.ok(exempt.length >= 1 && exempt.every((e) => e.startsWith('PATCH /me/') || RAW_FILE_UPLOADS.includes(e)),
932
934
  'exemptions are all /me/* prefs patches or a named raw-file upload: ' + exempt.join(', '));
@@ -439,7 +439,6 @@ await t('both routes are requireBuilder-gated and registered', async () => {
439
439
  assert.ok(layer, `${method.toUpperCase()} ${p} registered`);
440
440
  const handles = layer.route.stack.map((s) => s.handle);
441
441
  assert.ok(handles.includes(auth.requireBuilder), `${p} must be requireBuilder-gated`);
442
- assert.ok(!handles.includes(auth.allowBoxScope), `${p} must NOT carry allowBoxScope`);
443
442
  }
444
443
  });
445
444
 
package/tests/go_live.mjs CHANGED
@@ -44,7 +44,7 @@ const okTarget = {
44
44
 
45
45
  // A divergence result shaped like deploy-divergence.check() returns.
46
46
  const div = (over = {}) => ({
47
- ok: true, onMain: '1.19.306', live: { coreVersion: '1.19.305' },
47
+ ok: true, onMain: '1.19.306', live: { version: '1.0.0', coreVersion: '1.19.305' },
48
48
  hasUnreleased: false, unreleased: { count: 0 }, ...over,
49
49
  });
50
50
 
@@ -82,14 +82,14 @@ test('--allow-unreleased downgrades that halt to a note rather than dropping it'
82
82
 
83
83
  test('a no-op bump is a halt, not a success', () => {
84
84
  const p = G.planGoLive({
85
- div: div({ live: { coreVersion: '1.19.306' } }), target: okTarget, targetVersion: '1.19.306',
85
+ div: div({ live: { version: '1.0.0', coreVersion: '1.19.306' } }), target: okTarget, targetVersion: '1.19.306',
86
86
  });
87
87
  assert.ok(p.halts.find((x) => x.code === 'ALREADY_LIVE'));
88
88
  });
89
89
 
90
90
  test('live ahead of the target halts rather than silently downgrading', () => {
91
91
  const p = G.planGoLive({
92
- div: div({ live: { coreVersion: '1.19.310' } }), target: okTarget, targetVersion: '1.19.306',
92
+ div: div({ live: { version: '1.0.0', coreVersion: '1.19.310' } }), target: okTarget, targetVersion: '1.19.306',
93
93
  });
94
94
  assert.ok(p.halts.find((x) => x.code === 'LIVE_AHEAD'));
95
95
  });
@@ -19,11 +19,12 @@
19
19
  // government.manage (the low-trust builder's own attempt)
20
20
  // V6 config: a role template proposing system keys is stripped; a seeded
21
21
  // role's template grants are dropped wholesale; env overrides labels only
22
- // V7 session-scope: an allow-list naming a system or principal key THROWS
22
+ // V7 session-scope: a registry row that declares ANY allow-list THROWS (the
23
+ // narrowing form was removed with its last user, task 1003894)
23
24
  //
24
25
  // The wall behind the wall (a grant row forced past every vector above):
25
26
  // V8 a forced principal grant resolves to NOTHING (effectivePermissions)
26
- // V9 a box-scoped session narrows away every forced system grant
27
+ // V9 a session of an undeclared source (a leftover dev-box token) is refused
27
28
  // V10 no route consumes the substrate-enforced keys via requirePermission —
28
29
  // a forced deploy/protected-path/ssh grant admits nothing in-app
29
30
  // V11 the substrate blocks a sub-floor builder on EVERY protected surface,
@@ -328,27 +329,28 @@ test('V6: the env layer relabels only — it cannot add a permission or a role',
328
329
  });
329
330
 
330
331
  // ===========================================================================
331
- // V7 — the session-scope vector (an allow-list can never name system power)
332
+ // V7 — the session-scope vector (a registry row can carry no allow-list at all)
332
333
  // ===========================================================================
333
334
 
334
- test('V7: a session-scope allow-list naming ANY system key throws at validation', () => {
335
- for (const k of SYSTEM_KEYS) {
335
+ test('V7: a session-scope row declaring an allow-list is refused at validation, whatever it names', () => {
336
+ // Enumerated from the catalog, as the rest of the matrix is: a system key, a
337
+ // principal key and a plain operational key are all refused, because the refusal
338
+ // is about the SHAPE (a narrowing scope with no enforcement behind it would read as
339
+ // a restriction while granting full authority), not about which key it names.
340
+ for (const k of [...SYSTEM_KEYS, ...catalog.principalKeys(), 'task.ship']) {
336
341
  assert.throws(
337
- () => sessionScopes.validateAgainstCatalog([
342
+ () => sessionScopes.validateScopes([
338
343
  sessionScopes.normalizeScope({ source: 'evil', permissions: [k] }),
339
344
  ]),
340
- new RegExp(k.replace(/\./g, '\\.')),
345
+ /Narrowing session scopes were removed/,
341
346
  `a scope allowing ${k} must be refused at load`,
342
347
  );
343
348
  }
344
349
  });
345
350
 
346
- test('V7: the LIVE session-scope registry carries no system or principal key', () => {
351
+ test('V7: the LIVE session-scope registry declares no scope on any row', () => {
347
352
  for (const scope of sessionScopes.SESSION_SCOPES) {
348
- for (const k of scope.permissions) {
349
- assert.equal(catalog.isSystem(k), false, `scope "${scope.source}" allows system key ${k}`);
350
- assert.equal(catalog.isPrincipalKey(k), false, `scope "${scope.source}" allows principal key ${k}`);
351
- }
353
+ assert.equal(scope.declaresScope, false, `source "${scope.source}" declares a scope`);
352
354
  }
353
355
  });
354
356
 
@@ -362,14 +364,13 @@ test('V8: a forced principal grant resolves to nothing (structurally dropped)',
362
364
  'principal keys confer nothing through the role axis no matter how the row got written');
363
365
  });
364
366
 
365
- test('V9: a box-scoped session narrows away EVERY forced system grant', () => {
366
- const forcedEverything = catalog.permissionKeys(); // as if every key were granted
367
- const boxHolds = resolver.narrowToSessionScope(forcedEverything, 'box');
368
- for (const k of boxHolds) {
369
- assert.equal(catalog.isSystem(k), false, `a box token surfaced system key ${k}`);
367
+ test('V9: an undeclared session source is refused outright, never treated as "full"', () => {
368
+ // Rows with source 'box' stay in builder_sessions until task 1003895 deletes them.
369
+ // They must be undeclared to BOTH loaders, which is what makes requireBuilder and
370
+ // the page gate refuse them (tests/government_session_scope.mjs runs the gate).
371
+ for (const source of ['box', 'not-a-source']) {
372
+ assert.equal(sessionScopes.scopeFor(source), null, `"${source}" must not be a declared session source`);
370
373
  }
371
- assert.deepEqual([...resolver.narrowToSessionScope(forcedEverything, 'not-a-source')], [],
372
- 'an unrecognised source resolves to nothing, never to "full"');
373
374
  });
374
375
 
375
376
  // ===========================================================================
@@ -1,13 +1,13 @@
1
1
  // tests/government_ownership_scope.mjs — ownership-scoped permissions
2
2
  // ("act on OWN X") as a first-class axis (BV1.R100, ADR 0151 §3). DB-free.
3
3
  //
4
- // The thing being proved is a CONJUNCTION. `box.manage.own` is not a grant that
4
+ // The thing being proved is a CONJUNCTION. `task.abandon.own` is not a grant that
5
5
  // happens to be named "own" — it is half a decision:
6
6
  //
7
7
  // 1. the ROLE fact — the builder holds the key (resolved from the DB, uncached);
8
8
  // 2. the OWNER fact — the concrete row is theirs (a REQUEST-scoped fact the
9
9
  // resolver structurally cannot know: it is handed a builder
10
- // id, never a box).
10
+ // id, never a task).
11
11
  //
12
12
  // The security failure mode this file exists to catch is (2) quietly disappearing,
13
13
  // leaving `.own` as an unconditional grant — i.e. `X.manage.own` silently becoming
@@ -103,20 +103,18 @@ test('A3: the modelled families are the ones the ownership sweep names', () => {
103
103
  // `task` joined the modelled families in task 1002443 (ADR 0297) — the first
104
104
  // family added because a ROUTE needed it rather than to complete the model.
105
105
  assert.deepEqual(Object.keys(RESOURCE_FAMILIES).sort(),
106
- ['box', 'builder_prefs', 'claim', 'credential', 'goal', 'memory', 'task']);
106
+ ['builder_prefs', 'claim', 'credential', 'goal', 'memory', 'task']);
107
107
  assert.deepEqual([...ownershipScopedKeys()].sort(),
108
- ['box.manage.own', 'credential.manage.own', 'goal.manage.own', 'me.wandering.set', 'memory.manage.own', 'task.abandon.own', 'task.act.own']);
109
- // The cross-owner side is NOT derivable from spelling: box's is `box.fleet.manage`,
110
- // and `task.abandon` carries no suffix at all — it predates the axis and was given
108
+ ['credential.manage.own', 'goal.manage.own', 'me.wandering.set', 'memory.manage.own', 'task.abandon.own', 'task.act.own']);
109
+ // The cross-owner side is NOT derivable from spelling: `task.abandon` carries no
110
+ // suffix at all — it predates the axis and was given
111
111
  // scope/resource/verb in place rather than renamed, because it is seeded in live
112
112
  // rank rows (governance_002) and a key rename is a migration, not a declaration.
113
113
  assert.deepEqual([...crossOwnerKeys()].sort(),
114
- ['box.fleet.manage', 'claim.release.any', 'memory.read.any', 'task.abandon']);
114
+ ['claim.release.any', 'memory.read.any', 'task.abandon']);
115
115
  // The escalation path is DECLARED per key, never derived from the resource
116
- // family (task 1003195). TWO real pairs now: `box.fleet.manage` is the same
117
- // `manage` verb over every owner's box, and `task.abandon` the same `act` verb
118
- // over every creator's task, so each genuinely subsumes doing it to your own.
119
- assert.deepEqual(crossOwnerKeysFor('box.manage.own'), ['box.fleet.manage']);
116
+ // family (task 1003195). ONE real pair: `task.abandon` is the same `act` verb
117
+ // over every creator's task, so it genuinely subsumes retiring your own.
120
118
  assert.deepEqual(crossOwnerKeysFor('task.abandon.own'), ['task.abandon']);
121
119
  // claim and memory each HAVE an `any` key in their family and still declare no
122
120
  // counterpart — the distinction the family-derived version could not make.
@@ -152,7 +150,7 @@ test('A4: RESOURCE_FAMILIES is frozen and agrees with each row it lists', () =>
152
150
  // ═══ B — `.own` does NOT imply `.any` (oracle: floors × the real rank gate) ════
153
151
 
154
152
  test('B1: for every paired family some rank reaches its OWN row but not everyone else\'s', () => {
155
- assert.ok(PAIRED.length >= 3, `expected the box/claim/memory pairs, saw ${PAIRED.length}`);
153
+ assert.ok(PAIRED.length >= 3, `expected the claim/memory/task pairs, saw ${PAIRED.length}`);
156
154
  for (const [resource, fam] of PAIRED) {
157
155
  for (const ownKey of fam.own) {
158
156
  for (const anyKey of fam.any) {
@@ -177,11 +175,11 @@ test('B2: the seed grants the own key to ranks it withholds the cross-owner key
177
175
  }
178
176
  }
179
177
  // The concrete claim, spelled out: a xenos may act on their own claim and their
180
- // own box; they may not release another builder's claim or close another's box.
178
+ // own task; they may not release another builder's claim or abandon another's task.
181
179
  assert.ok(RANK_SEED.xenos.includes('task.act.own'));
182
180
  assert.ok(!RANK_SEED.xenos.includes('claim.release.any'));
183
- assert.ok(RANK_SEED.xenos.includes('box.manage.own'));
184
- assert.ok(!RANK_SEED.xenos.includes('box.fleet.manage'));
181
+ assert.ok(RANK_SEED.xenos.includes('task.abandon.own'));
182
+ assert.ok(!RANK_SEED.xenos.includes('task.abandon'));
185
183
  });
186
184
 
187
185
  test('B3: cross-owner authority is never CHEAPER than own-only authority', () => {
@@ -228,7 +226,7 @@ test('C3: no owner supplied = no admit, however the caller spells the omission',
228
226
  // question. A handler that forgets it gets a denial, never a pass-through.
229
227
  await holding(EVERYTHING, async () => {
230
228
  for (const context of [undefined, null, {}, { ownerId: null }, { ownerId: undefined }, { ownerId: '' }, { ownerId: {} }, { ownerId: [] }, { ownerId: true }, { ownerId: NaN }]) {
231
- assert.equal(await authorizeOwned(ME, 'box.manage.own', context), false,
229
+ assert.equal(await authorizeOwned(ME, 'task.abandon.own', context), false,
232
230
  `an unidentifiable owner (${JSON.stringify(context)}) must deny`);
233
231
  }
234
232
  });
@@ -245,8 +243,8 @@ test('C4: authorizeOwned is the OWN entry point only — other keys fail closed'
245
243
  assert.equal(await authorizeOwned(ME, 'principal.bfg', { ownerId: ME }), false);
246
244
  assert.equal(await authorizeOwned(ME, 'nope.not.a.key', { ownerId: ME }), false);
247
245
  // A blank actor cannot own anything, even matching a blank owner.
248
- assert.equal(await authorizeOwned(null, 'box.manage.own', { ownerId: null }), false);
249
- assert.equal(await authorizeOwned('', 'box.manage.own', { ownerId: '' }), false);
246
+ assert.equal(await authorizeOwned(null, 'task.abandon.own', { ownerId: null }), false);
247
+ assert.equal(await authorizeOwned('', 'task.abandon.own', { ownerId: '' }), false);
250
248
  });
251
249
  });
252
250
 
@@ -257,7 +255,7 @@ test('C5: builderHasPermissions REFUSES an ownership-scoped key rather than answ
257
255
  `${key}: "do you hold it" has no unconditional answer — the caller wants authorizeOwned()`);
258
256
  }
259
257
  // One own key poisons the whole AND-list; unscoped keys still answer normally.
260
- assert.equal(await builderHasPermissions(ME, ['idea.triage', 'box.manage.own']), false);
258
+ assert.equal(await builderHasPermissions(ME, ['idea.triage', 'task.abandon.own']), false);
261
259
  assert.equal(await builderHasPermissions(ME, ['idea.triage', 'cost.log']), true);
262
260
  assert.equal(await builderHasPermissions(ME, ['claim.release.any']), true);
263
261
  });
@@ -266,17 +264,17 @@ test('C5: builderHasPermissions REFUSES an ownership-scoped key rather than answ
266
264
  // ═══ D — `.any` DOES reach another builder's resource ═════════════════════════
267
265
 
268
266
  test('D1: a DECLARED cross-owner counterpart reaches a row the caller does not own', async () => {
269
- // box is the catalog's one real pair, and it must keep working: `box.fleet.manage`
267
+ // task is the catalog's one real pair, and it must keep working: `task.abandon`
270
268
  // is the whole point of a cross-owner key. Asserted BOTH with and without the own
271
269
  // key, because the counterpart is a standalone admit — it does not merely widen a
272
270
  // grant the builder already has.
273
- await holding(['box.manage.own', 'box.fleet.manage'], async () => {
274
- assert.equal(await authorizeOwnedOrAny(ME, 'box.manage.own', { ownerId: SOMEONE_ELSE }), true,
275
- 'box.fleet.manage is exactly the authority to manage someone else\'s box');
276
- assert.equal(await authorizeOwnedOrAny(ME, 'box.manage.own', { ownerId: ME }), true);
271
+ await holding(['task.abandon.own', 'task.abandon'], async () => {
272
+ assert.equal(await authorizeOwnedOrAny(ME, 'task.abandon.own', { ownerId: SOMEONE_ELSE }), true,
273
+ 'task.abandon is exactly the authority to abandon someone else\'s task');
274
+ assert.equal(await authorizeOwnedOrAny(ME, 'task.abandon.own', { ownerId: ME }), true);
277
275
  });
278
- await holding(['box.fleet.manage'], async () => {
279
- assert.equal(await authorizeOwnedOrAny(ME, 'box.manage.own', { ownerId: SOMEONE_ELSE }), true);
276
+ await holding(['task.abandon'], async () => {
277
+ assert.equal(await authorizeOwnedOrAny(ME, 'task.abandon.own', { ownerId: SOMEONE_ELSE }), true);
280
278
  });
281
279
  });
282
280
 
@@ -346,7 +344,7 @@ test('D1c: every declared counterpart is cross-owner, same-resource, and verb-do
346
344
  `${p.key}'s counterpart ${key} (verb ${c.verb}) must dominate verb ${p.verb}`);
347
345
  }
348
346
  }
349
- assert.ok(declared >= 1, 'expected at least the box pair to be declared');
347
+ assert.ok(declared >= 1, 'expected at least the task pair to be declared');
350
348
  // A verb never dominates a wider one, and an unknown verb fails closed — the
351
349
  // relation is asserted directly so a future edit to the table is caught here.
352
350
  assert.equal(verbDominates('manage', 'read'), true);
@@ -447,8 +445,8 @@ function loadMutatedCatalog(label, mutate) {
447
445
  test('F1: a ".own" key that lost its declared scope throws at module load', () => {
448
446
  assert.throws(
449
447
  () => loadMutatedCatalog('missing scope', (s) => s.replace(
450
- `{ key: 'box.manage.own', system: false, floor: 'xenos', scope: 'own', resource: 'box',`,
451
- `{ key: 'box.manage.own', system: false, floor: 'xenos',`)),
448
+ `{ key: 'task.abandon.own', system: false, floor: 'xenos', scope: 'own', resource: 'task',`,
449
+ `{ key: 'task.abandon.own', system: false, floor: 'xenos',`)),
452
450
  /does not declare scope/,
453
451
  );
454
452
  });
@@ -456,7 +454,7 @@ test('F1: a ".own" key that lost its declared scope throws at module load', () =
456
454
  test('F2: an ownership scope with no resource family throws at module load', () => {
457
455
  assert.throws(
458
456
  () => loadMutatedCatalog('scope without resource', (s) => s.replace(
459
- `scope: 'own', resource: 'box',`, `scope: 'own',`)),
457
+ `scope: 'own', resource: 'task',`, `scope: 'own',`)),
460
458
  /scope and resource must come together/,
461
459
  );
462
460
  });
@@ -522,13 +520,13 @@ test('F7: a counterpart that is dangling, cross-resource, or not cross-owner thr
522
520
  assert.throws(
523
521
  () => loadMutatedCatalog('cross-resource counterpart', (s) => s.replace(
524
522
  `resource: 'credential', verb: 'manage', counterpart: []`,
525
- `resource: 'credential', verb: 'manage', counterpart: ['box.fleet.manage']`)),
523
+ `resource: 'credential', verb: 'manage', counterpart: ['task.abandon']`)),
526
524
  /never crosses resource families/,
527
525
  );
528
526
  assert.throws(
529
527
  () => loadMutatedCatalog('own key as counterpart', (s) => s.replace(
530
- `resource: 'box', verb: 'manage', counterpart: ['box.fleet.manage']`,
531
- `resource: 'box', verb: 'manage', counterpart: ['memory.manage.own']`)),
528
+ `resource: 'task', verb: 'act', counterpart: ['task.abandon']`,
529
+ `resource: 'task', verb: 'act', counterpart: ['memory.manage.own']`)),
532
530
  /is not cross-owner/,
533
531
  );
534
532
  });
@@ -587,7 +585,7 @@ test('G2: the role resolve is untouched — own-scoped keys still resolve, princ
587
585
  for (const key of ownershipScopedKeys()) {
588
586
  assert.ok(resolved.includes(key), `${key} must still resolve — parity depends on it`);
589
587
  }
590
- assert.deepEqual(effectivePermissions(['principal.bfg', 'box.manage.own']), ['box.manage.own']);
588
+ assert.deepEqual(effectivePermissions(['principal.bfg', 'task.abandon.own']), ['task.abandon.own']);
591
589
  assert.deepEqual(effectivePermissions(['idea.triage', 'not.a.real.permission']), ['idea.triage']);
592
590
  assert.deepEqual(effectivePermissions([]), []);
593
591
  assert.ok(Object.isFrozen(resolved));
@@ -601,7 +599,7 @@ test('G3: the kernel port carries the ownership axis on the ONE governance port'
601
599
  // still the same one object port the earlier slices registered
602
600
  assert.equal(typeof governmentPort.resolveBuilderPermissions, 'function');
603
601
  assert.equal(typeof governmentPort.resolvePrincipalPermissions, 'function');
604
- assert.equal(governmentPort.isOwnershipScoped('box.manage.own'), true);
602
+ assert.equal(governmentPort.isOwnershipScoped('task.abandon.own'), true);
605
603
  assert.equal(governmentPort.isOwnershipScoped('claim.release.any'), false);
606
604
  assert.equal(governmentPort.isOwnershipScoped('nope.not.a.key'), false);
607
605
  });
@@ -399,8 +399,8 @@ test('parsePageGates reads permission wherever it sits, and never borrows a sibl
399
399
 
400
400
  // ONE-DIRECTIONAL, and deliberately so. A floor STRICTER than the route middleware
401
401
  // is legitimate and common: the wall lives in the handler (task.confirm.grade_bypass
402
- // on an any-builder POST /tasks/:id/confirm; goal.manage.own's owner|manager check;
403
- // box.source.full's boxScopeForRank). A floor LOOSER than the middleware is never
402
+ // on an any-builder POST /tasks/:id/confirm; goal.manage.own's owner|manager check).
403
+ // A floor LOOSER than the middleware is never
404
404
  // legitimate — it would tell the governance tab a permission is delegable below the
405
405
  // rank the route in fact demands.
406
406
  test('B1: no permission floor is LOOSER than the rank gate its route actually carries', () => {