@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
@@ -10,7 +10,7 @@
10
10
  const { execSync } = require('node:child_process');
11
11
  const fs = require('node:fs');
12
12
  const path = require('node:path');
13
- const { cliClient, requireSession, fmtTouches, printOnboardingNudge, rewardPolicy, costPlusOnlyNote, detectWorktreeName } = require('./cli-lib');
13
+ const { cliClient, requireSession, fmtTouches, printOnboardingNudge, rewardPolicy, costPlusOnlyNote, detectWorktreeName, cliExit } = require('./cli-lib');
14
14
  const { nextNumbers, padAdr, padMig } = require('./next-number');
15
15
  const { CORE_NS } = require('./migration-namespace');
16
16
  const { contextPack, formatContextPack, resolveWorktreeRoot } = require('./context-pack');
@@ -147,10 +147,44 @@ function enforceWorktreeClaim(taskId, { allowMainCheckout }) {
147
147
  allowMainCheckout,
148
148
  taskId,
149
149
  });
150
- if (d.verdict === 'allow') return;
150
+ if (d.verdict === 'allow') return tree;
151
151
  for (const line of d.lines) console.error(line);
152
152
  if (d.verdict === 'refuse') process.exit(2);
153
153
  console.error('');
154
+ return tree;
155
+ }
156
+
157
+ // task 1002592 — a failed worktree DETECTION reaches the builder disguised as a
158
+ // claim CONFLICT, and that disguise is the whole of the original bug report.
159
+ // When detectWorktreeName() yields nothing the claim goes up with
160
+ // worktree_name: null; the server's one-active-claim guard keys on the WORKING
161
+ // TREE, so an unnamed claim falls back to the per-session compare and a
162
+ // genuinely-parallel worktree is refused as a duplicate. The builder then hunts
163
+ // for a claim conflict that does not exist.
164
+ //
165
+ // Only speak when null is WRONG. In the main checkout null is the documented
166
+ // correct answer and the refusal is genuine, so a note there would send the next
167
+ // reader down precisely the wrong path — that distinction is the fix, not a
168
+ // detail of it.
169
+ function worktreeDetectionNote(ctx, taskId) {
170
+ // The caller must have actually PROBED the tree. Without that this cannot tell
171
+ // "detection failed" from "nobody looked", and guessing would hang a worktree
172
+ // warning on every refusal rendered by a caller that never had tree info —
173
+ // including every pre-existing one. Absence of knowledge is not evidence.
174
+ if (!ctx || !('worktree' in ctx)) return [];
175
+ const { worktree, isMainCheckout } = ctx;
176
+ if (worktree) return []; // named fine — the refusal is genuine
177
+ if (isMainCheckout === true) return []; // null is correct here
178
+ const why = isMainCheckout === false
179
+ ? 'this IS a linked worktree, so a name was expected'
180
+ : 'this tree is not recognisable as the main checkout either';
181
+ return [
182
+ ` ⚠ This session could not name its worktree — auto-detect returned nothing, and ${why}.`,
183
+ ' An unnamed claim is compared per-SESSION instead of per-worktree, so a real parallel',
184
+ ' worktree is refused as a duplicate. That is very likely what you are looking at, and',
185
+ ' it is a path-detection problem, not a claim conflict. Name it explicitly to rule it out:',
186
+ ` node scripts/gds/claim.js ${taskId} --worktree <name>`,
187
+ ];
154
188
  }
155
189
 
