@bongos/core 1.19.715 → 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 (414) hide show
  1. package/.bongos-core.json +472 -407
  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/design/hall-direction-v2.md +3 -3
  291. package/docs/design/landing-page-direction-v2.md +1 -1
  292. package/docs/design/projects-hub-direction-v2.md +1 -1
  293. package/docs/file-map.md +3 -1
  294. package/docs/module-api-changelog.md +34 -1
  295. package/docs/modules-contract.md +25 -25
  296. package/docs/onboarding/browser-terminal-guide.md +1 -1
  297. package/docs/onboarding/diagrams/README.md +3 -3
  298. package/docs/onboarding/drachmae-and-karma.md +13 -13
  299. package/docs/onboarding/primer.md +7 -7
  300. package/docs/packs/artist.md +1 -1
  301. package/docs/packs/ideator.md +1 -1
  302. package/docs/project-context.template.md +3 -3
  303. package/docs/recipes/builder-box-lifecycle.md +5 -5
  304. package/docs/recipes/claude-design-loop.md +1 -1
  305. package/docs/recipes/core-release-pipeline.md +55 -14
  306. package/docs/recipes/figma-design-loop.md +1 -1
  307. package/docs/recipes/gds-db-backup.md +2 -2
  308. package/docs/recipes/instance-deploy.md +2 -2
  309. package/docs/recipes/instance-service-restart.md +3 -3
  310. package/docs/recipes/local-dev.md +2 -2
  311. package/docs/recipes/managed-settings-remote-control.md +3 -3
  312. package/docs/recipes/memory-backup.md +4 -4
  313. package/docs/recipes/memory-sync-conflicts.md +2 -2
  314. package/docs/recipes/multi-builder-merge.md +2 -2
  315. package/docs/recipes/ops-gotchas.md +4 -4
  316. package/docs/recipes/overnight-builder.md +1 -1
  317. package/docs/recipes/packaging-the-core.md +6 -6
  318. package/docs/recipes/private-npm-distribution.md +5 -5
  319. package/docs/recipes/search-index-ingestion.md +3 -3
  320. package/docs/recipes/self-host.md +5 -5
  321. package/docs/recipes/server-bring-up.md +1 -1
  322. package/docs/recipes/ship-and-regrade.md +2 -2
  323. package/docs/recipes/standalone-live-docs.md +1 -1
  324. package/docs/recipes/ui-look-before-you-ship.md +1 -1
  325. package/docs/recipes/upgrading-the-core.md +2 -2
  326. package/docs/recipes/work-on-a-task-from-your-dev-box.md +6 -6
  327. package/docs/research/cursor-competitive-analysis.md +3 -3
  328. package/docs/research/llm-model-agnostic-and-cheaper-grader.md +7 -7
  329. package/migrations/core_240_artist_gate_indexes.sql +48 -0
  330. package/migrations/core_240_goal_members_drop_legacy_role.sql +79 -0
  331. package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
  332. package/modules/lifecycle/cascade.js +37 -9
  333. package/modules/lifecycle/db-claims.js +5 -2
  334. package/modules/lifecycle/db-goals.js +4 -3
  335. package/modules/lifecycle/db-overrides.js +117 -4
  336. package/modules/lifecycle/db-tasks.js +63 -0
  337. package/modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql +12 -4
  338. package/modules/lifecycle/module.json +2 -1
  339. package/modules/lifecycle/pg-constraint-fail.js +49 -0
  340. package/modules/lifecycle/routes/artist-gate.js +101 -0
  341. package/modules/lifecycle/routes/claims.js +13 -1
  342. package/modules/lifecycle/routes/lifecycle.js +6 -0
  343. package/modules/lifecycle/routes/task-write-routes.js +7 -0
  344. package/modules/lifecycle/routes/tasks.js +24 -0
  345. package/modules/lifecycle/task-classifier.js +10 -1
  346. package/modules/lifecycle/tests/cascade.mjs +43 -0
  347. package/modules/provisioning/provisioning.js +79 -3
  348. package/modules/provisioning/routes/provisioning.js +6 -1
  349. package/modules/provisioning/tests/provisioning.mjs +8 -8
  350. package/modules/security/secret-scrub.js +40 -4
  351. package/modules/status-ui/public/README.md +1 -1
  352. package/modules/ui-design/docs/design-contract.md +2 -2
  353. package/modules/ui-design/kit/README.md +1 -1
  354. package/modules/ui-design/kit/serve.js +1 -0
  355. package/modules/ui-design/skills/README.md +6 -6
  356. package/modules/ui-design/skills/brandkit/SKILL.md +1 -1
  357. package/modules/ui-design/skills/design-taste-frontend/SKILL.md +1 -1
  358. package/modules/ui-design/skills/design-taste-frontend-v1/SKILL.md +1 -1
  359. package/modules/ui-design/skills/gpt-taste/SKILL.md +1 -1
  360. package/modules/ui-design/skills/high-end-visual-design/SKILL.md +1 -1
  361. package/modules/ui-design/skills/image-to-code/SKILL.md +1 -1
  362. package/modules/ui-design/skills/imagegen-frontend-mobile/SKILL.md +1 -1
  363. package/modules/ui-design/skills/imagegen-frontend-web/SKILL.md +1 -1
  364. package/modules/ui-design/skills/impeccable/SKILL.md +1 -1
  365. package/modules/ui-design/skills/industrial-brutalist-ui/SKILL.md +1 -1
  366. package/modules/ui-design/skills/minimalist-ui/SKILL.md +1 -1
  367. package/modules/ui-design/skills/redesign-existing-projects/SKILL.md +1 -1
  368. package/modules/ui-design/skills/stitch-design-taste/SKILL.md +1 -1
  369. package/modules/ui-design/skills/style/SKILL.md +2 -2
  370. package/modules/ui-design/styles/README.md +3 -3
  371. package/modules/ui-design/styles/blueprint/DESIGN.md +1 -1
  372. package/modules/ui-design/styles/chrome-world/DESIGN.md +1 -1
  373. package/package-lock.json +2 -2
  374. package/package.json +1 -1
  375. package/scripts/gds/audit-rank-roles.js +172 -0
  376. package/scripts/gds/build-cli-package.js +1 -0
  377. package/scripts/gds/claim.js +94 -9
  378. package/scripts/gds/fitness.js +8 -8
  379. package/scripts/gds/go-live.js +139 -5
  380. package/scripts/gds/land-watch.js +13 -5
  381. package/scripts/gds/mirror-redact.js +13 -2
  382. package/scripts/gds/package-core.js +11 -3
  383. package/scripts/gds/release.js +55 -0
  384. package/scripts/gds/ship-land.js +10 -2
  385. package/scripts/gds/ship-regen.js +41 -8
  386. package/scripts/gds/start.js +7 -4
  387. package/scripts/gds/worktree-claim-guard.js +27 -1
  388. package/scripts/public-mirror-export.js +17 -4
  389. package/src/bongos/api-errors.js +9 -0
  390. package/src/bongos/artist-gate.js +204 -0
  391. package/src/branding.js +15 -0
  392. package/src/module-api.js +34 -1
  393. package/tests/api_path_404.mjs +28 -0
  394. package/tests/artist_gate.mjs +234 -0
  395. package/tests/artist_gate_release.mjs +283 -0
  396. package/tests/artist_gate_route.mjs +174 -0
  397. package/tests/box_scope_predicate.mjs +117 -0
  398. package/tests/claim_error_surface.mjs +163 -1
  399. package/tests/cli_exit_no_abort.mjs +256 -3
  400. package/tests/currency_label.mjs +11 -4
  401. package/tests/go_live.mjs +144 -0
  402. package/tests/main_worktree_busy_reason.mjs +167 -0
  403. package/tests/membership_kind_lockstep.mjs +88 -0
  404. package/tests/mirror_redact.mjs +14 -0
  405. package/tests/module_api.mjs +6 -0
  406. package/tests/provision_settings_apply.mjs +13 -5
  407. package/tests/provisioning_settings.mjs +11 -7
  408. package/tests/provisioning_settings_apply.mjs +4 -4
  409. package/tests/provisioning_settings_env.mjs +59 -7
  410. package/tests/public_mirror_export.mjs +12 -0
  411. package/tests/rank_roles_audit.mjs +114 -0
  412. package/tests/scrubber_corpus.mjs +70 -2
  413. package/tests/start_rebase_warning.mjs +50 -0
  414. package/tests/task_field_bounds_match_db.mjs +140 -0
