@bongos/core 1.19.714 → 1.19.724

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (422) hide show
  1. package/.bongos-core.json +488 -413
  2. package/.claude/skills/backlog-review/SKILL.md +3 -3
  3. package/.claude/skills/blocker-review/SKILL.md +1 -1
  4. package/.claude/skills/blocker-solve/SKILL.md +2 -2
  5. package/.claude/skills/builder-reauth/SKILL.md +1 -1
  6. package/.claude/skills/builder-redteam/SKILL.md +1 -1
  7. package/.claude/skills/builder-ship/SKILL.md +1 -1
  8. package/.claude/skills/builder-start/SKILL.md +1 -1
  9. package/.claude/skills/design/SKILL.md +2 -2
  10. package/.claude/skills/feedback/SKILL.md +1 -1
  11. package/.claude/skills/goal-create/SKILL.md +1 -1
  12. package/.claude/skills/goal-review/SKILL.md +2 -2
  13. package/.claude/skills/idea-triage/SKILL.md +5 -5
  14. package/.claude/skills/merge-mode/SKILL.md +4 -4
  15. package/.claude/skills/new-project/SKILL.md +17 -17
  16. package/.claude/skills/planning-session/SKILL.md +11 -11
  17. package/.claude/skills/recall/SKILL.md +2 -2
  18. package/.claude/skills/strand-fix/SKILL.md +1 -1
  19. package/.devcontainer/README.md +2 -2
  20. package/CONTRIBUTING.md +2 -2
  21. package/GOVERNANCE.md +4 -4
  22. package/clients/bongos-client/README.md +1 -1
  23. package/clients/bongos-client/bongos-client.global.js +4 -0
  24. package/clients/bongos-client/index.cjs +4 -0
  25. package/clients/bongos-client/index.d.ts +5 -0
  26. package/clients/bongos-client/index.mjs +4 -0
  27. package/config/branding.neutral.json +4 -2
  28. package/docs/adr/0001-stack-choice-single-vps.md +2 -2
  29. package/docs/adr/0002-digitalocean-over-hetzner.md +1 -1
  30. package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
  31. package/docs/adr/0009-autotile-architecture.md +1 -1
  32. package/docs/adr/0011-terrain-id-rendering-model.md +3 -3
  33. package/docs/adr/0013-paths-as-object-overlays-with-autotile-dispatch.md +1 -1
  34. package/docs/adr/0014-families-canonical-over-vocabulary.md +1 -1
  35. package/docs/adr/0015-task-dependencies-and-auto-promotion.md +1 -1
  36. package/docs/adr/0016-trust-boundary-server-enforced-permissions.md +1 -1
  37. package/docs/adr/0018-three-rank-model-goes-live.md +3 -3
  38. package/docs/adr/0020-security-model-public-stub.md +9 -9
  39. package/docs/adr/0021-per-builder-skill-model-preferences.md +2 -2
  40. package/docs/adr/0022-secrets-policy.md +5 -5
  41. package/docs/adr/0024-cloneable-repo-local-first-memory.md +3 -3
  42. package/docs/adr/0024-multi-agent-system-architecture.md +10 -10
  43. package/docs/adr/0025-offsite-backup-vendor-digitalocean-spaces.md +1 -1
  44. package/docs/adr/0025-structured-criterion-task-link.md +1 -1
  45. package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +13 -13
  46. package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +14 -14
  47. package/docs/adr/0029-status-page-outage-resilience.md +1 -1
  48. package/docs/adr/0030-builders-hall-live-updates.md +1 -1
  49. package/docs/adr/0031-cloud-dev-environments-for-builders.md +7 -7
  50. package/docs/adr/0032-discord-mirror-of-gds.md +3 -3
  51. package/docs/adr/0033-discord-bot-service-principal.md +4 -4
  52. package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
  53. package/docs/adr/0035-builder-onboarding-three-paths.md +4 -4
  54. package/docs/adr/0036-builders-subdomain-and-ui-reorg.md +1 -1
  55. package/docs/adr/0037-discord-channel-management.md +1 -1
  56. package/docs/adr/0038-chromebook-ttyd-cloudflare-tunnel.md +5 -5
  57. package/docs/adr/0039-setup-first-onboarding-ux.md +6 -6
  58. package/docs/adr/0040-remote-control-default-browser-onramp.md +3 -3
  59. package/docs/adr/0041-temporary-grader-bypass-killswitch.md +2 -2
  60. package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +5 -5
  61. package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +2 -2
  62. package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
  63. package/docs/adr/0044-per-box-live-game-preview.md +1 -1
  64. package/docs/adr/0046-sandbox-first-review-gate.md +1 -1
  65. package/docs/adr/0047-discord-bugs-intake-anti-abuse.md +1 -1
  66. package/docs/adr/0049-split-parallel-safety-contract.md +1 -1
  67. package/docs/adr/0050-device-flow-admission-invite-gated-by-default.md +2 -2
  68. package/docs/adr/0051-full-session-transcript-corpus.md +2 -2
  69. package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +1 -1
  70. package/docs/adr/0053-scoped-dev-box-session.md +1 -1
  71. package/docs/adr/0054-session-token-reward-cost-plus.md +7 -7
  72. package/docs/adr/0055-server-mediated-branch-publish.md +2 -2
  73. package/docs/adr/0056-prod-deploy-script-mirror-and-healthcheck-retry.md +3 -3
  74. package/docs/adr/0057-container-cost-ledger.md +1 -1
  75. package/docs/adr/0057-discord-archon-approval-channels.md +2 -2
  76. package/docs/adr/0058-automatic-gate-surface-gatekeeper.md +6 -6
  77. package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +11 -11
  78. package/docs/adr/0060-gds-retrieval-layer.md +3 -3
  79. package/docs/adr/0061-context-layer-decomposition.md +2 -2
  80. package/docs/adr/0062-medusa-instance-model-decoupling.md +5 -5
  81. package/docs/adr/0063-generated-repo-map-symbol-skeleton.md +2 -2
  82. package/docs/adr/0064-rename-medusa-to-cloud-bongos.md +3 -3
  83. package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +5 -5
  84. package/docs/adr/0066-generated-file-map-sections.md +2 -2
  85. package/docs/adr/0067-session-start-fast-forward-pull.md +1 -1
  86. package/docs/adr/0068-shared-art-key-for-newcomers.md +1 -1
  87. package/docs/adr/0069-hall-live-channel-sse-decouple.md +3 -3
  88. package/docs/adr/0070-deterministic-session-card-delivery.md +1 -1
  89. package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +2 -2
  90. package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +2 -2
  91. package/docs/adr/0072-dev-box-code-staleness-visibility.md +2 -2
  92. package/docs/adr/0073-builder-needs-signal-and-byok-gemini-key.md +3 -3
  93. package/docs/adr/0073-secrets-scan-exclude-uri-detector.md +2 -2
  94. package/docs/adr/0076-in-session-deterministic-compaction.md +2 -2
  95. package/docs/adr/0077-content-addressed-llm-cache.md +2 -2
  96. package/docs/adr/0078-deterministic-first-cron.md +3 -3
  97. package/docs/adr/0079-value-gated-autonomous-engagement.md +2 -2
  98. package/docs/adr/0080-llm-cache-api-transport-and-trust-model.md +3 -3
  99. package/docs/adr/0081-tool-agnostic-design-layer.md +3 -3
  100. package/docs/adr/0082-server-side-merge-conflict-auto-resolution.md +2 -2
  101. package/docs/adr/0083-modular-architecture-strangler-build-plan.md +3 -3
  102. package/docs/adr/0085-remove-default-per-builder-spend-cap.md +2 -2
  103. package/docs/adr/0086-goal-scoped-work-hierarchy.md +15 -15
  104. package/docs/adr/0087-bongos-app-architecture-and-handoff-contract.md +5 -5
  105. package/docs/adr/0087-compete-on-governance-not-tooling-cursor.md +1 -1
  106. package/docs/adr/0088-consistent-character-generative-animation.md +5 -5
  107. package/docs/adr/0089-modular-multi-model-adversarial-grader.md +15 -15
  108. package/docs/adr/0090-metic-task-authoring.md +8 -8
  109. package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +3 -3
  110. package/docs/adr/0092-hosting-module-and-on-prem-target.md +11 -11
  111. package/docs/adr/0093-tranche-2-core-carve-sequence.md +5 -5
  112. package/docs/adr/0094-repo-atlas-self-drawing-codebase-map.md +1 -1
  113. package/docs/adr/0095-borrowed-memory-and-retrieval-concepts.md +6 -6
  114. package/docs/adr/0095-cross-agent-context-management.md +5 -5
  115. package/docs/adr/0096-require-reward-before-workable.md +5 -5
  116. package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +2 -2
  117. package/docs/adr/0097-retroactive-reward-backfill.md +4 -4
  118. package/docs/adr/0098-public-mirror-publish-manifest.md +3 -3
  119. package/docs/adr/0099-delayed-redacted-mirror-export.md +1 -1
  120. package/docs/adr/0100-two-repo-instance-model.md +6 -6
  121. package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +9 -9
  122. package/docs/adr/0102-finish-bounding-the-kernel.md +1 -1
  123. package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +6 -6
  124. package/docs/adr/0103-gdsv4-bongos-consolidation.md +3 -3
  125. package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +3 -3
  126. package/docs/adr/0105-instance-seed-migrations-out-of-core.md +2 -2
  127. package/docs/adr/0106-goal-owner-manager-authority.md +2 -2
  128. package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +6 -6
  129. package/docs/adr/0108-instance-composition-configurable-root.md +6 -6
  130. package/docs/adr/0109-self-describing-openapi-and-hosted-docs.md +3 -3
  131. package/docs/adr/0110-untrack-whole-file-generated-nav-docs.md +1 -1
  132. package/docs/adr/0111-instance-hosting-provisioning-module.md +18 -18
  133. package/docs/adr/0112-private-goals-invitations-join-requests.md +7 -7
  134. package/docs/adr/0113-sandbox-studio-descope-superseded-by-0044-0052.md +1 -1
  135. package/docs/adr/0114-instance-docs-discovery-contract.md +4 -4
  136. package/docs/adr/0115-scheduled-routines-portability.md +1 -1
  137. package/docs/adr/0117-api-versioning-and-deprecation-policy.md +1 -1
  138. package/docs/adr/0118-api-contract-source-of-truth-code-first.md +1 -1
  139. package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +2 -2
  140. package/docs/adr/0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md +1 -1
  141. package/docs/adr/0122-onboarding-diagram-renders-deploy-rendered-off-git.md +3 -3
  142. package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +1 -1
  143. package/docs/adr/0124-typed-api-response-bodies.md +2 -2
  144. package/docs/adr/0125-control-plane-scaffolds-standalone-instance-repo.md +2 -2
  145. package/docs/adr/0126-dedicated-cloudbongos-control-plane-droplet.md +3 -3
  146. package/docs/adr/0127-cross-box-fleet-observability.md +1 -1
  147. package/docs/adr/0128-provisioning-runner-privilege-and-db-auth-model.md +3 -3
  148. package/docs/adr/0129-cloudbongos-control-plane-pull-deploy.md +4 -4
  149. package/docs/adr/0130-provision-remote-exec-over-ssh.md +4 -4
  150. package/docs/adr/0131-rank-scoped-skill-visibility.md +2 -2
  151. package/docs/adr/0132-cohost-fleet-control-plane-migration.md +2 -2
  152. package/docs/adr/0133-github-app-manifest-onboarding-signin.md +1 -1
  153. package/docs/adr/0134-private-first-npm-distribution.md +3 -3
  154. package/docs/adr/0135-module-upstream-submission-interim-queue.md +1 -1
  155. package/docs/adr/0136-update-channel-subscription-policy.md +2 -2
  156. package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +6 -6
  157. package/docs/adr/0138-bundled-module-deps-declared-per-module.md +4 -4
  158. package/docs/adr/0142-project-context-is-prose-only.md +2 -2
  159. package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +2 -2
  160. package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +2 -2
  161. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +2 -2
  162. package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +4 -4
  163. package/docs/adr/0147-standalone-live-docs-regen.md +1 -1
  164. package/docs/adr/0149-bongos-dev-local-launcher.md +1 -1
  165. package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +3 -3
  166. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +20 -20
  167. package/docs/adr/0152-landing-gate-served-by-the-core-app.md +2 -2
  168. package/docs/adr/0152-metic-task-abandonment.md +7 -7
  169. package/docs/adr/0153-sequential-chain-runner.md +1 -1
  170. package/docs/adr/0154-goal-owner-criterion-authoring.md +7 -7
  171. package/docs/adr/0155-adopt-private-repo-widen-oauth-scope.md +2 -2
  172. package/docs/adr/0156-user-set-display-name-wins.md +1 -1
  173. package/docs/adr/0157-archon-is-rank-and-identity-only.md +10 -10
  174. package/docs/adr/0158-choke-point-verdict-taxonomy.md +1 -1
  175. package/docs/adr/0159-merge-gate-reconciliation.md +1 -1
  176. package/docs/adr/0160-main-audit-repo-verifiable-caller.md +1 -1
  177. package/docs/adr/0161-publish-on-merge.md +2 -2
  178. package/docs/adr/0163-legacy-display-name-resolution.md +1 -1
  179. package/docs/adr/0165-instances-hookless-by-default.md +1 -1
  180. package/docs/adr/0166-module-maintenance-and-deprecation-policy.md +3 -3
  181. package/docs/adr/0167-gate-trust-link-agpl-default.md +2 -2
  182. package/docs/adr/0167-module-catalog-source-vs-provenance.md +3 -3
  183. package/docs/adr/0169-bongos-serve-server-bring-up.md +1 -1
  184. package/docs/adr/0171-profile-rollup-join-and-consent.md +1 -1
  185. package/docs/adr/0172-editable-rank-roles-substrate-axis.md +2 -2
  186. package/docs/adr/0172-per-craft-compensation-ideator-credit-lane.md +4 -4
  187. package/docs/adr/0173-legacy-multi-ship-shortfall-not-backfilled.md +1 -1
  188. package/docs/adr/0174-government-terminology-ranks-not-roles.md +1 -1
  189. package/docs/adr/0177-goal-membership-kind.md +3 -3
  190. package/docs/adr/0178-the-copy-desk-flag-and-queue.md +1 -1
  191. package/docs/adr/0179-board-membership-predicate-grammar.md +1 -1
  192. package/docs/adr/0181-teardown-state-gate-and-address-retention.md +1 -1
  193. package/docs/adr/0182-project-visibility-two-axes.md +1 -1
  194. package/docs/adr/0183-criteria-close-themselves.md +1 -1
  195. package/docs/adr/0184-ship-requires-an-assurance.md +1 -1
  196. package/docs/adr/0185-spark-handoff-credit-split.md +1 -1
  197. package/docs/adr/0186-per-project-idea-templates.md +1 -1
  198. package/docs/adr/0187-collab-four-decisions.md +1 -1
  199. package/docs/adr/0187-oauth-handshake-cookie-lifetime.md +1 -1
  200. package/docs/adr/0188-patrol-runs-on-ci-not-the-server.md +2 -2
  201. package/docs/adr/0189-baseline-floors-and-the-freeze-detector.md +2 -2
  202. package/docs/adr/0191-majority-and-the-author-who-may-not-decide-alone.md +1 -1
  203. package/docs/adr/0192-a-category-orients-and-authorises-nothing.md +2 -2
  204. package/docs/adr/0193-pause-task-scoped-box-slices.md +1 -1
  205. package/docs/adr/0195-adr-numbers-are-checked-like-migration-numbers.md +2 -2
  206. package/docs/adr/0196-chrome-world-token-contract-and-the-landing-sheets.md +2 -2
  207. package/docs/adr/0197-the-ui-design-module.md +1 -1
  208. package/docs/adr/0198-third-party-skill-vendoring-policy.md +1 -1
  209. package/docs/adr/0203-doorway-changelog-out-of-module-api.md +2 -2
  210. package/docs/adr/0204-the-apex-is-dark-only.md +2 -2
  211. package/docs/adr/0205-federated-checkin-needs-the-hubs-own-signin-witness.md +1 -1
  212. package/docs/adr/0206-own-scoped-session-revocation-no-absolute-ceiling.md +1 -1
  213. package/docs/adr/0207-connections-implementation-departures-and-honest-limits.md +1 -1
  214. package/docs/adr/0208-application-lifecycle-the-handle-waits-and-the-vouch-has-no-minter.md +2 -2
  215. package/docs/adr/0209-one-budget-for-the-account-existence-oracle.md +1 -1
  216. package/docs/adr/0210-scouting-graduates-on-an-instance-asserted-owner-claim.md +1 -1
  217. package/docs/adr/0212-connections-on-the-profile-surface-and-addressing.md +1 -1
  218. package/docs/adr/0213-platform-terms-acceptance-is-the-hubs-own-witness.md +2 -2
  219. package/docs/adr/0215-the-recruiter-sliver-is-a-ceiling-not-a-step.md +6 -6
  220. package/docs/adr/0216-a-fake-pool-interpreter-must-fail-loudly-not-silently-ignore.md +2 -2
  221. package/docs/adr/0217-rank-gates-inviting-not-viewing.md +1 -1
  222. package/docs/adr/0218-the-apex-root-web-surface.md +2 -2
  223. package/docs/adr/0219-a-look-is-a-branding-pack-the-style-library.md +1 -1
  224. package/docs/adr/0220-an-in-house-rebuilt-skill-is-a-first-party-skill.md +1 -1
  225. package/docs/adr/0221-the-impeccable-method-is-one-skill-with-sub-command-playbooks.md +2 -2
  226. package/docs/adr/0222-the-image-family-under-the-hero-only-rule.md +2 -2
  227. package/docs/adr/0223-the-detector-is-two-tiers-in-the-kit.md +3 -3
  228. package/docs/adr/0224-module-skills-reach-the-core-checkout-as-untracked-self-ignored-copies.md +1 -1
  229. package/docs/adr/0225-privacy-suppresses-cross-project-surfaces-only.md +1 -1
  230. package/docs/adr/0226-one-stale-timer-reporting-degrades-enforcement-refuses.md +1 -1
  231. package/docs/adr/0227-a-task-network-map-is-refused-the-goal-graph-is-the-map.md +1 -1
  232. package/docs/adr/0230-a-look-skill-is-a-composition-grammar-the-palette-is-the-pack.md +1 -1
  233. package/docs/adr/0231-the-style-session-an-owner-authors-a-look-inside-the-contract.md +2 -2
  234. package/docs/adr/0232-rot-is-derived-not-swept-and-water-is-the-only-new-verb.md +2 -2
  235. package/docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md +2 -2
  236. package/docs/adr/0234-idea-routing-capture-time-promotion-landing-matrix-homeless-inbox.md +2 -2
  237. package/docs/adr/0235-a-fix-category-is-kindbug-under-a-goal-no-column-required.md +11 -11
  238. package/docs/adr/0236-many-ideas-one-task-the-lineage-key-is-the-pair.md +5 -5
  239. package/docs/adr/0237-a-starter-bundle-is-a-preset-over-the-always-on-core-keyed-by-declared-type.md +5 -5
  240. package/docs/adr/0238-the-join-door-is-a-provisioning-settings-policy-key.md +1 -1
  241. package/docs/adr/0239-a-recommendation-is-a-table-that-carries-its-own-reason.md +4 -4
  242. package/docs/adr/0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md +4 -4
  243. package/docs/adr/0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md +1 -1
  244. package/docs/adr/0242-a-cascade-is-a-declaration-table-on-the-event-that-already-exists.md +2 -2
  245. package/docs/adr/0243-a-bundle-adjustment-belongs-to-the-bundle-not-the-advice-about-it.md +4 -4
  246. package/docs/adr/0244-a-catalog-search-is-a-view-over-the-rows-never-an-edit-to-the-selection.md +3 -3
  247. package/docs/adr/0245-the-way-back-into-an-optional-step-is-that-step-s-own-answer-route.md +7 -7
  248. package/docs/adr/0246-catalog-membership-is-a-projection-not-a-federation-side-effect.md +1 -1
  249. package/docs/adr/0247-the-join-door-is-one-composed-answer-and-dark-matter-has-no-live-verdict-yet.md +2 -2
  250. package/docs/adr/0248-a-hub-project-s-owner-is-the-provisioning-row-carried-on-a-port.md +3 -3
  251. package/docs/adr/0249-the-invite-step-is-the-done-panel-s-first-act-not-a-rail-step.md +3 -3
  252. package/docs/adr/0250-strict-versioning-the-version-boundary-is-the-scope-gate.md +6 -6
  253. package/docs/adr/0251-a-suggestion-of-who-to-invite-is-a-recruiting-surface-and-owes-the-opt-out.md +5 -5
  254. package/docs/adr/0252-the-self-hosted-visibility-report-is-a-pull-and-auto-appear-buys-a-retraction-lever.md +19 -19
  255. package/docs/adr/0253-the-takedown-lever-has-two-writers-and-provenance-is-a-column.md +2 -2
  256. package/docs/adr/0254-a-relayed-link-is-contained-downward-or-it-is-not-published.md +6 -6
  257. package/docs/adr/0255-a-public-lists-ordering-is-part-of-its-payload.md +2 -2
  258. package/docs/adr/0256-the-visibility-ceiling-is-the-sign-in-gate-too-and-the-reader-moves-to-the-composer.md +3 -3
  259. package/docs/adr/0257-auth-resolves-before-the-hall-mounts-anything.md +1 -1
  260. package/docs/adr/0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md +1 -1
  261. package/docs/adr/0259-a-projects-departure-from-the-public-list-is-public.md +7 -7
  262. package/docs/adr/0260-the-application-is-the-consent-and-the-echo-is-the-gate.md +4 -4
  263. package/docs/adr/0261-a-preselect-always-carries-a-reason-the-bundle-summary-is-the-floor.md +1 -1
  264. package/docs/adr/0262-a-bug-never-lands-in-the-inbox.md +1 -1
  265. package/docs/adr/0263-how-a-version-closes.md +3 -3
  266. package/docs/adr/0264-the-ten-working-areas.md +3 -3
  267. package/docs/adr/0265-work-with-no-goal-and-the-order-the-cut-needs.md +5 -5
  268. package/docs/adr/0266-the-board-room-is-its-own-surface-reachable-by-whoever-may-vote.md +2 -2
  269. package/docs/adr/0267-unanimity-and-the-revise-and-re-sit-loop.md +1 -1
  270. package/docs/adr/0268-the-constitution-comes-from-two-roots.md +3 -3
  271. package/docs/adr/0269-the-cli-session-store-is-host-keyed-at-a-fixed-anchor.md +1 -1
  272. package/docs/adr/0270-rank-floor-derives-without-a-prediction.md +2 -2
  273. package/docs/adr/0271-the-ideator-surface-is-two-halves.md +3 -3
  274. package/docs/adr/0272-a-craft-is-an-identity-not-a-toolchain.md +4 -4
  275. package/docs/adr/0273-hall-nav-can-gate-on-a-permission.md +1 -1
  276. package/docs/adr/0274-one-kernel-three-role-packs.md +1 -1
  277. package/docs/adr/0275-one-source-for-a-written-role-responsibility.md +1 -1
  278. package/docs/adr/0276-the-skill-listing-budget-cannot-hold-every-trigger.md +1 -1
  279. package/docs/adr/0277-a-box-is-in-use-only-while-a-human-is-attached.md +1 -1
  280. package/docs/adr/0278-a-gated-project-still-takes-applications.md +2 -2
  281. package/docs/adr/0279-an-upgrade-is-proven-by-the-served-version-not-the-health-check.md +1 -1
  282. package/docs/adr/0280-the-unattended-lane-may-drive-a-co-tenant-because-the-roster-is-the-decision.md +1 -1
  283. package/docs/adr/0281-an-instance-identity-is-its-own-unix-account-and-pg-role.md +2 -2
  284. package/docs/adr/README.md +294 -294
  285. package/docs/api/openapi.json +72 -5
  286. package/docs/api-reference.md +8 -2
  287. package/docs/architecture.md +22 -22
  288. package/docs/branding-contract.md +5 -5
  289. package/docs/canonical-permissions.md +22 -22
  290. package/docs/copy-inventory.md +15 -13
  291. package/docs/copy-registry.json +35 -17
  292. package/docs/design/hall-direction-v2.md +3 -3
  293. package/docs/design/landing-page-direction-v2.md +1 -1
  294. package/docs/design/projects-hub-direction-v2.md +1 -1
  295. package/docs/file-map.md +3 -1
  296. package/docs/module-api-changelog.md +36 -1
  297. package/docs/modules-contract.md +25 -25
  298. package/docs/onboarding/browser-terminal-guide.md +1 -1
  299. package/docs/onboarding/diagrams/README.md +3 -3
  300. package/docs/onboarding/drachmae-and-karma.md +13 -13
  301. package/docs/onboarding/primer.md +7 -7
  302. package/docs/packs/artist.md +1 -1
  303. package/docs/packs/ideator.md +1 -1
  304. package/docs/project-context.template.md +3 -3
  305. package/docs/recipes/builder-box-lifecycle.md +5 -5
  306. package/docs/recipes/claude-design-loop.md +1 -1
  307. package/docs/recipes/core-release-pipeline.md +55 -14
  308. package/docs/recipes/figma-design-loop.md +1 -1
  309. package/docs/recipes/gds-db-backup.md +2 -2
  310. package/docs/recipes/instance-deploy.md +2 -2
  311. package/docs/recipes/instance-service-restart.md +3 -3
  312. package/docs/recipes/local-dev.md +2 -2
  313. package/docs/recipes/managed-settings-remote-control.md +3 -3
  314. package/docs/recipes/memory-backup.md +4 -4
  315. package/docs/recipes/memory-sync-conflicts.md +2 -2
  316. package/docs/recipes/multi-builder-merge.md +2 -2
  317. package/docs/recipes/ops-gotchas.md +4 -4
  318. package/docs/recipes/overnight-builder.md +1 -1
  319. package/docs/recipes/packaging-the-core.md +6 -6
  320. package/docs/recipes/private-npm-distribution.md +5 -5
  321. package/docs/recipes/search-index-ingestion.md +3 -3
  322. package/docs/recipes/self-host.md +5 -5
  323. package/docs/recipes/server-bring-up.md +1 -1
  324. package/docs/recipes/ship-and-regrade.md +2 -2
  325. package/docs/recipes/standalone-live-docs.md +1 -1
  326. package/docs/recipes/ui-look-before-you-ship.md +1 -1
  327. package/docs/recipes/upgrading-the-core.md +2 -2
  328. package/docs/recipes/work-on-a-task-from-your-dev-box.md +6 -6
  329. package/docs/research/cursor-competitive-analysis.md +3 -3
  330. package/docs/research/llm-model-agnostic-and-cheaper-grader.md +7 -7
  331. package/migrations/core_240_artist_gate_indexes.sql +48 -0
  332. package/migrations/core_240_goal_members_drop_legacy_role.sql +79 -0
  333. package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
  334. package/modules/hall-ui/public/watch.css +6 -0
  335. package/modules/hall-ui/public/watch.js +52 -6
  336. package/modules/lifecycle/cascade.js +37 -9
  337. package/modules/lifecycle/db-claims.js +5 -2
  338. package/modules/lifecycle/db-goals.js +4 -3
  339. package/modules/lifecycle/db-overrides.js +117 -4
  340. package/modules/lifecycle/db-tasks.js +63 -0
  341. package/modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql +12 -4
  342. package/modules/lifecycle/module.json +2 -1
  343. package/modules/lifecycle/pg-constraint-fail.js +49 -0
  344. package/modules/lifecycle/routes/artist-gate.js +101 -0
  345. package/modules/lifecycle/routes/claims.js +13 -1
  346. package/modules/lifecycle/routes/lifecycle.js +6 -0
  347. package/modules/lifecycle/routes/task-write-routes.js +7 -0
  348. package/modules/lifecycle/routes/tasks.js +24 -0
  349. package/modules/lifecycle/task-classifier.js +10 -1
  350. package/modules/lifecycle/tests/cascade.mjs +43 -0
  351. package/modules/provisioning/provisioning.js +79 -3
  352. package/modules/provisioning/routes/provisioning.js +6 -1
  353. package/modules/provisioning/tests/provisioning.mjs +8 -8
  354. package/modules/security/secret-scrub.js +40 -4
  355. package/modules/status-ui/public/README.md +1 -1
  356. package/modules/ui-design/docs/design-contract.md +2 -2
  357. package/modules/ui-design/kit/README.md +1 -1
  358. package/modules/ui-design/kit/serve.js +1 -0
  359. package/modules/ui-design/skills/README.md +6 -6
  360. package/modules/ui-design/skills/brandkit/SKILL.md +1 -1
  361. package/modules/ui-design/skills/design-taste-frontend/SKILL.md +1 -1
  362. package/modules/ui-design/skills/design-taste-frontend-v1/SKILL.md +1 -1
  363. package/modules/ui-design/skills/gpt-taste/SKILL.md +1 -1
  364. package/modules/ui-design/skills/high-end-visual-design/SKILL.md +1 -1
  365. package/modules/ui-design/skills/image-to-code/SKILL.md +1 -1
  366. package/modules/ui-design/skills/imagegen-frontend-mobile/SKILL.md +1 -1
  367. package/modules/ui-design/skills/imagegen-frontend-web/SKILL.md +1 -1
  368. package/modules/ui-design/skills/impeccable/SKILL.md +1 -1
  369. package/modules/ui-design/skills/industrial-brutalist-ui/SKILL.md +1 -1
  370. package/modules/ui-design/skills/minimalist-ui/SKILL.md +1 -1
  371. package/modules/ui-design/skills/redesign-existing-projects/SKILL.md +1 -1
  372. package/modules/ui-design/skills/stitch-design-taste/SKILL.md +1 -1
  373. package/modules/ui-design/skills/style/SKILL.md +2 -2
  374. package/modules/ui-design/styles/README.md +3 -3
  375. package/modules/ui-design/styles/blueprint/DESIGN.md +1 -1
  376. package/modules/ui-design/styles/chrome-world/DESIGN.md +1 -1
  377. package/package-lock.json +2 -2
  378. package/package.json +1 -1
  379. package/scripts/gds/audit-rank-roles.js +172 -0
  380. package/scripts/gds/backfill-task-rewards.js +9 -4
  381. package/scripts/gds/build-cli-package.js +1 -0
  382. package/scripts/gds/claim.js +94 -9
  383. package/scripts/gds/fitness.js +8 -8
  384. package/scripts/gds/go-live.js +139 -5
  385. package/scripts/gds/land-watch.js +13 -5
  386. package/scripts/gds/mirror-redact.js +13 -2
  387. package/scripts/gds/package-core.js +11 -3
  388. package/scripts/gds/release.js +55 -0
  389. package/scripts/gds/ship-land.js +10 -2
  390. package/scripts/gds/ship-regen.js +41 -8
  391. package/scripts/gds/start.js +7 -4
  392. package/scripts/gds/worktree-claim-guard.js +27 -1
  393. package/scripts/public-mirror-export.js +17 -4
  394. package/src/bongos/api-errors.js +9 -0
  395. package/src/bongos/artist-gate.js +204 -0
  396. package/src/branding.js +15 -0
  397. package/src/module-api.js +34 -1
  398. package/tests/api_path_404.mjs +28 -0
  399. package/tests/artist_gate.mjs +234 -0
  400. package/tests/artist_gate_release.mjs +283 -0
  401. package/tests/artist_gate_route.mjs +174 -0
  402. package/tests/box_scope_predicate.mjs +117 -0
  403. package/tests/claim_error_surface.mjs +163 -1
  404. package/tests/cli_exit_no_abort.mjs +256 -3
  405. package/tests/credit_log_single_writer.mjs +94 -0
  406. package/tests/currency_label.mjs +11 -4
  407. package/tests/go_live.mjs +144 -0
  408. package/tests/helpers.mjs +8 -0
  409. package/tests/main_worktree_busy_reason.mjs +167 -0
  410. package/tests/membership_kind_lockstep.mjs +88 -0
  411. package/tests/mirror_redact.mjs +14 -0
  412. package/tests/module_api.mjs +6 -0
  413. package/tests/provision_settings_apply.mjs +13 -5
  414. package/tests/provisioning_settings.mjs +11 -7
  415. package/tests/provisioning_settings_apply.mjs +4 -4
  416. package/tests/provisioning_settings_env.mjs +59 -7
  417. package/tests/public_mirror_export.mjs +12 -0
  418. package/tests/rank_roles_audit.mjs +114 -0
  419. package/tests/scrubber_corpus.mjs +70 -2
  420. package/tests/start_rebase_warning.mjs +50 -0
  421. package/tests/task_field_bounds_match_db.mjs +140 -0
  422. package/tests/watch_roster_self_row.mjs +289 -0