156
190
  function captureHeadSha() {
@@ -247,7 +281,25 @@ function claimErrorInfo(r) {
247
281
  // string), so every branch below silently missed and the refusal printed as
248
282
  // `[object Object]`. Pure (no console / no process.exit) + exported so it can be
249
283
  // unit-tested against the real envelope shapes. Returns { lines, exit }.
250
- function formatClaimFailure(r, taskId) {
284
+ //
285
+ // task 1002592: server-supplied prose (a task title, a flag reason, a hint) is
286
+ // echoed to the operator's terminal by the branches below. It is our own API, so
287
+ // this is hygiene rather than a live threat — but an unbounded string can carry
288
+ // control characters that repaint a terminal, and a runaway one can bury the
289
+ // guidance it was meant to add. Clip to one line, strip C0/C1 controls, cap length.
290
+ const MAX_LISTED_TASKS = 20;
291
+ function clip(s, max = 160) {
292
+ if (s == null) return '';
293
+ // eslint-disable-next-line no-control-regex
294
+ const flat = String(s).replace(/[--Ÿ]+/g, ' ').trim();
295
+ return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
296
+ }
297
+
298
+ // task 1002592: `ctx` carries what the CALLER knows about the tree
299
+ // ({ worktree, isMainCheckout }) so the session-claim branch can tell a real
300
+ // conflict apart from a failed worktree detection. Optional and defaulted — the
301
+ // function stays pure and every existing caller/test keeps working.
302
+ function formatClaimFailure(r, taskId, ctx) {
251
303
  const lines = [];
252
304
  const { code, message, d } = claimErrorInfo(r);
253
305
 
@@ -271,6 +323,7 @@ function formatClaimFailure(r, taskId) {
271
323
  ? `Run /builder-ship ${n} (or /builder-release ${n}) first, or open a fresh worktree/session for this task.`
272
324
  : 'Ship or release the in-flight claim, or start a fresh worktree/session.')}`);
273
325
  lines.push(' One claim per session keeps each ship isolated (task 1642). To work tasks in PARALLEL, open a separate worktree/session per task — each holds its own single claim — or use the hall work board’s multi-select bulk-claim.');
326
+ lines.push(...worktreeDetectionNote(ctx, taskId));
274
327
  return { lines, exit: 1 };
275
328
  }
276
329
  if (code === 'newcomer_must_ship_first') {
@@ -294,6 +347,26 @@ function formatClaimFailure(r, taskId) {
294
347
  lines.push(` ${d.what_to_do || `Run /builder-ship ${n || '<task>'} (or /builder-release ${n || '<task>'}) first, claim from a separate worktree, or claim with {"bind_session":false} (an unbound claim always coexists).`}`);
295
348
  return { lines, exit: 1 };
296
349
  }
350
+ // task 1002592: ADR 0120 part 3's owed-rebase gate refuses EVERY new claim, so
351
+ // it is the refusal a builder is most stuck behind — and it was the only one
352
+ // with no branch here, printing as a bare `[REBASE_REQUIRED]: REBASE_REQUIRED`
353
+ // code echo. The server builds a full way-forward menu for it
354
+ // (rebaseRequiredHint, task 1003768 — /strand-fix leads because it is the one
355
+ // exit that works with no GitHub push credential, the DOCUMENTED default
356
+ // posture) and this surface dropped it on the floor. Name the owed tasks, then
357
+ // print the server's menu.
358
+ if (code === 'REBASE_REQUIRED') {
359
+ const owed = (d.rebaseTasks || d.rebase_tasks || []).slice(0, MAX_LISTED_TASKS);
360
+ lines.push(owed.length
361
+ ? `Your queue is gated — ${owed.length} confirmed task(s) are waiting on your rebase:`
362
+ : 'Your queue is gated — a confirmed task is waiting on your rebase.');
363
+ for (const t of owed) {
364
+ lines.push(` - task ${t.id}${t.title ? ` ${clip(t.title)}` : ''}`);
365
+ if (t.reason) lines.push(` flagged: ${clip(t.reason)}`);
366
+ }
367
+ lines.push(` ${clip(d.hint, 600) || 'Clear it with /strand-fix N, or rebase the branch and re-ship — or /builder-release it to give up on one.'}`);
368
+ return { lines, exit: 1 };
369
+ }
297
370
  if (code === 'ALREADY_CLAIMED') {
298
371
  lines.push(`Task #${taskId} was just claimed by another session. Try a different task.`);
299
372
  return { lines, exit: 1 };
@@ -356,6 +429,11 @@ function formatClaimFailure(r, taskId) {
356
429
  }
357
430
  // Fallback — still readable: the message + code, never a raw [object Object].
358
431
  lines.push(`Claim failed (${r ? r.status : '?'})${code ? ` [${code}]` : ''}${message ? `: ${message}` : ''}.`);
432
+ // task 1002592: whatever the code, if the server sent a way forward, PRINT it.
433
+ // REBASE_REQUIRED sat in this fallback with a fully-formed hint in its body and
434
+ // nothing rendered it. The branches above stay the curated wording; this is the
435
+ // floor, so the next unmapped code can never be mute the same way.
436
+ if (d && d.hint) lines.push(` ${clip(d.hint, 600)}`);
359
437
  return { lines, exit: 1 };
360
438
  }
361
439
 
@@ -488,7 +566,7 @@ async function main() {
488
566
  // task 1002579: worktree-per-claim, enforced BEFORE anything happens — before
489
567
  // the session check, before the POST. The whole point is that the builder
490
568
  // learns at claim time, not from a stranded ship hours later.
491
- enforceWorktreeClaim(taskId, { allowMainCheckout: args.includes(ESCAPE_FLAG) });
569
+ const tree = enforceWorktreeClaim(taskId, { allowMainCheckout: args.includes(ESCAPE_FLAG) });
492
570
  await requireSession();
493
571
  const wIdx = args.indexOf('--worktree');
494
572
  // Explicit --worktree always wins; otherwise auto-detect a linked worktree so
@@ -527,7 +605,7 @@ async function main() {
527
605
  if (outcome === 'retry') {
528
606
  r = await postClaim();
529
607
  } else if (outcome === 'stop') {
530
- process.exit(1); // everything already printed; no claim landed
608
+ return cliExit(1); // everything already printed; no claim landed
531
609
  }
532
610
  }
533
611
  if (r.status === 201) {
@@ -651,9 +729,16 @@ async function main() {
651
729
  }
652
730
  // Any non-201 comes here. formatClaimFailure normalizes the R01 error envelope
653
731
  // and renders the refusal as guidance (task 1476) — one place, unit-tested.
654
- const failure = formatClaimFailure(r, taskId);
732
+ const failure = formatClaimFailure(r, taskId, {
733
+ worktree,
734
+ isMainCheckout: tree ? tree.isMainCheckout : null,
735
+ });
655
736
  for (const line of failure.lines) console.error(line);
656
- process.exit(failure.exit);
737
+ // task 1002592: this exit runs immediately after the claim request, which is
738
+ // exactly where Windows aborts teardown — "Assertion failed: !(handle->flags &
739
+ // UV_HANDLE_CLOSING)" — turning a refusal's intended exit 1 into 127. Measured
740
+ // 3/3 aborting before this change. Set the code, let the loop drain (1002734).
741
+ cliExit(failure.exit);
657
742
  }
658
743
 
659
744
  // Only auto-run when invoked directly (`node claim.js …`); a `require()` (the
@@ -661,8 +746,8 @@ async function main() {
661
746
  if (require.main === module) {
662
747
  main().catch((err) => {
663
748
  console.error('fatal:', err);
664
- process.exit(1);
749
+ cliExit(1);
665
750
  });
666
751
  }
667
752
 
668
- module.exports = { formatClaimFailure, claimErrorInfo, goalMembershipOfferPlan, materializeModeSkills };
753
+ module.exports = { formatClaimFailure, claimErrorInfo, goalMembershipOfferPlan, materializeModeSkills, worktreeDetectionNote };
@@ -520,17 +520,17 @@ const KERNEL_FILES = [
520
520
  // KERNEL (already in the module-scope-map kernel key) and imports only the
521
521
  // kernel (instance-config) + node built-ins, so it joins the roster cleanly.
522
522
  'src/bongos/repo-info.js',
523
- // task 1002331 (ADR 0182 D6 / ADR 0247): the project's composed join/apply door —
524
- // a deterministic classifier over three policy values, the same category as
523
+ // task 1002331 (ADR 0182 D6 / ADR 0247): the project's composed join/apply door — a
524
+ // deterministic classifier over three policy values, the same category as
525
525
  // route-rank-check.js and goal-scope-check.js above (a trust decision the doorway
526
526
  // hands to modules so no module re-derives it). The doorway re-exports it, so
527
527
  // leaving it off this roster reads as a kernel->non-kernel leak; it imports only
528
- // branding.js, instance-config.js and logger.js — all on this roster — so
529
- // joining costs nothing and buys the import-direction guarantee on the file that
530
- // decides who may knock. (Task 1003579 removed its require of auth-admission.js
531
- // and moved joinabilityMode the other way: the gate now reads the composed door,
532
- // so the reader had to sit with the composer or the two files would have cycled.)
533
- 'src/bongos/project-door.js',
528
+ // branding.js, instance-config.js and logger.js — all on this roster — so joining
529
+ // costs nothing and buys the import-direction guarantee on the file that decides
530
+ // who may knock. (Task 1003579 moved joinabilityMode the other way: the gate reads
531
+ // the composed door, so reader and composer had to sit together or the two would
532
+ // cycle.) The ARTIST GATE joins for the same reason task 1003575, ADR 0241.
533
+ 'src/bongos/project-door.js', 'src/bongos/artist-gate.js',
534
534
  ];
535
535
 
536
536
  // Kernel files exempt from the no-module-keys scan (Check 10) because naming a
@@ -44,7 +44,7 @@
44
44
  const fs = require('node:fs');
45
45
  const path = require('node:path');
46
46
  const { spawnSync } = require('node:child_process');
47
- const { arg, hasFlag } = require('./cli-lib');
47
+ const { arg, hasFlag, apiCall } = require('./cli-lib');
48
48
  const divergence = require('./deploy-divergence.js');
49
49
 
50
50
  const ROOT = path.resolve(__dirname, '..', '..');
@@ -370,6 +370,57 @@ function refreshMain({ run = spawnSync } = {}) {
370
370
  return { ok: true };
371
371
  }
372
372
 
373
+ // Ask the LIVE instance about its own artist gate (ADR 0241, task 1003575).
374
+ //
375
+ // The instance answers because it is the only party that can: the policy arrives
376
+ // in its web.env and its board holds the reviews. This script only decides what to
377
+ // do with the answer, and the refusal sentence is composed there rather than here
378
+ // so one wording serves every caller.
379
+ //
380
+ // NEVER THROWS. Every failure — no session, a 401, an instance too old to carry
381
+ // the route, a network error — resolves to { unread: <why> }, which renders as a
382
+ // note. See artistGateEntries for why an unread gate is not a halt.
383
+ // The FULL path, not a bare `/artist-gate`. cli-lib's apiCall composes
384
+ // `${base}${urlPath}` verbatim and `base` is the instance ORIGIN — every other
385
+ // caller in this repo spells `/api/bongos/…` by hand for the same reason. Getting
386
+ // this wrong is silent in the worst way: the request lands on the origin's
387
+ // catch-all, the gate reports itself unread on every instance including the ones
388
+ // that fully support it, and the deploy check never fires. Exported so a test can
389
+ // pin it against the route's REAL mount point in the generated OpenAPI spec.
390
+ const ARTIST_GATE_PATH = '/api/bongos/artist-gate';
391
+ // The levels a real answer can carry — mirrored from src/bongos/artist-gate.js
392
+ // rather than imported, because this script also runs from an INSTALLED core on a
393
+ // box where that path is inside the package, and a require of it here would make
394
+ // the deploy tool depend on the server's own tree. Pinned by the same test that
395
+ // pins the path.
396
+ const ARTIST_GATE_LEVELS = ['off', 'advisory', 'strict'];
397
+
398
+ async function readArtistGate(call = apiCall) {
399
+ try {
400
+ const res = await call('GET', ARTIST_GATE_PATH, null, { allowUnauthenticated: true, allowBoxScope: true });
401
+ if (res.status === 404) return { unread: `this instance does not serve ${ARTIST_GATE_PATH} — it predates ADR 0241` };
402
+ // A 5xx is a gate that EXISTS and BROKE, which is not the same class of
403
+ // unknown as "no session" or "too old to have the route". The gate is
404
+ // fail-closed by design, so the one case where we know it is there and
405
+ // cannot get an answer from it HALTS rather than degrading to a note —
406
+ // otherwise a broken read is a quieter way past the gate than the owner's
407
+ // own override, which is the asymmetry worth refusing.
408
+ if (res.status >= 500) return { broken: `GET ${ARTIST_GATE_PATH} returned ${res.status}` };
409
+ if (!res.ok) return { unread: `GET ${ARTIST_GATE_PATH} returned ${res.status}` };
410
+ const d = res.data || {};
411
+ // A 200 that is not this route's answer — an SPA index.html served by a
412
+ // catch-all, a proxy's own page — must read as UNREAD, never as a gate that
413
+ // holds nothing. "I got a 200 from something" is the one failure that would
414
+ // wave a deploy through in silence, which is worse than any halt.
415
+ if (!ARTIST_GATE_LEVELS.includes(d.mode)) {
416
+ return { unread: `GET ${ARTIST_GATE_PATH} answered ${res.status} but not with a gate — got mode ${JSON.stringify(d.mode)}` };
417
+ }
418
+ return { mode: d.mode, since: d.since || null, holds: !!d.holds, reviews: d.reviews || [], halt: d.halt || null };
419
+ } catch (e) {
420
+ return { unread: (e && e.message) || 'request failed' };
421
+ }
422
+ }
423
+
373
424
  // Parse a `key=value` marker into its parts. Kept pure + exported: the marker is
374
425
  // the answer to the trap that the release current when your fix merges usually
375
426
  // does NOT contain your fix (ADR 0161 carriers bump on their own cadence).
@@ -394,12 +445,68 @@ const HALTS = {
394
445
  MARKER_UNCHECKED: 'could not inspect the target version — the content proof did not run',
395
446
  BOX_TREE_DIRTY: 'the instance repo on the box has uncommitted changes',
396
447
  LIVE_AHEAD: 'live is AHEAD of the target',
448
+ ARTIST_REVIEW_UNRESOLVED: 'an unresolved artist review holds this deploy (ADR 0241)',
449
+ ARTIST_GATE_UNREADABLE: 'the artist gate is there and answering with an error — this deploy cannot tell whether a review holds it',
397
450
  };
398
451
 
399
- function planGoLive({ div, target, targetVersion, dirtyCheckout, allowUnreleased = false }) {
452
+ // The artist gate (ADR 0241, task 1003575). This is the ONE place in the deploy
453
+ // path that the artist's authority touches, and the boundary is the whole point:
454
+ // `strict` may delay a PIN MOVE and nothing else. Nothing in the ship, grade,
455
+ // merge or land path consults this — a builder's work is credited and on `main`
456
+ // long before a go-live runs, which is what lets this gate coexist with ADR 0162
457
+ // instead of recreating the strand it retired.
458
+ //
459
+ // It reads a STATE, not a verdict: the instance reports which artist review tasks
460
+ // are still open, never whether an artist approved anything. See
461
+ // src/bongos/artist-gate.js, which composes the refusal sentence this renders.
462
+ // The halt and/or note one gate read contributes. Shared by planGoLive and by the
463
+ // gitless (on-box, no checkout) path, so a lane that skips the git-derived checks
464
+ // does not also silently skip the gate — the deploy is the same deploy either way.
465
+ // PURE.
466
+ function artistGateEntries(gate) {
467
+ const halts = [];
468
+ const notes = [];
469
+ if (gate && gate.halt) {
470
+ halts.push({ code: 'ARTIST_REVIEW_UNRESOLVED', message: gate.halt.message, detail: gate.halt.detail });
471
+ }
472
+ // A gate that is THERE and BROKEN halts. This is the fail-closed half: we know
473
+ // the instance has the route, so "it 500s" is a gate we cannot get an answer
474
+ // from, not a gate that does not apply. Its remedy is the instance's own logs,
475
+ // and it is named as such rather than as an artist problem.
476
+ if (gate && gate.broken) {
477
+ halts.push({
478
+ code: 'ARTIST_GATE_UNREADABLE',
479
+ message: HALTS.ARTIST_GATE_UNREADABLE,
480
+ detail: `${gate.broken}. The route exists, so this is a fault on the instance, not a missing feature — read its logs for the GET /artist-gate failure. Deploying past it would mean moving the pin without knowing whether a review holds it.`,
481
+ });
482
+ }
483
+ // NOT KNOWING is a different thing again, and it must not render as "nothing
484
+ // holds". It is a note rather than a halt on purpose: a go-live is already
485
+ // deliberate and reversible, and halting every deploy because there is no CLI
486
+ // session — or because the instance predates the route entirely — would brick
487
+ // the pin with no task id to release against, which is the blocked-state-that-
488
+ // does-not-explain-itself failure ADR 0241 §4 names.
489
+ if (gate && gate.unread) {
490
+ notes.push({
491
+ code: 'ARTIST_GATE_UNREAD',
492
+ message: `could not read this instance's artist gate (${gate.unread}) — if the project runs artist_gate 'strict', an unresolved artist review may be holding this deploy and this plan cannot see it`,
493
+ });
494
+ }
495
+ return { halts, notes };
496
+ }
497
+
498
+ function planGoLive({ div, target, targetVersion, dirtyCheckout, allowUnreleased = false, artistGate = null }) {
400
499
  const halts = [];
401
500
  const notes = [];
402
501
 
502
+ // Evaluated FIRST, before the git-derived checks, because it is the halt whose
503
+ // remedy takes longest: a review someone has to do is not a thing you fix in the
504
+ // next ten seconds, and finding it out after the other five checks passed is
505
+ // strictly worse than finding it out now.
506
+ const gate = artistGateEntries(artistGate);
507
+ halts.push(...gate.halts);
508
+ notes.push(...gate.notes);
509
+
403
510
  if (dirtyCheckout) halts.push({ code: 'DIRTY_CHECKOUT', message: HALTS.DIRTY_CHECKOUT, detail: dirtyCheckout });
404
511
 
405
512
  if (!div || div.undetermined) {
@@ -446,6 +553,13 @@ function renderPlan(p, { targetName, configPath } = {}) {
446
553
  L.push(` target ${p.targetVersion || '(unresolved)'}`);
447
554
  if (p.marker) L.push(` verify ${p.marker.path} contains "${p.marker.needle}"`);
448
555
  if (p.registry) L.push(` registry ${p.registry.ok ? `carries ${p.targetVersion}` : 'MISSING'}`);
556
+ // The gate's state is worth a line even when it holds nothing: "artist gate
557
+ // advisory" is the answer to "why did nothing stop?", and a reader who cannot
558
+ // see the level has to go and find it.
559
+ if (p.artistGate && p.artistGate.mode) {
560
+ const open = (p.artistGate.reviews || []).length;
561
+ L.push(` artist gate ${p.artistGate.mode}${open ? ` · ${open} unresolved review${open === 1 ? '' : 's'}` : ' · nothing open'}`);
562
+ }
449
563
  L.push('');
450
564
  for (const n of p.notes || []) L.push(` ⚠ ${n.code} — ${n.message}`);
451
565
  if (!p.halts.length) {
@@ -790,9 +904,28 @@ async function runGoLive({ target, targetName, configPath, json, apply, allowUnr
790
904
  const dirtyRes = gitless ? { status: 1 } : spawnSync('git', ['status', '--porcelain'], { cwd: ROOT, encoding: 'utf8' });
791
905
  const dirtyCheckout = dirtyRes.status === 0 && (dirtyRes.stdout || '').trim() ? (dirtyRes.stdout || '').trim().split('\n').slice(0, 5).join(', ') : null;
792
906
 
907
+ // Always read, and deliberately NOT skippable by a flag. ADR 0241 §3 names one
908
+ // escape hatch — the project OWNER's override-request, rationale required and
909
+ // audit-logged — and a `--skip` here would be a second authority path that any
910
+ // builder with the shell could take silently, which is exactly the shape that
911
+ // ADR rejects. An unread gate already degrades to a note; that is the only
912
+ // by-pass, and it says so on screen.
913
+ const artistGate = await readArtistGate();
914
+
915
+ const gitlessGate = artistGateEntries(artistGate);
793
916
  const plan = gitless
794
- ? { halts: [], notes: [{ code: 'NO_CORE_CHECKOUT', message: `no core git checkout here — target ${targetVersion} was given explicitly, so the git-derived checks (unreleased-on-main, dirty tree) are skipped` }], from: null, targetVersion, target }
795
- : planGoLive({ div, target, targetVersion, dirtyCheckout, allowUnreleased });
917
+ ? {
918
+ halts: gitlessGate.halts,
919
+ notes: [
920
+ { code: 'NO_CORE_CHECKOUT', message: `no core git checkout here — target ${targetVersion} was given explicitly, so the git-derived checks (unreleased-on-main, dirty tree) are skipped` },
921
+ ...gitlessGate.notes,
922
+ ],
923
+ from: null,
924
+ targetVersion,
925
+ target,
926
+ }
927
+ : planGoLive({ div, target, targetVersion, dirtyCheckout, allowUnreleased, artistGate });
928
+ plan.artistGate = artistGate;
796
929
  plan.marker = marker;
797
930
  if (!fetched.ok && !fetched.skipped) {
798
931
  // Say so rather than silently planning off a ref of unknown age.
@@ -860,7 +993,8 @@ if (require.main === module) {
860
993
  }
861
994
 
862
995
  module.exports = {
863
- loadConfig, selectTarget, validateTarget, planGoLive, parseMarker, parseMarkerHits,
996
+ loadConfig, selectTarget, validateTarget, planGoLive, artistGateEntries, readArtistGate, ARTIST_GATE_PATH, ARTIST_GATE_LEVELS,
997
+ parseMarker, parseMarkerHits,
864
998
  renderPlan, renderReport, failureExcerpt, asOwner, isSafeToken, NPM_AUTH_PREAMBLE, refreshMain, isLocalTarget, gitRefExists, SUDO_PREAMBLE,
865
999
  applyGoLive, upgradeCommand, pinStep, pinModeOf,
866
1000
  CONFIG_TEMPLATE, HALTS, SEMVER_RE, PIN_MODES, DEFAULT_PIN_MODE,
@@ -21,7 +21,7 @@
21
21
 
22
22
  const fs = require('node:fs');
23
23
  const path = require('node:path');
24
- const { SESSION_PATH, cliClient } = require('./cli-lib');
24
+ const { SESSION_PATH, cliClient, cliExit } = require('./cli-lib');
25
25
  const shipCard = require('../../modules/lifecycle/ship-card');
26
26
 
27
27
  // Breadcrumbs live beside the session token (per-machine, per-instance config dir —
@@ -174,14 +174,22 @@ module.exports = {
174
174
  if (require.main === module) {
175
175
  const hookMode = process.argv.includes('--hook');
176
176
  (async () => {
177
+ // task 1002592: every exit below routes through cliExit (task 1002734's rule).
178
+ // This script exits 0 on ALL paths on purpose — it is hook-invoked and must
179
+ // never report failure — but on Windows the post-request teardown aborted with
180
+ // "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)" and handed the hook
181
+ // 127, which conventionally reads as "command not found". Measured 3/3 before
182
+ // this change: the one script written never to fail always reported failure.
183
+ // Draining also lets the --hook stdout write flush, which a hard exit can cut
184
+ // mid-JSON — the card hook parses that stdout.
177
185
  // Cheap idle fast-path: no breadcrumbs → do nothing (no network).
178
- if (readBreadcrumbs().length === 0) { if (!hookMode) console.error('land-watch: no pending lands.'); process.exit(0); }
186
+ if (readBreadcrumbs().length === 0) { if (!hookMode) console.error('land-watch: no pending lands.'); return cliExit(0); }
179
187
  const envelope = await watch();
180
188
  if (hookMode) {
181
189
  if (envelope) process.stdout.write(JSON.stringify(envelope));
182
- process.exit(0);
190
+ return cliExit(0);
183
191
  }
184
192
  console.error(envelope ? `land-watch: a task landed — shipped card ready (${envelope.title}).` : 'land-watch: nothing landed this pass.');
185
- process.exit(0);
186
- })().catch(() => process.exit(0)); // never throw from a hook-invoked script
193
+ return cliExit(0);
194
+ })().catch(() => cliExit(0)); // never throw from a hook-invoked script
187
195
  }
@@ -286,9 +286,20 @@ function findLeaks(text, sensitiveLiterals) {
286
286
  // findSecretLeak — true if the generic secret scrubber WOULD change this text, i.e. a
287
287
  // secret shape is present. The export gate runs this in addition to findLeaks so a
288
288
  // stray credential in an old commit fails the publish closed rather than leaking.
289
- function findSecretLeak(text, extraSecrets = []) {
289
+ //
290
+ // `opts` MUST be whatever the scrub being audited was given (task 1003873). Unlike
291
+ // findLeaks — which is deliberately STRICTER than redaction, and independent of it by
292
+ // design — this one is DIFFERENTIAL: it re-scrubs and asks "did anything change?". An
293
+ // option the audit does not share therefore makes it disagree with its own subject by
294
+ // construction, and reads that disagreement as a leak. That is not a safety margin, it
295
+ // is a false positive: task 1002460's knownDocNames allowlist spares a long ADR
296
+ // filename from the entropy sweep, an audit without the allowlist re-redacts it, the
297
+ // text differs, and EVERY published doc reports a phantom secret shape. Because the
298
+ // gate is fail-closed the packager then produces nothing — it took the publish lane
299
+ // down for seven consecutive core releases before anyone read past the symptom.
300
+ function findSecretLeak(text, extraSecrets = [], opts = undefined) {
290
301
  if (typeof text !== 'string' || text.length === 0) return false;
291
- return scrubSecrets(text, extraSecrets) !== text;
302
+ return scrubSecrets(text, extraSecrets, opts) !== text;
292
303
  }
293
304
 
294
305
  module.exports = {
@@ -193,7 +193,7 @@ function functionalSecretLeaks(view, envSecrets) {
193
193
  // * FUNCTIONAL: VERBATIM buffer. `leaks` = exact live-credential matches only (FATAL).
194
194
  // `advisories` = brand-identity substrings + public IPs that ship as-is (non-fatal, for
195
195
  // human awareness) — REUSES findLeaks so the detector is defined once.
196
- function processCoreFile({ pathRel, raw, rules, sensitiveLiterals, envSecrets }) {
196
+ function processCoreFile({ pathRel, raw, rules, sensitiveLiterals, envSecrets, knownDocNames }) {
197
197
  // An auto-loaded agent doc (CLAUDE.md/AGENTS.md) → ship a brand-free stub, never the
198
198
  // core's own project memory (task 2121). The stub is brand-free by construction, so it
199
199
  // needs no redaction and can never leak — it short-circuits ahead of the doc path.
@@ -201,7 +201,7 @@ function processCoreFile({ pathRel, raw, rules, sensitiveLiterals, envSecrets })
201
201
  return { buf: Buffer.from(CORE_AGENT_DOC_STUB, 'utf8'), leaks: [], advisories: [], kind: 'stub' };
202
202
  }
203
203
  if (isDocPath(pathRel)) {
204
- const r = redactBlob({ buf: raw, rules, sensitiveLiterals, envSecrets });
204
+ const r = redactBlob({ buf: raw, rules, sensitiveLiterals, envSecrets, knownDocNames });
205
205
  const leaks = r.leaks.map((l) => ({ label: `doc:${l.label}`, sample: l.sample }));
206
206
  if (r.secret) leaks.push({ label: 'doc:secret-shape' });
207
207
  return { buf: r.buf, leaks, advisories: [], kind: 'doc' };
@@ -817,6 +817,14 @@ function main() {
817
817
  // 4. Process every file IN MEMORY (processCoreFile: docs redacted, functional verbatim)
818
818
  // + the fail-closed no-leak gate. Nothing touches dist/ until the ENTIRE set is proven
819
819
  // clean (atomic abort). Brand/topology in functional files are non-fatal ADVISORIES.
820
+ // task 1002460 — the doc-link allowlist. The entropy sweep cannot tell a long
821
+ // ADR filename from a secret by shape, so it collapsed every one of them to
822
+ // <redacted>.md on the way out. This is the set of names actually being
823
+ // published, so a name in it is a FILE and not a guess; anything else, secrets
824
+ // included, is redacted exactly as before.
825
+ const knownDocNames = new Set(
826
+ publishable.map((e) => e.path.split('/').pop()).filter((n) => /\.(md|sql)$/i.test(n)),
827
+ );
820
828
  const staged = []; // { name, mode, buf } (name includes TAR_ROOT)
821
829
  const filesMeta = []; // { path, mode, sha256 } (source files only — the pin domain)
822
830
  const leaks = [];
@@ -829,7 +837,7 @@ function main() {
829
837
  try { raw = gitBuf(['cat-file', 'blob', e.sha]); }
830
838
  catch (err) { leaks.push({ path: e.path, label: `read-failed:${err.message}` }); continue; }
831
839
  const { buf, leaks: fileLeaks, advisories: fileAdvisories, kind } =
832
- processCoreFile({ pathRel: e.path, raw, rules, sensitiveLiterals, envSecrets });
840
+ processCoreFile({ pathRel: e.path, raw, rules, sensitiveLiterals, envSecrets, knownDocNames });
833
841
  for (const l of fileLeaks) leaks.push({ path: e.path, label: l.label, sample: l.sample });
834
842
  for (const a of fileAdvisories) advisories.push({ path: e.path, label: a.label, sample: a.sample });
835
843
  if (kind === 'doc') docCount++; else if (kind === 'stub') stubCount++; else funcCount++;
@@ -6,7 +6,46 @@
6
6
  //
7
7
  // The task returns to 'ready' status and is claimable by another session.
8
8
 
9
+ const { execFileSync } = require('node:child_process');
10
+ const path = require('node:path');
9
11
  const { cliClient, requireSession, arg } = require('./cli-lib');
12
+ const { parkedMainCheckoutWarning } = require('./worktree-claim-guard');
13
+
14
+ // gitAt(args, cwd) — run git with an ARGV ARRAY and no shell.
15
+ //
16
+ // Deliberately not `execSync('git -C "<path>" …')`. A resolved filesystem path is
17
+ // not trusted input: a directory name containing a quote, backtick or `$()` would
18
+ // break out of the quoting and execute under cmd.exe/sh. execFileSync never
19
+ // spawns a shell, so the path cannot be interpreted as anything but a path — a
20
+ // stronger guarantee than the `{ cwd }` form, which still shells the command
21
+ // string. (Found by the grader on this task, and it was right: the first cut
22
+ // interpolated the path into a shell string.)
23
+ //
24
+ // Kept local rather than imported from ship-git.js on purpose: release.js ships
25
+ // in the PUBLIC client-only @cloudbongos/cli package, and nothing in the ship-*
26
+ // family is in that manifest. tests/cli_package.mjs installs the tarball and RUNS
27
+ // every verb, so an import from outside it is a crash at require time, not a
28
+ // lint-level nit — that is exactly how this file broke once already.
29
+ function gitAt(args, cwd) {
30
+ try {
31
+ return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
32
+ } catch (_) {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ // The MAIN checkout, not this worktree — from a linked worktree they differ, and
38
+ // the parked-tree warning is only ever about the main one.
39
+ //
40
+ // Resolved with git rather than by importing ship-state's REPO_ROOT for the same
41
+ // packaging reason: ship-state pulls in modules/lifecycle/ship-preflight, which
42
+ // would drag server-side lifecycle code into a client-only package.
43
+ // `--git-common-dir` is the shared .git of the whole worktree family, so its
44
+ // parent is the main checkout from anywhere.
45
+ function mainCheckoutRoot() {
46
+ const common = gitAt(['rev-parse', '--path-format=absolute', '--git-common-dir'], process.cwd());
47
+ return common ? path.dirname(common) : null;
48
+ }
10
49
 
11
50
 
12
51
  (async () => {
@@ -65,6 +104,22 @@ const { cliClient, requireSession, arg } = require('./cli-lib');
65
104
  }
66
105
  console.log(`Released task #${r.data.taskId}: ${r.data.taskTitle}`);
67
106
  console.log(`Status returned to 'ready'. No credits awarded.`);
107
+ // task 1002871: a claim worked under the `--here` hatch leaves the MAIN
108
+ // checkout on the feature branch, and giving the claim up is exactly when that
109
+ // tree stops being watched. Warn, never restore — the branch may hold real
110
+ // uncommitted work, and release.js cannot tell that from debris. Strictly
111
+ // best-effort: a diagnostic must never fail a release that already succeeded.
112
+ try {
113
+ const root = mainCheckoutRoot();
114
+ const branch = root && gitAt(['rev-parse', '--abbrev-ref', 'HEAD'], root);
115
+ // gitAt already trims, so a non-empty string IS dirt.
116
+ const dirty = root && gitAt(['status', '--porcelain'], root);
117
+ const lines = parkedMainCheckoutWarning({ branch, dirty: !!dirty });
118
+ if (lines.length) {
119
+ console.log('');
120
+ for (const line of lines) console.log(line);
121
+ }
122
+ } catch (_) { /* never let a warning break a successful release */ }
68
123
  })().catch((err) => {
69
124
  console.error('fatal:', err);
70
125
  process.exit(1);
@@ -11,7 +11,7 @@
11
11
  const fs = require('node:fs');
12
12
  const os = require('node:os');
13
13
  const path = require('node:path');
14
- const { cliClient } = require('./cli-lib');
14
+ const { cliClient, isBoxScope } = require('./cli-lib');
15
15
  const { gitArgvOk, gitOk, sanitizeGitHubRefs, shellOk } = require('./ship-git.js');
16
16
  const { buildPrCreateArgv, buildPrMergeArgv, deployRunSucceeded, deployedCommitStatus, fetchDeployedVersion, ghArgvOk, ghJson, pushVia, resolveInstanceBase } = require('./ship-deploy-target.js');
17
17
  const { normalizeApiError } = require('./ship-io.js');
@@ -184,7 +184,15 @@ async function ciLandServer(taskId, branch, valueSummary) {
184
184
  id: taskId,
185
185
  body: { branch, head_sha: headSha, value_summary: valueSummary, bundle_b64: bundleB64 },
186
186
  });
187
- if (r.status === 403 && normalizeApiError(r.data).code === 'box_scope') {
187
+ // task 1002593: ONE definition of "is this a box-scope refusal". This site used
188
+ // to hand-roll the comparison, which is how it silently died when routes moved
189
+ // to the object error envelope — `r.data.error` stopped being the bare string
190
+ // and the branch could never fire. Normalizing here fixed the symptom but left
191
+ // a SECOND predicate for the same question, and two copies of a predicate are
192
+ // how one of them gets a fix the other never sees (migration 177 dropping 176's
193
+ // blocker clause is the same shape). cli-lib's isBoxScope is the one every
194
+ // other caller already uses; this is the last site that did not.
195
+ if (isBoxScope(r)) {
188
196
  console.log(' ci-land: this session is BOX-SCOPED — run /builder-reauth (browser, no terminal) to mint a real session, then re-ship. Task stays at confirmed.');
189
197
  return landBailed('this CLI session is box-scoped', '/builder-reauth');
190
198
  }