@@ -0,0 +1,204 @@
1
+ 'use strict';
2
+
3
+ // src/bongos/artist-gate.js — the artist gate: what an unresolved artist review
4
+ // holds, and what releases it (ADR 0241, task 1003575).
5
+ //
6
+ // WHAT IT ANSWERS: "an artist review task is open — does anything stop?" One
7
+ // value, read from one place, so the cascade that FILES those reviews and the
8
+ // deploy that they HOLD cannot disagree about the project's own policy:
9
+ //
10
+ // off no artist review tasks are filed at all; nothing is held
11
+ // advisory reviews are filed; nothing is held
12
+ // strict reviews are filed, and an unresolved one holds the DEPLOY
13
+ //
14
+ // THE TWO THINGS THAT MAKE THIS ADMISSIBLE, both from ADR 0241 and neither
15
+ // optional — a gate that loses either one is the gate ADR 0162 retired:
16
+ //
17
+ // 1. IT READS A STATE, NEVER A VERDICT. The blocking condition is "is there an
18
+ // unresolved artist review attached to this work?", never "did the artist
19
+ // approve it?". An artist saying the copy is bad stops nothing by itself, and
20
+ // an artist saying it is fine releases nothing by itself; what moves is the
21
+ // review task's STATUS, by the ordinary means (shipped, or dismissed in
22
+ // triage). That is what keeps the gate rank-consistent and machine-checkable
23
+ // instead of pinned to one person being available.
24
+ //
25
+ // 2. IT HOLDS THE DEPLOY, NOT THE SHIP. A builder's work still grades, merges,
26
+ // credits and lands on CI alone. Nothing here may ever reach the ship or land
27
+ // path — the wave-4 strand class that forced ADR 0162 is exactly what that
28
+ // separation prevents. The only thing this can delay is a pin move, which is
29
+ // already deliberate, already owner-initiated, and reversible by doing it
30
+ // later.
31
+ //
32
+ // PURE HALF / READ HALF. Everything below `unresolvedArtistReviews` is pure — a
33
+ // list of task-shaped objects in, a verdict out — so the halt matrix tests with no
34
+ // pack, no env and no database. Only `artistGateMode` touches the branding pack,
35
+ // and it defers that require to call time for the same reason project-door.js
36
+ // does.
37
+
38
+ const log = require('./logger').logger.child({ src: 'artist-gate' });
39
+
40
+ // The three levels, ORDERED loosest → strictest (ADR 0241 §2's table).
41
+ const ARTIST_GATE_LEVELS = Object.freeze(['off', 'advisory', 'strict']);
42
+
43
+ // A task status is RESOLVED when the work it asks for is done or the board has
44
+ // decided it will not be — 'shipped' and 'abandoned' are the two ways a task
45
+ // leaves the board, and 'abandoned' is what triage's dismiss writes. Everything
46
+ // else (backlog, ready, active, completed, confirmed) is an open request, which
47
+ // is the state ADR 0241 §1 says the gate reads. A row merged into another is
48
+ // resolved too: its successor carries the work.
49
+ const RESOLVED_REVIEW_STATUSES = Object.freeze(['shipped', 'abandoned']);
50
+
51
+ // The (source, source_ref-prefix) pair every cascade-filed artist review carries.
52
+ // Mirrored from modules/lifecycle/cascade.js rather than imported: core must not
53
+ // reach into a module (the fitness boundary), and a review's identity on the wire
54
+ // is a two-field fact, not a behaviour. The cascade's own test pins the spelling.
55
+ const ARTIST_REVIEW_SOURCE = 'cascade';
56
+ const ARTIST_REVIEW_REF_PREFIX = 'cascade-artist-review-on-ship-';
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // The pure half
60
+ // ---------------------------------------------------------------------------
61
+
62
+ // Is this task row a cascade-filed artist review? PURE and total.
63
+ function isArtistReview(task) {
64
+ if (!task) return false;
65
+ return String(task.source || '') === ARTIST_REVIEW_SOURCE
66
+ && String(task.source_ref || '').startsWith(ARTIST_REVIEW_REF_PREFIX);
67
+ }
68
+
69
+ // Is this review still an open request? PURE and total. A merged row counts as
70
+ // resolved even at an open status — the merge moved the work, and holding a
71
+ // deploy on a row nobody will ever action again is the stall this gate must not
72
+ // have.
73
+ function isReviewResolved(task) {
74
+ if (!task) return true;
75
+ if (task.merged_into_task_id != null) return true;
76
+ return RESOLVED_REVIEW_STATUSES.includes(String(task.status || ''));
77
+ }
78
+
79
+ // The reviews that actually HOLD, out of every review row handed in. PURE.
80
+ //
81
+ // Two filters, and the second is ADR 0241 §5 — tightening is not retroactive. A
82
+ // project that raises its gate to 'strict' must not inherit a backlog of reviews
83
+ // for work nobody remembers, so a review filed BEFORE the raise never holds. An
84
+ // absent or unparseable `since` means no cutoff, which is the right answer for a
85
+ // project that has been strict since it stood up (the default) — it is not a
86
+ // licence to ignore reviews, so it must not read as one.
87
+ //
88
+ // A review with no readable created_at is KEPT: the cutoff exists to spare a
89
+ // project work it never chose, not to let an unreadable row slip the gate.
90
+ function unresolvedArtistReviews(tasks, { since = null } = {}) {
91
+ const cutoff = since ? Date.parse(since) : NaN;
92
+ const list = Array.isArray(tasks) ? tasks : [];
93
+ return list.filter((t) => {
94
+ if (!isArtistReview(t) || isReviewResolved(t)) return false;
95
+ if (Number.isNaN(cutoff)) return true;
96
+ const filed = Date.parse(t.created_at);
97
+ if (Number.isNaN(filed)) return true;
98
+ return filed >= cutoff;
99
+ });
100
+ }
101
+
102
+ // Does the gate hold, and if so WHAT holds it and WHICH control releases it?
103
+ // Returns null when nothing is held, else a halt-shaped object. PURE.
104
+ //
105
+ // The sentence is the point, not decoration. ADR 0241 §4's second guard is
106
+ // explicit: the refusal must name what holds the deploy and which control
107
+ // releases it, in one line, because ADR 0162's incident was made worse by a
108
+ // blocked state that did not explain itself. So the detail names the review ids
109
+ // AND both real ways out — resolve them, or the project OWNER releases past with
110
+ // an override-request (rationale required, audit-logged; a self-release is
111
+ // permitted and recorded as such, ADR 0241 §3).
112
+ //
113
+ // Keep this sentence TRUE to the mechanism, not merely to the ADR's prose. The
114
+ // first cut of this gate described an escape hatch the code did not wire, which
115
+ // is worse than no sentence: a refusal that names a control nobody can actually
116
+ // use is a stall with extra steps. The release lives in
117
+ // modules/lifecycle/db-overrides.js (the gate-release class) and the gate reads
118
+ // it through db-tasks.listOpenArtistReviews — change one, re-read the other two.
119
+ // `truncated` says the caller's read hit its scan cap, so `reviews` is a floor
120
+ // rather than the whole queue. It cannot change the VERDICT — every row handed in
121
+ // is a holder, so one is enough — only the count, which is then reported as a
122
+ // floor ("200+") instead of a number the sentence cannot stand behind.
123
+ function artistGateHalt({ mode, reviews = [], truncated = false } = {}) {
124
+ if (mode !== 'strict') return null;
125
+ const open = Array.isArray(reviews) ? reviews : [];
126
+ if (!open.length) return null;
127
+ // Named as `task N`, never a bare `#N`: this sentence is copied into chat, commit
128
+ // messages and session logs, where a bare hash autolinks to a GitHub issue that
129
+ // does not exist (the kernel's linkify rule).
130
+ const ids = open.slice(0, 5).map((t) => String(t.id));
131
+ const more = open.length - ids.length;
132
+ const named = ids.map((id) => `task ${id}`).join(', ');
133
+ return {
134
+ code: 'ARTIST_REVIEW_UNRESOLVED',
135
+ message: `${open.length}${truncated ? '+' : ''} unresolved artist review${open.length === 1 && !truncated ? '' : 's'} hold this deploy — the project runs artist_gate 'strict'`,
136
+ detail: `Held by ${named}${more > 0 ? ` and ${more} more` : ''}. `
137
+ + 'Two ways out, both ordinary: resolve or dismiss those reviews on the board, '
138
+ + 'or release one — POST /tasks/:id/override-request with a rationale, then the PROJECT OWNER '
139
+ + 'approves it at POST /override-requests/:id/decide (owner only, not an Archon; a self-release '
140
+ + 'is allowed and is audit-logged as a self-override). A released review stays open on the board; '
141
+ + "what it stops doing is holding the pin. Setting this project's artist_gate to 'advisory' "
142
+ + 'stops reviews holding deploys at all.',
143
+ reviewIds: open.map((t) => String(t.id)),
144
+ };
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // The read half
149
+ // ---------------------------------------------------------------------------
150
+
151
+ // artistGateMode — this project's stored artist strictness, and ONLY that. The
152
+ // one reader for the knob, so the cascade, the deploy gate and any surface that
153
+ // reports the policy cannot disagree about what the owner set.
154
+ //
155
+ // An unreadable pack, or a value this core cannot read, resolves to 'strict'. That
156
+ // is fail-closed in the direction ADR 0241 §4 already chose as the DEFAULT, so an
157
+ // instance whose pack went missing behaves like a fresh one rather than silently
158
+ // dropping a policy the owner set — and 'strict' holds only a pin, which the owner
159
+ // can always release. `read` is injectable for the unit tests only (the pack is
160
+ // memoized for the process lifetime, which is why applying a change restarts the
161
+ // instance).
162
+ let warnedUnknownArtistGate = false;
163
+ function artistGateMode(read) {
164
+ const readPack = read || require('../branding').branding; // eslint-disable-line global-require
165
+ let v;
166
+ try { v = readPack().project?.artistGate; } catch (err) {
167
+ log.error('[artist-gate] branding pack unreadable — treating the project as strict:', err.message);
168
+ return 'strict';
169
+ }
170
+ if (ARTIST_GATE_LEVELS.includes(v)) return v;
171
+ // Absent is the honest case for a self-hosted instance that has never been
172
+ // pushed a setting, and for the neutral pack before a value was written. It
173
+ // takes the default rather than a warning.
174
+ if (v === undefined || v === null || v === '') return 'strict';
175
+ if (!warnedUnknownArtistGate) {
176
+ warnedUnknownArtistGate = true;
177
+ log.error(`[artist-gate] unrecognised artistGate ${JSON.stringify(v)} — treating the project as strict`);
178
+ }
179
+ return 'strict';
180
+ }
181
+
182
+ // The moment this project's gate was last RAISED to 'strict', or null. Written by
183
+ // the platform when the owner tightens the setting (provisioning.artistGateStamp)
184
+ // and carried in on <PREFIX>_ARTIST_GATE_SINCE. null = no cutoff (ADR 0241 §5).
185
+ function artistGateSince(read) {
186
+ const readPack = read || require('../branding').branding; // eslint-disable-line global-require
187
+ let v;
188
+ try { v = readPack().project?.artistGateSince; } catch { return null; }
189
+ if (typeof v !== 'string' || !v) return null;
190
+ return Number.isNaN(Date.parse(v)) ? null : v;
191
+ }
192
+
193
+ module.exports = {
194
+ ARTIST_GATE_LEVELS,
195
+ RESOLVED_REVIEW_STATUSES,
196
+ ARTIST_REVIEW_SOURCE,
197
+ ARTIST_REVIEW_REF_PREFIX,
198
+ isArtistReview,
199
+ isReviewResolved,
200
+ unresolvedArtistReviews,
201
+ artistGateHalt,
202
+ artistGateMode,
203
+ artistGateSince,
204
+ };
package/src/branding.js CHANGED
@@ -69,6 +69,14 @@ const ENV_OVERRIDES = [
69
69
  // view | apply | full. The ceiling on the visibility axis that JOINABILITY may
70
70
  // only narrow; composed into one door by src/bongos/project-door.js.
71
71
  ['JOIN_GRANT', ['project', 'joinGrant']],
72
+ // The artist gate (ADR 0241, task 1003575) — off | advisory | strict. `off`
73
+ // generates no artist review tasks; `strict` lets an unresolved one hold this
74
+ // instance's DEPLOY (never a builder's ship). Its companion stamp says when the
75
+ // gate was last raised, so the hold is not retroactive (ADR 0241 §5); empty is
76
+ // skipped by the loop below, which reads as "no cutoff" — correct for a project
77
+ // that has been strict since it stood up.
78
+ ['ARTIST_GATE', ['project', 'artistGate']],
79
+ ['ARTIST_GATE_SINCE', ['project', 'artistGateSince']],
72
80
  ];
73
81
 
74
82
  const isObj = (v) => v && typeof v === 'object' && !Array.isArray(v);
@@ -250,6 +258,13 @@ function clientBranding(b = branding()) {
250
258
  // stranger's client should be able to say what THIS door does before knocking.
251
259
  // `full` = this axis narrows nothing, so an un-pushed instance is unchanged.
252
260
  joinGrant: b.project?.joinGrant || 'full',
261
+ // The artist gate (ADR 0241, task 1003575). Public for the read-back reason
262
+ // its neighbours are: a manage page has to be able to tell a SAVED setting
263
+ // from a LIVE one, and this instance's own manifest is the only honest
264
+ // answer to "what did it restart with?". The fallback is the platform
265
+ // DEFAULT, which for this one key is 'strict' rather than today's behaviour
266
+ // — the owner's ruling, ADR 0241 §4.
267
+ artistGate: b.project?.artistGate || 'strict',
253
268
  },
254
269
  };