@@ -0,0 +1,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);
@@ -159,17 +159,34 @@ const RESTART_SHAPES = new Set(['co-tenant', 'standalone']);
159
159
  // which is why it is the default — DEFAULT_SETTINGS must be today's behaviour, and
160
160
  // today a public project runs whatever door its joinability runs. Composed at the
161
161
  // door by src/bongos/project-door.js; ADR 0247.
162
+ //
163
+ // `artist_gate` is the project's ARTIST STRICTNESS (ADR 0241, task 1003575) — the
164
+ // owner's constraint from goal 1000074, verbatim: blocking strictness is per
165
+ // project, not global. `off` generates no artist review tasks at all; `advisory`
166
+ // generates them and blocks nothing; `strict` generates them and an unresolved one
167
+ // holds the DEPLOY — the instance's pin may not move while one is open. It never
168
+ // holds a builder's SHIP: that is the line ADR 0241 draws to keep this compatible
169
+ // with ADR 0162, and it is why a gate that stops work can exist here at all.
170
+ //
171
+ // It is the only key here whose default is NOT today's behaviour. `strict` is an
172
+ // owner ruling (ADR 0241 §4), recorded there against its author's recommendation
173
+ // and coherent because strict holds only the pin: the worst case is a project whose
174
+ // deploy waits on a review nobody has done, which is visible and owner-releasable,
175
+ // never a stranded builder.
162
176
  const SETTINGS_VOCAB = Object.freeze({
163
177
  platform_visibility: ['public', 'gated'],
164
178
  joinability: ['open', 'apply', 'invite_only'],
165
179
  visibility: ['public', 'private', 'stealth'],
166
180
  join_grant: ['view', 'apply', 'full'],
181
+ artist_gate: ['off', 'advisory', 'strict'],
167
182
  });
