@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
@@ -19,15 +19,66 @@ const { mostRecentClaimHolderId } = require('./db-shared.js');
19
19
  // believes the grader was wrong and asks an Archon to confirm anyway.
20
20
 
21
21
 
22
+ // THE SECOND THING AN OVERRIDE REQUEST CAN BE (task 1003575, ADR 0241 §3).
23
+ //
24
+ // The original request is a GRADER BYPASS: a `completed` task whose ship the
25
+ // subagent panel rejected, flipped to `confirmed` and paid. ADR 0241 needed an
26
+ // escape hatch for a different refusal — a deploy held by an unresolved artist
27
+ // review — and was explicit that it must be "the mechanism that already exists…
28
+ // no new machinery, no second authority path". So it is the SAME table, the same
29
+ // request flow and the same decide endpoint; only the target class and the effect
30
+ // of an approval differ.
31
+ //
32
+ // A GATE RELEASE differs from a grader bypass in three ways, and each one is the
33
+ // reason it could not simply reuse the existing path unchanged:
34
+ //
35
+ // • Its target is NOT at 'completed'. An artist review sits at `backlog` and
36
+ // usually always will — ADR 0241 guard #4 calls "a strict project with no
37
+ // artist" the normal case for a new instance. The completed-only check below
38
+ // would have made the one documented escape hatch unusable for the exact
39
+ // scenario it was written for.
40
+ // • Approving it CONFIRMS NOTHING AND PAYS NOTHING. A released review is still
41
+ // an open request on the board; what the release buys is that it stops
42
+ // holding the pin. Flipping it to `confirmed` and crediting it would pay for
43
+ // work nobody did and erase the request.
44
+ // • Only the PROJECT OWNER may decide it (§3, owner ruling: not the builder,
45
+ // not any Metic+, not the artist) — a narrower gate than the Archon floor the
46
+ // grader bypass carries, enforced here rather than only at the route so no
47
+ // second caller can widen it.
48
+ //
49
+ // Whether a task is in this class is asked of core's ONE artist-gate reader, so
50
+ // "is this an unresolved artist review" has the same answer here, at the deploy
51
+ // gate and in the cascade that files them.
52
+ function gateReleaseTarget(taskRow) {
53
+ const gate = api.artistGate;
54
+ return gate.isArtistReview(taskRow) && !gate.isReviewResolved(taskRow);
55
+ }
56
+
57
+ // WHO is asked "who owns this project?" — and the answer is the doorway unless we
58
+ // are demonstrably in a test. Same NODE_ENV gate as the `deps.pool` seam below,
59
+ // and for a sharper version of its reason (#538): this reader decides who may
60
+ // release a deploy-blocking review, so a future caller that forwarded an options
61
+ // object carrying a `getFoundingBuilder` key — a batch decide path, a CLI helper,
62
+ // a careless refactor — would otherwise silently swap out the owner check in
63
+ // production. Pure over its inputs, so the gate itself is unit-testable without a
64
+ // database (which the decide path, holding a transaction, is not).
65
+ function resolveOwnerReader(deps = {}, env = process.env) {
66
+ return (typeof deps.getFoundingBuilder === 'function' && env.NODE_ENV === 'test')
67
+ ? deps.getFoundingBuilder
68
+ : api.getFoundingBuilder;
69
+ }
70
+
71
+ const OVERRIDE_TASK_COLUMNS = 'id, status, source, source_ref, merged_into_task_id';
72
+
22
73
  async function createOverrideRequest({ taskId, requesterId, rationale }) {
23
74
  const { rows: taskRows } = await pool.query(
24
- `SELECT id, status FROM tasks WHERE id = $1`,
75
+ `SELECT ${OVERRIDE_TASK_COLUMNS} FROM tasks WHERE id = $1`,
25
76
  [taskId]
26
77
  );
27
78
  if (taskRows.length === 0) {
28
79
  throw Object.assign(new Error('task not found'), { code: 'TASK_NOT_FOUND' });
29
80
  }
30
- if (taskRows[0].status !== 'completed') {
81
+ if (taskRows[0].status !== 'completed' && !gateReleaseTarget(taskRows[0])) {
31
82
  throw Object.assign(new Error('task not in completed state'), {
32
83
  code: 'TASK_NOT_COMPLETED',
33
84
  currentStatus: taskRows[0].status,
@@ -83,6 +134,30 @@ async function decideOverrideRequest({ requestId, archonId, decision, note }, de
83
134
  // (same pattern as credit_grant.mjs). Production callers omit it → module pool.
84
135
  const activePool = (deps.pool && process.env.NODE_ENV === 'test') ? deps.pool : pool;
85
136
  return withTx(async (client) => {
137
+ // Which KIND of override this is — decided from the target task, before the
138
+ // request row is touched, because the two classes differ in who may decide
139
+ // them and in what an approval does. See gateReleaseTarget above.
140
+ const { rows: targetRows } = await client.query(
141
+ `SELECT ${OVERRIDE_TASK_COLUMNS} FROM tasks
142
+ WHERE id = (SELECT task_id FROM task_override_requests WHERE id = $1)`,
143
+ [requestId]
144
+ );
145
+ const isGateRelease = targetRows.length > 0 && gateReleaseTarget(targetRows[0]);
146
+
147
+ // THE NARROWER GATE (ADR 0241 §3). The grader bypass admits any Archon; a
148
+ // gate release admits the PROJECT OWNER and nobody else — "not the builder,
149
+ // not any Metic+, and not the artist" is the owner's own wording, and the
150
+ // reason it is here rather than only at the route is that a second caller
151
+ // (a CLI, a future surface) would otherwise inherit the wider Archon floor.
152
+ if (isGateRelease) {
153
+ const owner = await resolveOwnerReader(deps)();
154
+ if (!owner || String(owner.id) !== String(archonId)) {
155
+ throw Object.assign(new Error('only the project owner may release an artist gate'), {
156
+ code: 'OVERRIDE_OWNER_ONLY',
157
+ });
158
+ }
159
+ }
160
+
86
161
  // RETURNING requested_by_builder_id so the approve path doesn't re-read the
87
162
  // row it just locked (#538): the work was the ORIGINAL REQUESTER's, and both
88
163
  // the credit AND the ship attribution must go to them.
@@ -102,7 +177,41 @@ async function decideOverrideRequest({ requestId, archonId, decision, note }, de
102
177
  const row = rows[0];
103
178
 
104
179
  let confirmResult = null;
105
- if (decision === 'approve') {
180
+ let gateRelease = null;
181
+ if (decision === 'approve' && isGateRelease) {
182
+ // A RELEASE CONFIRMS NOTHING AND PAYS NOTHING. The approved request row IS
183
+ // the release — the deploy gate reads it and stops holding — and the review
184
+ // stays an ordinary open request on the board, because the work it asks for
185
+ // has not been done. Anything else here would pay for work nobody did.
186
+ //
187
+ // And it is NEVER QUIET (ADR 0241 §3). A self-release — the owner releasing
188
+ // a review filed against their own work, which on a solo instance is the
189
+ // normal case and is deliberately PERMITTED rather than impossible — is
190
+ // recorded as such, so the ledger shows who waved what through and why.
191
+ // The rationale already lives on the request row; this names the act.
192
+ const selfOverride = String(row.requested_by_builder_id) === String(archonId);
193
+ gateRelease = { taskId: String(row.task_id), selfOverride };
194
+ const writeAudit = deps.insertAuditLog || api.insertAuditLog;
195
+ // Best-effort: the release is durable in the request row, and losing the
196
+ // audit line must not roll back a decision the owner made. A failure here
197
+ // is loud in the logs rather than silent.
198
+ try {
199
+ await writeAudit({
200
+ builderId: archonId,
201
+ route: '/api/bongos/override-requests/:id/decide',
202
+ method: 'POST',
203
+ requestBodyRedacted: {
204
+ override_kind: 'artist_gate_release',
205
+ self_override: selfOverride,
206
+ task_id: String(row.task_id),
207
+ request_id: String(row.id),
208
+ },
209
+ responseStatus: 200,
210
+ });
211
+ } catch (e) {
212
+ api.logger('lifecycle').error(`[override] artist-gate release ${row.id}: audit write failed — ${e && e.message}`);
213
+ }
214
+ } else if (decision === 'approve') {
106
215
  // BOTH the credit AND the ship attribution (shipped_by, which drives the
107
216
  // builders' hall + peer-vote feed) go to the builder who actually DID the
108
217
  // work — the task's most-recent claim holder — never the approving Archon.
@@ -162,7 +271,7 @@ async function decideOverrideRequest({ requestId, archonId, decision, note }, de
162
271
  }
163
272
  }
164
273
 
165
- return { request: row, confirmResult };
274
+ return { request: row, confirmResult, gateRelease };
166
275
  }, { pool: activePool });
167
276
  }
168
277
 
@@ -178,4 +287,8 @@ module.exports = {
178
287
  createOverrideRequest,
179
288
  decideOverrideRequest,
180
289
  listOverrideRequests,
290
+ // pure — exported so the release class and its authority seam can be asserted
291
+ // without a database (the decide path itself holds a transaction and cannot be)
292
+ gateReleaseTarget,
293
+ resolveOwnerReader,
181
294
  };
@@ -1037,6 +1037,68 @@ async function updateTaskStatus(id, status) {
1037
1037
  await pool.query(`UPDATE tasks SET status = $2 WHERE id = $1`, [id, status]);
1038
1038
  }
1039
1039
 
1040
+ // The cascade-filed ARTIST REVIEW rows that are still open (ADR 0241, task
1041
+ // 1003575) — the state the deploy gate reads. Narrow projection on purpose: the
1042
+ // caller needs an id and a filing date to name what holds a deploy and to apply
1043
+ // the non-retroactivity cutoff, and nothing else belongs on that wire. The TITLE
1044
+ // is deliberately absent: it is builder-written text, nothing consumes it, and a
1045
+ // value that is safe as JSON today is an injection vector the first time a
1046
+ // surface renders it as HTML.
1047
+ //
1048
+ // THIS IS A PRE-FILTER, NOT THE RULE. The SQL narrows to "cascade artist reviews
1049
+ // that are not obviously finished"; the authoritative verdict — including ADR
1050
+ // 0241 §5's non-retroactivity cutoff — is src/bongos/artist-gate.js's pure
1051
+ // `unresolvedArtistReviews`, so the definition of "unresolved" lives in exactly
1052
+ // one place and a change to it cannot land in the JS and miss the SQL. The
1053
+ // pre-filter is deliberately the LOOSER of the two: a row it lets through is
1054
+ // re-judged, a row it drops is never seen again.
1055
+ //
1056
+ // The (source, source_ref) shape is the cascade's own (modules/lifecycle/
1057
+ // cascade.js `cascadeSourceRef`) and is passed IN rather than spelled here, so
1058
+ // this query cannot drift from the rule that files the rows.
1059
+ //
1060
+ // AN APPROVED OVERRIDE REQUEST IS A RELEASE (ADR 0241 §3, db-overrides.js): the
1061
+ // project owner has waved this review past, with a rationale and an audit line,
1062
+ // so it no longer holds a deploy. It is still an open request on the board — the
1063
+ // release buys the pin, not the work — which is why this is excluded HERE rather
1064
+ // than by changing the task's status.
1065
+ //
1066
+ // Indexed by `idx_tasks_source_ref` (core_240): without it this is a
1067
+ // sequential scan of `tasks` on a route any builder can call and every deploy
1068
+ // does call.
1069
+ //
1070
+ // THE CAP CANNOT WEAKEN THE GATE, and it is worth saying why rather than leaving
1071
+ // a reader to work it out. Every row this returns is a HOLDER — resolved and
1072
+ // released reviews are excluded in SQL — so a project with more open reviews than
1073
+ // the cap still gets a held verdict from the first one. What truncation can
1074
+ // affect is the COUNT in the refusal, so the caller is told when it happened
1075
+ // (`truncated`) and says "200+" rather than a number it cannot stand behind.
1076
+ // Oldest-first is the right order for the same reason: the cutoff is a date, so
1077
+ // the rows nearest the gate's own boundary are the ones kept.
1078
+ const ARTIST_REVIEW_SCAN_LIMIT = 200;
1079
+
1080
+ async function listOpenArtistReviews({ source, refPrefix, resolvedStatuses = [], limit = ARTIST_REVIEW_SCAN_LIMIT } = {}) {
1081
+ if (!source || !refPrefix) return { reviews: [], truncated: false };
1082
+ const cap = Math.max(1, Math.min(1000, Number(limit) || ARTIST_REVIEW_SCAN_LIMIT));
1083
+ const { rows } = await pool.query(
1084
+ `SELECT id, status, source, source_ref, created_at, merged_into_task_id
1085
+ FROM tasks t
1086
+ WHERE t.source = $1
1087
+ AND t.source_ref LIKE $2
1088
+ AND t.merged_into_task_id IS NULL
1089
+ AND NOT (t.status = ANY($3::text[]))
1090
+ AND NOT EXISTS (
1091
+ SELECT 1 FROM task_override_requests r
1092
+ WHERE r.task_id = t.id AND r.status = 'approved')
1093
+ ORDER BY t.created_at ASC
1094
+ LIMIT $4`,
1095
+ // cap + 1: one row past the cap is how "there are more" is learned without a
1096
+ // second COUNT query that could disagree with the page it describes.
1097
+ [source, `${refPrefix}%`, resolvedStatuses, cap + 1]
1098
+ );
1099
+ return { reviews: rows.slice(0, cap), truncated: rows.length > cap };
1100
+ }
1101
+
1040
1102
  module.exports = {
1041
1103
  CREDITS_AWARDED_SUBQUERY,
1042
1104
  REWARD_SUGGESTION_CAP,
@@ -1047,6 +1109,7 @@ module.exports = {
1047
1109
  getTask,
1048
1110
  listActiveClaims,
1049
1111
  listClaimableTasks,
1112
+ listOpenArtistReviews,
1050
1113
  listTasks,
1051
1114
  rankCrossDisciplineRecommendations,
1052
1115
  rewardGateAssignment,
@@ -1,7 +1,7 @@
1
1
  -- lifecycle_001_goal_first_leads.sql — backfill goal OWNERS into the member list
2
2
  -- (task 1735). The goal authority model: the OWNER is goals.created_by (fixed at
3
- -- create, moved only by POST /goals/:id/transfer); goal_members.role 'lead' is
4
- -- the MANAGER tier the hall renders. From now on db.createGoal inserts the
3
+ -- create, moved only by POST /goals/:id/transfer); goal_members.membership_kind
4
+ -- 'lead' is the MANAGER tier the hall renders. From now on db.createGoal inserts the
5
5
  -- creator's lead row atomically; this brings the goals created before that rule
6
6
  -- up to the same shape so owners appear in their own member lists.
7
7
  --
@@ -13,14 +13,22 @@
13
13
  -- Data-only (no schema change), idempotent (the upsert converges), and
14
14
  -- replay-safe on a fresh DB (zero goals → zero rows; no builder id is
15
15
  -- hard-coded — learning 1238).
16
+ --
17
+ -- THE COLUMN IS `membership_kind` (task 1003089, ADR 0177). This file wrote the
18
+ -- legacy `role` until core_240 dropped it, and it is the reason that DROP was not
19
+ -- a pure deletion: module migrations run AFTER the core ones on every fresh
20
+ -- install, so a stale name here would have broken `cloudbongos init` for new
21
+ -- instances while staying invisible on existing ones (already recorded in
22
+ -- `schema_migrations`, so never re-run). Zero-row on a fresh DB, but a missing
23
+ -- column is a plan-time error, not a row-time one.
16
24
 
17
25
  BEGIN;
18
26
 
19
- INSERT INTO goal_members (goal_id, builder_id, role)
27
+ INSERT INTO goal_members (goal_id, builder_id, membership_kind)
20
28
  SELECT g.id, g.created_by, 'lead'
21
29
  FROM goals g
22
30
  WHERE g.created_by IS NOT NULL
23
- ON CONFLICT (goal_id, builder_id) DO UPDATE SET role = 'lead';
31
+ ON CONFLICT (goal_id, builder_id) DO UPDATE SET membership_kind = 'lead';
24
32
 
25
33
  INSERT INTO schema_migrations (version) VALUES ('lifecycle_001_goal_first_leads')
26
34
  ON CONFLICT (version) DO NOTHING;
@@ -25,7 +25,8 @@
25
25
  "visuals",
26
26
  "kickoff-seed",
27
27
  "rot",
28
- "project-settings"
28
+ "project-settings",
29
+ "artist-gate"
29
30
  ],
30
31
  "pollers": [
31
32
  "publish-reconciler"
@@ -0,0 +1,49 @@
1
+ // modules/lifecycle/pg-constraint-fail.js — turn a Postgres CHECK violation into a
2
+ // 400 that names the constraint, instead of an opaque 500 (task 1002845).
3
+ //
4
+ // WHY. A range the route validator accepts but the DB CHECK rejects is a
5
+ // self-inflicted wound: the INSERT raises SQLSTATE 23514, the generic catch logs
6
+ // it server-side and answers `create_failed` / "internal error", and the caller —
7
+ // an agent, usually — has no idea which field was wrong. Idea 1000456 records
8
+ // roughly four blind retries spent on exactly that during task 1604, and task
9
+ // 1002845 measured it still happening: POST /tasks {manual_degree:0} → 500
10
+ // "internal error".
11
+ //
12
+ // The first fix is always to make the validator match the DB so the INSERT never
13
+ // sees a bad value (task 1233 did that for `priority`; task 1002845 for
14
+ // `manual_degree`). This is the BACKSTOP for the next mismatch nobody has found
15
+ // yet — it cannot know which field is at fault, but the constraint name says so.
16
+ //
17
+ // WHAT IS SAFE TO ECHO. `err.constraint` only — a schema identifier the operator
18
+ // wrote, carrying no request or row data. NEVER `err.detail`: Postgres puts the
19
+ // offending ROW VALUES in it ("Failing row contains (1, secret, ...)"), so echoing
20
+ // it would leak other columns of the caller's own record into an error body, and
21
+ // on a shared instance potentially values the caller never sent.
22
+
23
+ 'use strict';
24
+
25
+ // Postgres SQLSTATE for a CHECK constraint violation.
26
+ const CHECK_VIOLATION = '23514';
27
+
28
+ // describeCheckViolation(err) → { code, status, message, details } | null
29
+ //
30
+ // Pure. Returns null for anything that is not a CHECK violation, so a caller can
31
+ // use it as a guard and fall through to its existing generic handling.
32
+ function describeCheckViolation(err) {
33
+ if (!err || err.code !== CHECK_VIOLATION) return null;
34
+ const constraint = typeof err.constraint === 'string' && err.constraint.trim()
35
+ ? err.constraint.trim()
36
+ : null;
37
+ return {
38
+ code: 'constraint_violation',
39
+ status: 400,
40
+ message: constraint
41
+ // Deliberately does not guess WHICH field: one CHECK can span several
42
+ // columns, and naming the wrong one is worse than naming none.
43
+ ? `a value in this request violates the database constraint "${constraint}" — check the field it guards against the accepted range`
44
+ : 'a value in this request violates a database constraint',
45
+ details: { constraint },
46
+ };
47
+ }
48
+
49
+ module.exports = { describeCheckViolation, CHECK_VIOLATION };
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ // modules/lifecycle/routes/artist-gate.js — the artist gate's STATE, in one read
4
+ // (ADR 0241, task 1003575).
5
+ //
6
+ // WHY A ROUTE. The gate's two halves live on opposite sides of the wire. The
7
+ // POLICY (`artist_gate`) is pushed into this instance's branding pack by the
8
+ // platform and read by the core; the STATE it reads — which artist review tasks
9
+ // are still open — is this instance's own board. The thing that has to consult
10
+ // both is `scripts/gds/go-live.js`, the pin move, which runs from a core checkout
11
+ // against a LIVE instance and can see neither from where it stands. So the
12
+ // instance answers the question about itself, in one call, and the deploy script
13
+ // decides what to do with the answer.
14
+ //
15
+ // IT DECIDES NOTHING AND HOLDS NOTHING. This route is a read: it reports the mode,
16
+ // the open reviews and the halt sentence that would be shown. Nothing about a ship,
17
+ // a land or a grade passes through here — ADR 0241's whole reconciliation with ADR
18
+ // 0162 is that the artist's authority attaches to the DEPLOY, and a read surface
19
+ // that could reach the ship path would smuggle that back in.
20
+ //
21
+ // MODULE BOUNDARY: reaches core only through the published doorway
22
+ // `../../../src/module-api` (which carries the one artist-gate reader); `../db` is
23
+ // this module's own data layer.
24
+
25
+ const express = require('express');
26
+ const api = require('../../../src/module-api');
27
+ const db = require('../db');
28
+
29
+ const log = api.logger('lifecycle');
30
+
31
+ module.exports = function buildArtistGateRouter() {
32
+ const router = express.Router();
33
+
34
+ // GET /artist-gate — this project's artist strictness and what it is holding.
35
+ //
36
+ // rank: any authenticated builder. The payload is ordinary board state — a
37
+ // policy value the instance already publishes as its own behaviour, and task
38
+ // ids/titles any builder can read on the hall — so a floor above requireBuilder
39
+ // would gate a fact the board already shows. Nothing here is writable.
40
+ //
41
+ // Response:
42
+ // mode 'off' | 'advisory' | 'strict' the project's setting as resolved
43
+ // since ISO string | null when it was last RAISED to strict
44
+ // holds boolean does anything hold a deploy now?
45
+ // truncated boolean `reviews` hit the scan cap — a floor
46
+ // reviews [{ id, created_at }] the open reviews that count
47
+ // halt { code, message, detail } | null the refusal, pre-composed
48
+ //
49
+ // `halt` is composed HERE rather than by the caller on purpose: ADR 0241 §4's
50
+ // second guard is that the refusal must name what holds the deploy and which
51
+ // control releases it, and a sentence assembled independently by each caller is
52
+ // a sentence that eventually says something different. One composer, one wording.
53
+ router.get('/artist-gate', api.requireBuilder, async (req, res) => {
54
+ try {
55
+ const gate = api.artistGate;
56
+ const mode = gate.artistGateMode();
57
+ const since = gate.artistGateSince();
58
+
59
+ // At 'off' no reviews are filed, so there is nothing to read and nothing to
60
+ // hold — answer without touching the database.
61
+ const scan = mode === 'off' ? { reviews: [], truncated: false } : await db.listOpenArtistReviews({
62
+ source: gate.ARTIST_REVIEW_SOURCE,
63
+ refPrefix: gate.ARTIST_REVIEW_REF_PREFIX,
64
+ resolvedStatuses: gate.RESOLVED_REVIEW_STATUSES,
65
+ });
66
+ const open = gate.unresolvedArtistReviews(scan.reviews, { since });
67
+ // `truncated` only survives if the cutoff did not itself empty the page:
68
+ // reporting "200+" over a list the non-retroactivity filter cut to three
69
+ // would be a worse lie than the number it replaced.
70
+ const truncated = scan.truncated && open.length === scan.reviews.length;
71
+ const halt = gate.artistGateHalt({ mode, reviews: open, truncated });
72
+
73
+ // A gate read is a policy comparison — a cached one is wrong the moment a
74
+ // review is dismissed (the project-settings / rot.js precedent).
75
+ res.set('Cache-Control', 'no-store');
76
+ res.json({
77
+ ok: true,
78
+ mode,
79
+ since,
80
+ holds: !!halt,
81
+ // A floor, not a census, when the scan hit its cap — said out loud so a
82
+ // caller never reads this list as "and that is all of them".
83
+ truncated,
84
+ // IDS AND DATES ONLY — deliberately no TITLE. A title is builder-written
85
+ // board text, and it was the only untrusted string this payload carried;
86
+ // nothing consumes it (go-live names ids, and a reader who wants the title
87
+ // has the board). Carrying it would have left a value that is safe as JSON
88
+ // today and becomes an injection vector the first time some surface renders
89
+ // it as HTML. Dropping it is the fix rather than escaping it, because an
90
+ // HTML-escaped value in a JSON API is wrong for every correct consumer.
91
+ reviews: open.map((t) => ({ id: String(t.id), created_at: t.created_at })),
92
+ halt,
93
+ });
94
+ } catch (err) {
95
+ log.error('[lifecycle] GET /artist-gate', err);
96
+ res.fail('artist_gate_read_failed', { status: 500, message: 'internal error' });
97
+ }
98
+ });
99
+
100
+ return router;
101
+ };
@@ -85,9 +85,21 @@ function taskNotReadyHint(taskId, currentStatus) {
85
85
  // Ordering is the fix: the universally-available command leads, /merge-mode is
86
86
  // marked as needing a push credential rather than dropped (it is still the right
87
87
  // tool on a credentialed machine), and /builder-release stays as the give-up exit.
88
+ //
89
+ // AND THE VERB CHANGED (task 1002788). It used to say "rebase the branch and
90
+ // re-ship", which is advice that can DEADLOCK the builder who follows it. The
91
+ // branch is already published; server-mediated publish is append-only by
92
+ // deliberate design (github-push.js: "Refuse non-fast-forward implicitly (no
93
+ // --force, ever)"), so a rebase rewrites history the server already holds and
94
+ // every subsequent publish is rejected non-fast-forward. The builder is then
95
+ // gated from new claims AND unable to land the one they hold. Reproduced
96
+ // end-to-end on task 1002485. `git merge origin/main` reaches the same
97
+ // up-to-date branch and stays a fast-forward, which is why the recovery text in
98
+ // ship-land.js already names merge and explicitly says "Do NOT reset or
99
+ // re-rebase". This is the same advice, given BEFORE the deadlock instead of after.
88
100
  function rebaseRequiredHint(rebaseTasks) {
89
101
  const ids = (rebaseTasks || []).map((t) => `task ${t.id}`);
90
- return `Your queue is gated: ${ids.length} confirmed task(s) await your rebase (${ids.join(', ') || 'unknown'}). 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. To give up on one instead, /builder-release it. If CI is simply backed up, waiting is correct: a task already landed clears the gate on its own.`;
102
+ return `Your queue is gated: ${ids.length} confirmed task(s) are waiting to land (${ids.join(', ') || 'unknown'}). Clear it with /strand-fix N (works on any machine), or bring the branch up to date with \`git merge origin/main\` and re-ship — MERGE, never rebase: server-mediated publish is append-only, and rewriting a published branch makes it un-pushable (task 1002788). /merge-mode only if this machine has a GitHub push credential. To give up on one instead, /builder-release it. If CI is simply backed up, waiting is correct: a task already landed clears the gate on its own.`;
91
103
  }
92
104
 
93
105
  // task 1642 (+ the worktree-keying fix) — the pure one-active-claim decision.
@@ -54,6 +54,12 @@ function registerSeams() {
54
54
  getTask: (id) => db.getTask(id),
55
55
  findTaskBySourceRef: (ref) => db.findTaskBySourceRef(ref),
56
56
  createTask: (spec) => db.createTask(spec),
57
+ // The project's artist gate (ADR 0241, task 1003575). Resolved through the
58
+ // doorway's one reader rather than from branding().project, so the level that
59
+ // decides whether a review is FILED and the level that decides whether one
60
+ // HOLDS a deploy can never be read differently. At 'off' the artist-review
61
+ // rule files nothing.
62
+ artistGateMode: () => api.artistGate.artistGateMode(),
57
63
  log: api.logger ? api.logger('lifecycle') : null,
58
64
  }));
59
65
  }
@@ -26,6 +26,7 @@ const { classifyTaskKind, VALID_KINDS, VALID_DISCIPLINES, VOTABLE_TASK_STATUSES,
26
26
  const { isModuleKey, moduleKeys } = api.moduleScopeMap;
27
27
  const { validateOrRespond, LIMITS, parseId } = api;
28
28
  const { badKind, detectsMigration } = require('./task-route-helpers.js');
29
+ const { describeCheckViolation } = require('../pg-constraint-fail.js');
29
30
  const { goalNotOpenRefusal } = require('./goal-route-authz.js');
30
31
  // task 1003208: structured logging (pino via the doorway) — was console.*.
31
32
  const log = api.logger('lifecycle');
@@ -275,6 +276,12 @@ module.exports = function registerTaskWriteRoutes(router) {
275
276
  });
276
277
  } catch (err) {
277
278
  log.error('[gds] POST /tasks', err);
279
+ // task 1002845: a CHECK violation is a CLIENT-correctable problem wearing a
280
+ // 500. Name the constraint so the next validator/DB range mismatch is
281
+ // self-diagnosing instead of costing another round of blind retries. The
282
+ // constraint name only — err.detail carries the failing row's values.
283
+ const cv = describeCheckViolation(err);
284
+ if (cv) return res.fail(cv.code, { status: cv.status, message: cv.message, details: cv.details });
278
285
  res.fail('create_failed', { status: 500, message: 'internal error' });
279
286
  }
280
287
  });
@@ -997,6 +997,12 @@ module.exports = function buildTasksRouter() {
997
997
  // asks an Archon to flip the task to confirmed anyway. Audit-logged via the
998
998
  // global write-audit middleware. Open requests surface in the Archon queue.
999
999
  // rank: any-builder — opens a request; the decision still rests with Archon.
1000
+ //
1001
+ // The SAME route also opens the artist gate's release (ADR 0241 §3, task
1002
+ // 1003575): a request whose target is an unresolved artist review is admitted
1003
+ // at ANY status, because such a review sits at `backlog` and a completed-only
1004
+ // check would make the one documented escape hatch unusable for the exact case
1005
+ // it was written for. Who may DECIDE it is narrower — see the decide route.
1000
1006
  router.post('/tasks/:id/override-request', auth.requireBuilder, async (req, res) => {
1001
1007
  const id = parseId(req, res, { code: 'bad_task_id' });
1002
1008
  if (id === null) return;
@@ -1328,6 +1334,14 @@ module.exports = function buildTasksRouter() {
1328
1334
  // updates; the task stays at completed and the builder must release or
1329
1335
  // re-ship after fixing whatever the grader caught.
1330
1336
  // rank: archon — grader-bypass authority; same gate as POST /tasks/:id/confirm.
1337
+ //
1338
+ // ONE EXCEPTION, and it is NARROWER than this gate rather than wider: a request
1339
+ // whose target is an unresolved ARTIST REVIEW is a gate release (ADR 0241 §3,
1340
+ // db-overrides.js), and only the PROJECT OWNER may decide it. That check lives
1341
+ // in decideOverrideRequest, not here, so it cannot be bypassed by a second
1342
+ // caller; this route only renders its refusal (403 override_owner_only).
1343
+ // Approving one confirms nothing and pays nothing — it stops the review holding
1344
+ // the DEPLOY and leaves it open on the board.
1331
1345
  router.post('/override-requests/:id/decide', auth.requireBuilder, auth.requirePermission('override_request.decide'), async (req, res) => {
1332
1346
  const id = parseId(req, res, { code: 'bad_request_id' });
1333
1347
  if (id === null) return;
@@ -1351,6 +1365,16 @@ module.exports = function buildTasksRouter() {
1351
1365
  if (err.code === 'REQUEST_NOT_OPEN') {
1352
1366
  return res.fail('request_not_open', 409);
1353
1367
  }
1368
+ if (err.code === 'OVERRIDE_OWNER_ONLY') {
1369
+ // The artist gate's release is narrower than this route's Archon floor
1370
+ // (ADR 0241 §3): the PROJECT OWNER decides it, nobody else. 403 with the
1371
+ // reason spelled out — an Archon refused here is not a bug, and a refusal
1372
+ // that does not say which authority is missing sends them hunting.
1373
+ return res.fail('override_owner_only', {
1374
+ status: 403,
1375
+ message: 'Releasing a deploy held by an artist review is the project owner\'s call alone — not an Archon\'s (ADR 0241). Resolve or dismiss the review instead, or ask the owner to decide this request.',
1376
+ });
1377
+ }
1354
1378
  if (err.code === 'OVERRIDE_NO_REQUESTER') {
1355
1379
  // Data-integrity guard (#538): an approved override with no requester to
1356
1380
  // credit. Should never happen (NOT NULL column); refuse rather than
@@ -173,9 +173,18 @@ const VALID_DISCIPLINES = new Set(['engineer', 'artist', 'ideator', 'ui', 'uncla
173
173
  // `create_failed` constraint-violation message, and taught the context pack the
174
174
  // wrong range — task 1233.) `convention` is retained so the pack can surface the
175
175
  // human-facing "use P1–P5 tiers" label alongside the accepted range.
176
+ // task 1002845: `manual_degree` had the SAME defect priority had, and is fixed the
177
+ // same way — tighten the validator to the DB, never migrate the DB to the
178
+ // validator. The CHECK is `manual_degree BETWEEN 1 AND 5`
179
+ // (migrations/003_pms.sql, never widened since), so min:0 let a 0 pass the route
180
+ // and then die in the INSERT as a 23514, which the generic catch answered as an
181
+ // opaque 500 `create_failed` with the constraint name never reaching the caller.
182
+ // Measured against the live API before the fix: POST /tasks {manual_degree:0}
183
+ // → 500 "internal error". Idea 1000456 records roughly four blind retries spent
184
+ // on this during task 1604.
176
185
  const TASK_FIELD_BOUNDS = {
177
186
  priority: { min: 1, max: 5, convention: { min: 1, max: 5, label: 'P1–P5 planning tiers' } },
178
- manual_degree: { min: 0, max: 5 },
187
+ manual_degree: { min: 1, max: 5 },
179
188
  };
180
189
 
181
190
  // Task statuses that can receive peer-acclaim votes (a work must be at least
@@ -250,5 +250,48 @@ await test('the review inherits the shipped task goal and version, not goal 1000
250
250
  assert.equal(matched[0].spec.versionId, 'BONGOS-V1');
251
251
  });
252
252
 
253
+ // --- the project's artist gate decides whether it is filed at all (ADR 0241) --
254
+
255
+ await test("artist_gate 'off' files NO artist review — the one level at which the rule is silent", () => {
256
+ assert.equal(cascade.cascadesFor(shippedCopy(), cascade.CASCADES, { artistGate: 'off' }).matched.length, 0);
257
+ // The other two levels both FILE; what separates them is whether an open review
258
+ // holds a deploy, which is not this file's business at all.
259
+ assert.equal(cascade.cascadesFor(shippedCopy(), cascade.CASCADES, { artistGate: 'advisory' }).matched.length, 1);
260
+ assert.equal(cascade.cascadesFor(shippedCopy(), cascade.CASCADES, { artistGate: 'strict' }).matched.length, 1);
261
+ });
262
+
263
+ await test('an UNREAD policy files — silence is never read as an opt-out', () => {
264
+ // No context at all (an old caller), and an explicitly-undefined level (a reader
265
+ // that threw) must both behave like the gate's default, which is 'strict'.
266
+ assert.equal(cascade.cascadesFor(shippedCopy()).matched.length, 1);
267
+ assert.equal(cascade.cascadesFor(shippedCopy(), cascade.CASCADES, {}).matched.length, 1);
268
+ assert.equal(cascade.cascadesFor(shippedCopy(), cascade.CASCADES, { artistGate: undefined }).matched.length, 1);
269
+ });
270
+
271
+ await test('a gate reader that THROWS costs the run nothing — it is reported, and the review is still filed', async () => {
272
+ const created = [];
273
+ const report = await cascade.runCascadesForShip({ id: '7001' }, {
274
+ getTask: async () => shippedCopy(),
275
+ findTaskBySourceRef: async () => null,
276
+ createTask: async (spec) => { created.push(spec); return { id: '7002' }; },
277
+ artistGateMode: () => { throw new Error('pack unreadable'); },
278
+ });
279
+ assert.equal(created.length, 1, 'an unreadable policy must not silently behave like off');
280
+ assert.ok(report.errors.some((e) => /artistGateMode\(\) threw/.test(e)), 'and it says so');
281
+ });
282
+
283
+ await test("runCascadesForShip honours 'off' end to end — nothing is created, nothing errors", async () => {
284
+ let creates = 0;
285
+ const report = await cascade.runCascadesForShip({ id: '7001' }, {
286
+ getTask: async () => shippedCopy(),
287
+ findTaskBySourceRef: async () => null,
288
+ createTask: async () => { creates++; return { id: '7002' }; },
289
+ artistGateMode: () => 'off',
290
+ });
291
+ assert.equal(creates, 0);
292
+ assert.deepEqual(report.created, []);
293
+ assert.deepEqual(report.errors, [], 'a project that switched the gate off has no problem to report');
294
+ });
295
+
253
296
  console.log(`\n${passed} passed, ${failed} failed`);
254
297
  process.exit(failed ? 1 : 0);