255
270
  }
package/src/module-api.js CHANGED
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.715'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.724'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -164,6 +164,13 @@ module.exports = {
164
164
  // db.js into the bounded src/bongos/db-kernel.js, which this surface resolves
165
165
  // through — so loading a kernel accessor no longer drags in the monolith.
166
166
  getBuilderById: (...a) => require('./bongos/db-kernel').getBuilderById(...a),
167
+ // getFoundingBuilder — the instance's PROJECT OWNER: its first admitted human
168
+ // builder (task 1002739). Kernel identity like getBuilderById beside it, and
169
+ // published because ADR 0241 §3 gives the artist gate's one escape hatch to
170
+ // "the project owner only — not the builder, not any Metic+, not the artist",
171
+ // which a module cannot enforce without being able to ask who that is. It is
172
+ // a READ of an existing row; it grants nothing by itself.
173
+ getFoundingBuilder: (...a) => require('./bongos/db-kernel').getFoundingBuilder(...a),
167
174
  insertAuditLog: (...a) => require('./bongos/db-kernel').insertAuditLog(...a),
168
175
  get LIVE_RANK_LADDER() { return require('./bongos/db-kernel').LIVE_RANK_LADDER; },
169
176
  // The ladder as the canonical 1-based rank → tier map (BV1.R108) — modules
@@ -303,6 +310,32 @@ module.exports = {
303
310
  // the other two had closed. Optional `{ publishable }` for a caller that
304
311
  // holds a publish verdict — see src/bongos/project-door.js.
305
312
  projectJoinDoor: (opts) => require('./bongos/project-door').effectiveJoinDoor(opts),
313
+ // artistGate — the project's artist strictness and the pure verdict helpers
314
+ // that read it (ADR 0241, task 1003575): 'off' | 'advisory' | 'strict'.
315
+ // `off` is what tells the lifecycle cascade not to file artist reviews at
316
+ // all, and `strict` is what lets an unresolved one hold a DEPLOY (never a
317
+ // ship). Published as the whole small surface rather than one call because
318
+ // two different modules ask two different questions of it — "should I file
319
+ // this?" and "does anything hold?" — and both must resolve the knob through
320
+ // this one reader, never from branding().project directly: the fail-closed
321
+ // default and the non-retroactivity cutoff live in there.
322
+ // The members are named ONE BY ONE rather than re-exported wholesale: a
323
+ // `return require(…)` would publish whatever that file happens to export
324
+ // next, which is how a doorway stops being a contract.
325
+ get artistGate() {
326
+ const g = require('./bongos/artist-gate'); // eslint-disable-line global-require
327
+ return Object.freeze({
328
+ ARTIST_REVIEW_SOURCE: g.ARTIST_REVIEW_SOURCE,
329
+ ARTIST_REVIEW_REF_PREFIX: g.ARTIST_REVIEW_REF_PREFIX,
330
+ RESOLVED_REVIEW_STATUSES: g.RESOLVED_REVIEW_STATUSES,
331
+ isArtistReview: g.isArtistReview,
332
+ isReviewResolved: g.isReviewResolved,
333
+ unresolvedArtistReviews: g.unresolvedArtistReviews,
334
+ artistGateHalt: g.artistGateHalt,
335
+ artistGateMode: g.artistGateMode,
336
+ artistGateSince: g.artistGateSince,
337
+ });
338
+ },
306
339
  // readApplicantProfileFromHub(githubId) — the reviewer queue's LIVE per-render
307
340
  // read of one applicant's hub profile view (privacy spec D7, task
308
341
  // 1002972). Published as a NARROW port on purpose: the underlying call is
@@ -144,5 +144,33 @@ t('rebaseRequiredHint: says waiting is correct when CI is merely backed up', ()
144
144
  assert.match(rebaseRequiredHint([{ id: 7 }]), /waiting is correct/);
145
145
  });
146
146
 
147
+ // task 1002788 — the menu must not name the move that deadlocks the reader.
148
+ //
149
+ // This hint fires on a branch that is ALREADY PUBLISHED, and server-mediated
150
+ // publish is append-only by deliberate design (github-push.js: "Refuse
151
+ // non-fast-forward implicitly (no --force, ever)"). The old wording — "rebase
152
+ // the branch and re-ship" — rewrites history the server already holds, so every
153
+ // later publish is rejected non-fast-forward and the builder is left gated from
154
+ // new claims AND unable to land the one they hold. Reproduced end-to-end on task
155
+ // 1002485. ship-land.js's recovery text already says "Do NOT reset or re-rebase";
156
+ // this is the same advice given BEFORE the deadlock rather than after it.
157
+ t('rebaseRequiredHint: prescribes MERGE, and never instructs a rebase', () => {
158
+ const hint = rebaseRequiredHint([{ id: 7 }]);
159
+ assert.ok(hint.includes('git merge origin/main'),
160
+ 'must name the fast-forward-safe command, not just forbid the other one');
161
+ // Narrow on purpose: bans INSTRUCTING a rebase, not the word — "never rebase"
162
+ // is the sentence that carries the lesson and must keep working.
163
+ for (const re of [/\brebase\s*\+\s*re-ship\b/i, /\brebase (the|its|your) branch\b/i, /\brebases it\b/i]) {
164
+ assert.ok(!re.test(hint), `must not instruct a rebase (matched ${re}): ${hint}`);
165
+ }
166
+ assert.match(hint, /never rebase/i, 'and must say why, so the next reader does not re-add it');
167
+ });
168
+
169
+ t('rebaseRequiredHint: still leads with /strand-fix — merge is the manual fallback, not the first offer', () => {
170
+ const hint = rebaseRequiredHint([{ id: 7 }]);
171
+ assert.ok(hint.indexOf('/strand-fix') < hint.indexOf('git merge origin/main'),
172
+ 'the command that works on any machine must still come first (task 1003768)');
173
+ });
174
+
147
175
  console.log(`\napi_path_404: ${passed} passed, ${failed} failed`);
148
176
  process.exit(failed ? 1 : 0);
@@ -0,0 +1,234 @@
1
+ // tests/artist_gate.mjs
2
+ //
3
+ // The artist gate (ADR 0241, task 1003575) — src/bongos/artist-gate.js.
4
+ //
5
+ // WHAT IS ACTUALLY WORTH PINNING HERE is not that the filter filters. It is the
6
+ // four properties ADR 0241 spends its whole argument on, each of which is a way
7
+ // this gate could quietly become the gate ADR 0162 retired:
8
+ //
9
+ // 1. IT READS A STATE, NEVER A VERDICT. Nothing here may consult who reviewed,
10
+ // what they thought, or whether they approved — only whether the review task
11
+ // is still open. A verdict-reading gate is pinned to a person and stalls when
12
+ // that person is away.
13
+ // 2. IT NEVER HOLDS ANYTHING BUT AT 'strict'. `off` and `advisory` must be
14
+ // incapable of producing a halt, however many reviews are open.
15
+ // 3. TIGHTENING IS NOT RETROACTIVE (§5). A review filed before the gate was
16
+ // raised must not hold, or raising the setting means inheriting a backlog for
17
+ // work nobody remembers — which is what makes a setting frightening to raise.
18
+ // 4. THE REFUSAL EXPLAINS ITSELF (§4). It names what holds the deploy and which
19
+ // control releases it. ADR 0162's incident was made worse by a blocked state
20
+ // that did not say either.
21
+ //
22
+ // Pure: the module's read half is injectable, so nothing here needs a branding
23
+ // pack, an env or a database.
24
+ //
25
+ // Run: node --test tests/artist_gate.mjs
26
+
27
+ import { test } from 'node:test';
28
+ import { strict as assert } from 'node:assert';
29
+ // Static named imports, not createRequire: the same shape tests/project_join_door.mjs
30
+ // uses on the sibling kernel classifier. It reads better, and it is also what lets the
31
+ // dead-code ratchet SEE these exports being exercised — a createRequire call is opaque
32
+ // to it, so a fully-tested module reads as ten unused exports.
33
+ import {
34
+ ARTIST_GATE_LEVELS,
35
+ RESOLVED_REVIEW_STATUSES,
36
+ ARTIST_REVIEW_SOURCE,
37
+ ARTIST_REVIEW_REF_PREFIX,
38
+ isArtistReview,
39
+ isReviewResolved,
40
+ unresolvedArtistReviews,
41
+ artistGateHalt,
42
+ artistGateMode,
43
+ artistGateSince,
44
+ } from '../src/bongos/artist-gate.js';
45
+
46
+ const gate = {
47
+ ARTIST_GATE_LEVELS,
48
+ RESOLVED_REVIEW_STATUSES,
49
+ ARTIST_REVIEW_SOURCE,
50
+ ARTIST_REVIEW_REF_PREFIX,
51
+ isArtistReview,
52
+ isReviewResolved,
53
+ unresolvedArtistReviews,
54
+ artistGateHalt,
55
+ artistGateMode,
56
+ artistGateSince,
57
+ };
58
+
59
+ // A cascade-filed artist review row, as the board holds it.
60
+ const review = (over = {}) => ({
61
+ id: '9001',
62
+ title: 'Artist review: the copy and visuals that shipped in task 7001',
63
+ status: 'backlog',
64
+ source: 'cascade',
65
+ source_ref: 'cascade-artist-review-on-ship-7001',
66
+ created_at: '2026-09-10T00:00:00.000Z',
67
+ merged_into_task_id: null,
68
+ ...over,
69
+ });
70
+
71
+ // A pack reader, as branding().project would answer.
72
+ const pack = (project) => () => ({ project });
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // What counts as a review at all
76
+ // ---------------------------------------------------------------------------
77
+
78
+ test('only a CASCADE-filed artist review counts — a hand-filed task with a similar title does not', () => {
79
+ assert.equal(gate.isArtistReview(review()), true);
80
+ // Same source, different rule: another cascade must never hold a deploy this
81
+ // gate was not given authority over.
82
+ assert.equal(gate.isArtistReview(review({ source_ref: 'cascade-some-other-rule-7001' })), false);
83
+ // Same ref shape, not the cascade's source.
84
+ assert.equal(gate.isArtistReview(review({ source: 'manual' })), false);
85
+ assert.equal(gate.isArtistReview({ title: 'Artist review: please look at this' }), false);
86
+ assert.equal(gate.isArtistReview(null), false);
87
+ });
88
+
89
+ test('RESOLVED is a status, never a judgement — the two ways a task leaves the board, plus a merge', () => {
90
+ assert.equal(gate.isReviewResolved(review({ status: 'shipped' })), true);
91
+ assert.equal(gate.isReviewResolved(review({ status: 'abandoned' })), true, "triage's dismiss");
92
+ // A row merged into another is resolved even at an open status: its successor
93
+ // carries the work, and holding a deploy on a row nobody will action again is
94
+ // exactly the indefinite stall this gate must not have.
95
+ assert.equal(gate.isReviewResolved(review({ status: 'backlog', merged_into_task_id: '9100' })), true);
96
+ for (const status of ['backlog', 'ready', 'active', 'completed', 'confirmed']) {
97
+ assert.equal(gate.isReviewResolved(review({ status })), false, status);
98
+ }
99
+ assert.equal(gate.isReviewResolved(null), true, 'nothing is not an open request');
100
+ });
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // §5 — tightening is not retroactive
104
+ // ---------------------------------------------------------------------------
105
+
106
+ test('a review filed BEFORE the gate was raised does not hold; one filed after does', () => {
107
+ const older = review({ id: '9001', created_at: '2026-09-01T00:00:00.000Z' });
108
+ const newer = review({ id: '9002', created_at: '2026-09-12T00:00:00.000Z' });
109
+ const held = gate.unresolvedArtistReviews([older, newer], { since: '2026-09-10T00:00:00.000Z' });
110
+ assert.deepEqual(held.map((t) => t.id), ['9002']);
111
+ });
112
+
113
+ test('no cutoff means every open review counts — absent must not read as a licence to ignore', () => {
114
+ const rows = [review({ id: '9001' }), review({ id: '9002', created_at: '2020-01-01T00:00:00.000Z' })];
115
+ assert.equal(gate.unresolvedArtistReviews(rows, {}).length, 2);
116
+ assert.equal(gate.unresolvedArtistReviews(rows, { since: null }).length, 2);
117
+ assert.equal(gate.unresolvedArtistReviews(rows, { since: 'not a date' }).length, 2, 'junk is no cutoff, not no gate');
118
+ });
119
+
120
+ test('a review with an unreadable filing date is KEPT — the cutoff spares work, it does not excuse it', () => {
121
+ const held = gate.unresolvedArtistReviews([review({ created_at: null })], { since: '2026-09-10T00:00:00.000Z' });
122
+ assert.equal(held.length, 1);
123
+ });
124
+
125
+ test('resolved reviews are filtered out before the cutoff is even consulted', () => {
126
+ const rows = [review({ id: '9001', status: 'shipped' }), review({ id: '9002', status: 'abandoned' })];
127
+ assert.deepEqual(gate.unresolvedArtistReviews(rows, {}), []);
128
+ });
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // §2 / §4 — what holds, and what the refusal says
132
+ // ---------------------------------------------------------------------------
133
+
134
+ test("only 'strict' can hold — off and advisory are incapable of it, however many are open", () => {
135
+ const open = [review({ id: '9001' }), review({ id: '9002' })];
136
+ assert.equal(gate.artistGateHalt({ mode: 'off', reviews: open }), null);
137
+ assert.equal(gate.artistGateHalt({ mode: 'advisory', reviews: open }), null);
138
+ assert.ok(gate.artistGateHalt({ mode: 'strict', reviews: open }));
139
+ });
140
+
141
+ test("'strict' with nothing open holds nothing — the gate reads state, so no state is no hold", () => {
142
+ assert.equal(gate.artistGateHalt({ mode: 'strict', reviews: [] }), null);
143
+ assert.equal(gate.artistGateHalt({ mode: 'strict' }), null);
144
+ });
145
+
146
+ test('the refusal names WHAT holds the deploy and WHICH control releases it', () => {
147
+ const halt = gate.artistGateHalt({ mode: 'strict', reviews: [review({ id: '9001' }), review({ id: '9002' })] });
148
+ assert.equal(halt.code, 'ARTIST_REVIEW_UNRESOLVED');
149
+ assert.match(halt.message, /2 unresolved artist reviews/);
150
+ assert.match(halt.message, /strict/, 'and why it is holding at all');
151
+ // The two real ways out, and neither is "wait for someone".
152
+ assert.match(halt.detail, /task 9001, task 9002/);
153
+ assert.match(halt.detail, /resolve or dismiss/i);
154
+ assert.match(halt.detail, /override-request/);
155
+ assert.match(halt.detail, /OWNER/, 'the escape hatch is the owner, not any Metic+ (ADR 0241 §3)');
156
+ assert.match(halt.detail, /advisory/, 'and the setting that stops this happening again');
157
+ assert.deepEqual(halt.reviewIds, ['9001', '9002']);
158
+ });
159
+
160
+ test('the refusal never writes a bare #id — that autolinks to a GitHub issue that does not exist', () => {
161
+ const halt = gate.artistGateHalt({ mode: 'strict', reviews: [review()] });
162
+ assert.doesNotMatch(`${halt.message} ${halt.detail}`, /#\d/);
163
+ });
164
+
165
+ test('a TRUNCATED read reports a floor, and truncation can never change the verdict', () => {
166
+ const many = Array.from({ length: 3 }, (_, i) => review({ id: String(9000 + i) }));
167
+ const halt = artistGateHalt({ mode: 'strict', reviews: many, truncated: true });
168
+ assert.match(halt.message, /^3\+ unresolved artist reviews/, 'a floor, not a count it cannot stand behind');
169
+ // The verdict is identical either way: every row handed in is a holder, so one
170
+ // is enough and a capped read still holds.
171
+ assert.equal(artistGateHalt({ mode: 'strict', reviews: many }).code, halt.code);
172
+ // A single truncated row is still plural — "1+ review" would read as exactly one.
173
+ assert.match(artistGateHalt({ mode: 'strict', reviews: [review()], truncated: true }).message, /^1\+ unresolved artist reviews/);
174
+ assert.match(artistGateHalt({ mode: 'strict', reviews: [review()] }).message, /^1 unresolved artist review /);
175
+ // And truncation cannot manufacture a hold out of nothing.
176
+ assert.equal(artistGateHalt({ mode: 'strict', reviews: [], truncated: true }), null);
177
+ });
178
+
179
+ test('a long queue is summarised rather than dumped — five named, the rest counted', () => {
180
+ const many = Array.from({ length: 9 }, (_, i) => review({ id: String(9000 + i) }));
181
+ const halt = gate.artistGateHalt({ mode: 'strict', reviews: many });
182
+ assert.match(halt.detail, /and 4 more/);
183
+ assert.equal(halt.reviewIds.length, 9, 'but the machine-readable list is complete');
184
+ });
185
+
186
+ // ---------------------------------------------------------------------------
187
+ // The reader — fail-closed in the direction the default already points
188
+ // ---------------------------------------------------------------------------
189
+
190
+ test('the level is read from the pack, and only the three declared levels are accepted', () => {
191
+ assert.equal(gate.artistGateMode(pack({ artistGate: 'off' })), 'off');
192
+ assert.equal(gate.artistGateMode(pack({ artistGate: 'advisory' })), 'advisory');
193
+ assert.equal(gate.artistGateMode(pack({ artistGate: 'strict' })), 'strict');
194
+ });
195
+
196
+ test("an absent, unreadable or unrecognised level resolves to 'strict' — the platform default", () => {
197
+ // Absent is the honest case for a self-hosted instance nobody ever pushed a
198
+ // setting to; it takes the default rather than dropping the policy.
199
+ assert.equal(gate.artistGateMode(pack({})), 'strict');
200
+ assert.equal(gate.artistGateMode(pack({ artistGate: '' })), 'strict');
201
+ // A value this core cannot read is a policy it does not know — and the safe
202
+ // direction here is the strict one, because strict only ever delays a pin move.
203
+ assert.equal(gate.artistGateMode(pack({ artistGate: 'Strict' })), 'strict');
204
+ assert.equal(gate.artistGateMode(pack({ artistGate: 'sort-of' })), 'strict');
205
+ assert.equal(gate.artistGateMode(() => { throw new Error('no pack'); }), 'strict');
206
+ });
207
+
208
+ test('the cutoff is read from the pack too, and junk reads as no cutoff', () => {
209
+ assert.equal(gate.artistGateSince(pack({ artistGateSince: '2026-09-10T00:00:00.000Z' })), '2026-09-10T00:00:00.000Z');
210
+ assert.equal(gate.artistGateSince(pack({ artistGateSince: '' })), null, 'the empty env value the transport always writes');
211
+ assert.equal(gate.artistGateSince(pack({ artistGateSince: 'whenever' })), null);
212
+ assert.equal(gate.artistGateSince(pack({})), null);
213
+ assert.equal(gate.artistGateSince(() => { throw new Error('no pack'); }), null);
214
+ });
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // The property the whole ADR turns on
218
+ // ---------------------------------------------------------------------------
219
+
220
+ test('NOTHING in the gate reads a verdict — only source, ref, status, merge and date', () => {
221
+ // A row carrying every judgement-shaped field the board could hand it must
222
+ // behave identically to one carrying none: if this ever fails, the gate has
223
+ // started reading an opinion and ADR 0162's objection applies again.
224
+ const opinionated = review({
225
+ grade: 'fail', verdict: 'rejected', approved_by: null, review_outcome: 'needs-work', artist_says: 'this is slop',
226
+ });
227
+ assert.deepEqual(
228
+ gate.unresolvedArtistReviews([opinionated], {}).map((t) => t.id),
229
+ gate.unresolvedArtistReviews([review()], {}).map((t) => t.id)
230
+ );
231
+ const a = gate.artistGateHalt({ mode: 'strict', reviews: [opinionated] });
232
+ const b = gate.artistGateHalt({ mode: 'strict', reviews: [review()] });
233
+ assert.deepEqual(a, b, 'an approving artist releases nothing; a disapproving one holds nothing extra');
234
+ });