@bongos/core 1.19.714 → 1.19.724

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 (422) hide show
  1. package/.bongos-core.json +488 -413
  2. package/.claude/skills/backlog-review/SKILL.md +3 -3
  3. package/.claude/skills/blocker-review/SKILL.md +1 -1
  4. package/.claude/skills/blocker-solve/SKILL.md +2 -2
  5. package/.claude/skills/builder-reauth/SKILL.md +1 -1
  6. package/.claude/skills/builder-redteam/SKILL.md +1 -1
  7. package/.claude/skills/builder-ship/SKILL.md +1 -1
  8. package/.claude/skills/builder-start/SKILL.md +1 -1
  9. package/.claude/skills/design/SKILL.md +2 -2
  10. package/.claude/skills/feedback/SKILL.md +1 -1
  11. package/.claude/skills/goal-create/SKILL.md +1 -1
  12. package/.claude/skills/goal-review/SKILL.md +2 -2
  13. package/.claude/skills/idea-triage/SKILL.md +5 -5
  14. package/.claude/skills/merge-mode/SKILL.md +4 -4
  15. package/.claude/skills/new-project/SKILL.md +17 -17
  16. package/.claude/skills/planning-session/SKILL.md +11 -11
  17. package/.claude/skills/recall/SKILL.md +2 -2
  18. package/.claude/skills/strand-fix/SKILL.md +1 -1
  19. package/.devcontainer/README.md +2 -2
  20. package/CONTRIBUTING.md +2 -2
  21. package/GOVERNANCE.md +4 -4
  22. package/clients/bongos-client/README.md +1 -1
  23. package/clients/bongos-client/bongos-client.global.js +4 -0
  24. package/clients/bongos-client/index.cjs +4 -0
  25. package/clients/bongos-client/index.d.ts +5 -0
  26. package/clients/bongos-client/index.mjs +4 -0
  27. package/config/branding.neutral.json +4 -2
  28. package/docs/adr/0001-stack-choice-single-vps.md +2 -2
  29. package/docs/adr/0002-digitalocean-over-hetzner.md +1 -1
  30. package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
  31. package/docs/adr/0009-autotile-architecture.md +1 -1
  32. package/docs/adr/0011-terrain-id-rendering-model.md +3 -3
  33. package/docs/adr/0013-paths-as-object-overlays-with-autotile-dispatch.md +1 -1
  34. package/docs/adr/0014-families-canonical-over-vocabulary.md +1 -1
  35. package/docs/adr/0015-task-dependencies-and-auto-promotion.md +1 -1
  36. package/docs/adr/0016-trust-boundary-server-enforced-permissions.md +1 -1
  37. package/docs/adr/0018-three-rank-model-goes-live.md +3 -3
  38. package/docs/adr/0020-security-model-public-stub.md +9 -9
  39. package/docs/adr/0021-per-builder-skill-model-preferences.md +2 -2
  40. package/docs/adr/0022-secrets-policy.md +5 -5
  41. package/docs/adr/0024-cloneable-repo-local-first-memory.md +3 -3
  42. package/docs/adr/0024-multi-agent-system-architecture.md +10 -10
  43. package/docs/adr/0025-offsite-backup-vendor-digitalocean-spaces.md +1 -1
  44. package/docs/adr/0025-structured-criterion-task-link.md +1 -1
  45. package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +13 -13
  46. package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +14 -14
  47. package/docs/adr/0029-status-page-outage-resilience.md +1 -1
  48. package/docs/adr/0030-builders-hall-live-updates.md +1 -1
  49. package/docs/adr/0031-cloud-dev-environments-for-builders.md +7 -7
  50. package/docs/adr/0032-discord-mirror-of-gds.md +3 -3
  51. package/docs/adr/0033-discord-bot-service-principal.md +4 -4
  52. package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
  53. package/docs/adr/0035-builder-onboarding-three-paths.md +4 -4
  54. package/docs/adr/0036-builders-subdomain-and-ui-reorg.md +1 -1
  55. package/docs/adr/0037-discord-channel-management.md +1 -1
  56. package/docs/adr/0038-chromebook-ttyd-cloudflare-tunnel.md +5 -5
  57. package/docs/adr/0039-setup-first-onboarding-ux.md +6 -6
  58. package/docs/adr/0040-remote-control-default-browser-onramp.md +3 -3
  59. package/docs/adr/0041-temporary-grader-bypass-killswitch.md +2 -2
  60. package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +5 -5
  61. package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +2 -2
  62. package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
  63. package/docs/adr/0044-per-box-live-game-preview.md +1 -1
  64. package/docs/adr/0046-sandbox-first-review-gate.md +1 -1
  65. package/docs/adr/0047-discord-bugs-intake-anti-abuse.md +1 -1
  66. package/docs/adr/0049-split-parallel-safety-contract.md +1 -1
  67. package/docs/adr/0050-device-flow-admission-invite-gated-by-default.md +2 -2
  68. package/docs/adr/0051-full-session-transcript-corpus.md +2 -2
  69. package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +1 -1
  70. package/docs/adr/0053-scoped-dev-box-session.md +1 -1
  71. package/docs/adr/0054-session-token-reward-cost-plus.md +7 -7
  72. package/docs/adr/0055-server-mediated-branch-publish.md +2 -2
  73. package/docs/adr/0056-prod-deploy-script-mirror-and-healthcheck-retry.md +3 -3
  74. package/docs/adr/0057-container-cost-ledger.md +1 -1
  75. package/docs/adr/0057-discord-archon-approval-channels.md +2 -2
  76. package/docs/adr/0058-automatic-gate-surface-gatekeeper.md +6 -6
  77. package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +11 -11
  78. package/docs/adr/0060-gds-retrieval-layer.md +3 -3
  79. package/docs/adr/0061-context-layer-decomposition.md +2 -2
  80. package/docs/adr/0062-medusa-instance-model-decoupling.md +5 -5
  81. package/docs/adr/0063-generated-repo-map-symbol-skeleton.md +2 -2
  82. package/docs/adr/0064-rename-medusa-to-cloud-bongos.md +3 -3
  83. package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +5 -5
  84. package/docs/adr/0066-generated-file-map-sections.md +2 -2
  85. package/docs/adr/0067-session-start-fast-forward-pull.md +1 -1
  86. package/docs/adr/0068-shared-art-key-for-newcomers.md +1 -1
  87. package/docs/adr/0069-hall-live-channel-sse-decouple.md +3 -3
  88. package/docs/adr/0070-deterministic-session-card-delivery.md +1 -1
  89. package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +2 -2
  90. package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +2 -2
  91. package/docs/adr/0072-dev-box-code-staleness-visibility.md +2 -2
  92. package/docs/adr/0073-builder-needs-signal-and-byok-gemini-key.md +3 -3
  93. package/docs/adr/0073-secrets-scan-exclude-uri-detector.md +2 -2
  94. package/docs/adr/0076-in-session-deterministic-compaction.md +2 -2
  95. package/docs/adr/0077-content-addressed-llm-cache.md +2 -2
  96. package/docs/adr/0078-deterministic-first-cron.md +3 -3
  97. package/docs/adr/0079-value-gated-autonomous-engagement.md +2 -2
  98. package/docs/adr/0080-llm-cache-api-transport-and-trust-model.md +3 -3
  99. package/docs/adr/0081-tool-agnostic-design-layer.md +3 -3
  100. package/docs/adr/0082-server-side-merge-conflict-auto-resolution.md +2 -2
  101. package/docs/adr/0083-modular-architecture-strangler-build-plan.md +3 -3
  102. package/docs/adr/0085-remove-default-per-builder-spend-cap.md +2 -2
  103. package/docs/adr/0086-goal-scoped-work-hierarchy.md +15 -15
  104. package/docs/adr/0087-bongos-app-architecture-and-handoff-contract.md +5 -5
  105. package/docs/adr/0087-compete-on-governance-not-tooling-cursor.md +1 -1
  106. package/docs/adr/0088-consistent-character-generative-animation.md +5 -5
  107. package/docs/adr/0089-modular-multi-model-adversarial-grader.md +15 -15
  108. package/docs/adr/0090-metic-task-authoring.md +8 -8
  109. package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +3 -3
  110. package/docs/adr/0092-hosting-module-and-on-prem-target.md +11 -11
  111. package/docs/adr/0093-tranche-2-core-carve-sequence.md +5 -5
  112. package/docs/adr/0094-repo-atlas-self-drawing-codebase-map.md +1 -1
  113. package/docs/adr/0095-borrowed-memory-and-retrieval-concepts.md +6 -6
  114. package/docs/adr/0095-cross-agent-context-management.md +5 -5
  115. package/docs/adr/0096-require-reward-before-workable.md +5 -5
  116. package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +2 -2
  117. package/docs/adr/0097-retroactive-reward-backfill.md +4 -4
  118. package/docs/adr/0098-public-mirror-publish-manifest.md +3 -3
  119. package/docs/adr/0099-delayed-redacted-mirror-export.md +1 -1
  120. package/docs/adr/0100-two-repo-instance-model.md +6 -6
  121. package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +9 -9
  122. package/docs/adr/0102-finish-bounding-the-kernel.md +1 -1
  123. package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +6 -6
  124. package/docs/adr/0103-gdsv4-bongos-consolidation.md +3 -3
  125. package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +3 -3
  126. package/docs/adr/0105-instance-seed-migrations-out-of-core.md +2 -2
  127. package/docs/adr/0106-goal-owner-manager-authority.md +2 -2
  128. package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +6 -6
  129. package/docs/adr/0108-instance-composition-configurable-root.md +6 -6
  130. package/docs/adr/0109-self-describing-openapi-and-hosted-docs.md +3 -3
  131. package/docs/adr/0110-untrack-whole-file-generated-nav-docs.md +1 -1
  132. package/docs/adr/0111-instance-hosting-provisioning-module.md +18 -18
  133. package/docs/adr/0112-private-goals-invitations-join-requests.md +7 -7
  134. package/docs/adr/0113-sandbox-studio-descope-superseded-by-0044-0052.md +1 -1
  135. package/docs/adr/0114-instance-docs-discovery-contract.md +4 -4
  136. package/docs/adr/0115-scheduled-routines-portability.md +1 -1
  137. package/docs/adr/0117-api-versioning-and-deprecation-policy.md +1 -1
  138. package/docs/adr/0118-api-contract-source-of-truth-code-first.md +1 -1
  139. package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +2 -2
  140. package/docs/adr/0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md +1 -1
  141. package/docs/adr/0122-onboarding-diagram-renders-deploy-rendered-off-git.md +3 -3
  142. package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +1 -1
  143. package/docs/adr/0124-typed-api-response-bodies.md +2 -2
  144. package/docs/adr/0125-control-plane-scaffolds-standalone-instance-repo.md +2 -2
  145. package/docs/adr/0126-dedicated-cloudbongos-control-plane-droplet.md +3 -3
  146. package/docs/adr/0127-cross-box-fleet-observability.md +1 -1
  147. package/docs/adr/0128-provisioning-runner-privilege-and-db-auth-model.md +3 -3
  148. package/docs/adr/0129-cloudbongos-control-plane-pull-deploy.md +4 -4
  149. package/docs/adr/0130-provision-remote-exec-over-ssh.md +4 -4
  150. package/docs/adr/0131-rank-scoped-skill-visibility.md +2 -2
  151. package/docs/adr/0132-cohost-fleet-control-plane-migration.md +2 -2
  152. package/docs/adr/0133-github-app-manifest-onboarding-signin.md +1 -1
  153. package/docs/adr/0134-private-first-npm-distribution.md +3 -3
  154. package/docs/adr/0135-module-upstream-submission-interim-queue.md +1 -1
  155. package/docs/adr/0136-update-channel-subscription-policy.md +2 -2
  156. package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +6 -6
  157. package/docs/adr/0138-bundled-module-deps-declared-per-module.md +4 -4
  158. package/docs/adr/0142-project-context-is-prose-only.md +2 -2
  159. package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +2 -2
  160. package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +2 -2
  161. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +2 -2
  162. package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +4 -4
  163. package/docs/adr/0147-standalone-live-docs-regen.md +1 -1
  164. package/docs/adr/0149-bongos-dev-local-launcher.md +1 -1
  165. package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +3 -3
  166. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +20 -20
  167. package/docs/adr/0152-landing-gate-served-by-the-core-app.md +2 -2
  168. package/docs/adr/0152-metic-task-abandonment.md +7 -7
  169. package/docs/adr/0153-sequential-chain-runner.md +1 -1
  170. package/docs/adr/0154-goal-owner-criterion-authoring.md +7 -7
  171. package/docs/adr/0155-adopt-private-repo-widen-oauth-scope.md +2 -2
  172. package/docs/adr/0156-user-set-display-name-wins.md +1 -1
  173. package/docs/adr/0157-archon-is-rank-and-identity-only.md +10 -10
  174. package/docs/adr/0158-choke-point-verdict-taxonomy.md +1 -1
  175. package/docs/adr/0159-merge-gate-reconciliation.md +1 -1
  176. package/docs/adr/0160-main-audit-repo-verifiable-caller.md +1 -1
  177. package/docs/adr/0161-publish-on-merge.md +2 -2
  178. package/docs/adr/0163-legacy-display-name-resolution.md +1 -1
  179. package/docs/adr/0165-instances-hookless-by-default.md +1 -1
  180. package/docs/adr/0166-module-maintenance-and-deprecation-policy.md +3 -3
  181. package/docs/adr/0167-gate-trust-link-agpl-default.md +2 -2
  182. package/docs/adr/0167-module-catalog-source-vs-provenance.md +3 -3
  183. package/docs/adr/0169-bongos-serve-server-bring-up.md +1 -1
  184. package/docs/adr/0171-profile-rollup-join-and-consent.md +1 -1
  185. package/docs/adr/0172-editable-rank-roles-substrate-axis.md +2 -2
  186. package/docs/adr/0172-per-craft-compensation-ideator-credit-lane.md +4 -4
  187. package/docs/adr/0173-legacy-multi-ship-shortfall-not-backfilled.md +1 -1
  188. package/docs/adr/0174-government-terminology-ranks-not-roles.md +1 -1
  189. package/docs/adr/0177-goal-membership-kind.md +3 -3
  190. package/docs/adr/0178-the-copy-desk-flag-and-queue.md +1 -1
  191. package/docs/adr/0179-board-membership-predicate-grammar.md +1 -1
  192. package/docs/adr/0181-teardown-state-gate-and-address-retention.md +1 -1
  193. package/docs/adr/0182-project-visibility-two-axes.md +1 -1
  194. package/docs/adr/0183-criteria-close-themselves.md +1 -1
  195. package/docs/adr/0184-ship-requires-an-assurance.md +1 -1
  196. package/docs/adr/0185-spark-handoff-credit-split.md +1 -1
  197. package/docs/adr/0186-per-project-idea-templates.md +1 -1
  198. package/docs/adr/0187-collab-four-decisions.md +1 -1
  199. package/docs/adr/0187-oauth-handshake-cookie-lifetime.md +1 -1
  200. package/docs/adr/0188-patrol-runs-on-ci-not-the-server.md +2 -2
  201. package/docs/adr/0189-baseline-floors-and-the-freeze-detector.md +2 -2
  202. package/docs/adr/0191-majority-and-the-author-who-may-not-decide-alone.md +1 -1
  203. package/docs/adr/0192-a-category-orients-and-authorises-nothing.md +2 -2
  204. package/docs/adr/0193-pause-task-scoped-box-slices.md +1 -1
  205. package/docs/adr/0195-adr-numbers-are-checked-like-migration-numbers.md +2 -2
  206. package/docs/adr/0196-chrome-world-token-contract-and-the-landing-sheets.md +2 -2
  207. package/docs/adr/0197-the-ui-design-module.md +1 -1
  208. package/docs/adr/0198-third-party-skill-vendoring-policy.md +1 -1
  209. package/docs/adr/0203-doorway-changelog-out-of-module-api.md +2 -2
  210. package/docs/adr/0204-the-apex-is-dark-only.md +2 -2
  211. package/docs/adr/0205-federated-checkin-needs-the-hubs-own-signin-witness.md +1 -1
  212. package/docs/adr/0206-own-scoped-session-revocation-no-absolute-ceiling.md +1 -1
  213. package/docs/adr/0207-connections-implementation-departures-and-honest-limits.md +1 -1
  214. package/docs/adr/0208-application-lifecycle-the-handle-waits-and-the-vouch-has-no-minter.md +2 -2
  215. package/docs/adr/0209-one-budget-for-the-account-existence-oracle.md +1 -1
  216. package/docs/adr/0210-scouting-graduates-on-an-instance-asserted-owner-claim.md +1 -1
  217. package/docs/adr/0212-connections-on-the-profile-surface-and-addressing.md +1 -1
  218. package/docs/adr/0213-platform-terms-acceptance-is-the-hubs-own-witness.md +2 -2
  219. package/docs/adr/0215-the-recruiter-sliver-is-a-ceiling-not-a-step.md +6 -6
  220. package/docs/adr/0216-a-fake-pool-interpreter-must-fail-loudly-not-silently-ignore.md +2 -2
  221. package/docs/adr/0217-rank-gates-inviting-not-viewing.md +1 -1
  222. package/docs/adr/0218-the-apex-root-web-surface.md +2 -2
  223. package/docs/adr/0219-a-look-is-a-branding-pack-the-style-library.md +1 -1
  224. package/docs/adr/0220-an-in-house-rebuilt-skill-is-a-first-party-skill.md +1 -1
  225. package/docs/adr/0221-the-impeccable-method-is-one-skill-with-sub-command-playbooks.md +2 -2
  226. package/docs/adr/0222-the-image-family-under-the-hero-only-rule.md +2 -2
  227. package/docs/adr/0223-the-detector-is-two-tiers-in-the-kit.md +3 -3
  228. package/docs/adr/0224-module-skills-reach-the-core-checkout-as-untracked-self-ignored-copies.md +1 -1
  229. package/docs/adr/0225-privacy-suppresses-cross-project-surfaces-only.md +1 -1
  230. package/docs/adr/0226-one-stale-timer-reporting-degrades-enforcement-refuses.md +1 -1
  231. package/docs/adr/0227-a-task-network-map-is-refused-the-goal-graph-is-the-map.md +1 -1
  232. package/docs/adr/0230-a-look-skill-is-a-composition-grammar-the-palette-is-the-pack.md +1 -1
  233. package/docs/adr/0231-the-style-session-an-owner-authors-a-look-inside-the-contract.md +2 -2
  234. package/docs/adr/0232-rot-is-derived-not-swept-and-water-is-the-only-new-verb.md +2 -2
  235. package/docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md +2 -2
  236. package/docs/adr/0234-idea-routing-capture-time-promotion-landing-matrix-homeless-inbox.md +2 -2
  237. package/docs/adr/0235-a-fix-category-is-kindbug-under-a-goal-no-column-required.md +11 -11
  238. package/docs/adr/0236-many-ideas-one-task-the-lineage-key-is-the-pair.md +5 -5
  239. package/docs/adr/0237-a-starter-bundle-is-a-preset-over-the-always-on-core-keyed-by-declared-type.md +5 -5
  240. package/docs/adr/0238-the-join-door-is-a-provisioning-settings-policy-key.md +1 -1
  241. package/docs/adr/0239-a-recommendation-is-a-table-that-carries-its-own-reason.md +4 -4
  242. package/docs/adr/0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md +4 -4
  243. package/docs/adr/0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md +1 -1
  244. package/docs/adr/0242-a-cascade-is-a-declaration-table-on-the-event-that-already-exists.md +2 -2
  245. package/docs/adr/0243-a-bundle-adjustment-belongs-to-the-bundle-not-the-advice-about-it.md +4 -4
  246. package/docs/adr/0244-a-catalog-search-is-a-view-over-the-rows-never-an-edit-to-the-selection.md +3 -3
  247. package/docs/adr/0245-the-way-back-into-an-optional-step-is-that-step-s-own-answer-route.md +7 -7
  248. package/docs/adr/0246-catalog-membership-is-a-projection-not-a-federation-side-effect.md +1 -1
  249. package/docs/adr/0247-the-join-door-is-one-composed-answer-and-dark-matter-has-no-live-verdict-yet.md +2 -2
  250. package/docs/adr/0248-a-hub-project-s-owner-is-the-provisioning-row-carried-on-a-port.md +3 -3
  251. package/docs/adr/0249-the-invite-step-is-the-done-panel-s-first-act-not-a-rail-step.md +3 -3
  252. package/docs/adr/0250-strict-versioning-the-version-boundary-is-the-scope-gate.md +6 -6
  253. package/docs/adr/0251-a-suggestion-of-who-to-invite-is-a-recruiting-surface-and-owes-the-opt-out.md +5 -5
  254. package/docs/adr/0252-the-self-hosted-visibility-report-is-a-pull-and-auto-appear-buys-a-retraction-lever.md +19 -19
  255. package/docs/adr/0253-the-takedown-lever-has-two-writers-and-provenance-is-a-column.md +2 -2
  256. package/docs/adr/0254-a-relayed-link-is-contained-downward-or-it-is-not-published.md +6 -6
  257. package/docs/adr/0255-a-public-lists-ordering-is-part-of-its-payload.md +2 -2
  258. package/docs/adr/0256-the-visibility-ceiling-is-the-sign-in-gate-too-and-the-reader-moves-to-the-composer.md +3 -3
  259. package/docs/adr/0257-auth-resolves-before-the-hall-mounts-anything.md +1 -1
  260. package/docs/adr/0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md +1 -1
  261. package/docs/adr/0259-a-projects-departure-from-the-public-list-is-public.md +7 -7
  262. package/docs/adr/0260-the-application-is-the-consent-and-the-echo-is-the-gate.md +4 -4
  263. package/docs/adr/0261-a-preselect-always-carries-a-reason-the-bundle-summary-is-the-floor.md +1 -1
  264. package/docs/adr/0262-a-bug-never-lands-in-the-inbox.md +1 -1
  265. package/docs/adr/0263-how-a-version-closes.md +3 -3
  266. package/docs/adr/0264-the-ten-working-areas.md +3 -3
  267. package/docs/adr/0265-work-with-no-goal-and-the-order-the-cut-needs.md +5 -5
  268. package/docs/adr/0266-the-board-room-is-its-own-surface-reachable-by-whoever-may-vote.md +2 -2
  269. package/docs/adr/0267-unanimity-and-the-revise-and-re-sit-loop.md +1 -1
  270. package/docs/adr/0268-the-constitution-comes-from-two-roots.md +3 -3
  271. package/docs/adr/0269-the-cli-session-store-is-host-keyed-at-a-fixed-anchor.md +1 -1
  272. package/docs/adr/0270-rank-floor-derives-without-a-prediction.md +2 -2
  273. package/docs/adr/0271-the-ideator-surface-is-two-halves.md +3 -3
  274. package/docs/adr/0272-a-craft-is-an-identity-not-a-toolchain.md +4 -4
  275. package/docs/adr/0273-hall-nav-can-gate-on-a-permission.md +1 -1
  276. package/docs/adr/0274-one-kernel-three-role-packs.md +1 -1
  277. package/docs/adr/0275-one-source-for-a-written-role-responsibility.md +1 -1
  278. package/docs/adr/0276-the-skill-listing-budget-cannot-hold-every-trigger.md +1 -1
  279. package/docs/adr/0277-a-box-is-in-use-only-while-a-human-is-attached.md +1 -1
  280. package/docs/adr/0278-a-gated-project-still-takes-applications.md +2 -2
  281. package/docs/adr/0279-an-upgrade-is-proven-by-the-served-version-not-the-health-check.md +1 -1
  282. package/docs/adr/0280-the-unattended-lane-may-drive-a-co-tenant-because-the-roster-is-the-decision.md +1 -1
  283. package/docs/adr/0281-an-instance-identity-is-its-own-unix-account-and-pg-role.md +2 -2
  284. package/docs/adr/README.md +294 -294
  285. package/docs/api/openapi.json +72 -5
  286. package/docs/api-reference.md +8 -2
  287. package/docs/architecture.md +22 -22
  288. package/docs/branding-contract.md +5 -5
  289. package/docs/canonical-permissions.md +22 -22
  290. package/docs/copy-inventory.md +15 -13
  291. package/docs/copy-registry.json +35 -17
  292. package/docs/design/hall-direction-v2.md +3 -3
  293. package/docs/design/landing-page-direction-v2.md +1 -1
  294. package/docs/design/projects-hub-direction-v2.md +1 -1
  295. package/docs/file-map.md +3 -1
  296. package/docs/module-api-changelog.md +36 -1
  297. package/docs/modules-contract.md +25 -25
  298. package/docs/onboarding/browser-terminal-guide.md +1 -1
  299. package/docs/onboarding/diagrams/README.md +3 -3
  300. package/docs/onboarding/drachmae-and-karma.md +13 -13
  301. package/docs/onboarding/primer.md +7 -7
  302. package/docs/packs/artist.md +1 -1
  303. package/docs/packs/ideator.md +1 -1
  304. package/docs/project-context.template.md +3 -3
  305. package/docs/recipes/builder-box-lifecycle.md +5 -5
  306. package/docs/recipes/claude-design-loop.md +1 -1
  307. package/docs/recipes/core-release-pipeline.md +55 -14
  308. package/docs/recipes/figma-design-loop.md +1 -1
  309. package/docs/recipes/gds-db-backup.md +2 -2
  310. package/docs/recipes/instance-deploy.md +2 -2
  311. package/docs/recipes/instance-service-restart.md +3 -3
  312. package/docs/recipes/local-dev.md +2 -2
  313. package/docs/recipes/managed-settings-remote-control.md +3 -3
  314. package/docs/recipes/memory-backup.md +4 -4
  315. package/docs/recipes/memory-sync-conflicts.md +2 -2
  316. package/docs/recipes/multi-builder-merge.md +2 -2
  317. package/docs/recipes/ops-gotchas.md +4 -4
  318. package/docs/recipes/overnight-builder.md +1 -1
  319. package/docs/recipes/packaging-the-core.md +6 -6
  320. package/docs/recipes/private-npm-distribution.md +5 -5
  321. package/docs/recipes/search-index-ingestion.md +3 -3
  322. package/docs/recipes/self-host.md +5 -5
  323. package/docs/recipes/server-bring-up.md +1 -1
  324. package/docs/recipes/ship-and-regrade.md +2 -2
  325. package/docs/recipes/standalone-live-docs.md +1 -1
  326. package/docs/recipes/ui-look-before-you-ship.md +1 -1
  327. package/docs/recipes/upgrading-the-core.md +2 -2
  328. package/docs/recipes/work-on-a-task-from-your-dev-box.md +6 -6
  329. package/docs/research/cursor-competitive-analysis.md +3 -3
  330. package/docs/research/llm-model-agnostic-and-cheaper-grader.md +7 -7
  331. package/migrations/core_240_artist_gate_indexes.sql +48 -0
  332. package/migrations/core_240_goal_members_drop_legacy_role.sql +79 -0
  333. package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
  334. package/modules/hall-ui/public/watch.css +6 -0
  335. package/modules/hall-ui/public/watch.js +52 -6
  336. package/modules/lifecycle/cascade.js +37 -9
  337. package/modules/lifecycle/db-claims.js +5 -2
  338. package/modules/lifecycle/db-goals.js +4 -3
  339. package/modules/lifecycle/db-overrides.js +117 -4
  340. package/modules/lifecycle/db-tasks.js +63 -0
  341. package/modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql +12 -4
  342. package/modules/lifecycle/module.json +2 -1
  343. package/modules/lifecycle/pg-constraint-fail.js +49 -0
  344. package/modules/lifecycle/routes/artist-gate.js +101 -0
  345. package/modules/lifecycle/routes/claims.js +13 -1
  346. package/modules/lifecycle/routes/lifecycle.js +6 -0
  347. package/modules/lifecycle/routes/task-write-routes.js +7 -0
  348. package/modules/lifecycle/routes/tasks.js +24 -0
  349. package/modules/lifecycle/task-classifier.js +10 -1
  350. package/modules/lifecycle/tests/cascade.mjs +43 -0
  351. package/modules/provisioning/provisioning.js +79 -3
  352. package/modules/provisioning/routes/provisioning.js +6 -1
  353. package/modules/provisioning/tests/provisioning.mjs +8 -8
  354. package/modules/security/secret-scrub.js +40 -4
  355. package/modules/status-ui/public/README.md +1 -1
  356. package/modules/ui-design/docs/design-contract.md +2 -2
  357. package/modules/ui-design/kit/README.md +1 -1
  358. package/modules/ui-design/kit/serve.js +1 -0
  359. package/modules/ui-design/skills/README.md +6 -6
  360. package/modules/ui-design/skills/brandkit/SKILL.md +1 -1
  361. package/modules/ui-design/skills/design-taste-frontend/SKILL.md +1 -1
  362. package/modules/ui-design/skills/design-taste-frontend-v1/SKILL.md +1 -1
  363. package/modules/ui-design/skills/gpt-taste/SKILL.md +1 -1
  364. package/modules/ui-design/skills/high-end-visual-design/SKILL.md +1 -1
  365. package/modules/ui-design/skills/image-to-code/SKILL.md +1 -1
  366. package/modules/ui-design/skills/imagegen-frontend-mobile/SKILL.md +1 -1
  367. package/modules/ui-design/skills/imagegen-frontend-web/SKILL.md +1 -1
  368. package/modules/ui-design/skills/impeccable/SKILL.md +1 -1
  369. package/modules/ui-design/skills/industrial-brutalist-ui/SKILL.md +1 -1
  370. package/modules/ui-design/skills/minimalist-ui/SKILL.md +1 -1
  371. package/modules/ui-design/skills/redesign-existing-projects/SKILL.md +1 -1
  372. package/modules/ui-design/skills/stitch-design-taste/SKILL.md +1 -1
  373. package/modules/ui-design/skills/style/SKILL.md +2 -2
  374. package/modules/ui-design/styles/README.md +3 -3
  375. package/modules/ui-design/styles/blueprint/DESIGN.md +1 -1
  376. package/modules/ui-design/styles/chrome-world/DESIGN.md +1 -1
  377. package/package-lock.json +2 -2
  378. package/package.json +1 -1
  379. package/scripts/gds/audit-rank-roles.js +172 -0
  380. package/scripts/gds/backfill-task-rewards.js +9 -4
  381. package/scripts/gds/build-cli-package.js +1 -0
  382. package/scripts/gds/claim.js +94 -9
  383. package/scripts/gds/fitness.js +8 -8
  384. package/scripts/gds/go-live.js +139 -5
  385. package/scripts/gds/land-watch.js +13 -5
  386. package/scripts/gds/mirror-redact.js +13 -2
  387. package/scripts/gds/package-core.js +11 -3
  388. package/scripts/gds/release.js +55 -0
  389. package/scripts/gds/ship-land.js +10 -2
  390. package/scripts/gds/ship-regen.js +41 -8
  391. package/scripts/gds/start.js +7 -4
  392. package/scripts/gds/worktree-claim-guard.js +27 -1
  393. package/scripts/public-mirror-export.js +17 -4
  394. package/src/bongos/api-errors.js +9 -0
  395. package/src/bongos/artist-gate.js +204 -0
  396. package/src/branding.js +15 -0
  397. package/src/module-api.js +34 -1
  398. package/tests/api_path_404.mjs +28 -0
  399. package/tests/artist_gate.mjs +234 -0
  400. package/tests/artist_gate_release.mjs +283 -0
  401. package/tests/artist_gate_route.mjs +174 -0
  402. package/tests/box_scope_predicate.mjs +117 -0
  403. package/tests/claim_error_surface.mjs +163 -1
  404. package/tests/cli_exit_no_abort.mjs +256 -3
  405. package/tests/credit_log_single_writer.mjs +94 -0
  406. package/tests/currency_label.mjs +11 -4
  407. package/tests/go_live.mjs +144 -0
  408. package/tests/helpers.mjs +8 -0
  409. package/tests/main_worktree_busy_reason.mjs +167 -0
  410. package/tests/membership_kind_lockstep.mjs +88 -0
  411. package/tests/mirror_redact.mjs +14 -0
  412. package/tests/module_api.mjs +6 -0
  413. package/tests/provision_settings_apply.mjs +13 -5
  414. package/tests/provisioning_settings.mjs +11 -7
  415. package/tests/provisioning_settings_apply.mjs +4 -4
  416. package/tests/provisioning_settings_env.mjs +59 -7
  417. package/tests/public_mirror_export.mjs +12 -0
  418. package/tests/rank_roles_audit.mjs +114 -0
  419. package/tests/scrubber_corpus.mjs +70 -2
  420. package/tests/start_rebase_warning.mjs +50 -0
  421. package/tests/task_field_bounds_match_db.mjs +140 -0
  422. package/tests/watch_roster_self_row.mjs +289 -0