168
183
  // Default = today's behavior on every axis, including the join door: ADR 0182's
169
184
  // migration gives the hub column the same `public` default, "existing rows keep
170
- // today's behaviour".
185
+ // today's behaviour". The one exception is `artist_gate`, whose default is the
186
+ // owner's ruling rather than the status quo — see the note above it.
171
187
  const DEFAULT_SETTINGS = Object.freeze({
172
188
  platform_visibility: 'public', joinability: 'apply', visibility: 'public', join_grant: 'full',
189
+ artist_gate: 'strict',
173
190
  });
174
191
 
175
192
  // The project's PLANET (task 1003347, direction record §16): how the platform
@@ -422,9 +439,28 @@ function publicGithubApp(manifestRow) {
422
439
  // and the live push (the runner's settings-apply leg patches exactly these lines).
423
440
  function settingsEnvVars(row, prefix) {
424
441
  const s = effectiveSettings(row);
425
- return Object.fromEntries(Object.keys(SETTINGS_VOCAB).map((key) => [`${prefix}_${key.toUpperCase()}`, s[key]]));
442
+ const vars = Object.fromEntries(Object.keys(SETTINGS_VOCAB).map((key) => [`${prefix}_${key.toUpperCase()}`, s[key]]));
443
+ for (const [suffix, read] of Object.entries(SETTINGS_COMPANION_ENV)) vars[`${prefix}_${suffix}`] = read(row);
444
+ return vars;
426
445
  }
427
446
 
447
+ // COMPANION variables: env the instance needs to OBEY a setting, which is not
448
+ // itself a setting anyone chooses (task 1003575). Declared as a closed table for
449
+ // the same reason SETTINGS_VOCAB is — the drift test asserts the transport carries
450
+ // exactly the vocabulary plus these, so a companion can be added deliberately and
451
+ // never by accident.
452
+ //
453
+ // ARTIST_GATE_SINCE is the only one today: ADR 0241 §5 makes a tightening
454
+ // non-retroactive, and an instance cannot honour that without knowing WHEN the
455
+ // gate was raised. Always emitted — the runner patches these lines in place, and a
456
+ // key that came and went would leave a stale line behind — with empty meaning "no
457
+ // cutoff". src/branding.js skips an empty override, so the instance reads no
458
+ // cutoff and every unresolved review counts, which is the right answer for a
459
+ // project that has been strict since it stood up.
460
+ const SETTINGS_COMPANION_ENV = Object.freeze({
461
+ ARTIST_GATE_SINCE: (row) => artistGateStrictSince(row) || '',
462
+ });
463
+
428
464
  // The web.env block the runner writes at STANDUP (task 1003139): a comment that
429
465
  // tells the truth about where the values come from, then the settingsEnvVars
430
466
  // lines. Changing one on an ALREADY-RUNNING instance is the 'settings-apply'
@@ -496,6 +532,44 @@ function settingsPushOwed(row) {
496
532
  return stored[SETTINGS_PUSH_OWED_KEY] === true;
497
533
  }
498
534
 
535
+ // The moment `artist_gate` was last RAISED to 'strict' (ADR 0241 §5, task
536
+ // 1003575). Same reserved-key shape and rationale as SETTINGS_PUSH_OWED_KEY
537
+ // above — platform bookkeeping, not a setting an owner declares, so it is
538
+ // deliberately out of SETTINGS_VOCAB and can neither leak through
539
+ // effectiveSettings nor be written by a client patch.
540
+ //
541
+ // WHY IT HAS TO EXIST. ADR 0241 §5: tightening applies to work that ships AFTER
542
+ // the change, never backwards. Without a stamp, flipping advisory → strict would
543
+ // hand the next deploy a backlog of review tasks for work nobody remembers — the
544
+ // exact thing that makes a setting frightening to raise. The stamp is what lets
545
+ // the gate ignore them.
546
+ const ARTIST_GATE_STRICT_SINCE_KEY = 'artist_gate_strict_since';
547
+
548
+ // Read the stamp, or null. An unparseable value reads as null (no cutoff) rather
549
+ // than as a date nobody can name. PURE.
550
+ function artistGateStrictSince(row) {
551
+ const stored = row && row.settings && typeof row.settings === 'object' && !Array.isArray(row.settings)
552
+ ? row.settings : {};
553
+ const v = stored[ARTIST_GATE_STRICT_SINCE_KEY];
554
+ if (typeof v !== 'string' || !v) return null;
555
+ return Number.isNaN(Date.parse(v)) ? null : v;
556
+ }
557
+
558
+ // The extra jsonb keys a settings save must ALSO store, given what the row holds
559
+ // now and what is being patched onto it. Today that is exactly the strict stamp,
560
+ // and it is written only on a RAISE — a save that leaves the gate where it was,
561
+ // or lowers it, must not move the cutoff forward, or every unrelated settings
562
+ // change would quietly re-date the gate and un-hold reviews it was already
563
+ // holding. Lowering deliberately LEAVES the stamp: nothing reads it below
564
+ // 'strict', and keeping it means a project that goes strict → advisory → strict
565
+ // by mistake does not lose its history. PURE (row + patch + clock in, object out).
566
+ function artistGateStamp(row, patch, now = new Date()) {
567
+ const next = (patch || {}).artist_gate;
568
+ if (next !== 'strict') return {};
569
+ if (effectiveSettings(row).artist_gate === 'strict') return {};
570
+ return { [ARTIST_GATE_STRICT_SINCE_KEY]: new Date(now).toISOString() };
571
+ }
572
+
499
573
  // A description as it may be STORED: trimmed, collapsed of runaway whitespace,
500
574
  // capped, and empty-means-absent. Returns null for anything that isn't real prose,
501
575
  // because null is what leaves the project dark-matter — an empty string stored in a
@@ -1334,12 +1408,14 @@ module.exports = {
1334
1408
  SETTINGS_APPLY_FROM, SETTINGS_APPLY_SHAPES,
1335
1409
  RESTART_FROM, RESTART_SHAPES,
1336
1410
  TIER_MONTHLY_USD, CERT_ELIGIBLE_STATES,
1337
- SETTINGS_VOCAB, DEFAULT_SETTINGS, PROJECT_DETAIL_VOCAB, DESCRIPTION_MAX,
1411
+ SETTINGS_VOCAB, DEFAULT_SETTINGS, SETTINGS_COMPANION_ENV, PROJECT_DETAIL_VOCAB, DESCRIPTION_MAX,
1338
1412
  PLANET_TEMPLATES, PLANET_ACCENTS, PLANET_VOCAB,
1339
1413
  // pure
1340
1414
  isValidSlug, costEstimateUsd, publicInstance, normalizeHostname,
1341
1415
  defaultDomainForSlug, isReservedHostname, effectiveSettings, planetChoice,
1342
1416
  settingsPushOwed,
1417
+ // pure — the artist gate's non-retroactivity stamp (ADR 0241 §5, task 1003575)
1418
+ ARTIST_GATE_STRICT_SINCE_KEY, artistGateStrictSince, artistGateStamp,
1343
1419
  normalizeDescription, normalizeProjectDetail, projectDetailPatch, projectDetail,
1344
1420
  // pure — GitHub App Manifest flow (task 2080)
1345
1421
  githubAppName, buildGithubAppManifest, manifestPostUrl, publicGithubApp,
@@ -908,7 +908,12 @@ module.exports = function provisioningRoutes() {
908
908
  // true: the re-send is unchanged, so it would otherwise hit "No change".
909
909
  const pushOwed = provisioning.settingsPushOwed(inst);
910
910
  const policyResent = (!!inst.error_note || pushOwed) && policyTouched;
911
- const updated = changed ? await provisioning.updateInstanceSettings(pool, id, patch) : inst;
911
+ // Raising `artist_gate` to 'strict' also stamps WHEN (ADR 0241 §5 makes the
912
+ // tightening non-retroactive, and the instance cannot honour that without the
913
+ // date). It rides the same single UPDATE as the setting it belongs to, and is
914
+ // empty on every other save — see artistGateStamp.
915
+ const storedPatch = { ...patch, ...provisioning.artistGateStamp(inst, patch) };
916
+ const updated = changed ? await provisioning.updateInstanceSettings(pool, id, storedPatch) : inst;
912
917
  if (changed) {
913
918
  await provisioning.recordEvent(pool, {
914
919
  instanceId: id, ownerBuilderId: inst.owner_builder_id, event: 'settings',
@@ -263,27 +263,27 @@ await ta('recordDnsNote / clearErrorNote are NARROW single-column updates — no
263
263
  console.log('\nPer-project settings (task 1003040):');
264
264
 
265
265
  t('effectiveSettings: defaults for a bare/empty row = today\'s behavior; stored keys overlay; junk falls back', () => {
266
- assert.deepEqual(P.effectiveSettings(null), { platform_visibility: 'public', joinability: 'apply', visibility: 'public', join_grant: 'full' });
267
- assert.deepEqual(P.effectiveSettings({}), { platform_visibility: 'public', joinability: 'apply', visibility: 'public', join_grant: 'full' });
268
- assert.deepEqual(P.effectiveSettings({ settings: {} }), { platform_visibility: 'public', joinability: 'apply', visibility: 'public', join_grant: 'full' });
266
+ assert.deepEqual(P.effectiveSettings(null), { platform_visibility: 'public', joinability: 'apply', visibility: 'public', join_grant: 'full', artist_gate: 'strict' });
267
+ assert.deepEqual(P.effectiveSettings({}), { platform_visibility: 'public', joinability: 'apply', visibility: 'public', join_grant: 'full', artist_gate: 'strict' });
268
+ assert.deepEqual(P.effectiveSettings({ settings: {} }), { platform_visibility: 'public', joinability: 'apply', visibility: 'public', join_grant: 'full', artist_gate: 'strict' });
269
269
  assert.deepEqual(
270
270
  P.effectiveSettings({ settings: { platform_visibility: 'gated' } }),
271
- { platform_visibility: 'gated', joinability: 'apply', visibility: 'public', join_grant: 'full' },
271
+ { platform_visibility: 'gated', joinability: 'apply', visibility: 'public', join_grant: 'full', artist_gate: 'strict' },
272
272
  'a stored key overlays only itself'
273
273
  );
274
274
  assert.deepEqual(
275
275
  P.effectiveSettings({ settings: { platform_visibility: 'bogus', joinability: 'invite_only', future_key: 'x', visibility: 'unlisted', join_grant: 'everything' } }),
276
- { platform_visibility: 'public', joinability: 'invite_only', visibility: 'public', join_grant: 'full' },
276
+ { platform_visibility: 'public', joinability: 'invite_only', visibility: 'public', join_grant: 'full', artist_gate: 'strict' },
277
277
  'off-vocabulary values fall back to the default; unknown keys never leak out'
278
278
  );
279
279
  assert.deepEqual(P.effectiveSettings({ settings: ['not', 'an', 'object'] }),
280
- { platform_visibility: 'public', joinability: 'apply', visibility: 'public', join_grant: 'full' }, 'a non-object settings value reads as defaults');
280
+ { platform_visibility: 'public', joinability: 'apply', visibility: 'public', join_grant: 'full', artist_gate: 'strict' }, 'a non-object settings value reads as defaults');
281
281
  // the join door reads back like any other policy key (task 1002324)
282
282
  assert.deepEqual(P.effectiveSettings({ settings: { visibility: 'stealth' } }),
283
- { platform_visibility: 'public', joinability: 'apply', visibility: 'stealth', join_grant: 'full' }, 'a stored door overlays only itself');
283
+ { platform_visibility: 'public', joinability: 'apply', visibility: 'stealth', join_grant: 'full', artist_gate: 'strict' }, 'a stored door overlays only itself');
284
284
  // what a public join grants is a policy key like any other (task 1002331)
285
285
  assert.deepEqual(P.effectiveSettings({ settings: { join_grant: 'view' } }),
286
- { platform_visibility: 'public', joinability: 'apply', visibility: 'public', join_grant: 'view' }, 'the grant overlays only itself');
286
+ { platform_visibility: 'public', joinability: 'apply', visibility: 'public', join_grant: 'view', artist_gate: 'strict' }, 'the grant overlays only itself');
287
287
  });
288
288
 
289
289
  t('SETTINGS_VOCAB / DEFAULT_SETTINGS: every default is on its own vocabulary (lockstep guard)', () => {
@@ -132,11 +132,47 @@ function looksHighEntropySecret(tok) {
132
132
  // floor and slip the entropy net — but layers 1–4 (literal env values, PEM,
133
133
  // known shapes, key=value assignments) already cover the realistic ways such a
134
134
  // value reaches a transcript.
135
+ const DOC_EXTS = ['.md', '.sql'];
135
136
  const ENTROPY_CANDIDATE_RE = /[A-Za-z0-9_-]{20,}/g;
136
- function redactHighEntropy(text) {
137
- return text.replace(ENTROPY_CANDIDATE_RE, (tok) => (looksHighEntropySecret(tok) ? REDACTED : tok));
137
+
138
+ // DOC-PATH EXEMPTION (task 1002460) an ALLOWLIST, not a shape guess.
139
+ //
140
+ // THE FALSE POSITIVE. A long filename slug is lowercase + digits + hyphens with
141
+ // H >= 3.7, so rule B fires and package-core collapsed every ADR and migration
142
+ // filename to `<redacted>.md` on the publish path.
143
+ //
144
+ // WHY NOT A SHAPE RULE. Three were tried and each leaked. Any predicate built on
145
+ // what a filename LOOKS like also admits a secret dressed that way, because a
146
+ // random lowercase run is shape-identical to words — and entropy cannot separate
147
+ // them either (real filenames measure up to H=4.35, random lowercase words 4.54).
148
+ // scrubSecrets feeds the PUBLIC mirror (scripts/gds/mirror-redact.js,
149
+ // scripts/public-mirror-export.js), so a shape hole is a smuggling channel.
150
+ //
151
+ // WHAT THIS DOES INSTEAD. The caller passes the set of filenames it is actually
152
+ // publishing; a candidate is spared only when the token plus its extension IS one
153
+ // of them. A secret is not a file in the tree, so it cannot be in the set.
154
+ //
155
+ // RECALL IS UNCHANGED BY CONSTRUCTION, and that is the point. With no set — every
156
+ // transcript scrub, every caller that does not opt in — there is no exemption at
157
+ // all and the verdict is byte-identical to before this task. Only the publish path,
158
+ // which enumerates the tree anyway, supplies one.
159
+ function redactHighEntropy(text, opts) {
160
+ const known = opts && opts.knownDocNames;
161
+ if (!known || !known.size) {
162
+ return text.replace(ENTROPY_CANDIDATE_RE, (tok) => (looksHighEntropySecret(tok) ? REDACTED : tok));
163
+ }
164
+ return text.replace(ENTROPY_CANDIDATE_RE, (tok, offset, whole) => {
165
+ if (!looksHighEntropySecret(tok)) return tok;
166
+ // Cheap boundary reads at a known offset — no slicing of the surrounding
167
+ // document, which runs over whole markdown files and whole transcripts.
168
+ for (const ext of DOC_EXTS) {
169
+ if (whole.startsWith(ext, offset + tok.length) && known.has(tok + ext)) return tok;
170
+ }
171
+ return REDACTED;
172
+ });
138
173
  }
139
174
 
175
+
140
176
  // ENV secret collection — literal secret VALUES from the process env, so they are
141
177
  // redacted wherever they appear even with no recognizable shape (Apple/MAC_* CI
142
178
  // secrets, a baked GDS_TOKEN, GITHUB_TOKEN, DO_API_TOKEN, …). Key match mirrors
@@ -164,7 +200,7 @@ function collectEnvSecrets(env = process.env) {
164
200
  return out;
165
201
  }
166
202
 
167
- function scrubSecrets(text, extraSecrets = []) {
203
+ function scrubSecrets(text, extraSecrets = [], opts = undefined) {
168
204
  if (typeof text !== 'string' || text.length === 0) return text;
169
205
  let out = text;
170
206
  // Literal dynamic secrets first (longest first so a substring can't shadow a
@@ -190,7 +226,7 @@ function scrubSecrets(text, extraSecrets = []) {
190
226
  // Catch-all LAST: an unkeyed, unknown-shape, high-entropy token. The earlier
191
227
  // layers have already collapsed known secrets to "<redacted>" (no candidate),
192
228
  // so this only sweeps what survived.
193
- out = redactHighEntropy(out);
229
+ out = redactHighEntropy(out, opts);
194
230
  return out;
195
231
  }
196
232
 
@@ -1,6 +1,6 @@
1
1
  # `modules/status-ui/public/` — the status dashboard ("The Watch")
2
2
 
3
- These files are the source of `status.example.com`. The surface moved here from `public-status/` in **BV1.R84** when the status dashboard became a first-class module ([ADR 0093](../../../docs/adr/<redacted>.md) §1); the loader's web-surface seam serves this `public/` dir on the `status.` host (see [`../CLAUDE.md`](../CLAUDE.md)). As of **#566 / [ADR 0029](../../../docs/adr/<redacted>.md)** they are **mirrored OFF-BOX** so the status page survives a droplet outage.
3
+ These files are the source of `status.example.com`. The surface moved here from `public-status/` in **BV1.R84** when the status dashboard became a first-class module ([ADR 0093](../../../docs/adr/0093-tranche-2-core-carve-sequence.md) §1); the loader's web-surface seam serves this `public/` dir on the `status.` host (see [`../CLAUDE.md`](../CLAUDE.md)). As of **#566 / [ADR 0029](../../../docs/adr/0029-status-page-outage-resilience.md)** they are **mirrored OFF-BOX** so the status page survives a droplet outage.
4
4
 
5
5
  ## Why off-box
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Design contract — neutral adapter interface
2
2
 
3
- **Status:** Accepted · authored by [task 1377](https://example.com/builders#/task/1377) · governed by [ADR 0081](../../../docs/adr/<redacted>.md) · lives in the `ui-design` module since [task 1003324](https://cloudbongos.com/builders#/task/1003324) ([ADR 0197](../../../docs/adr/<redacted>.md)) — the token files it names stay in `config/` as the host boundary
3
+ **Status:** Accepted · authored by [task 1377](https://example.com/builders#/task/1377) · governed by [ADR 0081](../../../docs/adr/0081-tool-agnostic-design-layer.md) · lives in the `ui-design` module since [task 1003324](https://cloudbongos.com/builders#/task/1003324) ([ADR 0197](../../../docs/adr/0197-the-ui-design-module.md)) — the token files it names stay in `config/` as the host boundary
4
4
 
5
5
  The repo is the single source of truth for design. Every design tool is a swappable adapter that reads from and writes to this contract — it does not own the source. This file is the contract every adapter must conform to. The minimal validator (`modules/ui-design/scripts/validate-design.js`; `node scripts/gds/validate-design.js` is a thin shim over it) enforces the machine-checkable parts.
6
6
 
@@ -96,7 +96,7 @@ Rules:
96
96
  | Freeform canvas art | — | `assets/ui/<surface>/` | Asset |
97
97
  | Pixel-art game tiles | see `art/` pipeline | — | Art pipeline (not this contract) |
98
98
 
99
- Pixel-art game tiles (world tiles, characters, objects) are out of scope for this contract — they are governed by the `art/` pipeline and [ADR 0088](../../../docs/adr/<redacted>.md).
99
+ Pixel-art game tiles (world tiles, characters, objects) are out of scope for this contract — they are governed by the `art/` pipeline and [ADR 0088](../../../docs/adr/0088-consistent-character-generative-animation.md).
100
100
 
101
101
  ---
102
102
 
@@ -1,6 +1,6 @@
1
1
  # `modules/ui-design/kit/` — look before you ship
2
2
 
3
- The stub, the renderer/auditor, the interaction probes, the mock checker and the contrast probe any builder points at any surface of any instance ([task 1003320](https://cloudbongos.com/builders#/task/1003320), [ADR 0197](../../../docs/adr/<redacted>.md)). Plain Node; the browser is the system Edge or Chrome via `playwright-core` (a devDependency — nothing is downloaded).
3
+ The stub, the renderer/auditor, the interaction probes, the mock checker and the contrast probe any builder points at any surface of any instance ([task 1003320](https://cloudbongos.com/builders#/task/1003320), [ADR 0197](../../../docs/adr/0197-the-ui-design-module.md)). Plain Node; the browser is the system Edge or Chrome via `playwright-core` (a devDependency — nothing is downloaded).
4
4
 
5
5
  ```
6
6
  node modules/ui-design/kit/serve.js # the stub (SURFACE, PREFIX, AUTH=1, CHILD=1, LIVE=…, FAIL=…, STYLE=<look>, STUB_PORT)
@@ -188,6 +188,7 @@ const STUB_VOCAB = {
188
188
  joinability: ['open', 'apply', 'invite_only'],
189
189
  visibility: ['public', 'private', 'stealth'],
190
190
  join_grant: ['view', 'apply', 'full'],
191
+ artist_gate: ['off', 'advisory', 'strict'],
191
192
  planet_template: null, // the template set is long; shape is checked, not membership
192
193
  planet_accent: ['sea', 'terracotta', 'gold-soft', 'sun-lit', 'accent'],
193
194
  card_backdrop: ['nebula', 'dust', 'deepfield', 'chrome'],