@@ -0,0 +1,174 @@
1
+ // tests/artist_gate_route.mjs
2
+ //
3
+ // GET /artist-gate — the one read the deploy consults (ADR 0241, task 1003575).
4
+ //
5
+ // The pure verdict logic is pinned in tests/artist_gate.mjs; what is worth pinning
6
+ // HERE is the wiring, and specifically the three things a route can get wrong in a
7
+ // way no pure test would see:
8
+ //
9
+ // • 'off' must not even ASK the board. At `off` no reviews are filed, so a query
10
+ // would be a per-deploy read that can only ever return rows the gate ignores.
11
+ // • the DB read must be handed the CASCADE's own (source, ref) spelling, not a
12
+ // second copy of it — a drifted prefix silently matches nothing, and a gate
13
+ // that finds nothing looks exactly like a gate that is satisfied.
14
+ // • `holds` and `halt` must agree. Two fields describing one fact is two chances
15
+ // to disagree, and the caller trusts whichever it read.
16
+ //
17
+ // DB-free: the data-access fn is stubbed on the cached module object (the
18
+ // provisioning_settings.mjs pattern).
19
+ //
20
+ // Run: node --test tests/artist_gate_route.mjs
21
+
22
+ import { test } from 'node:test';
23
+ import { strict as assert } from 'node:assert';
24
+ import { createRequire } from 'node:module';
25
+ import http from 'node:http';
26
+
27
+ const require = createRequire(import.meta.url);
28
+
29
+ const db = require('../modules/lifecycle/db.js');
30
+ const api = require('../src/module-api.js');
31
+ const gateModule = require('../src/bongos/artist-gate.js');
32
+
33
+ let rows = [];
34
+ let scanTruncated = false;
35
+ let queries = [];
36
+ db.listOpenArtistReviews = async (opts) => { queries.push(opts); return { reviews: rows, truncated: scanTruncated }; };
37
+
38
+ let mode = 'strict';
39
+ let since = null;
40
+ api.requireBuilder = (req, _res, next) => { req.builder = { id: 7, rank: 'thetes' }; next(); };
41
+ // The route must resolve the level through the doorway's ONE reader; stubbing it
42
+ // here is also what proves it does not read branding().project behind its back.
43
+ api.artistGate = { ...gateModule, artistGateMode: () => mode, artistGateSince: () => since };
44
+
45
+ const express = require('express');
46
+ const buildRouter = require('../modules/lifecycle/routes/artist-gate.js');
47
+ const app = express();
48
+ app.use((_req, res, next) => {
49
+ res.fail = (code, opts) => res.status((opts && opts.status) || 400).json({ error: { code } });
50
+ next();
51
+ });
52
+ app.use('/api/bongos', buildRouter());
53
+
54
+ const server = app.listen(0);
55
+ await new Promise((r) => server.once('listening', r));
56
+ const base = `http://127.0.0.1:${server.address().port}/api/bongos`;
57
+
58
+ function get(path) {
59
+ return new Promise((resolve, reject) => {
60
+ http.get(`${base}${path}`, (res) => {
61
+ let d = '';
62
+ res.on('data', (c) => { d += c; });
63
+ res.on('end', () => { let j = null; try { j = JSON.parse(d); } catch { /* non-JSON */ } resolve({ status: res.statusCode, body: j, headers: res.headers, raw: d }); });
64
+ }).on('error', reject);
65
+ });
66
+ }
67
+
68
+ const review = (over = {}) => ({
69
+ id: '9001', title: 'Artist review: the copy and visuals that shipped in task 7001',
70
+ status: 'backlog', source: 'cascade', source_ref: 'cascade-artist-review-on-ship-7001',
71
+ created_at: '2026-09-12T00:00:00.000Z', merged_into_task_id: null, ...over,
72
+ });
73
+
74
+ function reset({ level = 'strict', cutoff = null, open = [], truncated = false } = {}) {
75
+ mode = level; since = cutoff; rows = open; scanTruncated = truncated; queries = [];
76
+ }
77
+
78
+ test("at 'off' the board is never asked — nothing is filed, so there is nothing to read", async () => {
79
+ reset({ level: 'off', open: [review()] });
80
+ const r = await get('/artist-gate');
81
+ assert.equal(r.status, 200, r.raw);
82
+ assert.equal(r.body.mode, 'off');
83
+ assert.equal(r.body.holds, false);
84
+ assert.deepEqual(r.body.reviews, []);
85
+ assert.equal(queries.length, 0, 'a per-deploy query whose rows are ignored is a query that should not run');
86
+ });
87
+
88
+ test('the DB read is handed the cascade\'s OWN source and ref prefix, never a second copy', async () => {
89
+ reset({ open: [] });
90
+ await get('/artist-gate');
91
+ assert.equal(queries.length, 1);
92
+ assert.equal(queries[0].source, gateModule.ARTIST_REVIEW_SOURCE);
93
+ assert.equal(queries[0].refPrefix, gateModule.ARTIST_REVIEW_REF_PREFIX);
94
+ assert.deepEqual(queries[0].resolvedStatuses, gateModule.RESOLVED_REVIEW_STATUSES);
95
+ });
96
+
97
+ test("at 'strict' an open review is reported as holding, with the refusal already composed", async () => {
98
+ reset({ open: [review()] });
99
+ const r = await get('/artist-gate');
100
+ assert.equal(r.body.mode, 'strict');
101
+ assert.equal(r.body.holds, true);
102
+ assert.deepEqual(r.body.reviews.map((t) => t.id), ['9001']);
103
+ assert.equal(r.body.halt.code, 'ARTIST_REVIEW_UNRESOLVED');
104
+ assert.match(r.body.halt.detail, /override-request/, 'the caller must not have to invent the way out');
105
+ });
106
+
107
+ test("at 'advisory' the same open review is reported but holds nothing", async () => {
108
+ reset({ level: 'advisory', open: [review()] });
109
+ const r = await get('/artist-gate');
110
+ assert.equal(r.body.holds, false);
111
+ assert.equal(r.body.halt, null);
112
+ assert.deepEqual(r.body.reviews.map((t) => t.id), ['9001'], 'advisory still SHOWS the queue — it just does not stop');
113
+ });
114
+
115
+ test('the cutoff is applied to what the board returned (ADR 0241 §5)', async () => {
116
+ reset({ cutoff: '2026-09-11T00:00:00.000Z', open: [review({ id: '9001', created_at: '2026-09-01T00:00:00.000Z' }), review({ id: '9002' })] });
117
+ const r = await get('/artist-gate');
118
+ assert.deepEqual(r.body.reviews.map((t) => t.id), ['9002'], 'the pre-raise review is not this gate\'s business');
119
+ assert.equal(r.body.since, '2026-09-11T00:00:00.000Z', 'and the cutoff itself is reported, so the answer is checkable');
120
+ });
121
+
122
+ test('`holds` and `halt` can never disagree', async () => {
123
+ for (const open of [[], [review()]]) {
124
+ for (const level of ['off', 'advisory', 'strict']) {
125
+ reset({ level, open });
126
+ const r = await get('/artist-gate');
127
+ assert.equal(r.body.holds, r.body.halt !== null, `${level} / ${open.length} open`);
128
+ }
129
+ }
130
+ });
131
+
132
+ test('the payload carries NO task title — the one untrusted string it could have carried', async () => {
133
+ reset({ open: [review()] });
134
+ const r = await get('/artist-gate');
135
+ assert.deepEqual(Object.keys(r.body.reviews[0]).sort(), ['created_at', 'id']);
136
+ assert.doesNotMatch(r.raw, /Artist review: the copy/, 'builder-written board text never reaches this wire');
137
+ });
138
+
139
+ test('a capped scan reports a FLOOR, not a census — the verdict is unchanged either way', async () => {
140
+ reset({ open: [review({ id: '9001' }), review({ id: '9002' })], truncated: true });
141
+ const r = await get('/artist-gate');
142
+ assert.equal(r.body.truncated, true, 'the caller must never read the list as "and that is all"');
143
+ assert.equal(r.body.holds, true, 'truncation cannot weaken the gate — every row returned is a holder');
144
+ assert.match(r.body.halt.message, /^2\+ unresolved artist reviews/);
145
+ });
146
+
147
+ test('truncation is DROPPED when the cutoff shortened the page — "200+" over three rows is the worse lie', async () => {
148
+ reset({
149
+ cutoff: '2026-09-11T00:00:00.000Z',
150
+ open: [review({ id: '9001', created_at: '2026-09-01T00:00:00.000Z' }), review({ id: '9002' })],
151
+ truncated: true,
152
+ });
153
+ const r = await get('/artist-gate');
154
+ assert.equal(r.body.truncated, false);
155
+ assert.match(r.body.halt.message, /^1 unresolved artist review /);
156
+ });
157
+
158
+ test('the read is never cached — a dismissed review must free the deploy immediately', async () => {
159
+ reset({ open: [review()] });
160
+ const r = await get('/artist-gate');
161
+ assert.equal(r.headers['cache-control'], 'no-store');
162
+ });
163
+
164
+ test('a database failure is a 500 with no internals, never a quiet "nothing holds"', async () => {
165
+ reset({ open: [] });
166
+ db.listOpenArtistReviews = async () => { throw new Error('relation "tasks" does not exist'); };
167
+ const r = await get('/artist-gate');
168
+ assert.equal(r.status, 500);
169
+ assert.equal(r.body.error.code, 'artist_gate_read_failed');
170
+ assert.doesNotMatch(r.raw, /relation/, 'the error text never reaches the wire');
171
+ db.listOpenArtistReviews = async (opts) => { queries.push(opts); return { reviews: rows, truncated: scanTruncated }; };
172
+ });
173
+
174
+ test.after(() => server.close());
@@ -0,0 +1,117 @@
1
+ // tests/box_scope_predicate.mjs
2
+ //
3
+ // task 1002593 — "is this a box-scope refusal?" must have exactly ONE definition,
4
+ // and it must survive the error-envelope shape.
5
+ //
6
+ // THE BUG THIS PINS. A box-scoped session (ADR 0053) may call only the dev-box
7
+ // bootstrap endpoints; everything else answers 403 box_scope. Callers branched on
8
+ // `res.data.error === 'box_scope'`. Then routes moved to the object error envelope
9
+ // (ADR 0116) — `data.error` became `{ code, message, details }` — and every one of
10
+ // those comparisons silently became dead code. Nothing failed; the branch just
11
+ // stopped firing, and builders got the generic 403 path instead of the guided
12
+ // re-auth the branch existed to give them. Task 1002162 fixed it in cli-lib by
13
+ // adding isBoxScope(); scripts/gds/ship-land.js was the last site still deciding
14
+ // for itself, and task 1002593 repointed it.
15
+ //
16
+ // WHY A DRIFT GUARD AND NOT JUST UNIT CASES. The original defect was not a wrong
17
+ // answer — it was a comparison that could never be true. A unit test of the good
18
+ // predicate cannot see a second, dead one somewhere else in the tree; only a scan
19
+ // can. So the last test here is the one that actually protects against a repeat.
20
+ //
21
+ // Run: node tests/box_scope_predicate.mjs
22
+
23
+ import { strict as assert } from 'node:assert';
24
+ import fs from 'node:fs';
25
+ import path from 'node:path';
26
+ import { createRequire } from 'node:module';
27
+ import { fileURLToPath } from 'node:url';
28
+
29
+ const require = createRequire(import.meta.url);
30
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
31
+ const CLI_DIR = path.join(ROOT, 'scripts', 'gds');
32
+ const { isBoxScope } = require(path.join(CLI_DIR, 'cli-lib.js'));
33
+
34
+ let passed = 0;
35
+ let failed = 0;
36
+ function t(name, fn) {
37
+ try { fn(); passed++; console.log(` PASS ${name}`); }
38
+ catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
39
+ }
40
+
41
+ const ENVELOPE = { status: 403, data: { error: { code: 'box_scope', message: 'box-scoped session' } } };
42
+ const LEGACY = { status: 403, data: { error: 'box_scope' } };
43
+
44
+ t('fires for the CURRENT object error envelope — the shape that killed the old comparison', () => {
45
+ assert.equal(isBoxScope(ENVELOPE), true);
46
+ });
47
+
48
+ t('the old hand-rolled comparison is provably dead against that same shape', () => {
49
+ // Not a tautology: this is the regression itself. If someone reintroduces
50
+ // `data.error === 'box_scope'` anywhere, this is what it evaluates to.
51
+ assert.equal(ENVELOPE.data.error === 'box_scope', false,
52
+ 'the envelope shape must NOT satisfy a bare string comparison — that is the whole defect');
53
+ });
54
+
55
+ t('still fires for the LEGACY flat shape, so a pinned older core keeps working', () => {
56
+ assert.equal(isBoxScope(LEGACY), true);
57
+ });
58
+
59
+ t('does not fire on a different code at 403', () => {
60
+ assert.equal(isBoxScope({ status: 403, data: { error: { code: 'rank_too_low_for_task' } } }), false);
61
+ assert.equal(isBoxScope({ status: 403, data: { error: 'forbidden' } }), false);
62
+ });
63
+
64
+ t('does not fire on a non-403, even carrying the code', () => {
65
+ assert.equal(isBoxScope({ status: 200, data: { error: { code: 'box_scope' } } }), false,
66
+ 'status is part of the contract — a 200 mentioning box_scope is not a refusal');
67
+ assert.equal(isBoxScope({ status: 500, data: { error: { code: 'box_scope' } } }), false);
68
+ });
69
+
70
+ t('tolerates missing/!odd bodies instead of throwing', () => {
71
+ for (const bad of [undefined, null, {}, { status: 403 }, { status: 403, data: null }, { status: 403, data: {} }]) {
72
+ assert.equal(isBoxScope(bad), false, `${JSON.stringify(bad)} must be a quiet false, not a throw`);
73
+ }
74
+ assert.equal(isBoxScope({ status: 403, data: { error: null } }), false,
75
+ 'a null error must not be read as an envelope');
76
+ });
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // The drift guard. One definition, and no second opinion anywhere in the CLI.
80
+ // ---------------------------------------------------------------------------
81
+ t('no CLI script decides box-scope for itself — cli-lib owns the predicate', () => {
82
+ const files = fs.readdirSync(CLI_DIR).filter((f) => f.endsWith('.js'));
83
+ // Fail loudly on a scan that found nothing: an empty sweep looks exactly like a
84
+ // clean one, and a guard that silently stops scanning protects nothing.
85
+ assert.ok(files.length > 20, `only ${files.length} scripts scanned — the sweep is not reading the CLI directory`);
86
+
87
+ const offenders = [];
88
+ let sawTheCanary = false;
89
+ for (const f of files) {
90
+ const src = fs.readFileSync(path.join(CLI_DIR, f), 'utf8');
91
+ if (f === 'cli-lib.js') {
92
+ // The one legitimate definition. Confirms the scan can actually see the
93
+ // pattern it is looking for, so a regex that has drifted off the source
94
+ // shape reports as a failure here rather than as a clean sweep everywhere.
95
+ sawTheCanary = /===\s*'box_scope'/.test(src);
96
+ continue;
97
+ }
98
+ // Strip comments: several files DISCUSS the old comparison on purpose.
99
+ const code = src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '');
100
+ for (const m of code.matchAll(/[^\n]*===\s*'box_scope'[^\n]*/g)) {
101
+ offenders.push(`${f}: ${m[0].trim()}`);
102
+ }
103
+ }
104
+
105
+ assert.ok(sawTheCanary, 'cli-lib.js no longer contains the comparison — this guard is looking for the wrong pattern');
106
+ assert.deepEqual(offenders, [],
107
+ `these sites decide box-scope themselves instead of calling isBoxScope(); that is how the comparison died last time:\n ${offenders.join('\n ')}`);
108
+ });
109
+
110
+ t('ship-land.js — the site task 1002593 repointed — uses the shared predicate', () => {
111
+ const src = fs.readFileSync(path.join(CLI_DIR, 'ship-land.js'), 'utf8');
112
+ assert.match(src, /isBoxScope\s*\(/, 'the box-scope branch must call the shared predicate');
113
+ assert.match(src, /require\('\.\/cli-lib'\)/, 'and import it from cli-lib rather than redefining it');
114
+ });
115
+
116
+ console.log(`\nbox_scope_predicate: ${passed} passed, ${failed} failed`);
117
+ process.exit(failed === 0 ? 0 : 1);
@@ -13,8 +13,10 @@
13
13
  import { strict as assert } from 'node:assert';
14
14
  import { createRequire } from 'node:module';
15
15
 
16
+ import { readFileSync } from 'node:fs';
17
+
16
18
  const require = createRequire(import.meta.url);
17
- const { formatClaimFailure, claimErrorInfo, goalMembershipOfferPlan } = require('../scripts/gds/claim.js');
19
+ const { formatClaimFailure, claimErrorInfo, goalMembershipOfferPlan, worktreeDetectionNote } = require('../scripts/gds/claim.js');
18
20
 
19
21
  let passed = 0;
20
22
  let failed = 0;
@@ -29,6 +31,12 @@ const envelope = (code, details = {}, message = code, status = 409) => ({
29
31
  });
30
32
  const text = (r) => r.lines.join('\n');
31
33
 
34
+ // task 1002592 — populated by the guard test below, then checked against the
35
+ // codes the server can actually refuse a claim with. Declared here so the drift
36
+ // guard reads the SAME array the coverage test exercises rather than a second
37
+ // hand-typed copy that could itself drift.
38
+ const CLAIM_REFUSAL_CODES_UNDER_TEST = [];
39
+
32
40
  // The regression guard: no refusal may ever render a raw object.
33
41
  t('never dumps [object Object] for any known code', () => {
34
42
  const codes = [
@@ -37,8 +45,16 @@ t('never dumps [object Object] for any known code', () => {
37
45
  'newcomer_must_ship_first', 'ALREADY_CLAIMED', 'MODULE_CONFLICT',
38
46
  'TOUCHES_CONFLICT', 'TASK_NOT_READY', 'TASK_NOT_FOUND', 'DEPS_NOT_SHIPPED',
39
47
  'OPEN_BLOCKER', 'GOAL_MEMBERSHIP_REQUIRED',
48
+ // task 1002592: REBASE_REQUIRED was missing from this list for its whole
49
+ // life, which is exactly why its rendering gap survived — the guard that
50
+ // looked like it covered every refusal only covered the ones someone
51
+ // remembered to type. The drift guard at the bottom of this file now checks
52
+ // this array against what the server can actually throw.
53
+ 'REBASE_REQUIRED', 'REWARD_REQUIRED', 'BUILDER_INACTIVE', 'INSUFFICIENT_RANK',
54
+ 'XENOS_RESTRICTED', 'rank_too_low_for_task',
40
55
  'some_unmapped_future_code',
41
56
  ];
57
+ CLAIM_REFUSAL_CODES_UNDER_TEST.push(...codes);
42
58
  for (const c of codes) {
43
59
  const out = text(formatClaimFailure(envelope(c, { active_task_id: 5 }, 'msg'), 12));
44
60
  assert.ok(!out.includes('[object Object]'), `${c} produced a raw object dump`);
@@ -191,5 +207,151 @@ t('claimErrorInfo: reads envelope and flat shapes identically', () => {
191
207
  assert.equal(flat.code, 'X'); assert.equal(flat.d.a, 1);
192
208
  });
193
209
 
210
+ // ---------------------------------------------------------------------------
211
+ // task 1002592 — three measured defects in this surface.
212
+ // ---------------------------------------------------------------------------
213
+
214
+ // 1. REBASE_REQUIRED. ADR 0120 part 3's gate refuses EVERY new claim, so it is
215
+ // the refusal a builder is most stuck behind, and it was the only one with no
216
+ // branch: it printed `Claim failed (409) [REBASE_REQUIRED]: REBASE_REQUIRED.`
217
+ // while a fully-formed remediation menu sat unread in the body.
218
+ const REBASE_HINT = 'Your queue is gated: 1 confirmed task(s) await your rebase (task 1003575). Clear it with /strand-fix N (works on any machine), or rebase the branch and re-ship — /merge-mode only if this machine has a GitHub push credential.';
219
+
220
+ t('REBASE_REQUIRED: names the owed tasks and relays the server hint', () => {
221
+ const out = text(formatClaimFailure(envelope('REBASE_REQUIRED', {
222
+ hint: REBASE_HINT,
223
+ rebaseTasks: [{ id: 1003575, title: 'The artist deploy gate', reason: 'strand:unresolvable_conflict:docs/module-api-changelog.md' }],
224
+ }), 1002592));
225
+ assert.match(out, /task 1003575/, 'must name the owed task id');
226
+ assert.match(out, /The artist deploy gate/, 'must name the owed task title');
227
+ assert.match(out, /strand:unresolvable_conflict/, 'must surface why it was flagged');
228
+ assert.match(out, /strand-fix/, 'must relay the hint, which leads with the universally-available exit');
229
+ // The regression proper: the old output was ONLY the bare code echo.
230
+ assert.ok(!/^Claim failed \(409\) \[REBASE_REQUIRED\]: REBASE_REQUIRED\.$/m.test(out),
231
+ 'must not fall through to the bare code echo');
232
+ });
233
+
234
+ t('REBASE_REQUIRED: still guides when the server sends no hint and no task list', () => {
235
+ const out = text(formatClaimFailure(envelope('REBASE_REQUIRED', {}), 5));
236
+ assert.match(out, /gated/i, 'must say the queue is gated');
237
+ assert.match(out, /strand-fix|builder-release/, 'must name a way out even with an empty body');
238
+ });
239
+
240
+ t('REBASE_REQUIRED: server prose is clipped, flattened and capped before it hits the terminal', () => {
241
+ const out = text(formatClaimFailure(envelope('REBASE_REQUIRED', {
242
+ hint: 'H'.repeat(2000),
243
+ rebaseTasks: Array.from({ length: 50 }, (_, i) => ({
244
+ id: 1000 + i,
245
+ title: `T${'i'.repeat(400)}`,
246
+ reason: 'line oneline two\nline three',
247
+ })),
248
+ }), 5));
249
+ assert.ok(!//.test(out), 'escape sequences must never reach the terminal verbatim');
250
+ assert.ok(!/line one.*\n.*line two/.test(out), 'a multi-line reason must be flattened onto its own single line');
251
+ for (const line of out.split('\n')) {
252
+ assert.ok(line.length <= 620, `a rendered line ran to ${line.length} chars — the cap is not holding`);
253
+ }
254
+ const listed = out.split('\n').filter((l) => /^ {2}- task /.test(l)).length;
255
+ assert.ok(listed > 0 && listed <= 20, `expected a bounded task list, got ${listed} rows`);
256
+ });
257
+
258
+ // 2. The fallback floor. Any code the server sends a hint for must print it,
259
+ // so the NEXT unmapped code cannot be mute the way REBASE_REQUIRED was.
260
+ t('fallback: an unmapped code still prints the server hint when one is supplied', () => {
261
+ const out = text(formatClaimFailure(
262
+ envelope('some_future_gate', { hint: 'Do the thing, then retry.' }, 'nope', 409), 3));
263
+ assert.match(out, /some_future_gate/, 'still names the code');
264
+ assert.match(out, /Do the thing, then retry\./, 'and prints the way forward');
265
+ });
266
+
267
+ t('fallback: no hint means no invented guidance', () => {
268
+ const out = text(formatClaimFailure(envelope('some_future_gate', {}, 'nope', 409), 3));
269
+ assert.match(out, /some_future_gate/);
270
+ assert.ok(!/undefined/.test(out), 'must not print a stray undefined when no hint exists');
271
+ });
272
+
273
+ // 3. The original bug report: a failed worktree DETECTION arrives disguised as a
274
+ // claim CONFLICT. The note must fire when null is WRONG and stay silent when
275
+ // null is the correct answer — that distinction is the whole fix.
276
+ t('worktreeDetectionNote: silent when the worktree WAS named (the refusal is genuine)', () => {
277
+ assert.deepEqual(worktreeDetectionNote({ worktree: 'task-42', isMainCheckout: false }, 42), []);
278
+ });
279
+
280
+ t('worktreeDetectionNote: silent in the MAIN CHECKOUT, where a null name is correct', () => {
281
+ assert.deepEqual(worktreeDetectionNote({ worktree: null, isMainCheckout: true }, 42), [],
282
+ 'blaming path detection here would send the reader down exactly the wrong path');
283
+ });
284
+
285
+ t('worktreeDetectionNote: fires on a LINKED worktree with no name, and offers --worktree', () => {
286
+ const out = worktreeDetectionNote({ worktree: null, isMainCheckout: false }, 42).join('\n');
287
+ assert.match(out, /could not name its worktree/i);
288
+ assert.match(out, /linked worktree, so a name was expected/i, 'must say a name was expected here');
289
+ assert.match(out, /--worktree/, 'must print the explicit fallback');
290
+ assert.match(out, /claim\.js 42 --worktree/, 'the fallback must be runnable as printed');
291
+ assert.match(out, /not a claim conflict/i, 'must correct the misreading the report describes');
292
+ });
293
+
294
+ t('worktreeDetectionNote: fires when the tree kind is UNKNOWN, and hedges rather than asserts', () => {
295
+ const out = worktreeDetectionNote({ worktree: null, isMainCheckout: null }, 42).join('\n');
296
+ assert.match(out, /--worktree/);
297
+ assert.match(out, /not recognisable as the main checkout/i);
298
+ assert.ok(!/linked worktree, so a name was expected/i.test(out),
299
+ 'must not claim this IS a linked worktree when the probe could not tell');
300
+ });
301
+
302
+ t('worktreeDetectionNote: a missing ctx is silent, never a crash', () => {
303
+ assert.deepEqual(worktreeDetectionNote(undefined, 42), []);
304
+ assert.deepEqual(worktreeDetectionNote({}, 42), [], 'an empty ctx knows nothing — say nothing');
305
+ });
306
+
307
+ t('session_already_has_active_claim: carries the detection note only when ctx warrants it', () => {
308
+ const e = envelope('session_already_has_active_claim', { active_task_id: 7, active_task_title: 'Other' });
309
+ const withNote = text(formatClaimFailure(e, 42, { worktree: null, isMainCheckout: false }));
310
+ assert.match(withNote, /--worktree/, 'an unnamed linked worktree must get the note');
311
+ const named = text(formatClaimFailure(e, 42, { worktree: 'task-42', isMainCheckout: false }));
312
+ assert.ok(!/--worktree/.test(named), 'a named worktree means the conflict is real — no note');
313
+ const mainCheckout = text(formatClaimFailure(e, 42, { worktree: null, isMainCheckout: true }));
314
+ assert.ok(!/--worktree/.test(mainCheckout), 'the main checkout must stay silent');
315
+ const noCtx = text(formatClaimFailure(e, 42));
316
+ assert.ok(!/--worktree/.test(noCtx), 'no ctx (every pre-existing caller) must behave as before');
317
+ assert.match(noCtx, /#7/, 'and must still render the original guidance');
318
+ });
319
+
320
+ // ---------------------------------------------------------------------------
321
+ // The drift guard. REBASE_REQUIRED was thrown by the server and absent from the
322
+ // coverage list above for its whole life, which is how a mute refusal survived a
323
+ // test that looked like it covered every refusal. Derive the truth from the
324
+ // server source instead of trusting a hand-typed array.
325
+ //
326
+ // A static scan fails SILENT — finding nothing looks identical to finding no
327
+ // problem — so this asserts the extraction actually worked before it asserts
328
+ // anything about the result.
329
+ // ---------------------------------------------------------------------------
330
+ t('drift guard: every code claimTask can throw is covered above (or consciously excluded)', () => {
331
+ const src = readFileSync(new URL('../modules/lifecycle/db-claims.js', import.meta.url), 'utf8');
332
+ const start = src.indexOf('async function claimTask');
333
+ assert.ok(start !== -1, 'could not find claimTask in db-claims.js — the guard is reading the wrong file');
334
+ const thrown = new Set(
335
+ [...src.slice(start).matchAll(/throw \{\s*code: '([A-Za-z_]+)'/g)].map((m) => m[1])
336
+ );
337
+
338
+ // Fail loudly on a scan that found nothing, and on one that lost its canary.
339
+ assert.ok(thrown.size >= 8, `extracted only ${thrown.size} codes — the regex has drifted off the source shape`);
340
+ assert.ok(thrown.has('REBASE_REQUIRED'), 'canary missing: the extraction is not seeing real throws');
341
+ assert.ok(thrown.has('TASK_NOT_FOUND'), 'canary missing: the extraction is not seeing real throws');
342
+
343
+ // Codes that exist on the claim path but can never reach the single-claim CLI.
344
+ // Listed explicitly so adding a server code forces a decision rather than
345
+ // silently widening this exemption.
346
+ const NOT_A_SINGLE_CLAIM_REFUSAL = new Set([
347
+ 'BATCH_EMPTY', 'BATCH_DUPLICATE_IDS', 'BATCH_FAILED', // the hall's bulk-claim route
348
+ ]);
349
+
350
+ const covered = new Set(CLAIM_REFUSAL_CODES_UNDER_TEST);
351
+ const missing = [...thrown].filter((c) => !covered.has(c) && !NOT_A_SINGLE_CLAIM_REFUSAL.has(c));
352
+ assert.deepEqual(missing, [],
353
+ `these server codes are refusable but untested here — add them to the codes[] array above (or to NOT_A_SINGLE_CLAIM_REFUSAL with a reason): ${missing.join(', ')}`);
354
+ });
355
+
194
356
  console.log(`\nclaim_error_surface: ${passed} passed, ${failed} failed`);
195
357
  process.exit(failed === 0 ? 0 : 1);